/* =================================================================
   Youen Frémont — Page projet en planches (style mobile) et entrée-sortie d'une page
   Feuille 4 sur 8. La feuille unique a été découpée le 2026-09-19 aux
   frontières de ses sections, sans qu'une règle ne change de place : les huit
   fichiers se chargent dans l'ordre de base.njk, et cet ordre EST la cascade.
   Les tokens et les surfaces vivent dans style.css, chargée en premier.
   ================================================================= */

/* -----------------------------------------------------------------
  10. Page projet — une suite de planches hautes d'un écran.
   Ici, le style mobile : elles s'empilent et la page se lit comme les autres.
   Le passage à l'horizontale est plus bas, dans « Grand écran ».
   ----------------------------------------------------------------- */
/* La page projet est entièrement sur la surface couleur — elle porte la classe
   dans base.njk. L'aplat ne s'arrête pas au titre : il tient d'un bout à l'autre
   du défilement, et tout le texte prend l'encre de la surface. */
.planche {
  position: relative;
}

/* L'en-tête est fixe et survole tout. Sur une page projet elle passe au-dessus
   de l'aplat, puis d'images bord à bord dont on ne sait rien : ses boutons
   gardent donc un fond noir, le seul qui tienne sur une photo quelconque.
   Le voile, lui, reste ici : à la verticale, le contenu passe dessous et il le
   sépare de l'en-tête. Il ne tombe qu'à l'horizontale, où chaque planche est un
   écran neuf et lui réserve sa hauteur. */
.entete.entete--planches .bouton-rond,
.entete.entete--planches .bouton-pilule {
  background-color: var(--noir);
  color: var(--blanc);
}

/* Sans cette règle ils n'avaient aucun état de survol : le fond noir, plus
   spécifique, l'emportait sur celui des boutons d'en-tête. Ils se remplissent
   donc de l'encre du couple, comme partout ailleurs. */
.entete.entete--planches .bouton-rond:hover,
.entete.entete--planches .bouton-rond:focus-visible,
.entete.entete--planches .bouton-pilule:hover,
.entete.entete--planches .bouton-pilule:focus-visible {
  background-color: var(--remplissage);
  color: var(--remplissage-texte);
}

/* --- Planche 1 : le titre --- */
.planche--titre {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100svh;
  padding: var(--hauteur-entete) var(--marge-page) var(--pas-6);
  text-align: center;
}

/* Même parti que le nom sur le hero : le titre du projet est une affiche, il
   prend la couleur de sa surface. Le texte courant garde --aplat-texte. */
.planche-titre__texte {
  color: var(--titre);
  font-size: var(--texte-xl);
  font-family: var(--police-titre);
  font-weight: var(--graisse-forte);
  line-height: 0.9;
  letter-spacing: var(--approche-titre-planche);
  text-transform: uppercase;
}

/* Le geste attendu n'est pas celui qu'on a l'habitude de faire sur une page :
   il se dit. La flèche est décorative, le mot suffit à la lecture d'écran. */
.planche-titre__indice {
  margin-top: var(--pas-6);
  font-size: var(--texte-xs);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.planche-titre__indice::after {
  content: " ↓";
}

/* --- Planche 2 : la landing --- */
.landing__texte {
  padding: var(--pas-7) var(--marge-page);
}

.landing__titre {
  margin-bottom: var(--pas-4);
  color: var(--titre);
  font-size: var(--texte-titre-projet);
  font-family: var(--police-titre);
  font-weight: var(--graisse-forte);
  line-height: var(--interligne-titre);
  letter-spacing: var(--approche-titre);
  text-transform: uppercase;
}

/* Le sous-titre d'un projet : ce qui sépare deux séries qui portent le même
   titre. Il ne concurrence pas le titre — même approche de libellé que
   l'étiquette de type, corps réduit, encre atténuée.
   Traitement validé tel quel le 2026-09-19. */
.landing__sous-titre {
  margin-top: calc(-1 * var(--pas-3));
  margin-bottom: var(--pas-4);
  color: var(--encre-attenuee);
  font-size: var(--texte-s);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.etiquettes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pas-2);
  margin-bottom: var(--pas-5);
}

/* Sur l'aplat, une pastille franche trancherait comme un corps étranger : c'est
   le fond qui se décale à peine vers l'encre, et le texte garde la sienne. */
.etiquette {
  display: inline-block;
  padding: 0.4em 1em;
  border-radius: var(--rayon-pilule);
  background-color: var(--pastille);
  font-size: var(--texte-xs);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.etiquette--type {
  margin-bottom: var(--pas-3);
}

/* Texte courant, plus grand que les libellés des crédits qui restent au petit
   corps. La mesure est plafonnée : au-delà de ~90 signes par ligne, l'œil perd
   le début de la ligne suivante. */
.projet__texte {
  max-width: 90ch;
  font-size: var(--texte-m);
}

.projet__texte p + p {
  margin-top: var(--pas-4);
}

/* Crédits en grille de deux colonnes sous un filet, comme la référence — et non
   plus en liste de lignes séparées, qui prenait toute la hauteur d'une colonne. */
.credits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pas-5) var(--pas-5);
  margin-top: var(--pas-6);
  padding-top: var(--pas-5);
  border-top: 1px solid var(--trait);
}

.credits dt {
  margin-bottom: var(--pas-1);
  font-size: var(--texte-xs);
  color: var(--encre-attenuee);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.credits dd {
  font-size: var(--texte-s);
  /* Une valeur de crédit peut être une URL ou une adresse d'un seul tenant. À
     320 px la colonne ne fait que 132 px : sans césure forcée, le mot sort de la
     fenêtre et sa fin est coupée. Constaté sur l'adresse de l'hébergeur. */
  overflow-wrap: anywhere;
}

.landing__visuel {
  /* Relatif pour le bouton de son, quand le projet ouvre sur un extrait. */
  position: relative;
  background-color: var(--emplacement);
}

/* Sans image, le bloc n'aurait aucune hauteur propre : la place est tenue. */
.landing__visuel:empty {
  aspect-ratio: 16 / 9;
}

/* Sur la landing, le visuel remplit sa colonne jusqu'aux bords : c'est un
   bandeau d'ouverture, pas une planche. L'extrait suit la même règle que
   l'image — sans quoi il restait dans ses proportions au milieu d'une colonne
   deux fois plus haute que lui, et la couleur de la page se voyait au-dessus et
   en dessous. Le recadrage est assumé ICI, et seulement ici : dans les planches,
   un extrait reste entier. */
.landing__visuel img,
.landing__visuel .visuel-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- Planches de visuels : les images sont le contenu, elles vont bord à bord,
   sans arrondi et dans leurs proportions d'origine. La couleur de la page passe
   entre elles, et dans les bandes d'une image montrée entière. --- */
.planche--visuels {
  display: grid;
  /* La marge est portée par un jeton, que la classe `planche--marge-*` remplace.
     Le gabarit en pose toujours une : chaque disposition a la sienne par défaut,
     et le champ `marge:` d'une planche l'écrase. Un seul réglage tient le
     retrait et l'écart — c'est la même chose à l'œil, la quantité d'aplat. */
  --planche-retrait: 0px;
  --planche-ecart: var(--pas-1);
  gap: var(--planche-ecart);
  padding: var(--planche-retrait);
  background-color: var(--fond);
}

/* Les cinq crans de marge. Ils valent pour toutes les dispositions, images
   comme extraits, et à toutes les largeurs — sur téléphone la planche retombe
   en colonne unique, mais l'air autour des images reste ce qu'on a réglé.
   `aucune` ne met pas le retrait et l'écart à la même valeur : bord à bord veut
   dire aucun retrait autour de la planche, et un filet de la couleur de la page
   entre deux images collées — sans quoi elles se lisent comme une seule. */
.planche--marge-aucune  { --planche-retrait: 0px; --planche-ecart: var(--planche-marge-aucune); }
.planche--marge-fine    { --planche-retrait: var(--planche-marge-fine);    --planche-ecart: var(--planche-marge-fine); }
.planche--marge-moyenne { --planche-retrait: var(--planche-marge-moyenne); --planche-ecart: var(--planche-marge-moyenne); }
.planche--marge-large   { --planche-retrait: var(--planche-marge-large);   --planche-ecart: var(--planche-marge-large); }
.planche--marge-immense { --planche-retrait: var(--planche-marge-immense); --planche-ecart: var(--planche-marge-immense); }

/* Les quatre crans de taille. Seule la diagonale les lit aujourd'hui : partout
   ailleurs une image remplit sa cellule, et un plafond n'y aurait rien à
   plafonner. Le jeton est nommé pour la planche, non pour la diagonale, afin
   qu'une autre disposition puisse s'en servir sans le renommer. */
.planche--taille-petite  { --planche-taille: var(--planche-taille-petite); }
.planche--taille-moyenne { --planche-taille: var(--planche-taille-moyenne); }
.planche--taille-grande  { --planche-taille: var(--planche-taille-grande); }
.planche--taille-immense { --planche-taille: var(--planche-taille-immense); }

.planche--visuels img {
  width: 100%;
}

/* Le cadre rogne : sans ce clip, une image agrandie par un recadrage libre
   déborderait sur les planches voisines. Il ne change rien tant qu'aucun
   recadrage n'agrandit — une image en `cover` tient déjà dans son cadre. */
.planche--visuels picture,
.landing__visuel,
.carte-projet__visuel {
  overflow: hidden;
}

/* --- Le cadrage d'une image rognée ----------------------------------------
   Une image montrée en `cover` est rognée : le cadre décide ce qu'il garde, et
   par défaut c'est le centre — ce qui coupe une tête aussi bien qu'un ciel. Le
   champ `cadrages` d'un projet nomme, média par média, la part à garder.

   Les valeurs sont écrites ici et pas en style en ligne : le gabarit ne fait que
   nommer celle qu'il veut, exactement comme le `data-ratio` d'un extrait vidéo.
   Neuf ancrages, la grille des tiers — assez pour dire « garde le haut » ou
   « garde la droite », et rien à régler au pixel près.

   La règle vaut partout où une image est rognée : la vignette d'une carte, le
   visuel de la planche d'ouverture, et les planches de visuels. */
img[data-cadrage="haut"]        { object-position: 50% 0%; }
img[data-cadrage="bas"]         { object-position: 50% 100%; }
img[data-cadrage="gauche"]      { object-position: 0% 50%; }
img[data-cadrage="droite"]      { object-position: 100% 50%; }
img[data-cadrage="haut-gauche"] { object-position: 0% 0%; }
img[data-cadrage="haut-droite"] { object-position: 100% 0%; }
img[data-cadrage="bas-gauche"]  { object-position: 0% 100%; }
img[data-cadrage="bas-droite"]  { object-position: 100% 100%; }
img[data-cadrage="centre"]      { object-position: 50% 50%; }

/* --- Le recadrage libre ----------------------------------------------------
   Les neuf ancrages ci-dessus disent « garde le haut » ou « garde la droite »,
   et ils suffisent la plupart du temps. Ils ne savent pas agrandir : une photo
   dont on ne veut qu'un quart reste hors de leur portée, et une image montrée
   entière — `pleine`, `scope`, une diagonale — ne se recadre pas du tout.

   Le recadrage libre répond aux deux, avec le même modèle que les ancrages, en
   continu au lieu de neuf crans :

     object-position  quel point de l'image on garde, en pour cent. « haut »
                      n'est rien d'autre que 50 % 0 %.
     scale            de combien on agrandit, autour de ce même point — d'où
                      transform-origin, qui suit les mêmes coordonnées. Sans
                      lui, un zoom ramènerait toujours au centre et les coins
                      seraient hors d'atteinte.

   Les trois nombres ne peuvent pas s'écrire ici : ils sont propres à une image.
   Ils ne s'écrivent pas non plus en style en ligne, que CLAUDE.md interdit. Ils
   sont donc rendus dans un bloc <style> par le gabarit, une règle par média,
   accrochée au nom du média — voir V3/cadres.js. Le HTML ne porte qu'un nom,
   comme pour `data-cadrage` et `data-ratio`.

   Ce qui reste ici, c'est ce qui vaut pour tous. */
img[data-cadre] {
  /* Une image agrandie doit rogner, pas déborder : sans `cover`, une image
     montrée entière garderait ses bandes d'aplat en s'agrandissant, et le zoom
     ne montrerait rien de plus. C'est le recadrage qui décide, il prend donc la
     main sur la disposition — d'où le `:not([data-cadre])` des règles qui
     montrent l'image entière, plus bas. */
  object-fit: cover;
}

/* --- Le cadre d'une image recadrée -----------------------------------------
   Recadrer, c'est choisir ce qu'on garde — pas changer la forme du bloc. Une
   image recadrée garde donc les proportions de la photo, et `ratio:` permet
   d'en choisir d'autres quand on le veut. Le zoom rogne à l'intérieur : il
   n'élargit jamais le cadre.

   La forme est portée par le CADRE et non par l'image, et ce n'est pas un
   détail : `scale` s'applique après le rendu de l'image, si bien qu'une image
   qui porterait elle-même la forme verrait son zoom déborder jusqu'aux bords de
   la cellule. Seul un élément qui n'est pas remplacé sait clipper — d'où la
   forme sur le <picture>, qui a déjà son `overflow: hidden`.

   La forme elle-même s'écrit par média, dans le bloc <style> de la page : voir
   cadres.js. Ce qui reste ici vaut pour tous. */
.visuel-cadre > img,
.visuel-taille > img {
  /* Le cadre tient la boîte, l'image la remplit — d'où les maximums annulés :
     ceux des dispositions qui montrent l'image entière s'adressent à l'image, et
     ils rétréciraient une image qui n'a plus à se dimensionner elle-même. */
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
}

/* Tant qu'aucune image n'est fournie, la place est tenue pour que la mise en
   page se juge, dans les proportions de l'image de la maquette. */
/* Un extrait se montre entier, dans ses proportions : on ne recadre pas un
   cadrage — c'est le métier de Youen. L'affiche occupe la même place que la
   vidéo, donc rien ne saute quand la lecture démarre.

   Trois planches vidéo, qui se distinguent par l'échelle et rien d'autre :
   plein cadre, en encart, en duo. Aucune ne rogne, aucune ne change le lecteur.
   Sur téléphone elles se ressemblent forcément — la colonne fait déjà toute la
   largeur — et c'est à l'horizontale qu'elles se séparent. */
.planche--video,
.planche--video-encart,
.planche--video-duo,
.planche--video-pile {
  display: grid;
  place-items: center;
}

.visuel-video {
  /* Comme les images, et pour la même raison : un élément remplacé resté en
     ligne s'assied sur une ligne de texte, qui lui ajoute sept pixels de
     jambage en dessous. Sur la landing, ces sept pixels suffisaient à faire
     déborder la planche et à y poser une barre de défilement. */
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
}

/* L'extrait et son bouton de son tiennent dans un bloc. C'est le bloc qui
   prend la place dans la planche — plein cadre, encart, moitié —, l'extrait le
   remplit, et le bouton se pose dans son coin. Ajouté le 2026-09-14 avec le
   son : jusque-là l'extrait prenait la place lui-même. */
.extrait {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
}

/* L'encart respire déjà en colonne : c'est là qu'il se distingue du plein cadre
   sur téléphone, où toutes les largeurs se valent. La pile en fait autant, sur
   ses deux extraits. */
.planche--video-encart .extrait,
.planche--video-pile .extrait {
  width: 82%;
}

/* Le bouton de son, dans le coin bas droit de l'extrait, demandé le 2026-09-14.
   Fond noir, comme les boutons de l'en-tête sur une page projet : il se pose
   sur une image dont on ne sait rien. Il n'existe que pour le script — sans
   lui l'extrait garde les commandes du navigateur, qui ont leur volume — et
   pas davantage sous « réduire les animations », où rien ne se lance seul. */
.bouton-son {
  position: absolute;
  right: var(--pas-3);
  bottom: var(--pas-3);
  z-index: 1;
  background-color: var(--noir);
  color: var(--blanc);
}

.bouton-son:hover,
.bouton-son:focus-visible {
  background-color: var(--remplissage);
  color: var(--remplissage-texte);
}

html:not(.js) .bouton-son {
  display: none;
}

/* Un seul dessin pour les deux états : les ondes quand le son est porté, la
   barre quand il ne l'est pas. */
.bouton-son[aria-pressed="true"] .icone-son__barre,
.bouton-son[aria-pressed="false"] .icone-son__ondes {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .bouton-son {
    display: none;
  }
}

/* Un rapport nommé dans le fichier du projet, quand le relevé mesure le
   conteneur et non l'image — un scope tiré d'un 16/9 arrive avec ses bandes.
   Les valeurs sont écrites ici et pas dans le HTML : le gabarit ne fait que
   nommer celle qu'il veut. */
.visuel-video[data-ratio="2.39"] { aspect-ratio: 2.39; }
.visuel-video[data-ratio="2.35"] { aspect-ratio: 2.35; }
.visuel-video[data-ratio="1.85"] { aspect-ratio: 1.85; }
.visuel-video[data-ratio="16/9"] { aspect-ratio: 16 / 9; }
.visuel-video[data-ratio="4/3"] { aspect-ratio: 4 / 3; }
.visuel-video[data-ratio="1/1"] { aspect-ratio: 1; }
.visuel-video[data-ratio="9/16"] { aspect-ratio: 9 / 16; }

/* Le scope se montre sur du noir, et non sur l'aplat : ses bandes font partie
   du cadrage, elles doivent se lire comme telles. C'est la seule planche du
   site qui ne porte pas la couleur de la page. */
.planche--scope {
  background-color: var(--noir);
}

/* Une ligne de texte sous l'image : lieu, matériel, détail de tournage. Elle
   reste petite et à distance — l'image est le contenu, pas elle. */
.planche-legende {
  padding: var(--pas-3) var(--marge-page) var(--pas-4);
  font-size: var(--texte-xs);
  letter-spacing: var(--approche-libelle);
  text-transform: uppercase;
}

.visuel-vide {
  aspect-ratio: 16 / 9;
  background-color: var(--emplacement);
}

/* --- Planche d'appel : dernière planche, un bouton --- */
.planche--appel {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--pas-6);
  padding: var(--hauteur-entete) var(--marge-page) var(--pas-6);
}

/* Au bout d'un projet il n'y a qu'une chose à proposer : le bouton prend donc
   le corps d'un titre de section, et non celui d'un bouton. */
.bouton-appel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45em 1.1em;
  border-radius: var(--rayon-pilule);
  font-size: var(--texte-l);
  font-weight: var(--graisse-forte);
  letter-spacing: var(--approche-titre);
  text-align: center;
  text-transform: uppercase;
}

/* --- Sortie du projet --- */
/* Le même dessin que le bouton du menu, tourné : trois traits, un plus, une
   croix. Il ferme le projet comme celui du menu ferme le panneau. */
.bouton-quitter .icone-plus {
  transform: rotate(45deg);
}

/* Passé le bord droit de la dernière planche, on sort. L'indication n'apparaît
   qu'une fois arrivé au bout, quand elle devient vraie. */
.indice-sortie {
  font-size: var(--texte-xs);
  color: var(--encre-attenuee);
  letter-spacing: var(--approche-libelle);
  text-align: center;
  text-transform: uppercase;
  opacity: 0;
}

body[data-fin] .indice-sortie {
  opacity: 1;
}

/* -----------------------------------------------------------------
   11. Entrée et sortie d'une page.
   Deux mécaniques, une par famille de pages. Une page projet paraît et s'efface
   en fondu ; l'accueil, Work et contact se passent le relais par un volet
   d'aplat qui balaie du bas vers le haut.
   Ni l'une ni l'autre ne porte de contenu : si l'animation ne joue pas —
   mouvement refusé, JavaScript absent —, la page est simplement là, puis n'y
   est plus.
   ----------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .indice-sortie {
    transition: opacity 0.3s ease;
  }

  .planches,
  .entete--planches {
    animation: page-entree var(--duree-page) ease;
  }

  body[data-sortie] .planches,
  body[data-sortie] .entete--planches {
    animation: page-sortie var(--duree-page) ease forwards;
  }

  @keyframes page-entree {
    from {
      opacity: 0;
    }
  }

  @keyframes page-sortie {
    to {
      opacity: 0;
    }
  }

  /* Le volet d'aplat. Un seul pan de couleur, jamais deux : il monte pour
     couvrir la page qu'on quitte, et repart vers le haut pour découvrir celle
     qui arrive. Le mouvement est continu d'une page à l'autre, et la couleur
     change au passage — c'est celle de la page qu'on quitte qui monte, celle de
     la page qui arrive qui s'en va.

     C'est un pseudo-élément et non une balise : rien à poser dans le HTML, rien
     qui traîne dans la page quand le volet ne joue pas. Il n'existe que le
     temps où l'attribut est là — posé par l'en-tête du document à l'arrivée,
     par main.js au départ.

     Tout ce bloc vit sous « mouvement non refusé » : sans lui, aucun volet ne
     se peint, et l'attribut ne recouvre donc jamais rien. */
  html[data-volet]::after {
    position: fixed;
    inset: 0;
    /* Au-dessus de l'en-tête fixe (10). Le menu, lui, est une modale dans le
       calque supérieur du navigateur et reste au-dessus. */
    z-index: 50;
    background-color: var(--aplat);
    pointer-events: none;
    content: "";
  }

  /* À l'arrivée, le volet couvre déjà l'écran au premier pixel peint : c'est
     l'état de repos de la règle ci-dessus, et l'animation ne fait que le
     retirer. Sans quoi la page paraîtrait avant d'être recouverte. */
  html[data-volet="arrivee"]::after {
    animation: volet-arrivee var(--duree-volet) ease forwards;
  }

  /* Au départ c'est l'inverse : il attend hors champ, sous le bas de l'écran. */
  html[data-volet="sortie"]::after {
    transform: translateY(100%);
    animation: volet-sortie var(--duree-volet) ease both;
  }

  @keyframes volet-arrivee {
    to {
      transform: translateY(-100%);
    }
  }

  @keyframes volet-sortie {
    from {
      transform: translateY(100%);
    }

    to {
      transform: translateY(0);
    }
  }
}
