/* ─────────────────────────────────────────────────────────────
   POLICES AUTO-HÉBERGÉES
   Instrument Sans et Space Mono sont servies depuis notre propre
   dossier (assets/fonts/) et non depuis Google Fonts. Deux raisons :

   1. RGPD — une balise <link> vers fonts.gstatic.com transmet
      l'adresse IP de chaque visiteur à Google, à chaque page.
      Plus de requête tierce, donc plus de sous-traitant à déclarer
      dans politique-de-confidentialite.html.
   2. Performance — les fichiers (~48 Ko au total) partent du même
      serveur que la page : plus de résolution DNS ni de poignée de
      main TLS vers un domaine tiers avant l'affichage du texte.
   ───────────────────────────────────────────────────────────── */

/* Instrument Sans est une police variable (une seule graisse ne suffit
   pas partout : 420 en corps de texte, 560/600 pour les titres) —
   contrairement à Space Mono ci-dessous, qui n'existe qu'en deux
   graisses fixes (400 et 700), d'où les deux @font-face séparés.
   "font-display: swap" : le texte s'affiche tout de suite dans la
   police de secours, puis bascule une fois la police chargée. Le
   visiteur lit immédiatement au lieu de voir un blanc.
   "unicode-range" : le sous-ensemble latin, qui couvre tout le
   français — accents, œ (U+0153), guillemets « » (U+00AB/BB) et
   € (U+20AC). Inutile de charger le cyrillique ou le grec. */
@font-face {
  font-family: "Instrument Sans";
  src: url("../assets/fonts/instrument-sans-latin-var.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

@font-face {
  font-family: "Space Mono";
  src: url("../assets/fonts/space-mono-400-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

@font-face {
  font-family: "Space Mono";
  src: url("../assets/fonts/space-mono-700-latin.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

/* Réinitialisation simple */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  /* Instrument Sans pour tout le texte courant : une grotesque plus
     chaleureuse que l'ancienne Geist, choisie par Loïc pour rester
     discrète derrière les photos tout en gardant un peu de caractère.
     La hiérarchie vient de la graisse, de la taille et de
     l'interlignage, pas d'un caractère tapageur. */
  --font-sans: "Instrument Sans", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Space Mono, réservée aux PETITS RÔLES D'ÉTIQUETAGE : surtitres,
     métadonnées, légendes en capitales, compteurs, labels de
     formulaire, fil d'Ariane. Ces rôles ont un point commun : ce sont
     des repères, pas du texte à lire au fil. Le monospace leur donne
     une identité commune et un air d'archive qui va bien à un fonds
     photographique — Space Mono, avec son dessin de machine à écrire,
     appuie encore un peu plus cet esprit "fiche d'archive".
     Bénéfice technique : en monospace les chiffres ont tous la même
     largeur, donc les prix, durées et années s'alignent d'eux-mêmes. */
  --font-mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --bg: #0a0a0c;
  --bg-elevated: #131317;
  --ink: #f3f1ec;
  /* Ton intermédiaire entre --ink et --muted : pour les textes qui doivent
     avoir plus de présence que le corps de texte sans crier aussi fort
     qu'un titre (chapeaux de section, citations). */
  --ink-soft: #cfcbc2;
  --muted: #9b988f;
  --border: rgba(255, 255, 255, 0.09);
  --accent-1: #ff9a3c;
  --accent-2: #ff5e1a;

  /* Courbes d'animation : des versions plus marquées que les courbes CSS
     natives, qui manquent de caractère. Réutilisées partout plutôt que
     retapées à chaque fois — une seule valeur à ajuster si besoin.
     --ease-reveal existait déjà tapée en dur à 3 endroits (apparitions,
     lightbox, survol des cartes) ; on la nomme sans changer sa valeur. */
  --ease-reveal: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  /* Tailles des grands titres : mutualisées ici car les mêmes 3 rôles
     (héro, titre de page, titre de section) revenaient sur plusieurs pages. */
  --text-display-xl: clamp(2.8rem, 2rem + 4vw, 5rem);
  --text-display-lg: clamp(2.2rem, 1.6rem + 3vw, 3.4rem);
  --text-display-md: clamp(1.7rem, 1.4rem + 1.5vw, 2.3rem);

  /* Respiration verticale des sections, identique partout */
  --space-section: clamp(4.5rem, 3.5rem + 4vw, 8rem);

  /* Hauteur réelle du bandeau du haut (padding vertical + hauteur du nom).
     Nommée ici parce que le héro de la page d'accueil remonte SOUS le
     bandeau d'exactement cette valeur : les deux doivent bouger ensemble,
     sinon la photo dépasse ou laisse une bande vide. Redéfinie dans les
     deux media queries qui changent ce padding (voir plus bas).
     3,85rem (contre 5,25rem) : bandeau ~25% moins haut. Le padding
     vertical de .site-header a dû baisser plus que "proportionnellement"
     (1,5rem → 0,8rem) car la hauteur du nom "Loïc Terrier" à line-height
     hérité (~2,23rem) reste fixe — c'est elle qui mange l'essentiel de
     l'espace restant une fois le padding réduit. */
  --header-h: 3.85rem;

  /* ── SYSTÈME TYPOGRAPHIQUE ───────────────────────────────────
     Un seul pas d'échelle : chaque taille de texte courant vaut la
     précédente × 1,125. Les titres avancent 3 crans à la fois, ce qui
     revient à ×1,414 (1,125³ ≈ √2) — donc DEUX crans de titre DOUBLENT
     exactement la taille : 1,125 → 2,25 → 4,5 → 9. Douze tailles
     couvrent tout le site, contre trente avant (0,72rem, 0,78rem,
     0,82rem... des valeurs sans relation entre elles).

     Le nom de chaque token décrit un RÔLE ("titre de section"), jamais
     une valeur ("2,3rem") : si l'échelle change un jour, on ne retouche
     qu'ici, jamais les sélecteurs plus bas.
     ───────────────────────────────────────────────────────────── */
  --text-hero:          clamp(3.2rem, 1.6rem + 8vw, 9rem);      /* titre d'accueil, une fois par site */
  --text-title-page:    clamp(2.25rem, 1.3rem + 4.2vw, 4.5rem); /* titre de page (projet, livre) */
  --text-title-section: clamp(1.8rem, 1.2rem + 2.8vw, 3.2rem);  /* titre de section, titre contact */
  --text-title-sub:     clamp(1.6rem, 1.35rem + 1.2vw, 2.25rem);/* sous-section dans une page */
  --text-title-card:    clamp(1.25rem, 1.1rem + 0.7vw, 1.6rem); /* titre de carte, h3 */
  --text-quote:         clamp(1.4rem, 1.2rem + 0.9vw, 1.8rem);  /* citation détachée, témoignage */
  --text-lead:          clamp(1.125rem, 1.03rem + 0.4vw, 1.4rem); /* chapeau : prolonge un titre */
  --text-wordmark:      1.25rem;  /* nom du site dans l'en-tête */
  --text-nav:           clamp(0.8rem, 0.55rem + 0.4vw, 0.9rem); /* lien de nav en ligne */
  --text-nav-overlay:   1.25rem;  /* lien du menu plein écran mobile */
  --text-amount:        1.25rem;  /* montant mis en avant (mono) */
  --text-body:          1rem;     /* corps de texte — plancher, jamais en dessous */
  --text-body-sm:       0.9rem;   /* texte secondaire dense (cartes, listes) */
  --text-caption:       0.8rem;   /* légende en casse normale */
  --text-label:         0.8rem;   /* surtitre, étiquette, capitales (mono) */
  --text-meta:          0.8rem;   /* métadonnée, chiffres (mono) */

  /* Graisses : Instrument Sans est une police VARIABLE, donc ces valeurs
     ne sont pas limitées à 400/700 comme une police classique — on peut
     demander 420 ou 560. Trois zones, parce que le fond très sombre
     (--bg) n'agit pas dans le même sens partout : voir le commentaire
     de --w-display.
     --w-label fait exception : Space Mono (voir plus haut) n'existe
     qu'en 400 et 700, sans variable. On reste donc sur 400 — le
     contraste vient déjà des majuscules et du tracking, pas de la
     graisse. */
  --w-body:    420; /* 400 + 20 : sur fond noir, les fûts fins de la graisse
                        normale paraissent légèrement fragiles */
  --w-label:   400;
  --w-title:   600;
  --w-display: 560; /* on DESCEND par rapport à --w-title : au-delà de 70px,
                        le halo du texte clair sur fond noir ("blooming")
                        ajoute de la graisse perçue — un display à 650
                        se lit aussi épais qu'un 700+. 560 réel ≈ 620 perçu. */

  /* Mesure de lecture : 1ch = la largeur du chiffre "0", environ 20 % plus
     large que la lettre moyenne d'un texte français. 56ch se lit donc
     comme ~67 caractères réels — dans la fourchette de confort 45-75. */
  --measure:        56ch; /* corps de texte long */
  --measure-lead:   46ch; /* chapeaux, plus courts par nature */
  --measure-narrow: 38ch; /* colonnes étroites (aside, carte) */
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-body);
  font-weight: var(--w-body);
  /* 1,65, pas 1,7 : la valeur juste pour du 1rem sur fond sombre (voir
     --w-display plus haut sur le principe de compensation) — elle était
     simplement appliquée telle quelle à tout le texte, titres compris,
     avant que chaque rôle ne fixe son propre interlignage. */
  line-height: 1.65;
  letter-spacing: 0.005em; /* 0,045 − 0,04 × 1rem, la formule du système */
  color: var(--ink);
  background-color: var(--bg);
  /* clip, pas hidden : un overflow-x non "visible" force l'overflow-y à
     devenir "auto" (couplage CSS des deux axes), ce qui transforme le
     <body> en conteneur de défilement à sa place et casse le
     position: sticky du header (il défile avec la page au lieu de
     rester en haut). "clip" évite ce couplage tout en bloquant le
     débordement horizontal de la même façon. */
  overflow-x: clip;
}

/* Fond non uniforme : un voile fixe posé derrière tout le contenu, plutôt
   qu'un background-attachment: fixed (mal supporté sur iOS et coûteux au
   défilement). Deux halos très sourds donnent de la profondeur. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1100px 700px at 12% 0%, rgba(255, 154, 60, 0.055), transparent 62%),
    radial-gradient(900px 650px at 92% 18%, rgba(110, 125, 160, 0.05), transparent 60%),
    linear-gradient(180deg, #101015 0%, #0a0a0c 50%, #08080a 100%);
}

/* Grain façon film argentique : une texture SVG générée à la volée,
   posée par-dessus toute la page. Aucune image à charger. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.4;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.35'/%3E%3C/svg%3E");
}

/* Filet de sécurité : sans cette règle, tout lien pour lequel on aurait
   oublié un style s'affiche en bleu souligné, façon page brute. */
a {
  color: var(--accent-1);
  text-decoration-color: rgba(255, 154, 60, 0.4);
  text-underline-offset: 0.25em;
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: 3px;
}

/* Les champs de saisie sont traités à part. Un navigateur considère
   qu'un champ de texte cliqué à la souris mérite un indicateur de focus
   (on va y taper), contrairement à un lien ou un bouton : le cadre
   orange s'affichait donc à CHAQUE clic dans un champ, et encadrait tout
   le formulaire de rectangles. L'indicateur n'est pas supprimé pour
   autant — il passe sur le trait du bas du champ, qui fait déjà partie
   du dessin du formulaire (voir .form-row plus bas). */
input:focus-visible,
textarea:focus-visible {
  outline: none;
}

/* Sans le contraste serif/sans-serif d'avant, la hiérarchie vient
   maintenant de la graisse + de l'espacement des lettres : plus le texte
   est grand, plus les lettres sont serrées (règle Apple sur le tracking
   dépendant de la taille — sinon les grands titres semblent trop aérés). */
/* Chaque h1/h2/h3 du site est couvert par une règle de RÔLE dédiée plus
   bas (titre d'accueil, titre de page, titre de section...) — il n'y a
   donc plus de taille, graisse ou tracking "par défaut" ici : une valeur
   qu'on ne voit jamais s'appliquer est plus trompeuse qu'utile. Seul
   text-wrap: balance reste commun à tous les titres. */
h1, h2, h3 {
  text-wrap: balance;
}

/* "pretty" évite les lignes finales d'un seul mot (les « orphelines »),
   sans rééquilibrer toutes les lignes comme "balance" — ce qui, sur un
   paragraphe entier, donnerait un pavé aux lignes anormalement courtes. */
p, li, figcaption, blockquote {
  text-wrap: pretty;
}

/* Filet de sécurité contre les chaînes insécables (adresses e-mail, URLs)
   qui débordent de leur colonne faute de point de coupure — constaté sur
   l'e-mail des mentions légales, qui dépassait de sa case de 8px. */
p, li, figcaption, blockquote, .spec-item span {
  overflow-wrap: break-word;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
    /* Les durées seules ne suffisent pas : un transition-delay survit à
       la mise à zéro de la durée. Le bandeau du menu mobile, décalé de
       260 ms, restait invisible tout ce temps alors que le reste était
       déjà là — un délai est du mouvement retardé, pas de l'immobilité. */
    transition-delay: 0s !important;
  }

  html {
    scroll-behavior: auto;
  }
}

/* Surtitre de section : accompagné d'un trait fin plutôt que présenté
   comme une étiquette isolée, pour un rendu plus éditorial. */
/* ══ RÔLE : ÉTIQUETTE (mono) ══════════════════════════════════════
   En Space Mono : les chasses étant déjà égales, un tracking positif
   bien plus léger (0,06em) suffit à aérer les capitales — plus besoin
   de forcer l'espacement comme avec une grotesque (0,18em avant). */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-1);
  margin-bottom: 0.9rem;
}

.eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--border), transparent);
}

/* Centré (en-têtes de page projet) : un trait de chaque côté. Le hero de
   la home page utilise le style .eyebrow générique (aligné à gauche, un
   seul trait après), plus éditorial qu'un centrage symétrique. */
.project-hero-caption .eyebrow {
  justify-content: center;
}

.project-hero-caption .eyebrow::before {
  content: "";
  flex: 1;
  max-width: 4rem;
  height: 1px;
  background: linear-gradient(270deg, var(--border), transparent);
}

.project-hero-caption .eyebrow::after {
  flex: 1;
  max-width: 4rem;
}

/* Boutons : contour fin plutôt qu'aplat orange. Sur un fond aussi sobre,
   le dégradé plein écrasait tout le reste ; l'orange revient au survol,
   en remplissage, ce qui garde l'accent sans qu'il domine la page. */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 2rem;
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  border: 1px solid rgba(255, 255, 255, 0.22);
  font-family: var(--font-mono);
  font-weight: var(--w-label);
  font-size: var(--text-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease,
              transform 160ms var(--ease-out);
}

.pill:hover {
  background: var(--accent-1);
  border-color: var(--accent-1);
  /* Texte sombre sur l'orange : le blanc ne donnerait qu'un contraste de
     2,1:1, en dessous du minimum d'accessibilité (4,5:1). */
  color: #1a0d03;
}

/* Retour au clic : confirme que l'interface a bien pris le clic en compte,
   sur l'action la plus fréquente du site (CTA, envoi du formulaire). */
.pill:active {
  transform: scale(0.97);
}

/* Bouton pas encore relié à un vrai service (ex. inscription à une liste
   de diffusion) : même forme que .pill, mais l'aspect et le curseur disent
   clairement "pas encore cliquable" plutôt que de laisser croire à un lien
   mort. Voir ROADMAP.md pour le suivi de cette connexion. */
.pill[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Bouton de paiement en cours de redirection : même intention que
   [aria-disabled], mais pour un vrai <button disabled> (pas un lien). */
.pill:disabled {
  opacity: 0.6;
  cursor: wait;
}

/* Sans ce reset, .pill:hover (qui remplit en orange) s'applique quand même
   au survol d'un bouton désactivé — il semble cliquable pile pendant qu'il
   ne l'est pas. */
.pill[aria-disabled="true"]:hover,
.pill:disabled:hover {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.22);
  color: var(--ink);
}

.signup-note {
  margin-top: -1.25rem;
  color: var(--muted);
  font-size: var(--text-meta);
}

/* Erreur de paiement (Stripe indisponible / réseau), affichée dans la
   page à la place de l'alert() natif du navigateur. Bordure orange
   pleine légitime ici : c'est un état d'erreur actif, pas un décor
   ambiant (voir "The Signal Rule" dans DESIGN.md). */
.checkout-error {
  margin-top: 0.85rem;
  padding: 0.85rem 1.1rem;
  background: var(--bg-elevated);
  border: 1px solid var(--accent-1);
  border-radius: 8px;
  color: var(--ink);
  font-size: var(--text-body-sm);
}

.signup-note a {
  color: var(--ink-soft);
}

/* Taille et tracking hérités de .pill (rôle "étiquette" identique,
   juste plus compact) — seul le padding change ici. */
.pill-small {
  padding: 0.55rem 1.25rem;
  border-color: var(--border);
}

/* Navigation */
.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.8rem 2.5rem;
  position: sticky;
  top: 0;
  z-index: 50;
  /* Sur mobile, on cache/montre le bandeau en le glissant hors de l'écran
     (voir .header-hidden dans la media query mobile plus bas) : la
     transition vit ici, dans la règle de base, pour s'appliquer aussi au
     retour en arrière (réapparition) qu'à la sortie. */
  transition: transform 0.3s ease;
}

/* Fond flouté séparé dans un pseudo-élément : un backdrop-filter posé
   directement sur .site-header ferait de lui le bloc de confinement du
   menu mobile (position: fixed), qui se retrouverait coincé dans la
   hauteur du header au lieu de couvrir tout l'écran. */
/* Verre dépoli, pas aplat noir : la photo du héro passe SOUS le bandeau
   et reste devinable, floutée. Le dégradé descend du plus sombre (en
   haut, là où vivent le nom et la nav) vers presque rien — sur une photo
   très claire, un voile uniforme assez léger pour laisser voir l'image
   ne suffirait plus à tenir le contraste du texte. La saturation
   compense le lavage de couleur que produit le flou. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Voile LÉGER : l'état du haut de page, où l'on sait ce qui passe
     derrière (la photo du héro, ou le fond sombre des autres pages).
     C'est ici que la photo doit se deviner.
     Opacités baissées une première fois d'environ 26% (0,62→0,46 et
     0,46→0,34), puis une seconde fois d'environ 25% (0,46→0,34 et
     0,34→0,25) pour un bandeau encore plus transparent au repos. Sans
     risque pour le contraste : ce n'est PAS l'état mesuré à 4,74:1 (voir
     commentaire de ::after) — celui-ci ne concerne que le défilement, où
     ce voile s'additionne au second. Une fois défilé (.is-scrolled),
     .site-header.is-scrolled::before plus bas RESTAURE les valeurs
     d'origine pour garder le pire cas mesuré intact. */
  background: linear-gradient(180deg, rgba(10, 10, 12, 0.34) 0%, rgba(10, 10, 12, 0.25) 100%);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  /* Filet clair qui marque le bas du bandeau. Sans lui, le verre dépoli
     se fond dans la photo et le bandeau n'a plus de bord : on ne sait
     plus où il s'arrête. En ombre interne plutôt qu'en "border" : une
     bordure ajouterait 1px à la hauteur du bandeau, or --header-h sert à
     remonter le héro exactement dessous — le décalage se verrait.
     Opacité réduite ici (0,14 contre 0,32 une fois défilé) : en haut de
     page ce filet n'a rien à séparer de dur encore, il n'a pas besoin de
     se voir autant. */
  box-shadow: inset 0 -1px 0 rgba(255, 206, 160, 0.14);
}

/* Deuxième voile, qui vient s'ajouter au premier dès qu'on défile.
   Pourquoi deux couches plutôt qu'un seul dégradé qu'on changerait : un
   background-image ne s'anime pas, la bascule serait sèche — alors qu'une
   opacité, si.

   Pourquoi cette bascule existe : une fois la page défilée, n'importe
   quoi peut se retrouver sous le bandeau — l'affiche presque blanche
   d'Akhdar, la carte de l'Inde, la neige. Mesuré sur les 23 pages, le
   voile léger seul y laissait les liens à 2,9:1 (il en faut 4,5). Et ce
   n'est pas rattrapable par la couleur du texte : même du blanc pur
   plafonnait à 4,7:1. Les deux voiles cumulés donnent ~0,87 en haut et
   ~0,76 en bas, ce qui tient le pire cas du site (4,74:1).
   NE PAS baisser ces opacités : c'est exactement l'état mesuré à
   4,74:1, avec très peu de marge au-dessus du minimum de 4,5:1. Voir le
   commentaire de .site-header::before plus haut : la transparence
   demandée a été ajoutée uniquement au voile léger du repos, restauré
   ici via .site-header.is-scrolled::before pour que ce pire cas défilé
   reste identique à ce qui a été vérifié. */
.site-header::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(10, 10, 12, 0.66) 0%, rgba(10, 10, 12, 0.56) 100%);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.site-header.is-scrolled::after {
  opacity: 1;
}

/* Une fois défilé, le filet reprend son opacité pleine (0,32) : il sépare
   alors le bandeau d'un contenu qui défile juste en dessous, il doit se
   voir davantage.
   Le fond, lui, retrouve ici ses valeurs D'ORIGINE (0,62/0,46 — plus
   soutenues que le nouveau repos à 0,46/0,34) : cumulé avec ::after,
   c'est ce mélange précis qui a été mesuré à 4,74:1 sur les 23 pages.
   Le rendu défilé ne change donc pas ; seul l'état du haut de page,
   avant tout défilement, est devenu plus transparent. */
.site-header.is-scrolled::before {
  background: linear-gradient(180deg, rgba(10, 10, 12, 0.62) 0%, rgba(10, 10, 12, 0.46) 100%);
  box-shadow: inset 0 -1px 0 rgba(255, 206, 160, 0.32);
}

/* ══ RÈGLE : bandeau transparent sur tout héro plein écran ══════════
   Reprend, pour .project-hero (a-propos, formation, mariage), le même
   traitement que l'accueil (.hero, css/home-redesign.css) : verre dépoli
   nul au repos, la photo se voit "vraiment" à travers le bandeau — pas
   seulement devinée derrière un voile flouté. ":has(+ .project-hero)"
   rend la règle automatique : toute future page qui pose .project-hero
   juste après </header> en hérite sans rien ajouter au balisage. Les
   pages avec un bandeau de fil d'Ariane entre les deux (projets-vin,
   projet-inde, projet-mauritanie, projets-entreprises) ne matchent pas
   ce sélecteur — leur bandeau est déjà posé sur le fond sombre de la
   page, pas sur une photo, donc rien à changer là. ":not(.is-scrolled)"
   est nécessaire (pas juste une histoire d'ordre dans le fichier) : une
   fois défilé, ce sélecteur a la MÊME spécificité que
   ".site-header.is-scrolled::before" juste au-dessus — sans ce
   ":not()", lequel des deux gagne dépendrait de l'ordre des règles,
   fragile. Avec lui, l'état défilé (donc le contraste mesuré à 4,74:1)
   reste intact, identique à toutes les autres pages. */
.site-header:has(+ .project-hero):not(.is-scrolled)::before {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}

.site-name {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 1.35rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.site-header nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

/* --ink-soft, pas --muted : depuis que le bandeau est translucide, une
   photo claire peut passer juste derrière ces liens. Au plus mauvais cas
   (le mur ocre du héro, flouté sous le voile), --muted tombait à 3,3:1 —
   sous le minimum de 4,5:1 ; --ink-soft tient 5,9:1 sans pour autant
   crier aussi fort qu'un blanc plein, réservé au survol. */
.site-header nav a:not(.pill) {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 0.9rem;
  /* Soulignement qui se déploie au survol : un dégradé d'1px dont on
     anime seulement la largeur, sans ajouter d'élément ni de JS. */
  background-image: linear-gradient(var(--accent-1), var(--accent-1));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  padding-bottom: 2px;
  transition: color 0.25s ease, background-size 0.3s ease;
}

.site-header nav a:not(.pill):hover {
  color: var(--ink);
  background-size: 100% 1px;
}

/* Page actuelle dans le menu principal : même logique que l'état actif
   du fil d'Ariane, mais en orange signal — la nav est aussi un repère
   d'état, pas juste une liste de liens (voir "The Signal Rule"). */
.site-header nav a:not(.pill)[aria-current="page"] {
  color: var(--accent-1);
  background-size: 100% 1px;
}

.site-header nav a:not(.pill)[aria-current="page"]:hover {
  color: var(--accent-2);
}

/* Hero : plus de photo plein écran en fond (l'ambiance vient déjà du
   voile radial + grain posés sur toute la page par body::before/after,
   voir plus haut) — texte aligné à gauche façon page de titre éditoriale,
   plutôt que centré sur une image comme avant. */
.hero {
  position: relative;
  overflow: hidden;
}

.hero-content {
  position: relative;
  z-index: 1;
  max-width: 800px;
  margin: 0 auto;
  padding: 16vh 1.5rem 9vh;
}

/* ══ RÔLE : TITRE D'ACCUEIL ═══════════════════════════════════════
   Le seul --text-hero du site. Tracking calculé, pas fixe : un
   letter-spacing unique serait juste à UNE largeur d'écran (celle où
   --text-hero atteint tel ou tel px) et faux partout ailleurs. La
   formule (0,045rem − 0,04 × taille) resserre le texte à mesure qu'il
   grandit ; à 9rem elle donne −0,035em, exactement la valeur réglée à
   l'œil ici avant le passage au système. */
.hero-content h1 {
  color: var(--ink);
  font-size: var(--text-hero);
  font-weight: var(--w-display); /* 560, pas 600 : voir le commentaire sur --w-display */
  line-height: 0.95;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-hero));
  margin-bottom: 2rem;
}

.hero-content .eyebrow {
  margin-bottom: 1.2rem;
}

/* ══ RÔLE : CHAPEAU ══════════════════════════════════════════════
   Prolonge un titre, ne le répète pas en petit. Regroupe ici les 4
   endroits du site où ce rôle existait avec 4 tailles et 3 couleurs
   différentes (hero-subtitle, section-intro, book-tagline,
   project-hero-caption p) — un seul style pour tous désormais. */
.hero-subtitle,
.section-intro,
.book-tagline,
.project-hero-caption p {
  font-family: var(--font-sans);
  font-size: var(--text-lead);
  font-weight: var(--w-body);
  line-height: 1.5; /* 1,45 + 0,05 de compensation fond sombre */
  letter-spacing: calc(0.045rem - 0.04 * var(--text-lead));
  color: var(--ink-soft);
  max-width: var(--measure-lead);
  /* Pas de marge commune : sur les 4 usages, seul hero-subtitle en a
     besoin (le bouton en dessous doit être détaché) — remise à zéro par
     le reset global, ajoutée juste pour lui ci-dessous. */
}

.hero-subtitle {
  margin-bottom: 2rem;
}

/* Cascade légère au chargement : chaque ligne du héro apparaît un peu
   après la précédente, plutôt que tout d'un coup. En CSS pur (pas besoin
   de Motion ici, c'est une apparition unique au chargement, pas une
   interaction) — @media (prefers-reduced-motion) plus haut dans le fichier
   neutralise déjà animation-duration pour tout le monde, y compris ceci. */
.hero-content > * {
  opacity: 0;
  transform: translateY(14px);
  animation: hero-rise 1.1s var(--ease-out) forwards;
}

.hero-content .eyebrow { animation-delay: 0.1s; }
.hero-content h1 { animation-delay: 0.22s; }
.hero-content .hero-voice-two { animation-delay: 0.45s; }

@keyframes hero-rise {
  to {
    opacity: 1;
    transform: none;
  }
}

/* Repère discret entre le héro et la photo showcase : invite l'œil à
   continuer sans rien ajouter qui concurrence le titre ou la photo. */
.hero-scroll-cue {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  width: fit-content;
  margin: 0 auto;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.25s ease;
}

.hero-scroll-cue:hover,
.hero-scroll-cue:focus-visible {
  color: var(--ink);
}

.hero-scroll-cue:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: 6px;
  border-radius: 4px;
}

.hero-scroll-cue-label {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.7;
}

.hero-scroll-cue-icon {
  animation: hero-scroll-cue-drift 1.8s var(--ease-in-out) infinite;
}

@keyframes hero-scroll-cue-drift {
  0%, 100% { transform: translateY(0); opacity: 0.55; }
  50% { transform: translateY(5px); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .hero-scroll-cue-icon {
    animation: none;
  }
}

.showcase {
  position: relative;
  z-index: 1;
  max-width: 1080px;
  margin: 4rem auto 0;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.7);
}

.showcase-image {
  width: 100%;
  height: 56vh;
  min-height: 320px;
  max-height: 560px;
  object-fit: cover;
  display: block;
}

/* Sections générales */
main section {
  max-width: 1000px;
  margin: 0 auto;
  padding: var(--space-section) 1.5rem;
  position: relative;
}

/* Lien de sortie d'une section d'accueil vers sa page complète
   ("Voir tous mes projets"). Détaché de la grille au-dessus par une
   respiration franche, pour ne pas se lire comme une carte de plus. */
.section-more {
  margin: 2.5rem 0 0;
}

/* Section sur fond légèrement plus clair, pleine largeur : casse la
   monotonie du fond et sépare visuellement les grands blocs. */
.band {
  max-width: none;
  background: rgba(255, 255, 255, 0.018);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

.band > * {
  max-width: 1000px;
  margin-left: auto;
  margin-right: auto;
}

/* Apparition au défilement : léger fondu + montée.
   La classe est retirée dès que l'élément entre dans l'écran (voir script.js).
   Sans JavaScript, tout reste visible : rien ne disparaît.
   Même vitesse que .reveal-left/.reveal-right ci-dessous (2s) : un geste
   posé, pas un sursaut — tous les blocs de la page se révèlent au même
   rythme, qu'ils montent ou qu'ils glissent depuis un côté. */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 2s var(--ease-reveal),
              transform 2s var(--ease-reveal);
}

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

/* Décalage en cascade pour les éléments d'une même grille */
.reveal-delay-1 { transition-delay: 0.15s; }
.reveal-delay-2 { transition-delay: 0.3s; }
.reveal-delay-3 { transition-delay: 0.45s; }

/* Apparition dirigée : variante de .reveal pour une mise en page à deux
   colonnes (ex. À propos) — chaque bloc arrive depuis le côté où il se
   trouve déjà, pour renforcer la lecture gauche → droite, plutôt que de
   simplement monter comme .reveal. */
.reveal-left,
.reveal-right {
  opacity: 0;
  transition: opacity 2s var(--ease-reveal), transform 2s var(--ease-reveal);
}

.reveal-left {
  transform: translateX(-48px);
}

.reveal-right {
  transform: translateX(48px);
}

.reveal-left.is-visible,
.reveal-right.is-visible {
  opacity: 1;
  transform: none;
}

/* Montée franche : variante de .reveal pour des images hautes (les deux
   portraits de la collaboration, page Vin). Elles partent de plus bas
   que les autres blocs, pour qu'on sente vraiment le mouvement bas → haut. */
.reveal.reveal--rise {
  transform: translateY(120px);
}

.reveal.reveal--rise.is-visible {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal.reveal--rise,
  .reveal-left,
  .reveal-right {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ══ RÔLE : TITRE DE SECTION ══════════════════════════════════════
   La marge est en "em" pour qu'elle suive la taille du titre plutôt
   que d'être fixe. Regroupe ici .contact-title, qui dupliquait
   exactement le même token sous un autre nom. */
section h2,
.contact-title {
  font-size: var(--text-title-section);
  font-weight: var(--w-title);
  line-height: 1.08;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-section));
  margin-bottom: 0.5em;
}

section p {
  max-width: var(--measure);
  color: var(--muted);
}

/* "balance" seulement à partir du format tablette : sur un écran étroit,
   équilibrer la longueur des lignes produit deux lignes très courtes au
   lieu d'un bloc plein (constaté sur le chapeau de la section Films). */
@media (min-width: 601px) {
  .section-intro {
    text-wrap: balance;
  }
}

/* Intégration vidéo responsive (ratio 16:9) */
.video-embed {
  position: relative;
  width: 100%;
  padding-top: 56.25%;
  margin: 1.5rem 0;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
}

.video-embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Deux films secondaires côte à côte, sous le film principal : empiler
   trois vidéos de même taille allonge la page sans rien hiérarchiser.
   Ici la première vidéo garde toute la largeur, les suivantes non. */
.video-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.video-duo .video-embed {
  margin: 0 0 0.75rem;
}

.video-duo .video-caption {
  margin: 0;
}

@media (max-width: 760px) {
  .video-duo {
    grid-template-columns: 1fr;
    gap: 2rem;
  }
}

/* Trois films secondaires côte à côte sous un film principal pleine
   largeur (même logique que .video-duo, pour une série de trois). */
.video-trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.25rem;
  margin-top: 2.5rem;
}

.video-trio .video-embed {
  margin: 0 0 0.75rem;
}

.video-trio .video-caption {
  margin: 0;
}

@media (max-width: 900px) {
  .video-trio {
    grid-template-columns: 1fr;
    gap: 2rem;
  }
}

/* Légende sous une vidéo : elle nomme le film et sa durée, elle ne se lit
   pas au fil — d'où la taille "légende" et le ton effacé. Casse normale
   (pas de mono) : c'est une phrase, pas une étiquette. */
.video-caption {
  margin: -0.75rem 0 2.75rem;
  font-size: var(--text-caption);
  line-height: 1.55;
  letter-spacing: 0.009em;
  color: var(--muted);
  max-width: var(--measure);
}

/* Voile tactile posé sur la vidéo, mobile/tactile uniquement (voir
   js/script.js) : un <iframe> capte tout le tactile qui le touche,
   donc sans ce voile le balayage du carrousel de films ne marchait
   que sur le texte en dessous, jamais sur la vidéo elle-même. */
.film-carousel-touch-veil {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  .film-carousel-touch-veil {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: none;
    touch-action: pan-y;
  }
}

/* Carrousel de films : une piste horizontale, une diapositive visible à
   la fois, translatée par pourcentages (jamais de pixels en dur — la
   diapositive garde la bonne position quelle que soit sa taille réelle). */
.film-carousel {
  position: relative;
  margin-top: 1.5rem;
  /* Permet aux flèches ci-dessous d'utiliser des unités "cqw" (% de LA
     LARGEUR de ce conteneur) plutôt que des % de sa hauteur — nécessaire
     car la hauteur de la vidéo dépend de sa largeur (ratio 16:9), pas de
     la hauteur totale du carrousel (vidéo + texte + points). */
  container-type: inline-size;
}

.film-carousel-viewport {
  overflow: hidden;
  /* Pas de border-radius ici : les diapositives voisines doivent pouvoir
     apparaître jusqu'au bord, un arrondi couperait leur coin visible. */
}

.film-carousel-track {
  display: flex;
  /* flex-start, pas center : la vidéo de chaque diapositive doit toujours
     commencer en haut, quelle que soit la longueur du texte en dessous
     (.film-carousel-info n'a qu'un min-height) — sinon sa position
     verticale varie d'une diapositive à l'autre et les flèches ci-dessous
     ne peuvent pas être centrées sur la vidéo pour toutes à la fois. */
  align-items: flex-start;
  gap: 1.5rem;
  /* Le déplacement est piloté par Motion (ressort) en JavaScript ;
     aucune transition CSS ici, les deux se battraient sinon. */
}

/* Diapositive active à ~80% de la largeur, pour que les voisines
   dépassent visiblement de chaque côté ("on aperçoit la suivante,
   un peu cachée derrière"). */
.film-carousel-slide {
  flex: 0 0 80%;
  min-width: 0;
  opacity: 0.4;
  transform: scale(0.92);
  border-radius: 10px;
  overflow: hidden;
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.film-carousel-slide.is-active {
  opacity: 1;
  transform: scale(1);
}

@media (max-width: 700px) {
  .film-carousel-slide {
    flex-basis: 92%;
  }
}

.film-carousel-slide .video-embed {
  margin: 0;
}

.film-carousel-placeholder {
  width: 100%;
  padding-top: 56.25%;
  height: 0;
  margin: 0;
  border-radius: 10px;
}

.film-carousel-info {
  padding-top: 1.25rem;
  min-height: 8.5rem;
}

/* ══ RÔLE : TITRE DE CARTE ═══════════════════════════════════════
   h3 des films, des livres — deux composants qui portaient chacun
   leur propre taille (1,4rem ici, un clamp différent sur .book-card h3)
   pour le même rôle. */
.film-carousel-info h3,
.book-card h3 {
  font-size: var(--text-title-card);
  font-weight: var(--w-title);
  line-height: 1.25;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-card));
  margin-bottom: 0.3rem;
}

/* ══ RÔLE : MÉTADONNÉE (mono) ═════════════════════════════════════
   Casse normale (contrairement à --text-label) : ce sont des données
   à lire, pas des étiquettes. En mono, "2020 · 47 minutes" aligne ses
   chiffres proprement. */
.film-carousel-meta {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  color: var(--accent-1);
  margin-bottom: 0.7rem;
  max-width: none;
}

.film-carousel-info p:not(.film-carousel-meta) {
  color: var(--muted);
  max-width: 65ch;
}

/* Flèches : superposées au lecteur vidéo, discrètes, comme le lightbox */
/* Sans fond ni bordure, même esprit que les flèches de la visionneuse
   photo — elles se posent sur la zone estompée des diapositives voisines
   plutôt que d'attirer l'œil sur la diapositive active. Position verticale
   calée sur le centre de la vidéo (56,25% de ratio 16:9, réduit à la
   largeur de la diapositive active, 80% du carrousel). */
/* Zone cliquable étendue sur toute la hauteur de la vidéo, façon zones de
   clic gauche/droite d'un lecteur vidéo (YouTube, Netflix), plutôt que
   juste la taille de l'icône. La hauteur réelle de la vidéo dépend
   uniquement de sa LARGEUR (ratio 16:9 × 80% du carrousel ici) — d'où
   "cqw" (% de la largeur du conteneur .film-carousel, voir container-type
   ci-dessus) plutôt qu'un % de hauteur, qui inclurait à tort le texte et
   les points de navigation en dessous. 45cqw = 0.5625 (16:9) × 0.8. */
.film-carousel-arrow {
  position: absolute;
  top: 0;
  height: 45cqw;
  width: 4rem;
  max-width: 18%;
  display: flex;
  align-items: center;
  background: none;
  border: none;
  color: var(--ink);
  padding: 0;
  cursor: pointer;
  /* La barre de contrôle de YouTube (lecture, plein écran…) occupe le bas
     du lecteur. On retire cette bande du bouton, sinon la flèche vole les
     clics destinés au bouton plein écran — visible surtout sur mobile, où
     la vidéo est presque pleine largeur et passe donc sous les flèches.
     clip-path retire la zone du test de clic, pas seulement de l'affichage,
     d'où son usage ici plutôt qu'une simple hauteur réduite (qui aurait
     décalé l'icône vers le haut au lieu de la laisser centrée). */
  clip-path: inset(0 0 3rem 0);
  transition: color 0.2s ease, transform 160ms var(--ease-out);
}

.film-carousel-arrow svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (hover: hover) and (pointer: fine) {
  .film-carousel-arrow:hover {
    color: var(--accent-1);
  }
}

.film-carousel-prev:hover { transform: translateX(-3px); }
.film-carousel-next:hover { transform: translateX(3px); }
.film-carousel-prev:active { transform: translateX(-3px) scale(0.9); }
.film-carousel-next:active { transform: translateX(3px) scale(0.9); }

.film-carousel-prev { left: 0; justify-content: flex-start; padding-left: 0.5rem; }
.film-carousel-next { right: 0; justify-content: flex-end; padding-right: 0.5rem; }

@media (max-width: 700px) {
  /* Pleine largeur d'écran sur mobile : la section a un padding latéral
     qui rétrécit la vidéo, on le déborde ici. "margin-left: calc(50% - 50vw)"
     est l'astuce classique du "full-bleed" : se décaler de la moitié du
     parent moins la moitié de l'écran revient à recoller au bord gauche,
     quelle que soit la valeur du padding. */
  .film-carousel {
    width: 100vw;
    margin-left: calc(50% - 50vw);
  }

  .film-carousel-arrow {
    /* 0.5625 (16:9) × 0.92 (flex-basis mobile de .film-carousel-slide) */
    height: 51.75cqw;
  }
}

.film-carousel-dots {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

.film-carousel-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: none;
  padding: 0;
  background: var(--border);
  cursor: pointer;
  transition: background-color 200ms ease, transform 160ms var(--ease-out);
}

.film-carousel-dot[aria-current="true"] {
  background: var(--accent-1);
}

@media (max-width: 600px) {
  .film-carousel-arrow svg {
    width: 20px;
    height: 20px;
  }
}

/* À propos, en deux colonnes */
.about-split {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 3rem;
  align-items: center;
  /* Marges horizontales et largeur max en propre (pas seulement via
     "main section") : sur les pages sans <main> (a-propos.html,
     stages.html), rien d'autre ne les fournissait — le texte touchait
     le bord de l'écran une fois la grille repassée en une colonne sur
     mobile. Longhand uniquement (pas le raccourci "margin"/"padding")
     pour ne pas écraser le padding vertical déjà fourni par
     "main section" là où elle s'applique (page d'accueil). */
  max-width: 1000px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

/* Respiration entre paragraphes consécutifs. "p + p" ne vise que le
   paragraphe qui en suit un autre : jamais le premier, jamais un
   paragraphe isolé — donc aucun espace parasite dans les cartes ou les
   blocs à un seul paragraphe. Ces deux blocs n'avaient aucune marge et
   leurs paragraphes se collaient en un pavé compact. Passé de 1.15em à
   1.4em le 2026-09-16 (retour de Loïc : texte trop serré sur les longs
   récits d'À propos et de Formation). */
.about-text p + p,
.wedding-cta p + p {
  margin-top: 1.4em;
}

/* ══ RÔLE : SOUS-TITRE DE RÉCIT (h3 orphelin) ═══════════════════════
   Les <h3> de .section-wrap ("Les formats de formation") et .about-text
   ("Chefchaouen…", "L'hébergement", page Formation) n'avaient AUCUNE
   règle — ni taille, ni marge — collés au paragraphe au-dessus et en
   dessous (bug corrigé le 2026-09-16). Même rôle visuel que "Titre de
   carte" (.book-card h3), avec en plus une vraie marge verticale
   puisqu'ici le h3 vit au milieu d'un flux de texte, pas seul en tête
   de carte. */
.section-wrap h3,
.about-text h3 {
  font-size: var(--text-title-card);
  font-weight: var(--w-title);
  line-height: 1.25;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-card));
  margin-top: 2.25rem;
  margin-bottom: 0.6rem;
}

/* Premier h3 d'un bloc .about-text (ex. titre "Chefchaouen, Maroc…") :
   pas de marge parasite au-dessus, il ouvre déjà la colonne de texte. */
.about-text > h3:first-child {
  margin-top: 0;
}

.about-cta {
  margin-top: 2rem;
}

.about-photo-frame {
  position: relative;
  display: block;
  height: 100%;
}

.about-photo-link {
  position: relative;
  display: block;
  height: 100%;
  border-radius: 4px;
}

.about-photo-link:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: 4px;
  border-radius: 4px;
}

.about-photo {
  width: 100%;
  height: 100%;
  max-height: 460px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  display: block;
}

/* Indice de clic, toujours visible (pas seulement au survol : doit
   fonctionner au tactile aussi) — même vocabulaire visuel que
   .hero-scroll-cue-label (mono, majuscules, tracking). */
.about-photo-cue {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 1.1rem 1rem 0.8rem;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0) 100%);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-radius: 0 0 4px 4px;
  transition: color 0.25s ease, gap 0.25s ease;
}

@media (hover: hover) and (pointer: fine) {
  .about-photo-link:hover .about-photo-cue {
    color: var(--accent-1);
    gap: 0.6rem;
  }
}

.about-split + .about-split {
  margin-top: 3.5rem;
}

/* Deux coutures sans AUCUN espace sur les pages sans <main> (a-propos.html) :
   .project-intro n'a que 2rem de padding en bas, et .univers-section n'a
   aucun padding en haut (elle compte sur "main section" pour ça, qui ne
   s'applique pas ici) — .about-split n'a lui-même aucune marge propre.
   Résultat : le texte touchait directement le bloc suivant. */
.project-intro + .about-split {
  margin-top: 4rem;
}

.about-split + .univers-section {
  margin-top: 4rem;
}

/* Trait de séparation entre deux récits (ex. les deux stages précédents,
   formation.html) : marque "on change d'histoire" là où deux blocs
   .about-split + .gallery-grid + .photo-credit s'enchaînaient sans
   aucune coupure visuelle. */
.stage-divider {
  border: none;
  border-top: 1px solid var(--border);
  max-width: 140px;
  margin: 3.5rem auto;
}

/* Grande lettrine sur le premier paragraphe de l'intro À propos — esprit
   "carnet"/archive, cohérent avec le reste du système. Jamais Fraunces :
   réservée au héro de l'accueil (voir DESIGN.md). */
.lettrine {
  float: left;
  font-family: var(--font-sans);
  font-size: 3.4rem;
  font-weight: var(--w-title);
  line-height: 0.82;
  padding-top: 0.35rem;
  padding-right: 0.6rem;
  color: var(--ink);
}

/* Numéros 01-04 devant les 4 formats de formation — même famille visuelle
   que les surtitres (.eyebrow) et les autres repères mono du site. */
.format-number {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  color: var(--accent-1);
  margin-top: 2.25rem;
  margin-bottom: 0.3rem;
}

.format-number + h3 {
  margin-top: 0;
}

.section-wrap h2 + .format-number {
  margin-top: 0;
}

/* Section générique hors .book-body / .project-intro (pages sans <main>,
   comme stages.html) : mêmes marges horizontales que le reste de la page,
   sans padding vertical propre — la respiration vient des sections
   voisines (.book-body en a déjà beaucoup). */
.section-wrap {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

@media (max-width: 800px) {
  .about-split {
    grid-template-columns: 1fr;
  }

  .about-photo-frame {
    order: -1;
  }

  .about-photo {
    max-height: 320px;
  }

  /* Formation et Mariage sur l'accueil : titre et texte d'abord, photo
     après (demandé par Loïc, 2026-09-16) — inverse du reste du site, où
     la photo précède le texte sur mobile. Scopé à ces deux sections de
     l'accueil : À propos garde l'ordre par défaut (photo d'abord), tout
     comme les blocs .about-split des autres pages. */
  #stages .about-photo-frame,
  #mariage-teaser .about-photo-frame {
    order: 0;
  }
}

/* Citation en encart : sans serif, l'italique lirait comme une erreur
   plutôt qu'une citation élégante — on la distingue par la graisse et la
   taille à la place, toujours sans liseré ni fond (esprit magazine). */
/* ══ RÔLE : CITATION ══════════════════════════════════════════════
   Fusionne .pull-quote et .testimonial-quote blockquote : deux
   citations du site, un seul rôle. 34ch plutôt que 30 : à 30ch la
   citation tombait à 2 lignes très courtes sur grand écran. */
.pull-quote,
.testimonial-quote blockquote {
  font-weight: 500;
  font-size: var(--text-quote);
  line-height: 1.4;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-quote));
  color: var(--ink);
  max-width: 34ch;
  text-wrap: balance;
}

.pull-quote {
  margin: 2.5rem 0;
  padding: 0;
  border: none;
  background: none;
}

/* Repère visuel scopé à la citation de "Mon approche" (a-propos.html) :
   un trait fin, pas une couleur — la citation reste sans serif ni
   italique (voir plus haut), ce filet suffit à la détacher du texte
   sans rouvrir cette question. */
#approche .pull-quote {
  padding-left: 1.25rem;
  border-left: 2px solid var(--border);
}

/* Interligne plus posé pour les longs récits de la page À propos
   (Gange, Mon approche) — scopé via .about-text--relaxed pour ne pas
   changer .about-text ailleurs sur le site (formation.html, mariage.html,
   index.html). */
.about-text--relaxed p {
  line-height: 1.8;
}

/* Fond alterné sur la liste des festivals (#parcours) : 13 lignes très
   denses, le repère "une ligne sur deux" aide à suivre sans resserrer
   l'espacement. Débordement latéral compensé par une marge négative,
   pour que le fond dépasse légèrement le texte sans le décaler. */
.parcours-list li:nth-child(even) {
  background: var(--bg-elevated);
  margin-inline: -1rem;
  padding-inline: 1rem;
}

/* Témoignages courts en grille, sans photo (contrairement à
   .testimonial sur mariage.html qui en a une) — pensés pour être posés
   à plusieurs sur une page produit, pas un seul en vedette. */
.book-testimonials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
  margin: 2.5rem 0;
}

.book-testimonial {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1.5rem;
}

/* Poids et taille volontairement plus légers que .pull-quote : ce
   rôle-là est pensé pour UNE citation détachée en pleine largeur, pas
   pour deux citations côte à côte dans des cartes étroites — appliqué
   tel quel ici, le texte devenait épais et tassé. */
.book-testimonial blockquote {
  margin: 0 0 0.75rem;
  font-weight: 450;
  font-size: 1.05rem;
  line-height: 1.55;
  color: var(--ink);
}

.book-testimonial cite {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent-1);
}

/* Une seule carte .book-testimonial isolée (accueil : sections Livres et
   Formation) : pas besoin de la grille .book-testimonials, réservée à
   plusieurs témoignages côte à côte — juste une largeur raisonnable et
   un centrage, pour ne pas l'étirer sur toute la largeur de la section. */
.book-testimonial--single {
  max-width: 480px;
  margin: 2.5rem auto 0;
}

/* Posée directement dans une section .about-split (texte + photo à
   gauche/droite), une carte de témoignage ou le carrousel doit s'étendre
   sur les deux colonnes plutôt que se caler dans la première comme un
   troisième élément de la grille. */
.about-split > .book-testimonial,
.about-split > .testimonial-carousel {
  grid-column: 1 / -1;
}

/* Livres */
.book-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
  margin-top: 1.5rem;
}

.book-card {
  position: relative;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1.75rem;
  transition: border-color 0.25s ease, transform 160ms var(--ease-out);
}

/* Recommandation croisée INDE ↔ Nomades : un seul livre mis en avant.
   Sans cette limite, la carte unique s'étirait sur toute la largeur de
   la section (repeat(auto-fit) avec un seul élément) et sa couverture,
   recadrée en hauteur fixe (.book-cover), finissait rognée à l'extrême
   — au point de ne plus montrer qu'un fin bandeau de la photo. */
.book-grid--single {
  grid-template-columns: minmax(0, 320px);
}

/* Soulèvement au survol : uniquement sur souris/trackpad. Sur tactile,
   :hover se déclenche au tap et resterait "collé" jusqu'au tap suivant. */
@media (hover: hover) and (pointer: fine) {
  .book-card:has(.book-link:hover) {
    border-color: var(--accent-1);
    transform: translateY(-3px);
  }
}

/* Retour au clic : fonctionne partout, y compris au tactile. */
.book-card:has(.book-link:active) {
  transform: scale(0.98);
}

/* Typographie du rôle "titre de carte" définie plus haut ; ici,
   seulement l'espacement, propre à cet usage. */
.book-card h3 {
  margin-bottom: 0.4rem;
}

.book-meta {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  color: var(--accent-1);
  margin-bottom: 0.9rem;
  max-width: none;
}

.book-card p:not(.book-meta) {
  color: var(--muted);
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em; /* 0,045 − 0,04 × 0,9rem */
  margin-bottom: 1rem;
}

/* Même technique que .site-header nav a:not(.pill) : un soulignement en
   dégradé dont on anime la largeur, invisible au repos et qui se déploie
   au survol — plutôt qu'un border-bottom statique, toujours visible. */
.book-link {
  color: var(--ink);
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
  font-weight: 600; /* volontairement plus marqué que le corps : c'est un lien d'action */
  text-decoration: none;
  background-image: linear-gradient(var(--accent-1), var(--accent-1));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  padding-bottom: 0.1rem;
  transition: color 0.25s ease, background-size 0.3s ease;
}

.book-link:hover,
.book-link:focus-visible {
  color: var(--accent-1);
  background-size: 100% 1px;
}

/* "Lien étiré" : la carte entière devient cliquable, tout en ne
   gardant qu'un seul vrai lien sémantique pour l'accessibilité. */
.book-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

#contact {
  text-align: left;
}

/* Typographie du rôle "titre de section" définie plus haut ; ici,
   seulement ce qui est spécifique à cet usage : centrage, largeur, marge. */
.contact-title {
  text-align: center;
  max-width: 22ch;
  margin: 0 auto clamp(3rem, 5vw, 5rem);
}

.contact-layout {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 4rem;
  border-top: 1px solid var(--border);
  padding-top: 3rem;
}

.contact-aside {
  border-left: 1px solid var(--border);
  padding-left: 3rem;
}

.contact-aside p {
  max-width: 32ch;
}

.contact-aside-intro {
  color: var(--muted);
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
  margin-bottom: 1rem;
}

.contact-mail {
  color: var(--ink);
  text-decoration: none;
  /* Exception au rôle --text-meta (0,8rem) : une adresse e-mail se
     recopie au clavier, le mono lève l'ambiguïté l/1/I, et un cran de
     plus (0,9rem) la rend plus confortable à cliquer/sélectionner. */
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 450;
  border-bottom: 1px solid var(--accent-1);
  padding-bottom: 0.15rem;
  transition: color 0.2s ease;
  overflow-wrap: anywhere;
}

.contact-mail:hover {
  color: var(--accent-1);
}

.contact-aside-second {
  margin-top: 2.75rem;
}

.contact-links {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.contact-links a {
  color: var(--muted);
  text-decoration: none;
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
  transition: color 0.2s ease;
}

.contact-links a:hover {
  color: var(--ink);
}

@media (max-width: 800px) {
  .contact-layout {
    grid-template-columns: 1fr;
    gap: 3rem;
  }

  .contact-aside {
    border-left: none;
    border-top: 1px solid var(--border);
    padding-left: 0;
    padding-top: 2.5rem;
  }
}

/* Contenu présent pour les lecteurs d'écran, invisible à l'affichage.
   .hidden-field est le nom historique utilisé pour le piège à robots du
   formulaire ; .sr-only est le même motif, pour un usage plus général. */
.sr-only,
.hidden-field {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* Envoi du formulaire raté : contact.php renvoie vers contact.html#envoi-erreur,
   et :target n'affiche ce message que dans ce cas. Même allure que
   .checkout-error (état d'erreur actif). */
.form-send-error {
  display: none;
  margin-top: 1.5rem;
  padding: 0.85rem 1.1rem;
  background: var(--bg-elevated);
  border: 1px solid var(--accent-1);
  border-radius: 8px;
  color: var(--ink);
  font-size: var(--text-body-sm);
}

.form-send-error:target {
  display: block;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  max-width: 560px;
  margin-top: 1.5rem;
}

/* Deux champs côte à côte, qui s'empilent sur mobile */
.field-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.75rem;
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* 0,7rem → --text-label (0,8rem) : le seul texte que la personne DOIT
   lire pour remplir le formulaire méritait mieux que 11px. */
.form-row label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

/* Champs en ligne soulignée plutôt qu'en cadre : plus léger visuellement,
   le trait se colore quand le champ est actif. */
.form-row input,
.form-row textarea {
  font: inherit;
  /* 1rem, pas 0,95 : sous 16px, Safari iOS zoome automatiquement la
     page quand on touche un champ de saisie — bug réel, pas seulement
     une question d'échelle. */
  font-size: var(--text-body);
  color: var(--ink);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.5rem 0;
  resize: vertical;
  transition: border-color 0.25s ease;
}

.form-row input:hover,
.form-row textarea:hover {
  border-bottom-color: rgba(255, 255, 255, 0.25);
}

/* Le remplissage automatique du navigateur (nom, adresse...) impose son
   propre fond clair au champ, illisible sur un site sombre — et
   "background" ne suffit pas à l'écraser, seul ce box-shadow interne
   massif y arrive. Le délai de transition énorme empêche le fond clair
   d'apparaître même une fraction de seconde avant que la règle ne
   s'applique (certains navigateurs l'animent). */
.form-row input:-webkit-autofill,
.form-row input:-webkit-autofill:hover,
.form-row input:-webkit-autofill:focus,
.form-row input:-webkit-autofill:active,
.form-row input:autofill {
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
  -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
  box-shadow: 0 0 0 1000px var(--bg) inset;
  transition: background-color 600000s ease-in-out 0s, box-shadow 600000s ease-in-out 0s;
}

/* Le champ actif se signale par son trait du bas, qui se colore et
   double d'épaisseur. En box-shadow et non en border-width : un trait
   qui passerait de 1 à 2px décalerait le champ d'un pixel à chaque clic.
   Cet indicateur reste parfaitement visible au clavier — il remplace le
   cadre orange, il ne le supprime pas. */
.form-row input:focus,
.form-row textarea:focus {
  outline: none;
  border-bottom-color: var(--accent-1);
  box-shadow: 0 1px 0 0 var(--accent-1);
}

/* Champ en erreur (validation custom, voir js/script.js) : même
   mécanique que le focus juste au-dessus plutôt qu'un deuxième
   langage visuel — le trait passe en orange signal tant que l'erreur
   est affichée. */
.form-row input[aria-invalid="true"],
.form-row textarea[aria-invalid="true"] {
  border-bottom-color: var(--accent-1);
  box-shadow: 0 1px 0 0 var(--accent-1);
}

/* Message d'erreur sous un champ, ou sous un fieldset de choix. Même
   traitement mono/majuscules/tracking que les labels du formulaire,
   mais en orange signal : ce n'est pas une étiquette, c'est un état
   actif (voir "The Signal Rule" dans DESIGN.md). */
.field-error {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-1);
  margin-top: 0.35rem;
}

/* ══ Calendrier "maison" (flatpickr) ═══════════════════════════════
   Le champ date natif (<input type="date">) ne pouvait pas être habillé
   au-delà de sa propre bordure : le calendrier qui s'ouvre au clic est
   dessiné par le navigateur/l'OS, aucun CSS ne l'atteint. Le champ est
   donc devenu un <input type="text"> enrichi par flatpickr (bibliothèque
   JS légère, sans dépendance, chargée en CDN comme Motion) — le vrai
   calendrier est maintenant du HTML/CSS normal, dans les couleurs du
   site. Les règles ci-dessous surchargent son thème par défaut (clair) :
   elles doivent rester chargées APRÈS le CSS de flatpickr (voir l'ordre
   des <link> dans le <head> de chaque page). */
.flatpickr-calendar {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 24px 48px -20px rgba(0, 0, 0, 0.6);
  font-family: var(--font-sans);
}

.flatpickr-calendar.arrowTop::before,
.flatpickr-calendar.arrowTop::after {
  display: none;
}

.flatpickr-months {
  padding: 0.9rem 0.75rem 0.25rem;
}

.flatpickr-current-month {
  font-size: var(--text-body);
  color: var(--ink);
  padding: 0;
}

.flatpickr-current-month .cur-month {
  font-weight: var(--w-title);
  color: var(--ink);
}

.flatpickr-current-month .numInputWrapper {
  color: var(--ink);
}

.flatpickr-current-month input.cur-year {
  font-weight: var(--w-title);
  color: var(--ink);
  background: transparent;
}

.numInputWrapper span.arrowUp,
.numInputWrapper span.arrowDown {
  border: none;
  /* Flatpickr les cache par défaut (opacity: 0) jusqu'au survol exact de
     l'année — ce qui les rendait invisibles en pratique : seuls les
     jours et le mois semblaient sélectionnables (retour de Loïc,
     2026-09-15). Visibles en permanence pour que le changement d'année
     se voie au premier coup d'œil. */
  opacity: 1;
}

.numInputWrapper span.arrowUp:after {
  border-bottom-color: var(--ink-soft);
}

.numInputWrapper span.arrowDown:after {
  border-top-color: var(--ink-soft);
}

.numInputWrapper span.arrowUp:hover:after {
  border-bottom-color: var(--accent-1);
}

.numInputWrapper span.arrowDown:hover:after {
  border-top-color: var(--accent-1);
}

.numInputWrapper span:hover {
  background: transparent;
}

.numInputWrapper:hover {
  background: transparent;
}

.flatpickr-prev-month,
.flatpickr-next-month {
  color: var(--ink-soft);
  fill: var(--ink-soft);
}

.flatpickr-prev-month:hover,
.flatpickr-next-month:hover {
  color: var(--accent-1);
}

.flatpickr-prev-month svg,
.flatpickr-next-month svg {
  fill: currentColor;
}

.flatpickr-weekdays {
  background: transparent;
}

.flatpickr-weekday {
  background: transparent;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.flatpickr-days {
  border: none;
}

.dayContainer {
  padding-bottom: 0.5rem;
}

.flatpickr-day {
  color: var(--ink);
  border-radius: 6px;
}

.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: var(--muted);
  opacity: 0.5;
}

.flatpickr-day:hover {
  background: var(--bg);
  border-color: var(--border);
}

.flatpickr-day.today {
  border-color: var(--accent-1);
}

.flatpickr-day.today:hover {
  background: var(--bg);
}

.flatpickr-day.selected,
.flatpickr-day.selected:hover {
  background: var(--accent-1);
  border-color: var(--accent-1);
  /* Texte sombre sur l'orange, même règle que .pill:hover ailleurs sur
     le site : le blanc plafonne sous le minimum d'accessibilité sur ce
     fond clair. */
  color: #1a0d03;
}

.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover {
  color: var(--muted);
  opacity: 0.3;
}

/* Le champ affiché (altInput) reprend le même style souligné que les
   autres champs du formulaire — flatpickr le pose comme un input tout
   nu par défaut, sans nos règles ".form-row input". */
.form-row input.flatpickr-alt-input {
  font: inherit;
  font-size: var(--text-body);
  color: var(--ink);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 0;
  padding: 0.5rem 0;
}

.form-row input.flatpickr-alt-input:hover {
  border-bottom-color: rgba(255, 255, 255, 0.25);
}

.form-row input.flatpickr-alt-input:focus {
  outline: none;
  border-bottom-color: var(--accent-1);
  box-shadow: 0 1px 0 0 var(--accent-1);
}

.form-choice {
  border: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  align-items: center;
}

.form-choice legend {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 0.7rem;
}

.form-choice label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s ease;
}

.form-choice label:hover {
  color: var(--ink);
}

.form-choice input[type="radio"] {
  accent-color: var(--accent-1);
  cursor: pointer;
}

.form-choice label:has(input:checked) {
  color: var(--ink);
}

.contact-form button {
  align-self: flex-start;
  margin-top: 0.5rem;
}

@media (max-width: 600px) {
  .field-pair {
    grid-template-columns: 1fr;
  }
}

/* Portfolio */
/* Rythme éditorial plutôt qu'une grille uniforme : la première photo
   (portrait d'enfant) prend le double d'espace en carré, une photo de
   paysage/route s'étire en bandeau large — les 6 autres restent en
   tuiles standard. Les tailles sont contrôlées par la grille
   (grid-auto-rows), pas par une hauteur fixe sur chaque image, pour que
   les tuiles agrandies remplissent vraiment leur espace. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 190px;
  gap: 1rem;
  margin-top: 1.5rem;
}

.gallery-grid figure {
  margin: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.gallery-grid figure:first-child {
  grid-column: span 2;
  grid-row: span 2;
}

.gallery-grid figure:nth-child(7) {
  grid-column: span 2;
}

/* Le cadre arrondi est porté par un conteneur dédié : l'image peut ainsi
   zoomer à l'intérieur sans déborder des coins. */
.gallery-grid .photo-frame {
  flex: 1;
  overflow: hidden;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.gallery-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s var(--ease-reveal);
}

/* Sous 900px, moins de colonnes : les tuiles agrandies deviendraient
   disproportionnées, on repasse en grille régulière. */
@media (max-width: 900px) {
  .gallery-grid {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 220px;
  }

  .gallery-grid figure:first-child,
  .gallery-grid figure:nth-child(7) {
    grid-column: span 1;
    grid-row: span 1;
  }
}

/* Zoom au survol : uniquement souris/trackpad, sinon il resterait
   "collé" en zoom après un tap sur mobile. */
@media (hover: hover) and (pointer: fine) {
  .gallery-grid figure:hover img {
    transform: scale(1.04);
  }

  .gallery-grid figure:hover .photo-frame {
    border-color: rgba(255, 255, 255, 0.22);
  }
}

.gallery-grid figcaption {
  margin-top: 0.7rem;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  transition: color 0.25s ease;
}

.gallery-grid figure:hover figcaption {
  color: var(--ink);
}

/* Bande Expositions */
.expo-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.5rem;
}

.expo-strip img {
  flex: 0 0 auto;
  width: 320px;
  height: 220px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  transition: border-color 0.25s ease;
}

@media (hover: hover) and (pointer: fine) {
  .expo-strip img:hover {
    border-color: rgba(255, 255, 255, 0.22);
  }
}

/* Couverture de livre dans la carte */
.book-cover {
  width: 100%;
  height: 200px;
  object-fit: cover;
  border-radius: 4px;
  margin-bottom: 1.2rem;
  border: 1px solid var(--border);
}

footer {
  text-align: center;
  padding: 3.5rem 1.5rem;
  color: var(--muted);
  font-size: var(--text-caption);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-caption));
  border-top: 1px solid var(--border);
}

footer p + p {
  margin-top: 0.6rem;
  opacity: 0.7;
}

footer a {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

footer a:hover {
  color: var(--ink);
}

/* Bouton retour en haut : même langage que .hero-scroll-cue (icône nue,
   sans cercle ni fond) — un repère discret, pas un bouton. Révèle son
   intitulé au survol en étirant la largeur du texte (pas besoin de JS
   pour ce détail-là). */
.back-to-top {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 45;
  /* Force sa propre couche de rendu : sans ça, une iframe vidéo qui passe
     au même endroit à l'écran peut s'afficher par-dessus malgré le
     z-index (les iframes ont leur propre couche de composition dans le
     navigateur, qui ne respecte pas toujours l'ordre CSS habituel). */
  will-change: transform;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: none;
  border: none;
  padding: 0.5rem;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out),
              color 0.2s ease;
}

.back-to-top.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

.back-to-top svg {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.back-to-top-label {
  display: inline-block;
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  transition: max-width 0.3s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .back-to-top:hover {
    color: var(--ink);
  }

  .back-to-top:hover .back-to-top-label {
    max-width: 8rem;
  }
}

@media (max-width: 600px) {
  .back-to-top {
    right: 1rem;
    bottom: 1rem;
  }
}

/* Desktop : plus un repère flottant dans un coin, mais un palier calme
   entre le contenu et le pied de page — sa position dans le HTML a
   changé en conséquence (juste avant <footer>, plus après). L'intitulé
   reste affiché en permanence ici (pas besoin du jeu de révélation au
   survol, réservé au mobile où la place manque). */
@media (min-width: 601px) {
  .back-to-top {
    position: static;
    flex-direction: column;
    gap: 0.85rem;
    margin: 0 auto;
    padding: 3.5rem 1.5rem;
  }

  .back-to-top svg {
    width: 32px;
    height: 32px;
  }

  .back-to-top-label {
    max-width: none;
    text-transform: uppercase;
    letter-spacing: 0.1em;
  }
}

/* ============================================================
   Visionneuse photo (lightbox)
   ============================================================ */
/* Curseur de la loupe système remplacé par un simple pointeur : l'icône
   "loupe +" du navigateur est datée et jure avec le reste. C'est le zoom
   au survol de la photo qui indique qu'elle est cliquable. */
.is-zoomable {
  cursor: pointer;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: auto 1fr auto;
  /* minmax(0, 1fr) et pas "auto" : une rangée auto prend la hauteur
     NATURELLE de la photo (1 300 px pour une image de 2 000 px), la
     scène déborde de l'écran et la légende et le compteur passent
     dessous, invisibles. Ici la rangée vaut exactement l'écran moins
     les marges, et la photo s'y adapte. */
  grid-template-rows: minmax(0, 1fr);
  align-items: center;
  gap: 1rem;
  padding: 5.5rem 1.5rem 3rem;
  background: rgba(6, 6, 8, 0.96);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.lightbox.is-open {
  opacity: 1;
}

.lightbox[hidden] {
  display: none;
}

/* Compteur discret, sous la légende plutôt que dans une barre pleine
   largeur — moins de chrome, la photo reste le seul point d'attention. */
.lightbox-counter {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  /* Pas besoin de font-variant-numeric: tabular-nums ici : en mono,
     tous les caractères ont déjà la même chasse, chiffres compris. */
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  color: var(--muted);
  opacity: 0.65;
  text-align: center;
}

/* Fermeture : une croix seule, sans texte ni fond — juste l'icône,
   discrète en coin. */
.lightbox-close {
  position: absolute;
  top: 1.5rem;
  right: 1.75rem;
  z-index: 1;
  display: flex;
  background: none;
  border: none;
  padding: 0.4rem;
  cursor: pointer;
  color: var(--muted);
  transition: color 0.2s ease, transform 160ms var(--ease-out);
}

/* Contour clavier neutre plutôt qu'orange ici : ce bouton est posé sur
   des photos très variées (parfois déjà orangées), l'anneau --accent-1
   y devenait plus criard qu'ailleurs sur le site. Toujours visible au
   clavier (accessibilité), juste moins voyant sur ce bouton précis. */
.lightbox-close:focus-visible {
  outline-color: var(--ink);
}

@media (hover: hover) and (pointer: fine) {
  .lightbox-close:hover {
    color: var(--ink);
  }
}

.lightbox-close:active {
  transform: scale(0.94);
}

.lightbox-close svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/* Flèches : discrètes, sans pastille ni bordure */
.lightbox-arrow {
  background: none;
  border: none;
  padding: 1rem 0.5rem;
  cursor: pointer;
  color: var(--muted);
  transition: color 0.2s ease, transform 0.2s ease;
}

.lightbox-arrow svg {
  width: 30px;
  height: 30px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (hover: hover) and (pointer: fine) {
  .lightbox-arrow:hover {
    color: var(--ink);
  }

  .lightbox-prev:hover { transform: translateX(-3px); }
  .lightbox-next:hover { transform: translateX(3px); }
}

.lightbox-prev:active { transform: translateX(-3px) scale(0.94); }
.lightbox-next:active { transform: translateX(3px) scale(0.94); }

.lightbox-arrow[hidden] {
  display: none;
}

/* Scène centrale : photo + légende */
.lightbox-stage {
  margin: 0;
  min-width: 0;
  min-height: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
}

.lightbox-frame {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  flex: 1;
  width: 100%;
}

/* Indicateur de chargement : un fin anneau qui tourne */
.lightbox-frame.is-loading::after {
  content: "";
  position: absolute;
  width: 28px;
  height: 28px;
  border: 1.5px solid var(--border);
  border-top-color: var(--accent-1);
  border-radius: 50%;
  animation: lightbox-spin 0.7s linear infinite;
}

@keyframes lightbox-spin {
  to { transform: rotate(360deg); }
}

.lightbox-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 2px;
  box-shadow: 0 30px 90px -30px rgba(0, 0, 0, 0.9);
  opacity: 0;
  transition: opacity 0.35s ease, transform 0.45s var(--ease-reveal);
}

/* La nouvelle photo arrive du côté correspondant au sens de navigation */
.lightbox-image.from-right { transform: translateX(28px); }
.lightbox-image.from-left  { transform: translateX(-28px); }

.lightbox-image.is-ready {
  opacity: 1;
  transform: none;
}

.lightbox-caption {
  flex: 0 0 auto;
  max-width: var(--measure);
  text-align: center;
  font-size: var(--text-caption);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-caption));
  line-height: 1.55;
  color: var(--muted);
}

.lightbox-caption[hidden] {
  display: none;
}

@media (max-width: 700px) {
  .lightbox {
    grid-template-columns: 1fr;
    padding: 4.5rem 1rem 2rem;
  }

  /* Sur mobile, les flèches passent en bas : au pouce plutôt qu'au bord,
     et la photo occupe toute la largeur. Le balayage reste possible. */
  .lightbox-arrow {
    position: absolute;
    bottom: 1.25rem;
    padding: 0.75rem 1.25rem;
  }

  .lightbox-prev { left: 15%; }
  .lightbox-next { right: 15%; }

  .lightbox-arrow svg {
    width: 24px;
    height: 24px;
  }

  .lightbox-stage {
    padding-bottom: 3.5rem;
  }

  .lightbox-topbar {
    padding: 1.25rem;
  }

  .lightbox-close span {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lightbox-image,
  .lightbox-image.from-right,
  .lightbox-image.from-left {
    transform: none;
  }
}

/* Pages livres */
.book-page-header {
  max-width: 760px;
  margin: 0 auto;
  padding: 4rem 1.5rem 2rem;
  text-align: center;
}

/* Fil d'Ariane : remplace l'ancien duo "surtitre + lien retour" par une
   vraie indication de position dans le site. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  margin-bottom: 1.75rem;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  color: var(--muted);
}

.breadcrumb a {
  color: var(--muted);
  text-decoration: none;
  transition: color 0.2s ease;
}

.breadcrumb a:hover {
  color: var(--ink);
}

.breadcrumb span[aria-hidden] {
  color: rgba(255, 255, 255, 0.25);
}

.breadcrumb [aria-current="page"] {
  color: var(--ink);
}

.back-link {
  display: inline-block;
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  margin-bottom: 1.5rem;
}

.back-link:hover {
  color: var(--ink);
}

/* --text-title-page, pas --text-title-section : un <h1> de page doit
   peser pareil sur une page livre et sur une page projet (voir
   .project-hero-caption h1) — avant, cette page avait un titre plus
   discret que projet-inde.html pour un rôle identique. */
.book-page-header h1 {
  font-size: var(--text-title-page);
  font-weight: var(--w-display);
  line-height: 1.02;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-page));
  margin-bottom: 0.6rem;
}

/* Typographie du rôle "chapeau" définie plus haut (--text-lead) ; ici,
   seulement ce qui est spécifique à cet usage : centrage horizontal. */
.book-tagline {
  margin: 0 auto;
  text-wrap: balance;
}

/* Page de confirmation (merci.html) : centrée, plus aérée. */
.page-message {
  padding-top: 7rem;
}

.page-message .pill {
  margin-top: 2rem;
}

/* Page 404 : le grand nombre reprend le traitement mono des repères
   d'archive (prix, compteurs) plutôt que le sans-serif des titres —
   c'est un repère, pas un vrai titre. */
.error-code {
  font-family: var(--font-mono);
  font-size: clamp(4rem, 2.5rem + 8vw, 9rem);
  font-weight: var(--w-label);
  line-height: 1;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

.book-cover-feature {
  max-width: 640px;
  margin: 0 auto 2.5rem;
  padding: 0 1.5rem;
}

/* Vidéo de présentation : plus large que la couverture, alignée sur la
   largeur de .book-photo-strip (1000px) pour rester cohérente avec le
   bloc juste au-dessus. Classe modificateur scopée (.book-cover-feature
   seule est aussi utilisée pour la couverture sur livre-inde.html, qui
   n'a pas de vidéo et doit rester à 640px). */
.book-cover-feature--video {
  max-width: 1000px;
}

.book-cover-feature img {
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid var(--border);
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, 0.6);
}

/* Galerie du livre : une grande photo + une rangée de vignettes en
   dessous pour la changer, comme sur une fiche produit. Même traitement
   flottant (rayon 12px, ombre) que l'ancienne .book-cover-feature —
   c'est la même photo de couverture, juste avec d'autres vues du livre
   à portée de clic.
   La photo passe avant les vignettes dans le HTML pour l'ordre de
   tabulation clavier (on veut pouvoir l'atteindre au clavier sans
   d'abord traverser 8 vignettes), mais s'affiche en second : "order: -1"
   la remonte visuellement au-dessus des vignettes. Conteneur large
   (jusqu'à 960px) pour que les doubles pages du livre, très larges,
   gardent une taille de lecture confortable — une colonne de vignettes
   à côté aurait mangé cette largeur sans rien apporter de plus qu'une
   rangée en dessous. */
.book-gallery {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  max-width: min(92vw, 960px);
  margin: 0 auto 2.5rem;
  padding: 0 1.5rem;
}

.book-gallery-main {
  order: -1;
}

.book-gallery-thumbs {
  display: flex;
  flex-direction: row;
  justify-content: center;
  /* "safe" évite qu'une rangée trop large pour l'écran (mobile) ne
     cache ses premières vignettes hors champ, sans moyen de les
     atteindre en faisant défiler vers la gauche — sans ce mot-clé,
     centrer un conteneur qui déborde peut couper l'accès au début
     du contenu. Ignoré par les très vieux navigateurs, qui gardent
     alors le simple "center" ci-dessus. */
  justify-content: safe center;
  gap: 0.75rem;
  overflow-x: auto;
  padding-bottom: 0.25rem;
  /* Cette rangée défile au doigt sur mobile ; la barre de défilement du
     navigateur n'y ajoute rien (le geste suffit) et casse le look épuré
     des vignettes. scrollbar-width pour Firefox, ::-webkit-scrollbar
     pour Chrome/Safari : les deux sont nécessaires, aucune des deux
     propriétés ne couvre tous les navigateurs à elle seule. */
  scrollbar-width: none;
  /* Sans la barre de défilement, plus rien n'indiquait qu'il y a d'autres
     vignettes à droite : la dernière semblait juste coupée net par le
     bord de l'écran. Ce fondu en bout de rangée remplace ce repère. */
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
}

.book-gallery-thumbs::-webkit-scrollbar {
  display: none;
}

.book-gallery-thumb {
  flex: 0 0 84px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: none;
  cursor: pointer;
  overflow: hidden;
  line-height: 0;
  transition: border-color 0.2s ease, opacity 0.2s ease;
}

.book-gallery-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
}

.book-gallery-thumb:hover {
  border-color: rgba(255, 255, 255, 0.35);
}

.book-gallery-thumb.is-active {
  border-color: var(--accent-1);
}

.book-gallery-thumb:not(.is-active) {
  opacity: 0.7;
}

.book-gallery-main img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  /* Un plafond uniquement en "vh" (hauteur d'écran) grandit avec la
     hauteur de la fenêtre, jamais avec sa largeur : sur un écran large
     mais pas très haut, ce plafond devenait la seule contrainte active
     et la photo restait bloquée à la même taille quelle que soit la
     largeur disponible — elle semblait "ne pas être responsive".
     Un plafond uniquement en pixels fixes (900px) a un défaut inverse :
     sur une fenêtre courte (portable posé sur un petit écran, fenêtre
     pas agrandie), une couverture presque carrée peut alors dépasser
     largement la hauteur visible avant même d'avoir défilé — elle
     semble "coupée", comme si le haut de la photo (un turban, un
     front) remplissait tout l'écran sans qu'on devine qu'il y a un
     visage juste en dessous.
     Les deux valeurs ensemble (min) règlent les deux cas : la largeur
     du conteneur (960px max, voir .book-gallery) fait grandir ou
     rétrécir la photo comme avant, tout en restant toujours contenue
     dans la partie raisonnablement visible de l'écran, quelle que
     soit la hauteur de la fenêtre. */
  max-height: min(900px, 82vh);
  margin: 0 auto;
  border-radius: 12px;
  border: 1px solid var(--border);
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, 0.6);
  transition: opacity 0.2s ease;
  cursor: pointer;
}

@media (max-width: 640px) {
  .book-gallery-thumb {
    flex: 0 0 64px;
  }
}

/* Une grande photo + deux petites empilées, plutôt que 3 vignettes
   à parts égales — la première image (déjà la plus forte des trois
   dans le choix éditorial) prend le double d'espace. */
.book-photo-strip {
  display: grid;
  grid-template-columns: 2fr 1fr;
  grid-template-rows: repeat(2, 1fr);
  gap: 0.75rem;
  height: 460px;
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.book-photo-strip img:first-child {
  grid-row: span 2;
}

.book-photo-strip img {
  width: 100%;
  height: 100%;
  /* Sans ça, la hauteur naturelle de l'image (à sa largeur affichée)
     empêche les deux petites images de faire vraiment la même hauteur :
     min-height vaut "auto" par défaut sur un enfant de grille, qui passe
     alors avant le partage égal du "1fr". */
  min-height: 0;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--border);
  display: block;
}

/* Doubles-pages du livre (contrairement aux photos de reportage : très
   larges et avec du texte à lire) — la grille "1 grande + 2 petites"
   ci-dessus recadrerait le texte. Empilées pleine largeur à la place,
   proportion naturelle conservée, rien de coupé. */
.book-photo-strip--pages {
  display: flex;
  flex-direction: column;
  height: auto;
}

.book-photo-strip--pages img,
.book-photo-strip--pages img:first-child {
  grid-row: auto;
  height: auto;
  object-fit: initial;
}

.book-body {
  max-width: 700px;
  margin: 0 auto;
  padding: 3.5rem 1.5rem 6rem;
}

.book-body > p {
  /* Le conteneur (700px) reste large exprès : il porte aussi la grille
     de specs et les cartes de prix, qui ont besoin de cette largeur pour
     garder leurs colonnes. Seul le texte courant est resserré à une
     mesure de lecture confortable (var(--measure) ≈ 700px → ~87
     caractères par ligne avant cette limite, ~67 après). */
  max-width: var(--measure);
  color: var(--muted);
  margin-bottom: 1.5rem;
}

.spec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1rem;
  margin: 2rem 0;
  padding: 1.5rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
}

/* Page livre : caractéristiques et prix affichés côte à côte juste sous
   les photos, avant le texte de présentation — un visiteur pressé voit
   l'essentiel (ce que c'est, combien ça coûte) avant d'avoir à lire le
   descriptif. Scopée à ce conteneur : .spec-grid, .price-tiers et
   .signup-note restent inchangés partout ailleurs (boutique, stages,
   mentions légales), seules ces déclinaisons imbriquées changent leur
   marge pour s'insérer proprement dans la rangée.
   "align-items: stretch" + la carte prix habillée comme le bloc
   caractéristiques (même fond, même bordure) : sur Nomades, la valeur
   "Collaboration" est longue et rend le bloc caractéristiques bien plus
   haut que la carte prix — sans ça, la carte prix flottait, trop courte,
   à côté d'un grand vide. Les deux "cartes" font maintenant la même
   hauteur, avec le contenu du prix centré dedans. */
.book-specs-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: stretch;
  gap: 1.5rem;
  margin: 2rem 0;
}

.book-specs-row .spec-grid {
  margin: 0;
}

.book-price-col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 220px;
  padding: 1.5rem;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.book-price-col .price-tiers {
  margin: 0;
}

/* La carte prix perd son propre encadré : c'est .book-price-col qui
   porte maintenant le fond et la bordure, pour ne pas empiler deux
   boîtes l'une dans l'autre. */
.book-price-col .price-card {
  padding: 0;
  border: none;
}

.book-price-col .signup-note {
  margin-top: 1rem;
}

@media (max-width: 640px) {
  .book-specs-row {
    grid-template-columns: 1fr;
  }

  .book-price-col {
    max-width: none;
  }
}

.spec-item {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.spec-label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-1);
}

.price-tiers {
  display: flex;
  gap: 1rem;
  margin: 0 0 2rem;
  flex-wrap: wrap;
}

.price-card {
  padding: 1rem 1.5rem;
  border: 1px solid var(--border);
  border-radius: 10px;
}

.price-card .pill {
  margin-top: 1rem;
  width: 100%;
  justify-content: center;
}

/* Pas de text-transform: le contenu ("À partir de") est déjà écrit en
   casse normale dans le HTML — le rôle étiquette n'impose pas les
   capitales, seulement mono + petite taille + tracking. */
.price-label {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 0.2rem;
}

/* Montant mis en avant : mono pour l'alignement naturel des chiffres,
   text-align géré ailleurs — ici juste taille et graisse. */
.price-amount {
  font-family: var(--font-mono);
  font-size: var(--text-amount);
  font-weight: var(--w-label);
}

@media (max-width: 600px) {
  .book-photo-strip {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    height: auto;
  }

  .book-photo-strip img:first-child {
    grid-row: span 1;
  }

  .book-photo-strip img {
    height: 220px;
  }
}

/* Pages légales (mentions légales, politique de confidentialité) */
.book-body h2 {
  font-size: var(--text-title-card);
  font-weight: var(--w-title);
  line-height: 1.25;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-card));
  margin: 2.5rem 0 1rem;
}

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

.book-body ul {
  padding-left: 1.2rem;
  margin-bottom: 1.5rem;
  color: var(--muted);
}

.book-body li {
  margin-bottom: 0.4rem;
}

/* Pages Boutique et Mariage */
.shop-section,
.univers-section,
.wedding-intro,
.wedding-gallery,
.wedding-cta,
.page-cta {
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 1.5rem 4rem;
}

.wedding-intro {
  /* var(--measure) plutôt que 720px : à l'ancienne taille (1,15rem) ce
     conteneur donnait ~78 caractères par ligne, bien au-delà du confort
     de lecture (45-75). */
  max-width: var(--measure);
  padding-top: 1rem;
  text-align: center;
}

/* Le bouton vers la galerie vivait dans l'en-tête, collé sous le
   chapeau ; il descend ici, après la phrase qui explique l'approche —
   on propose de voir les photos une fois qu'on a dit de quoi il s'agit. */
.wedding-intro-cta {
  margin-top: 2rem;
}

.wedding-intro p {
  /* 1rem, pas 1,15 : à cette taille le texte ne gagnait aucune
     hiérarchie, seulement des lignes trop longues — sa présence vient
     de la couleur (--ink, pas --muted) et de sa position, pas de la
     taille. Voir le même raisonnement sur .project-intro p. */
  color: var(--ink);
  font-size: var(--text-body);
  margin-bottom: 1.2rem;
}

.wedding-gallery h2,
.wedding-cta h2,
.page-cta h2 {
  font-size: var(--text-title-section);
  font-weight: var(--w-title);
  line-height: 1.08;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-section));
  margin-bottom: 0.5em;
}

.wedding-gallery .section-intro {
  color: var(--muted);
  max-width: 62ch;
  margin-bottom: 0.5rem;
}

/* Témoignage : photo à gauche, citation à droite */
.testimonial {
  max-width: 1000px;
  margin: 0 auto;
  padding: 2rem 1.5rem 5rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  align-items: center;
}

.testimonial-photo {
  min-height: 420px;
  overflow: hidden;
  border-radius: 4px;
}

.testimonial-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.testimonial-quote {
  margin: 0;
  position: relative;
  padding-top: 2.5rem;
}

/* Grand guillemet ouvrant, en filigrane */
.testimonial-quote::before {
  content: "\201C";
  position: absolute;
  top: -1.5rem;
  left: -0.5rem;
  font-weight: 700;
  /* Fluide : à 6rem fixe, ce guillemet décoratif devenait un pavé de
     96px sur un écran de 375px de large. */
  font-size: clamp(3.5rem, 2rem + 5vw, 6rem);
  line-height: 1;
  color: var(--muted);
  opacity: 0.35;
}

/* Le témoignage de référence était tout en majuscules : illisible sur un
   paragraphe long (on reconnaît les mots à leur silhouette, que les
   capitales effacent). Une graisse intermédiaire (entre le corps de texte
   et les titres) garde la même distinction, en restant lisible. */
.testimonial-quote blockquote {
  margin-bottom: 2.5rem;
}

.testimonial-name {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 0.4rem;
}

/* --text-meta, casse normale : les capitales forcées n'apportaient rien
   ici (contrairement à testimonial-name, qui reste un vrai label). */
.testimonial-place {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-meta));
  color: var(--muted);
}

@media (max-width: 800px) {
  .testimonial {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }

  .testimonial-photo {
    min-height: 320px;
  }
}

/* ══════════════════════════════════════════════════════════════════
   Carrousel de témoignages (accueil, section mariage)
   ----------------------------------------------------------------
   Même moteur JS que le carrousel de films (data-film-carousel,
   voir js/script.js) : flèches, points, clavier et balayage tactile
   sont déjà gérés, seul le CSS change. Chaque diapositive réutilise
   .testimonial-photo / .testimonial-quote (voir plus haut, même
   structure que le témoignage de mariage.html), mais contrairement au
   carrousel de films, une seule diapositive est visible à la fois,
   pleine largeur — pas l'effet "on aperçoit le voisin".
   ══════════════════════════════════════════════════════════════════ */
.testimonial-carousel .film-carousel-slide {
  flex: 0 0 100%;
  opacity: 1;
  transform: none;
  border-radius: 0;
  /* Le témoignage a besoin de déborder verticalement pour son grand
     guillemet décoratif (voir .testimonial-quote::before) : la piste
     du carrousel de films masquait ce débordement (overflow: hidden),
     pensé pour rogner les coins d'une vidéo, pas pour une citation. */
  overflow: visible;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: center;
}

.testimonial-carousel .testimonial-photo {
  min-height: 360px;
  /* Cadre arrondi avec bordure, comme les autres photos du site
     (voir .photo-frame) — .testimonial-photo n'en porte pas par
     défaut sur mariage.html, on l'ajoute ici sans toucher à l'autre
     page. */
  border: 1px solid var(--border);
}

/* home-redesign.css (test en cours, page d'accueil uniquement) ajoute une
   ombre portée à .film-carousel-slide.is-active, pensée pour un pavé
   vidéo plein cadre — sur une diapositive texte + photo avec un espace
   entre les deux, l'ombre engloberait aussi ce vide. On la retire ici. */
.testimonial-carousel .film-carousel-slide.is-active {
  box-shadow: none;
}

/* Les flèches du carrousel de films calculent leur hauteur en cqw pour
   suivre le ratio 16:9 d'une vidéo (voir plus haut, ligne ~1054) —
   sans rapport avec une photo de témoignage. Hauteur fixe à la place,
   calée sur celle du cadre photo pour rester centrée dessus. */
.testimonial-carousel .film-carousel-arrow {
  height: 360px;
  clip-path: none;
}

@media (max-width: 700px) {
  .testimonial-carousel .film-carousel-slide {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  .testimonial-carousel .testimonial-photo {
    min-height: 280px;
  }

  .testimonial-carousel .film-carousel-arrow {
    height: 280px;
  }
}

.wedding-cta,
.page-cta {
  max-width: 720px;
  padding-top: 2rem;
  border-top: 1px solid var(--border);
  text-align: center;
}

.wedding-cta p,
.page-cta p {
  color: var(--muted);
  max-width: 58ch;
  margin: 0 auto 1.75rem;
}

/* Emplacement photo en attente (page Mariage), en attendant les vrais
   visuels. Le texte est un vrai <span> : lisible par un lecteur d'écran. */
.placeholder-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 260px;
  padding: 1.5rem;
  background: var(--bg-elevated);
  border: 1px dashed var(--border);
  border-radius: 4px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Nav resserrée entre tablette et petit desktop : 6 liens + bouton +
   "Loïc Terrier" tiennent tout juste sur cette plage de largeurs. */
@media (min-width: 601px) and (max-width: 950px) {
  :root {
    --header-h: 3.3rem; /* 1,4rem de padding + le nom à 1,15rem */
  }

  .site-header {
    padding: 0.7rem 1.5rem;
  }

  .site-header nav {
    gap: 1rem;
  }

  .site-header nav a:not(.pill) {
    font-size: 0.82rem;
  }

  .site-name {
    font-size: 1.15rem;
  }
}

/* Bouton hamburger : caché par défaut (desktop) */
.burger-toggle {
  display: none;
}

/* Bandeau du bas du menu : il n'a de sens qu'à l'intérieur du menu plein
   écran, donc nulle part ailleurs que sur mobile. */
.nav-footer {
  display: none;
}

/* Mobile : écrans de moins de 600px de large */
@media (max-width: 600px) {
  /* Le nom passe en position absolue plus bas : c'est le bouton burger
     (2,75rem) qui donne sa hauteur à la rangée, pas le nom. */
  :root {
    --header-h: 3.25rem;
  }

  .site-header {
    /* Pas de position: relative ici : ça annulait le position: sticky
       de la règle de base, donc le header défilait avec la page au
       lieu de rester en haut — le bouton "Loïc Terrier" (repositionné
       en absolute juste en dessous) n'a pas besoin de ce changement,
       "sticky" sert tout aussi bien de repère de positionnement. */
    justify-content: flex-end;
    padding: 0.25rem 1.5rem;
    z-index: 100;
  }

  /* Auto-hide au défilement (mobile uniquement) : le bandeau glisse hors
     de l'écran quand on défile vers le bas (plus de place pour la
     photo), et revient dès qu'on remonte, même d'un pixel — c'est ce
     qui le rend consultable sans avoir à revenir en haut de la page.
     Un simple "transform: translateY" : la classe est posée/retirée par
     js/script.js (comparaison de window.scrollY d'un défilement à
     l'autre), désactivé tant que le menu est ouvert (voir le JS). */
  .site-header.header-hidden {
    transform: translateY(-100%);
  }

  /* Le nom passe en position absolue pour pouvoir être centré
     indépendamment du bouton burger, qui reste calé à droite.
     z-index au-dessus du menu plein écran (90) : le nom reste lisible
     par-dessus le menu ouvert, comme le logo de la-guilde.org — sinon
     l'écran perd tout repère une fois le menu déplié. */
  .site-name {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 95;
  }

  /* Bouton hamburger, visible uniquement sur mobile */
  .burger-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    /* 2,75rem = 44px, le minimum d'une cible tactile confortable. Les
       trois barres gardent leur largeur : c'est la zone cliquable qui
       grandit, pas le dessin. */
    width: 2.75rem;
    height: 2.75rem;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    transition: transform 160ms var(--ease-out);
    /* Le menu plein écran (nav, juste en dessous) a son propre z-index
       pour passer au-dessus du reste de la page — sans ce z-index-ci,
       il passait aussi au-dessus du bouton lui-même une fois ouvert,
       rendant le bouton ✕ visible mais impossible à cliquer. */
    position: relative;
    z-index: 95;
  }

  /* Élément mobile par nature : pas de survol à gérer, juste le retour au clic. */
  .burger-toggle:active {
    transform: scale(0.9);
  }

  /* Cercle qui se referme autour de la croix (repris de la-guilde.org) :
     une fois le menu ouvert, ce bouton ne veut plus dire "ouvrir" mais
     "fermer" — le cercle le signale sans ajouter de mot. */
  .burger-toggle::before {
    content: "";
    position: absolute;
    /* -0,15rem sur un bouton de 44px : le cercle fait toujours ~49px,
       comme avant l'agrandissement de la zone tactile. */
    inset: -0.15rem;
    border: 1.5px solid var(--ink);
    border-radius: 50%;
    opacity: 0;
    transform: scale(0.75);
    transition: opacity 0.18s ease, transform 0.28s var(--ease-out);
  }

  .burger-toggle.is-active::before {
    opacity: 1;
    transform: scale(1);
  }

  .burger-toggle span {
    display: block;
    width: 22px;
    height: 2px;
    background: var(--ink);
    border-radius: 2px;
    transition: transform 0.3s ease, opacity 0.2s ease;
  }

  /* Icône burger -> croix quand le menu est ouvert */
  .burger-toggle.is-active span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }
  .burger-toggle.is-active span:nth-child(2) {
    opacity: 0;
  }
  .burger-toggle.is-active span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }

  /* Menu plein écran. Chronométré sur la-guilde.org : chez eux le panneau
     est là en moins de 250 ms, sans rideau qui se déroule — c'est la
     vivacité qui fait l'effet, pas la longueur du mouvement. On garde
     juste un très court décalage entre les liens pour que ça "se déplie"
     au lieu d'apparaître d'un bloc. */
  .site-header nav {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* flex-start + marges auto sur le premier et le dernier lien, plutôt
       que "center" : le résultat est identique tant que ça tient à
       l'écran, mais sur un très petit téléphone les marges auto tombent
       à zéro au lieu de déborder en haut ET en bas — avec "center", la
       première entrée devient alors inatteignable, même en faisant
       défiler. */
    justify-content: flex-start;
    gap: 0.35rem;
    overflow-y: auto;
    /* 9,5rem en bas : la place du bandeau, posé par-dessus en absolu. */
    padding: 5rem 1.5rem 9.5rem;
    background: var(--bg);
    opacity: 0;
    transform: translateY(-1.25rem);
    pointer-events: none;
    /* visibility, et pas seulement opacity : un élément à opacity 0 reste
       dans l'ordre de tabulation et reste annoncé par les lecteurs
       d'écran. Sans cette ligne, les 9 liens du menu FERMÉ étaient
       atteignables au clavier et s'activaient vraiment — on quittait la
       page en tabulant vers un lien invisible. Le délai 0.26s ne coupe
       la visibilité qu'une fois le fondu de fermeture terminé. */
    visibility: hidden;
    transition: opacity 0.2s ease, transform 0.26s var(--ease-out),
                visibility 0s linear 0.26s;
  }

  .site-header nav.is-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    visibility: visible;
    transition: opacity 0.2s ease, transform 0.26s var(--ease-out),
                visibility 0s;
  }

  /* ":not(.pill)" n'est pas décoratif ici : sans lui, ce sélecteur pèse
     moins lourd que la règle générique des liens de nav (plus haut), qui
     impose 0,9rem — les entrées du menu plein écran s'affichaient donc en
     14px au lieu des 20px prévus par --text-nav-overlay, quelle que soit
     la valeur écrite ici. */
  .site-header nav > a:not(.pill) {
    font-size: var(--text-nav-overlay);
    /* Padding, pas seulement du texte : une ligne de 20px ne fait que
       33px de haut, sous le minimum d'une cible tactile. Le gap de la
       colonne est réduit d'autant pour garder le même rythme visuel. */
    padding: 0.6rem 1rem;
    /* Le soulignement de la page courante se cale en bas de la boîte :
       sans ce recalage, il tomberait 0,6rem sous le texte. */
    background-position: 0 calc(100% - 0.55rem);
    opacity: 0;
    transform: translateY(0.6rem);
    /* Cette règle réécrit "transition" en entier : sans y remettre
       color/background-size (posés par .site-header nav a:not(.pill) plus
       haut), le survol changeait bien de couleur et de taille de fond,
       mais d'un coup, sans le liseré animé qu'on a sur ordinateur. */
    transition: opacity 0.26s ease, transform 0.3s var(--ease-out),
                color 0.25s ease, background-size 0.3s ease;
  }

  .site-header nav > a:first-of-type { margin-top: auto; }
  .site-header nav > a:last-of-type { margin-bottom: auto; }

  .site-header nav.is-open > a {
    opacity: 1;
    transform: translateY(0);
  }

  /* Le décalage n'est posé que sur l'état ouvert : à la fermeture, la
     règle ne s'applique plus, tout repart donc ensemble et d'un coup —
     un menu qui se referme entrée par entrée donnerait l'impression de
     traîner.
     4 valeurs, pas une seule : "transition-delay: 0.04s" tout seul se
     recopie sur les 4 propriétés animées (opacity, transform, color,
     background-size, dans cet ordre) — le liseré du survol héritait donc
     du même retard que l'ouverture, jusqu'à 0,28s de plus que sur
     ordinateur. Ici, seules opacity/transform (l'ouverture) sont
     décalées ; color/background-size (le survol) restent à 0s. */
  .site-header nav.is-open > a:nth-of-type(1) { transition-delay: 0.04s, 0.04s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(2) { transition-delay: 0.08s, 0.08s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(3) { transition-delay: 0.12s, 0.12s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(4) { transition-delay: 0.16s, 0.16s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(5) { transition-delay: 0.2s, 0.2s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(6) { transition-delay: 0.24s, 0.24s, 0s, 0s; }
  .site-header nav.is-open > a:nth-of-type(7) { transition-delay: 0.28s, 0.28s, 0s, 0s; }

  /* Le lien de la page actuelle a déjà son soulignement à 100% avant
     l'ouverture (même style que sur ordinateur) : sans animation dédiée,
     aucune transition ne se joue puisque la valeur ne change pas à
     l'ouverture. On rejoue donc le même tracé que sur desktop via une
     keyframe, avec un délai fixe qui tombe au milieu de l'apparition des
     liens (cohérent quelle que soit la position du lien actif dans la liste). */
  @keyframes underline-reveal {
    from { background-size: 0% 1px; }
    to { background-size: 100% 1px; }
  }

  .site-header nav.is-open > a[aria-current="page"] {
    animation: underline-reveal 0.4s var(--ease-out) forwards;
    animation-delay: 0.2s;
  }

  /* ── Bandeau du bas du menu ────────────────────────────────────────
     Posé en absolu plutôt qu'en dernier élément de la colonne : les
     liens restent ainsi centrés dans toute la hauteur de l'écran (ce
     qui n'aurait plus été vrai s'il fallait leur réserver une place en
     bas).
     Le padding haut/gauche/droite (l'espace visuel du cadre autour du
     texte) vit maintenant sur .nav-footer-cta plus bas, pas ici : sinon
     la zone CLIQUABLE du lien (qui ne fait que la taille de son texte,
     "display: block" ne l'étire pas jusqu'aux bords) restait plus petite
     que le cadre orange visible autour — on pouvait cliquer à côté du
     texte, à l'intérieur du cadre, sans rien déclencher. Il ne reste ici
     que le padding bas : c'est la zone des icônes réseaux sociaux, un
     lien séparé, à ne pas toucher. */
  .nav-footer {
    display: block;
    position: absolute;
    left: 1.25rem;
    right: 1.25rem;
    bottom: 1.25rem;
    padding: 0 0 1rem;
    /* Teinte orange très diluée plutôt que le gris des cartes : ce bloc
       est une invitation, pas une information de plus — il doit se
       détacher du noir du menu sans pour autant crier comme un aplat
       d'accent plein. */
    border: 1px solid rgba(255, 154, 60, 0.35);
    border-radius: 14px;
    background: rgba(255, 154, 60, 0.07);
    text-align: center;
    opacity: 0;
    transform: translateY(0.6rem);
    transition: opacity 0.26s ease, transform 0.3s var(--ease-out);
  }

  .site-header nav.is-open .nav-footer {
    opacity: 1;
    transform: translateY(0);
    transition-delay: 0.26s;
  }

  /* Ces liens-ci ne sont pas des entrées de menu : on annule le
     soulignement qui se déploie et la taille des liens du menu.
     Le padding (repris de .nav-footer, voir plus haut) fait de ce lien
     un bloc qui remplit toute la largeur/le haut du cadre : la zone
     cliquable couvre ainsi tout le cadre orange visible au-dessus des
     icônes réseaux sociaux, pas juste le texte. */
  .site-header nav a.nav-footer-cta {
    display: block;
    background-image: none;
    padding: 1.1rem 1.25rem 0.9rem;
    color: var(--ink);
    font-size: 0.9rem;
    line-height: 1.5;
    text-wrap: balance;
  }

  /* L'icône suit le fil du texte au lieu d'être un élément de rangée à
     part : en flex, elle se retrouvait plaquée à gauche d'un texte sur
     deux lignes, détachée de la phrase qu'elle illustre. */
  .nav-footer-cta svg {
    vertical-align: -0.22em;
    margin-right: 0.45rem;
    color: var(--accent-1);
  }

  /* Retour visuel au clic sur toute la zone : sans lui, rien ne
     confirmait que le cadre entier (pas seulement le texte) est
     cliquable, au tactile où il n'y a pas de survol pour le montrer. */
  .site-header nav a.nav-footer-cta:active {
    background: rgba(255, 154, 60, 0.14);
  }

  .nav-footer-social {
    display: flex;
    justify-content: center;
    gap: 0.85rem;
    /* Pas de margin-top ici : l'espace au-dessus vient désormais du
       padding-bottom de .nav-footer-cta (même valeur, 0,9rem) — sinon
       les deux s'additionnaient et l'espace doublait. Le mettre côté
       CTA plutôt qu'ici l'inclut dans SA zone cliquable, pile ce qu'on
       veut : cliquable jusqu'au bord des icônes sociales, pas avant. */
    margin-top: 0;
  }

  .site-header nav .nav-footer-social a {
    display: flex;
    align-items: center;
    justify-content: center;
    /* 44px : cible tactile. Le cercle est le seul contour qui identifie
       ces deux boutons, donc sa bordure doit tenir 3:1 — var(--border)
       (blanc à 9 %) plafonnait à 1,3:1 sur ce fond. */
    width: 2.75rem;
    height: 2.75rem;
    border: 1px solid rgba(255, 255, 255, 0.38);
    border-radius: 50%;
    background-image: none;
    padding-bottom: 0;
    color: var(--ink-soft);
    transition: color 0.25s ease, border-color 0.25s ease;
  }

  .site-header nav .nav-footer-social a:active {
    color: var(--accent-1);
    border-color: var(--accent-1);
  }

  .hero-content {
    padding: 14vh 1.25rem 12vh;
  }

  .showcase {
    margin-top: 2.5rem;
    border-radius: 10px;
  }

  .showcase-image {
    height: 42vh;
  }

  main section {
    padding: 3.5rem 1.25rem;
  }
}

/* ============================================
   Pages Projets (INDE / MAURITANIE)
   Reproduction fidèle : photos pleine largeur + légendes
   ============================================ */

/* Barre de fil d'Ariane au-dessus du hero plein écran : sur le fond
   sombre de la page, pas sur la photo — son contraste ne dépend donc pas
   de la luminosité de la photo affichée (certaines sont très claires). */
.project-breadcrumb-bar {
  padding: 1.5rem 1.5rem 0;
}

.project-breadcrumb-bar .breadcrumb {
  margin-bottom: 0;
}

/* Les autres pages posent ce bandeau hors de <main> ; la page Mariage,
   elle, a un <main> qui enveloppe tout. Sans cette règle, la gouttière
   et la largeur max de "main section" enfermaient la photo dans un
   cadre de 1000px, et la légende en absolu tombait à côté. On préfère
   neutraliser la contrainte plutôt que sortir le <h1> de <main>. */
main > .project-hero {
  max-width: none;
  padding: 0;
}

/* Hero de projet : photo plein écran + titre en surimpression */
.project-hero {
  position: relative;
  width: 100%;
  height: 78vh;
  min-height: 420px;
  overflow: hidden;
}

.project-hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.project-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Deux dégradés superposés. Le premier (voir plus haut dans les
     commentaires d'origine) assombrit le bas pour le chapeau — même
     couleur que le voile des tuiles Projets (.univers-tile::after), mais
     la zone sombre s'étale plus haut avant de s'effacer, pour rester
     lisible même sur une photo claire tout en laissant le haut de la
     photo respirer.
     Le second (nouveau) assombrit le HAUT, sous le bandeau devenu
     transparent au repos (voir ".site-header:has(+ .project-hero)"
     plus haut) — même principe et mêmes valeurs que le premier voile du
     héro d'accueil (.hero-media::after, "The Darken-The-Source Rule") :
     c'est la photo qu'on assombrit, pas le bandeau, pour que les liens
     de nav tiennent leur contraste quelle que soit la photo derrière. */
  background:
    linear-gradient(180deg, rgba(10, 10, 12, 0.6) 0%, rgba(10, 10, 12, 0.5) 10%, transparent 22%),
    linear-gradient(
      0deg,
      rgba(10, 10, 12, 0.88) 0%,
      rgba(10, 10, 12, 0.7) 20%,
      rgba(10, 10, 12, 0.35) 45%,
      rgba(10, 10, 12, 0.08) 68%,
      rgba(10, 10, 12, 0) 85%
    );
}

.project-hero-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2.5rem;
  z-index: 1;
  text-align: center;
  padding: 0 1.5rem;
}

.project-hero-caption .eyebrow {
  color: var(--accent-1);
}

.project-hero-caption h1 {
  font-size: var(--text-title-page);
  font-weight: var(--w-display);
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-page));
  color: var(--ink);
  margin-bottom: 0.4rem;
}

/* Typographie du rôle "chapeau" définie plus haut ; ici, seulement ce
   qui est spécifique à cet usage : centrage horizontal. */
.project-hero-caption p {
  margin: 0 auto;
  text-wrap: balance;
}

/* Repère visuel scopé (#apropos-hero, a-propos.html, 2026-09-18) : la
   hiérarchie type de ce hero monte d'un cran par rapport aux autres
   .project-hero (formation, mariage) — testé en mode live puis validé
   par Loïc. Scopé à cette page plutôt qu'étendu à .project-hero-caption
   partout, pour ne pas changer les deux autres pages qui partagent ce
   composant. */
#apropos-hero .project-hero-caption h1 {
  font-size: calc(var(--text-title-page) * 1.15);
  font-weight: 650;
  letter-spacing: calc(0.045rem - 0.045 * var(--text-title-page) * 1.15);
}

#apropos-hero .project-hero-caption .eyebrow {
  letter-spacing: 0.11em;
  font-size: 0.88rem;
}

#apropos-hero .project-hero-caption .eyebrow::before,
#apropos-hero .project-hero-caption .eyebrow::after {
  max-width: 6rem;
}

#apropos-hero .project-hero-caption p:last-child {
  font-size: var(--text-lead);
}

/* Toujours #apropos-hero (a-propos.html, 2026-09-18) : la légende se lit
   maintenant en premier, ancrée en haut de la photo plutôt qu'en bas —
   testé en mode live (variante « ancré en haut » parmi quatre), validé
   par Loïc. Les deux voiles de ::after sont ceux d'origine, juste
   inversés (haut ↔ bas), aucune nouvelle valeur de couleur. */
#apropos-hero .project-hero-caption {
  top: 2.5rem;
  bottom: auto;
}

#apropos-hero::after {
  background:
    linear-gradient(0deg, rgba(10, 10, 12, 0.6) 0%, rgba(10, 10, 12, 0.5) 10%, transparent 22%),
    linear-gradient(
      180deg,
      rgba(10, 10, 12, 0.88) 0%,
      rgba(10, 10, 12, 0.7) 20%,
      rgba(10, 10, 12, 0.35) 45%,
      rgba(10, 10, 12, 0.08) 68%,
      rgba(10, 10, 12, 0) 85%
    );
}

/* Bloc d'introduction centré */
.project-intro {
  /* var(--measure) plutôt que 720px : mêmes ~78 caractères/ligne que
     .wedding-intro avant correction — le même bug de mesure, sur une
     autre page. */
  max-width: var(--measure);
  margin: 0 auto;
  padding: 5rem 1.5rem 2rem;
  text-align: center;
}

.project-intro p {
  color: var(--ink);
  font-size: var(--text-body);
  margin-bottom: 1.5rem;
  text-align: left;
}

.project-intro p:last-child {
  color: var(--muted);
  font-size: var(--text-body);
  margin-top: 1.5rem;
}

/* Bloc de texte secondaire (récit détaillé), plus discret que l'intro. */
.project-intro-secondary {
  padding-top: 2rem;
}

.project-intro-secondary p,
.project-intro-secondary p:last-child {
  color: var(--muted);
  font-size: var(--text-body);
  margin-top: 0;
  margin-bottom: 1.2rem;
}

/* Titre de section au milieu d'une page projet (ex. "L'équipe"). */
.project-section-title {
  padding-bottom: 1rem;
}

.project-section-title h2 {
  /* Même rôle que les titres de section de la page d'accueil : les pages
     projet ne passent pas par "main section", elles ont donc besoin de
     leur propre règle pour y accéder. */
  font-size: var(--text-title-section);
  font-weight: var(--w-title);
  line-height: 1.08;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-section));
}

/* Carte d'itinéraire */
.project-map {
  max-width: 720px;
  margin: 2rem auto 4rem;
  padding: 0 1.5rem;
}

.project-map img {
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: #efe9dd;
}

/* Séquence de photos : grille éditoriale à 12 colonnes. Les photos ne
   s'empilent plus toutes à la même largeur — certaines occupent toute la
   page, d'autres se regroupent par deux, d'autres sont décalées. */
.project-photos {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 1.5rem 5rem;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 4rem 2rem;
  align-items: end;
}

.project-photo {
  /* Par défaut (mobile et repli) : pleine largeur */
  grid-column: 1 / -1;
}

.project-photo figure {
  margin: 0;
}

.project-photo img {
  width: 100%;
  height: auto;
  /* Plafond de hauteur : sans lui, une photo verticale occupe deux écrans
     et casse le rythme. La photo n'est jamais recadrée, elle se réduit. */
  max-height: 82vh;
  object-fit: contain;
  border-radius: 3px;
  display: block;
  margin: 0 auto;
}

.project-photo figcaption {
  margin-top: 1rem;
  color: var(--muted);
  font-size: var(--text-caption);
  font-weight: 450;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-caption));
  line-height: 1.55;
  max-width: var(--measure-lead);
}

/* Rythme sur grand écran : cycle de 5 photos qui alterne pleine largeur,
   paire côte à côte, et bloc décalé — comme une double page de magazine. */
@media (min-width: 900px) {
  .project-photo:nth-child(5n + 1) {
    grid-column: 1 / -1;
  }

  .project-photo:nth-child(5n + 2) {
    grid-column: 1 / span 6;
  }

  .project-photo:nth-child(5n + 3) {
    grid-column: 7 / span 6;
  }

  .project-photo:nth-child(5n + 4) {
    grid-column: 2 / span 7;
  }

  .project-photo:nth-child(5n + 5) {
    grid-column: 6 / span 7;
  }

  /* Les photos en pleine largeur respirent davantage */
  .project-photo:nth-child(5n + 1) img {
    max-height: 88vh;
  }

  /* Celles regroupées par deux sont ramenées à une hauteur commune pour
     que la paire s'aligne visuellement. */
  .project-photo:nth-child(5n + 2) img,
  .project-photo:nth-child(5n + 3) img {
    max-height: 62vh;
  }
}

/* Section livre / conclusion */
.project-book {
  max-width: 900px;
  margin: 0 auto;
  padding: 4rem 1.5rem 6rem;
  border-top: 1px solid var(--border);
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 2.5rem;
  align-items: center;
}

.project-book img {
  width: 100%;
  height: auto;
  border-radius: 8px;
  border: 1px solid var(--border);
}

/* La photo est cliquable, même bouton de destination que le lien texte
   juste à côté (voir .about-photo-link : même principe ailleurs sur le
   site, où la photo d'une section "texte + photo + bouton" mène déjà au
   même endroit que le bouton). */
.project-book-photo {
  display: block;
  border-radius: 8px;
  transition: opacity 0.2s ease;
}

.project-book-photo:hover,
.project-book-photo:focus-visible {
  opacity: 0.85;
}

.project-book-photo:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: 4px;
}

.project-book h2 {
  font-size: var(--text-title-sub);
  font-weight: var(--w-title);
  line-height: 1.15;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-sub));
  margin-bottom: 1rem;
}

.project-book p {
  color: var(--muted);
  margin-bottom: 1.5rem;
}

/* Section équipe (Mauritanie) */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
  max-width: 1000px;
  margin: 1.5rem auto 0;
  padding: 0 1.5rem;
}

.team-member img {
  width: 100%;
  height: 240px;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--border);
  margin-bottom: 0.9rem;
}

/* Nom sous une photo d'équipe = repère, pas un titre : rôle étiquette
   plutôt qu'un h3 classique. */
.team-member h3 {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  margin-bottom: 0.3rem;
}

.team-member p {
  color: var(--muted);
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
  max-width: none;
}

@media (max-width: 700px) {
  .project-book {
    grid-template-columns: 1fr;
    text-align: center;
  }
  .project-book img {
    max-width: 260px;
    margin: 0 auto;
  }
}

/* ── Bandes "étape par étape" (page Formation) ───────────────────────
   Demandé par Loïc : faire davantage sentir une progression en
   descendant la page. Pas de couleur — voir "The Tinted-Surface Ceiling
   Rule" dans DESIGN.md : l'orange reste réservé au signal (boutons,
   lien actif), il perdrait son sens s'il devenait un fond décoratif.
   À la place, un dégradé de noir très doux (--bg-elevated ↔ --bg,
   deux tons déjà utilisés partout ailleurs pour les cartes), qui
   alterne de sens d'une section à l'autre pour marquer une rupture
   entre chaque étape sans jamais crier. */
.formation-step {
  border-radius: 24px;
  padding: 2.75rem 1.5rem;
  margin: 0 auto 1.5rem;
}

/* Citation posée sur une photo, voile sombre en dessous — même principe
   que .project-hero (le grand visuel en haut de chaque page projet),
   en plus court et avec des coins arrondis puisqu'il s'agit ici d'un
   "chapitre" au milieu de la page, pas de l'ouverture. Repris à la
   demande de Loïc, qui aimait ce traitement dans son ancien PDF de
   stage : une photo, un léger assombrissement, le texte lisible dessus. */
.formation-quote-feature {
  position: relative;
  height: 52vh;
  min-height: 320px;
  max-width: 1000px;
  margin: 0 auto 1.5rem;
  border-radius: 20px;
  overflow: hidden;
}

.formation-quote-feature img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.formation-quote-feature::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(0, 0, 0, 0.78) 0%, rgba(0, 0, 0, 0.1) 55%, rgba(0, 0, 0, 0.3) 100%);
}

.formation-quote-feature blockquote {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2.25rem;
  z-index: 1;
  text-align: center;
  padding: 0 2rem;
  margin: 0;
  font-weight: 500;
  font-size: var(--text-quote);
  line-height: 1.4;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-quote));
  color: var(--ink);
  max-width: 38ch;
  margin-inline: auto;
  text-wrap: balance;
}

.formation-step--a {
  background: linear-gradient(155deg, var(--bg-elevated), var(--bg) 75%);
}

.formation-step--b {
  background: linear-gradient(155deg, var(--bg), var(--bg-elevated) 75%);
}

/* Une journée type, en 3 étapes illustrées — remplace l'ancien bloc de
   texte seul. Chaque étape a sa propre petite bande en dégradé
   (alternée), plus une photo : le rythme "étape par étape" se voit
   autant qu'il se lit. */
.day-steps {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.day-step {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 1.75rem;
  align-items: center;
  border-radius: 18px;
  padding: 1.75rem;
  margin-bottom: 1.25rem;
}

.day-step:nth-child(odd) {
  background: linear-gradient(135deg, var(--bg-elevated), transparent 80%);
}

.day-step:nth-child(even) {
  background: linear-gradient(225deg, var(--bg-elevated), transparent 80%);
}

.day-step img {
  width: 100%;
  height: 180px;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid var(--border);
}

.day-step h3 {
  margin-bottom: 0.5rem;
}

.day-step p {
  margin: 0;
}

@media (max-width: 640px) {
  .day-step {
    grid-template-columns: 1fr;
  }
  .day-step img {
    height: 200px;
  }
}

/* FAQ, une carte par question. Retour de Loïc le 2026-09-15 : l'ancien
   dégradé orange alterné faisait trop "multicolore et laid" — retour
   au traitement carte standard du site (--bg-elevated + --border).
   Passée en accordéon le 2026-09-16 : <details>/<summary> nativement
   accessible au clavier, pas de JS. Un seul <details> ouvert à la fois
   (attribut "name" partagé, groupement natif du navigateur, zéro JS) :
   avec 8 questions, tout laisser ouvrable en même temps aurait juste
   recréé le mur de texte que l'accordéon est censé éviter. La hauteur
   s'anime avec la technique grid-template-rows 0fr → 1fr : pas besoin
   d'une nouvelle dépendance, ça marche dans tous les navigateurs
   modernes, et ça permet d'animer aussi bien l'ouverture que la
   fermeture (une simple transition de max-height ne sait animer que
   l'ouverture). */
.faq-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.faq-item {
  display: grid;
  grid-template-rows: auto 0fr;
  border-radius: 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  overflow: hidden;
  transition: grid-template-rows 0.35s ease;
}

.faq-item[open] {
  grid-template-rows: auto 1fr;
}

.faq-item summary {
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.5rem 1.75rem;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ink);
  list-style: none;
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}

/* Triangle natif du navigateur : une seule règle ne suffit pas, chaque
   moteur a le sien (WebKit/Blink via un pseudo-élément propriétaire,
   Firefox via un ::marker de liste standard). */
.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary::marker {
  content: "";
}

.faq-item summary:hover,
.faq-item summary:focus-visible,
.faq-item[open] summary {
  color: var(--accent-1);
  background: rgba(255, 255, 255, 0.04);
}

.faq-item summary:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: -2px;
}

/* Chevron dessiné en CSS (pas d'image) : un coin de carré, tourné pour
   pointer vers le bas au repos, vers le haut une fois ouvert. */
.faq-item summary::after {
  content: "";
  flex-shrink: 0;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid var(--ink-soft);
  border-bottom: 2px solid var(--ink-soft);
  transform: rotate(45deg);
  transition: transform 0.35s ease, border-color 0.2s ease;
}

.faq-item summary:hover::after,
.faq-item summary:focus-visible::after,
.faq-item[open] summary::after {
  border-color: var(--accent-1);
}

.faq-item[open] summary::after {
  transform: rotate(225deg);
}

.faq-answer {
  grid-row: 2;
  /* Sans ce "display" explicite, le navigateur pose lui-même un
     display: none sur ce contenu tant que <details> n'est pas ouvert
     (comportement natif) — ce qui empêcherait toute transition de
     grid-template-rows au moment de la fermeture (le contenu
     disparaîtrait d'un coup). En le forçant à rester affiché, c'est
     notre grille ci-dessus (et son overflow: hidden) qui décide seule
     de ce qui se voit, dans les deux sens. */
  display: block;
  overflow: hidden;
  /* Petit padding-top : sans lui, le texte de la réponse touche presque
     la question juste au-dessus, les deux se lisent comme collés. */
  padding: 0.4rem 1.75rem 1.75rem;
}

.faq-answer p {
  margin: 0;
  color: var(--muted);
}

.faq-answer p + p {
  margin-top: 0.75rem;
}

@media (prefers-reduced-motion: reduce) {
  .faq-item,
  .faq-item summary::after {
    transition: none;
  }
}

/* ── Grille d'univers (page Projets) ─────────────────────────────────
   La page Projets est une page de découverte : ce sont les PHOTOS qui
   doivent porter le choix, pas un texte à côté d'une vignette. D'où une
   tuile où l'image occupe tout le cadre, avec le titre posé dessus.
   Le dégradé sombre en bas n'est pas décoratif : c'est lui qui garantit
   le contraste du texte quelle que soit la photo placée derrière. */
.univers-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 1.5rem var(--space-section);
}

/* Quand le nombre d'univers publiés est impair, la dernière tuile se
   retrouverait seule sur sa rangée, à côté d'un trou qui ressemble à une
   erreur. Elle prend alors toute la largeur, en format plus panoramique :
   ça se lit comme un choix. La règle se défait d'elle-même dès que le
   nombre d'univers redevient pair (et couvre aussi le cas d'un seul). */
.univers-grid > .univers-tile:last-child:nth-child(odd) {
  grid-column: 1 / -1;
}

.univers-grid > .univers-tile:last-child:nth-child(odd) {
  aspect-ratio: 16 / 9;
}

/* La photo, le voile et le texte occupent la MÊME case de grille : ils se
   superposent sans positionnement absolu. Conséquence importante : si le
   texte est plus haut que la photo (écran étroit, légende longue), la
   case s'agrandit au lieu de laisser le texte déborder par-dessus la
   photo — ce qui arrivait avec un texte positionné en absolu. */
.univers-tile {
  position: relative;
  display: grid;
  grid-template-areas: "stack";
  /* La rangée vaut exactement la hauteur de la tuile (donnée par
     aspect-ratio). Sans le minmax(0, ...), une image apporte sa hauteur
     NATURELLE comme minimum : la rangée devient plus haute que la tuile,
     et le texte, calé en bas de rangée, se fait couper par le cadre.
     Les formats choisis plus bas laissent partout la place au texte. */
  grid-template-rows: minmax(0, 1fr);
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 6px;
  border: 1px solid var(--border);
  text-decoration: none;
  color: var(--ink);
  isolation: isolate;
}

.univers-tile > .univers-tile-media,
.univers-tile > .univers-tile-text {
  grid-area: stack;
  min-width: 0;
}

/* Conteneur de la photo, plus grand que la case visible (marge négative
   verticale) : le léger effet de profondeur au défilement (js/script.js,
   déplacement de quelques pixels lié au scroll) a toujours de la marge à
   parcourir sans jamais découvrir un bord — le overflow:hidden de
   .univers-tile écrête le débordement. Le déplacement s'applique ici,
   sur le conteneur, plutôt que directement sur l'image : elle garde ainsi
   son propre zoom au survol (transform séparé) sans que les deux
   s'écrasent l'un l'autre. */
.univers-tile-media {
  min-height: 0;
  height: calc(100% + 40px);
  margin-block: -20px;
}

.univers-tile img {
  display: block;
  width: 100%;
  height: 100%;
  /* Sans ce 0, la photo impose sa hauteur NATURELLE comme minimum à la
     rangée : la rangée devient plus haute que la tuile et le texte, calé
     en bas de rangée, se retrouve coupé par le cadre. Seul le texte doit
     pouvoir agrandir la rangée. */
  min-height: 0;
  object-fit: cover;
  transform: scale(1.001); /* évite un liseré d'1px au zoom */
  transition: transform 0.7s var(--ease-reveal);
}

.univers-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(10, 10, 12, 0.95) 0%,
    rgba(10, 10, 12, 0.89) 30%,
    rgba(10, 10, 12, 0.64) 50%,
    rgba(10, 10, 12, 0.22) 72%,
    rgba(10, 10, 12, 0) 88%
  );
}

.univers-tile-text {
  align-self: end;
  z-index: 1;
  padding: 1.75rem;
}

.univers-tile :is(h2, h3) {
  font-size: var(--text-title-card);
  font-weight: var(--w-title);
  line-height: 1.15;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-card));
  margin: 0 0 0.35rem;
}

/* Le trait orange sous le titre est le seul signal de couleur : il naît
   au survol et au focus clavier, jamais à l'état de repos. */
.univers-tile :is(h2, h3)::after {
  content: "";
  display: block;
  width: 0;
  height: 1px;
  margin-top: 0.5rem;
  background: var(--accent-1);
  transition: width 0.5s var(--ease-reveal);
}

.univers-tile p {
  margin: 0;
  max-width: var(--measure-narrow);
  color: var(--ink-soft);
  font-size: var(--text-body-sm);
  letter-spacing: 0.009em;
}

.univers-tile-meta {
  display: block;
  margin-bottom: 0.75rem;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.univers-tile:hover img,
.univers-tile:focus-visible img {
  transform: scale(1.05);
}

.univers-tile:hover :is(h2, h3)::after,
.univers-tile:focus-visible :is(h2, h3)::after {
  width: 2.5rem;
}

@media (max-width: 760px) {
  .univers-grid {
    grid-template-columns: 1fr;
    gap: 1rem;
  }
  /* Une colonne : plus de « trou » à combler, donc la tuile seule reprend
     un format haut. Le 21/9 panoramique serait bien trop bas pour porter
     un titre et trois lignes de texte sur 390 px de large. */
  .univers-tile,
  .univers-grid > .univers-tile:last-child:nth-child(odd) {
    aspect-ratio: 4 / 5;
  }
  .univers-tile-text {
    padding: 1.25rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .univers-tile img {
    transition: none;
  }
  .univers-tile:hover img,
  .univers-tile:focus-visible img {
    transform: none;
  }
}

/* ── Galerie de photos carrées (`.gallery-grid--square`) ─────────────
   Les photos de la série « vin » nous arrivent toutes en carré 2000×2000.
   La mosaïque par défaut de .gallery-grid (une tuile double, une tuile
   large, des rangées de 190px) est réglée pour des photos de formats
   variés : appliquée à des carrés, elle les recadrerait en bandeaux et
   perdrait la composition. Ici, toutes les cases sont carrées et de
   même taille — la lecture en planche-contact, qui va bien à un fonds
   photographique.
   Ce bloc est volontairement placé en fin de fichier, APRÈS la requête
   média de .gallery-grid : sans sa propre requête média, il écraserait
   aussi le comportement mobile de la galerie d'origine. */
.gallery-grid--square {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: auto;
}

.gallery-grid--square figure:first-child,
.gallery-grid--square figure:nth-child(7) {
  grid-column: auto;
  grid-row: auto;
}

.gallery-grid--square figure {
  height: auto;
}

.gallery-grid--square .photo-frame {
  flex: none;
  aspect-ratio: 1;
}

@media (max-width: 900px) {
  .gallery-grid--square {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: auto;
  }
}

/* ══ PLANCHE CONTACT (backstage, page Vin) ═══════════════════════
   Variante serrée de la galerie carrée : plus de colonnes, un joint
   fin, comme une planche contact. Placée après .gallery-grid--square
   pour l'emporter, requête média comprise. */
.gallery-grid--planche {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.5rem;
}

@media (max-width: 900px) {
  .gallery-grid--planche {
    gap: 0.35rem;
  }
}

/* ══ COLLABORATION : texte + deux portraits décalés ═══════════════
   Reprend la mise en page du site de Marie-Blandine Roussel : deux
   demi-visages côte à côte qui, ensemble, n'en font qu'un, le second
   descendu d'environ un sixième de sa hauteur. margin-top en % se
   calcule sur la LARGEUR de la colonne : 32 % de largeur ≈ 16 % de
   la hauteur d'un portrait deux fois plus haut que large. */
.collab-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
  max-width: 1180px;
  margin: 0 auto;
  padding: var(--space-section) 1.5rem;
}

.collab-text {
  max-width: var(--measure);
}

.collab-role {
  margin-top: -0.25rem;
  margin-bottom: 1.25rem;
  font-size: var(--text-lead);
  color: var(--ink-soft);
}

.collab-text p + p {
  margin-top: 1em;
}

.photo-credit {
  margin-top: 1.5rem;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  color: var(--muted);
}

.collab-duo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  align-items: start;
}

.collab-duo img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
}

.collab-duo img:nth-child(2) {
  margin-top: 32%;
}

@media (max-width: 900px) {
  .collab-split {
    grid-template-columns: minmax(0, 1fr);
  }

  .collab-duo {
    width: 100%;
    max-width: 26rem;
    margin-inline: auto;
  }
}

/* ══ COUVERTURE DE CARTE : cadrage ═══════════════════════════════
   .book-cover recadre en 200 px de haut. Pour une pochette (titre en
   haut) on garde le haut ; pour un portrait, le tiers supérieur, là
   où sont les yeux. */
.book-cover--top {
  object-position: 50% 0;
}

.book-cover--upper {
  object-position: 50% 30%;
}

/* .book-cover--full : pour les packshots (couverture de livre, pochette de
   DVD, jaquette de clé USB). L'objet entier doit rester visible, sans
   recadrage : object-fit: contain à la place de cover. L'espace vide
   éventuel prend le même fond que les autres cadres "image entière" du
   site (voir .print-photo img ci-dessous), pour ne pas ressembler à un
   bug. */
.book-cover--full {
  object-fit: contain;
  background: var(--bg-elevated);
}

/* ══ TIRAGES D'ART (tirages.html) ═════════════════════════════════
   Une œuvre = une photo entière (jamais recadrée : c'est ce qu'on
   achète) posée sur un passe-partout sombre, et son histoire à côté.
   Les œuvres alternent de côté pour rythmer la page. */
.print-status {
  margin-top: 1.25rem;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.print-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.print-feature + .print-feature {
  margin-top: var(--space-section);
}

.print-feature--reverse .print-photo {
  order: 2;
}

.print-photo img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: min(78vh, 720px);
  margin-inline: auto;
  padding: clamp(0.5rem, 1.5vw, 1rem);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 2px;
}

/* La colonne large suit toujours la photo, même quand elle passe à droite. */
.print-feature--reverse {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
}

.print-text {
  max-width: var(--measure);
}

.print-text h2 {
  font-size: var(--text-title-sub);
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-bottom: 1rem;
}

.print-text p:not(.eyebrow):not(.photo-credit) {
  font-size: var(--text-body);
  color: var(--ink-soft);
}

.print-text p + p {
  margin-top: 1em;
}

.print-formats {
  margin-top: var(--space-section);
}

.price-table-wrap {
  overflow-x: auto;
  margin-top: 1.5rem;
}

.price-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-body);
}

.price-table th,
.price-table td {
  padding: 0.9rem 1rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

.price-table thead th {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.price-table tbody th {
  font-weight: 600;
  color: var(--ink);
}

.price-table td {
  font-family: var(--font-mono);
  color: var(--ink-soft);
}

@media (max-width: 860px) {
  .print-feature {
    grid-template-columns: minmax(0, 1fr);
  }

  .print-feature--reverse .print-photo {
    order: 0;
  }
}

/* ══ LAURIERS (distinctions d'« Akhdar, le sel du désert ») ═════════
   Trois prix vérifiés (mails et lettre de nouvelles de Loïc). La
   branche est un seul fichier (images/laurier.svg) utilisé comme
   masque : sa couleur vient d'ici, pas du fichier, et la branche de
   droite est la même, retournée. Ce ne sont pas les logos officiels
   des festivals : ils pourront les remplacer si Loïc les fournit. */
.laurels {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(0.5rem, 3vw, 2.5rem);
  max-width: none;
  margin: 2.5rem auto 0;
  padding: 0;
}

.laurel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  width: 15rem;
  min-height: 8.5rem;
  padding: 0 3.3rem;
  text-align: center;
}

.laurel::before,
.laurel::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 3.2rem;
  height: 5.9rem;
  background: var(--ink-soft);
  opacity: 0.8;
  -webkit-mask: url("../images/laurier.svg") center / contain no-repeat;
  mask: url("../images/laurier.svg") center / contain no-repeat;
}

.laurel::before {
  left: 0;
  transform: translateY(-50%);
}

.laurel::after {
  right: 0;
  transform: translateY(-50%) scaleX(-1);
}

.laurel-kicker,
.laurel-year {
  font-family: var(--font-mono);
  /* 0.72rem ≈ 11.5px : pas en dessous de 11px, même pour une étiquette */
  font-size: 0.72rem;
  font-weight: var(--w-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.laurel-name {
  font-size: 1.15rem;
  font-weight: var(--w-title);
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.laurel-event {
  margin-top: 0.2rem;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

.laurel-year {
  margin-top: 0.3rem;
}

/* Bandeau de l'accueil : les trois lauriers centrés sous leur titre */
.laurels-band {
  text-align: center;
}

.laurels-band .section-intro {
  margin-inline: auto;
}

.laurels-band .section-more {
  margin-top: 2.5rem;
}

/* Bandeau de l'accueil : les logos des domaines/lieux qui font confiance
   à Loïc. Chaque logo arrive avec sa propre couleur d'origine (souvent
   noire, pensée pour un fond clair) — recolorés en amont en --ink-soft
   pour rester lisibles sur le fond sombre du site et former un ensemble
   cohérent, plutôt que quatre styles disparates. */
.trust-band {
  text-align: center;
}

.trust-logos {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4rem);
  max-width: 74rem;
  margin: 2.75rem auto 0;
  padding: 0;
}

.trust-logos img {
  display: block;
  height: 5.5rem;
  width: auto;
  max-width: 18rem;
  object-fit: contain;
  opacity: 0.7;
  transition: opacity 0.25s var(--ease-out);
}

.trust-logos img:hover,
.trust-logos img:focus-visible {
  opacity: 1;
}

@media (max-width: 700px) {
  .trust-logos img {
    height: 4rem;
    max-width: 12.5rem;
  }
}

/* ══ PARCOURS : expositions, festivals, projections (À propos) ══════ */
.gallery-grid--expo {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: auto;
  margin-top: 2.5rem;
}

.gallery-grid--expo figure:first-child,
.gallery-grid--expo figure:nth-child(7) {
  grid-column: auto;
  grid-row: auto;
}

.gallery-grid--expo figure {
  height: auto;
}

.gallery-grid--expo .photo-frame {
  flex: none;
  aspect-ratio: 3 / 2;
}

/* Une légende de lieu se lit comme une phrase : casse normale */
.gallery-grid--expo figcaption {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--text-meta);
  line-height: 1.45;
}

@media (max-width: 900px) {
  .gallery-grid--expo {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.parcours-list {
  list-style: none;
  max-width: none;
  margin: 2.5rem 0 0;
  padding: 0;
  border-top: 1px solid var(--border);
}

.parcours-list li {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) minmax(0, 18rem);
  gap: 0.35rem 1.5rem;
  align-items: baseline;
  padding: 1rem 0;
  border-bottom: 1px solid var(--border);
}

.parcours-year {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  color: var(--muted);
}

.parcours-event {
  color: var(--ink);
}

.parcours-place {
  color: var(--muted);
}

.parcours-detail {
  font-size: var(--text-body-sm);
  color: var(--ink-soft);
}

.parcours-more {
  margin-top: 1.5rem;
  font-size: var(--text-body-sm);
}

@media (max-width: 700px) {
  .parcours-list li {
    grid-template-columns: 4.5rem minmax(0, 1fr);
  }

  .parcours-detail {
    grid-column: 2;
  }
}

/* ══ FORMULAIRE EN TROIS ÉTAPES (page Contact + bas de l'accueil) ═══
   Sans JavaScript : les trois étapes s'affichent l'une sous l'autre et
   les boutons Continuer / Retour sont masqués (le formulaire marche
   quand même). Avec : .is-stepped, une étape à la fois. */
.step-form {
  gap: 0;
  scroll-margin-top: 6rem;
}

.step-progress {
  height: 1px;
  margin-bottom: 2.25rem;
  background: var(--border);
  overflow: hidden;
}

.step-progress-bar {
  display: block;
  height: 100%;
  background: var(--accent-1);
  transform: scaleX(0.3333);
  transform-origin: left center;
  transition: transform 0.6s var(--ease-reveal);
}

.form-step {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: none;
}

.form-step[hidden] {
  display: none;
}

.step-form:not(.is-stepped) .form-step + .form-step {
  margin-top: 3.5rem;
}

.step-form:not(.is-stepped) .step-progress,
.step-form:not(.is-stepped) [data-step-next],
.step-form:not(.is-stepped) [data-step-back] {
  display: none;
}

/* La légende d'un fieldset n'est pas un enfant flex comme les autres :
   l'espace sous elle se règle par sa propre marge. */
.form-step-head {
  display: block;
  width: 100%;
  margin-bottom: 1.75rem;
  padding: 0;
}

.form-step-count {
  display: block;
  margin-bottom: 0.7rem;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.form-step-title {
  display: block;
  font-size: var(--text-title-sub);
  font-weight: var(--w-title);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.form-step-lead {
  margin: -0.75rem 0 0;
  color: var(--ink-soft);
}

.form-optional {
  text-transform: none;
  letter-spacing: 0;
}

.form-step-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.5rem;
}

.contact-form .form-step-actions button {
  align-self: auto;
  margin-top: 0;
}

.form-step-actions .pill {
  margin-left: auto;
}

.step-back {
  padding: 0.6rem 0;
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: var(--w-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s ease;
}

.step-back:hover {
  color: var(--ink);
}

.step-back:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: 4px;
}

/* Types de projet : de grandes étiquettes à toucher plutôt que de
   petits boutons ronds. Le vrai bouton radio reste dans l'étiquette
   (invisible mais bien là) : clavier et lecteur d'écran fonctionnent. */
.form-choice--chips {
  gap: 0.6rem;
}

.form-choice--chips legend {
  width: 100%;
}

.form-choice--chips label {
  position: relative;
  padding: 0.6rem 1.15rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--ink-soft);
  transition: border-color 0.2s ease, color 0.2s ease;
}

.form-choice--chips input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.form-choice--chips label:hover {
  border-color: rgba(255, 255, 255, 0.3);
}

.form-choice--chips label:has(input:checked) {
  border-color: var(--accent-1);
  color: var(--ink);
}

.form-choice--chips label:has(input:focus-visible) {
  outline: 2px solid var(--accent-1);
  outline-offset: 3px;
}

.form-choice input[type="checkbox"] {
  accent-color: var(--accent-1);
  cursor: pointer;
}

.form-step.is-entering {
  animation: step-in 0.55s var(--ease-reveal) both;
}

.form-step.is-entering-back {
  animation: step-in-back 0.55s var(--ease-reveal) both;
}

@keyframes step-in {
  from { opacity: 0; transform: translateY(22px); }
}

@keyframes step-in-back {
  from { opacity: 0; transform: translateY(-22px); }
}

@media (prefers-reduced-motion: reduce) {
  .form-step.is-entering,
  .form-step.is-entering-back {
    animation: none;
  }

  .step-progress-bar {
    transition: none;
  }
}

/* ── Page Contact : le formulaire à gauche, Loïc à droite ─────────── */
.contact-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 22rem);
  gap: clamp(2.5rem, 7vw, 7rem);
  align-items: start;
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(2rem, 5vw, 4rem) 1.5rem var(--space-section);
}

.contact-page-main .breadcrumb {
  justify-content: flex-start;
  margin-bottom: 1.5rem;
}

.contact-page-title {
  font-size: var(--text-title-section);
  font-weight: var(--w-title);
  line-height: 1.05;
  letter-spacing: calc(0.045rem - 0.04 * var(--text-title-section));
  margin-bottom: 0.75rem;
}

.contact-page-lead {
  max-width: 44ch;
  margin-bottom: 2.5rem;
  color: var(--ink-soft);
}

.contact-page .contact-form {
  max-width: 600px;
  margin-top: 0;
}

.contact-page-direct {
  max-width: 600px;
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  font-size: var(--text-body-sm);
  color: var(--muted);
}

.contact-page-photo {
  position: sticky;
  top: 6rem;
  margin: 0;
}

.contact-page-photo-link {
  display: block;
}

.contact-page-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: 4px;
  transition: opacity 0.2s ease;
}

@media (hover: hover) and (pointer: fine) {
  .contact-page-photo-link:hover img {
    opacity: 0.85;
  }
}

.contact-page-photo figcaption {
  margin-top: 0.8rem;
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  color: var(--muted);
}

@media (max-width: 860px) {
  .contact-page {
    grid-template-columns: 1fr;
  }

  /* Sur téléphone, droit au formulaire : la photo passe sous le
     formulaire, en petit. */
  .contact-page-photo {
    position: static;
    max-width: 14rem;
  }
}

/* ══ VIDÉO À MINIATURE CHOISIE (.video-facade) ═══════════════════════
   Notre propre image à la place de la miniature automatique de
   YouTube ; le lecteur n'est chargé qu'au clic (voir js/script.js). */
.video-facade-button {
  position: absolute;
  inset: 0;
  display: block;
  cursor: pointer;
}

.video-facade-button img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease-reveal);
}

.video-facade-play {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 4.5rem;
  height: 4.5rem;
  margin: -2.25rem 0 0 -2.25rem;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  background: rgba(10, 10, 12, 0.5);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background 0.25s ease, border-color 0.25s ease;
}

.video-facade-play svg {
  width: 1.5rem;
  height: 1.5rem;
  margin-left: 0.15rem;
  fill: var(--ink);
  transition: fill 0.25s ease;
}

.video-facade-button:focus-visible {
  outline: 2px solid var(--accent-1);
  outline-offset: -4px;
}

@media (hover: hover) and (pointer: fine) {
  .video-facade-button:hover img {
    transform: scale(1.03);
  }

  .video-facade-button:hover .video-facade-play {
    background: var(--accent-1);
    border-color: var(--accent-1);
  }

  .video-facade-button:hover .video-facade-play svg {
    fill: #1a0d03;
  }
}

@media (prefers-reduced-motion: reduce) {
  .video-facade-button img {
    transition: none;
  }
}

/* ══ SÉRIE « CARTEL » : la mise en page des tirages, pour les pages
   projet (Inde, Mauritanie). Chaque photo est posée sous passe-partout,
   avec à côté un petit cartel (numéro + légende), comme dans une expo.
   Colonne photo plus large que pour un tirage à vendre (2fr / 1fr) et
   espacement réduit : ici on enchaîne une série, pas trois œuvres. */
.print-series {
  padding-block: clamp(1rem, 3vw, 2rem) var(--space-section);
}

.print-feature--cartel {
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  align-items: end;
}

.print-feature--cartel.print-feature--reverse {
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
}

.print-feature--cartel + .print-feature--cartel {
  margin-top: clamp(3rem, 7vw, 5.5rem);
}

.print-feature--cartel .print-photo img {
  cursor: zoom-in;
}

.print-caption {
  max-width: 30ch;
  font-size: var(--text-body);
  line-height: 1.5;
  color: var(--ink-soft);
}

@media (max-width: 860px) {
  .print-feature--cartel,
  .print-feature--cartel.print-feature--reverse {
    grid-template-columns: 1fr;
  }
}

/* ══ PRIX D'UN FILM : plusieurs tarifs dans la même carte ═══════════ */
.price-options {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0.5rem 0 1.25rem;
  padding: 0;
}

.price-options li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--text-body-sm);
  color: var(--muted);
}

.price-options li span:last-child {
  font-family: var(--font-mono);
  color: var(--ink);
}

/* Accueil : les tuiles d'univers posées dans une section existante */
.univers-grid--embedded {
  max-width: none;
  margin-top: 1.5rem;
  padding: 0;
}

/* Section « L'atelier » : une seule tuile, volontairement plus petite
   que la grille Projets — un peu plus grande que la photo de la section
   Formation juste en dessous, pas une bannière pleine largeur. */
.univers-grid--single {
  max-width: 480px;
}

.univers-grid--single > .univers-tile:last-child:nth-child(odd) {
  aspect-ratio: 4 / 3;
}

/* Sous-parties d'une section d'univers (ex. Les Larmes du Soleil :
   le bâtiment, la vie du lieu, les lodges) */
.univers-subtitle {
  margin-top: 3.5rem;
  font-size: var(--text-title-card);
  font-weight: var(--w-title);
  line-height: 1.15;
  color: var(--ink);
}

.univers-subtitle-intro {
  margin-top: 0.4rem;
  color: var(--muted);
}

/* L'attribut height="1200" du portrait deviendrait sa vraie hauteur :
   height: auto laisse aspect-ratio décider. */
.contact-page-photo img {
  height: auto;
}

/* La photo qui glisse depuis la droite (.reveal-right) dépassait de
   l'écran sur mobile tant qu'elle n'était pas encore apparue : la page
   pouvait alors défiler de côté. "clip" coupe ce dépassement sans créer
   de zone de défilement. */
.about-split,
/* .collab-split a le même motif (texte + photo qui glisse depuis la
   droite) et souffrait donc du même défaut : sur projets-voyage, la page
   pouvait encore se décaler de 24px de côté avant que la photo ne soit
   apparue. */
.collab-split {
  overflow-x: clip;
}
