:root {
  --white: #ffffff;
  --ink: #14142b;
  --blue: #1700ae;
  --rose: #ffb4d4;
  --caption: #6b6f8c;

  --display: "Libre Franklin", "Franklin Gothic Medium", Arial, sans-serif;
  --subtitle: "Trebuchet MS", "Segoe UI", Verdana, sans-serif;
  --cap: "Inter", "Source Sans 3", system-ui, sans-serif;

  /* Filet entre deux photos. Assez fin pour que la grille reste une grille,
     assez present pour qu'on voie ou une photo s'arrete — sur un telephone les
     photos s'empilent en pleine largeur et se toucheraient sans lui. */
  --photo-gap: 2px;

  /* Rayon des boutons et des panneaux. Deux crans seulement : le grand pour
     ce qui se clique en plein (boutons, panneau du menu), le petit pour les
     icones nues et les contours de focus, ou un grand rayon flotterait loin
     du trace. */
  --radius: 12px;
  --radius-sm: 6px;

  /* Largeur de la colonne de contenu, la ou une galerie pleine largeur serait
     de trop : les lecteurs video, et la galerie de 3d works qui les cotoie. Un
     seul chiffre pour les deux, sinon la page se lit en deux largeurs. */
  --col-max: 800px;

  --edge: clamp(1.25rem, 4vw, 3.5rem);
  --band: clamp(110px, 15vh, 160px);   /* ~4 cm */
  --gap-cap: clamp(56px, 9vh, 96px);    /* ~3 cm de marge legende */
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
img { max-width: 100%; display: block; }

body {
  background: var(--white);
  color: var(--ink);
  font-family: var(--cap);
  -webkit-font-smoothing: antialiased;
}

/* ---------- Spine : Accueil / Menu / Contacts (une seule feuille verticale) ---------- */
.scroller {
  height: 100dvh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
}
.slide {
  position: relative;
  min-height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  place-items: center;
  padding: var(--edge);
}

/* Coins nom / annee */
.corner {
  position: absolute;
  bottom: var(--edge);
  font-family: var(--subtitle);
  font-size: clamp(0.95rem, 1.7vw, 1.2rem);
  letter-spacing: 0.02em;
  color: var(--ink);
}
.corner.left { left: var(--edge); }
.corner.right { right: var(--edge); }

/* ---------- Accueil ---------- */
.home__center { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.home__arrow {
  color: var(--blue);
  margin-top: clamp(1.5rem, 5vh, 3rem);
  background: none; border: 0; padding: 0.5rem; cursor: pointer; line-height: 0;
}
.home__arrow svg { display: block; width: clamp(28px, 3vw, 40px); height: auto; }
.home__arrow:focus-visible { outline: 2px solid var(--blue); outline-offset: 6px; border-radius: var(--radius-sm); }
.home__logo { margin: 0; line-height: 0; }
/* Borne aussi en hauteur : sans le troisieme terme, un viewport bas (telephone
   en paysage) donnait un logotype si haut que la fleche et les coins passaient
   sous la flottaison, et `scroll-snap-stop: always` les rendait inatteignables.
   1.9156 = 1226/640, le rapport du fichier. */
.home__logo img { width: min(78vw, 680px, 52vh * 1.9156); height: auto; }

/* Intro : le logotype eclot depuis un point, jouee 1x/session (voir spine.js) */
.home__logo, .home__arrow { opacity: 0; }

#home.play .home__logo { animation: bloom 1.75s linear both; }
#home.play .home__arrow { animation: fadeArrow 0.9s ease both; animation-delay: 1.9s; }
#home.play .home__arrow svg { animation: bob 2.4s ease-in-out 2.9s infinite; }

/* Releve image par image sur la video de la cliente, d'ou les paliers plutot
   qu'une courbe nommee : aucun easing standard ne fait les deux choses qu'elle
   fait. D'abord une croissance geometrique — l'echelle est multipliee par
   environ deux a chaque image, si bien que le logo reste un point pendant un
   tiers du temps puis surgit ; ensuite un depassement de 7,8 % vers 0,7 s, qui
   se resorbe lentement jusqu'a 1,7 s. C'est un ressort, pas un fondu.

   Les pourcentages sont les temps reels rapportes aux 1,75 s de l'animation ;
   le timing est `linear` pour que ce soient bien ces paliers qui portent la
   courbe, et non un easing qui viendrait la deformer. */
@keyframes bloom {
    0%   { opacity: 0;   transform: scale(0); }
    4.7% { opacity: 0.3; transform: scale(0.005); }
    9.5% { opacity: 0.65; transform: scale(0.027); }
   14.3% { opacity: 0.87; transform: scale(0.078); }
   19.0% { opacity: 0.95; transform: scale(0.203); }
   21.4% { opacity: 1;   transform: scale(0.388); }
   23.8% { transform: scale(0.834); }
   26.2% { transform: scale(0.950); }
   28.6% { transform: scale(1.005); }
   33.3% { transform: scale(1.057); }
   40.5% { transform: scale(1.078); }   /* le pic */
   47.6% { transform: scale(1.062); }
   54.8% { transform: scale(1.039); }
   61.9% { transform: scale(1.023); }
   69.0% { transform: scale(1.014); }
   76.2% { transform: scale(1.007); }
   83.3% { transform: scale(1.004); }
  100%   { opacity: 1;   transform: scale(1); }
}
@keyframes fadeArrow { from { opacity: 0; } to { opacity: 1; } }
@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* Intro deja vue cette session : etat final direct */
#home.done .home__logo, #home.done .home__arrow { opacity: 1; animation: none; }

/* ---------- Menu ---------- */
/* Le rose du bandeau photography tient toute la feuille du menu, bord a bord.
   Il est pose sur la section et non sur un enfant : le fond couvre alors aussi
   le padding de .slide et les mentions d'annee et de nom, qui y sont posees en
   absolu — aucune lisiere blanche nulle part.

   L'encre suit le fond, comme .band--rose la porte sur les pages travaux : la
   presentation en herite au lieu de se la redeclarer. --caption, le gris
   qu'elle avait, ne tient que 2.98:1 sur ce rose ; --ink en donne 10.9. */
#menu { background: var(--rose); color: var(--ink); }

/* Trois bandes plutot qu'une pile : la presentation, les boutons, puis une bande
   vide de meme part que la premiere. Les deux 1fr se partagent a egalite ce qui
   reste une fois les boutons mesures, donc les boutons tombent exactement au
   milieu de la feuille — et l'ecart au texte n'est plus un gap devine mais ce
   que la hauteur laisse.

   `align-self: stretch` est necessaire : .slide centre ses enfants, le bloc
   ferait sinon sa hauteur de contenu et n'aurait rien a repartir.

   Si le texte est long — un telephone etroit — sa bande grandit au-dela de sa
   part et les boutons redescendent : 1fr vaut minmax(auto, 1fr), le contenu
   prime, rien ne deborde. */
.menu__wrap {
  align-self: stretch;
  width: min(100%, 60ch);
  display: grid;
  grid-template-rows: 1fr auto 1fr;
  justify-items: center;
  gap: clamp(2rem, 6vh, 4rem);
}
.menu__about { align-self: center; }

/* Les enfants se mesurent sur le panneau, pas sur la fenetre : en vw ils
   depasseraient de sa doublure sur un ecran etroit. */
.menu__nav {
  display: flex; flex-direction: column;
  gap: clamp(0.9rem, 2.2vh, 1.5rem); width: min(360px, 100%);
}

/* Presentation : mesure bornee en ch, pas en px — la longueur de ligne se regle
   sur la taille du texte, ce qui la garde lisible quel que soit le zoom.
   .intro, en tete des pages travaux, partage la mise en page ; seul le placement
   dans la page differe (plus bas, section Pages travaux). */
.menu__about, .intro {
  text-wrap: pretty;
  line-height: 1.65;
}
.intro {
  text-align: center;
  font-size: clamp(0.95rem, 1.6vw, 1.05rem); color: var(--caption);
}

/* Placee avant les boutons, la presentation ouvre la page au lieu de la clore :
   elle prend l'encre du texte courant et un cran de taille, la voix d'un
   paragraphe qu'on lit et non d'une legende qu'on survole. */
/* Justifiee, la presentation forme un bloc net sous le titre, et aucun mot n'est
   coupe : pas de `hyphens`, les mots restent entiers d'un bout a l'autre.

   Ce qui pose une contrainte de mesure. Justifier sans cesure oblige le
   navigateur a etirer les espaces d'une ligne pour rattraper le mot qui n'y
   tient pas ; plus la ligne est courte en nombre de mots, plus l'etirement se
   voit. Un corps plus petit met plus de mots par ligne, donc plus d'espaces
   pour absorber l'ecart : la taille reduite et l'absence de cesure se tiennent,
   ce n'est pas seulement une question de gout.

   La page entiere est declaree lang="en" : le paragraphe n'a rien a ajouter. */
.menu__about {
  margin: 0; max-width: min(52ch, 100%);
  font-size: clamp(0.9rem, 1.55vw, 1.05rem);
  text-align: justify;
}

/* Bouton : case bleue -> au survol/clic, case blanche, texte bleu, contour bleu */
.menu__btn {
  display: block; text-align: center; text-decoration: none;
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.1rem, 2.5vw, 1.5rem); letter-spacing: 0.02em;
  padding: clamp(0.9rem, 2.5vh, 1.4rem);
  background: var(--blue); color: var(--white);
  border: 2px solid var(--blue); border-radius: var(--radius); cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.menu__btn:hover, .menu__btn:active, .menu__btn:focus-visible {
  background: var(--white); color: var(--blue); outline: none;
}

/* ---------- Contacts ---------- */
/* Le bas de la feuille plonge vers le bleu de la charte plutot que vers un
   parme isole : la teinte est mixee depuis --blue, elle suit donc la charte si
   celle-ci bouge. 42 % est le point ou le pied de page est franchement bleu
   tout en gardant --ink au-dessus de 7:1 dessus — les mentions d'annee et de
   nom se posent la.

   Le blanc tient jusqu'a 70 % : le titre et la liste de contacts restent sur
   fond blanc, et la couleur ne monte que sous eux au lieu de leur passer
   derriere. */
#contacts {
  background: linear-gradient(180deg, var(--white) 70%,
              color-mix(in oklab, var(--blue) 42%, var(--white)) 100%);
}
.contacts__wrap { display: flex; flex-direction: column; align-items: center; gap: clamp(1.5rem, 5vh, 3rem); }
.contacts__title {
  font-family: var(--display); font-weight: 600;
  font-size: clamp(2.2rem, 8vw, 5rem); letter-spacing: 0.01em;
  text-transform: lowercase; margin: 0;
}
.contacts__list { display: flex; flex-direction: column; gap: clamp(0.9rem, 2.5vh, 1.5rem); }
.contacts__row { display: flex; align-items: center; gap: 1rem; }
.contacts__row .txt { font-family: var(--subtitle); font-size: clamp(1rem, 2.2vw, 1.35rem); letter-spacing: 0.02em; }

/* Profils : deux pastilles cliquables cote a cote, qui repassent l'une sous
   l'autre quand la place manque. Elles reprennent l'inversion des boutons du
   menu — contour bleu sur blanc, plein bleu au survol — pour qu'on lise tout de
   suite qu'elles se cliquent, sans peser autant que les boutons du menu. */
.contacts__links { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem; }
.social {
  display: inline-flex; align-items: center; gap: 0.6rem;
  padding: 0.7rem 1.25rem;
  font-family: var(--subtitle); font-size: clamp(0.95rem, 2vw, 1.15rem);
  letter-spacing: 0.02em; text-decoration: none; white-space: nowrap;
  color: var(--blue); background: var(--white);
  border: 2px solid var(--blue); border-radius: var(--radius);
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
/* L'icone se mesure sur le texte : elle suit le clamp sans avoir le sien. */
.social svg { width: 1.3em; height: 1.3em; flex: 0 0 auto; }
.social:hover, .social:focus-visible {
  background: var(--blue); color: var(--white); outline: none;
  transform: translateY(-2px);
}
.social:active { transform: none; }

/* Etoile-fleur 10 branches : 1 tour a l'arrivee sur Contacts (voir spine.js) */
.flower { width: clamp(30px, 3.5vw, 44px); height: clamp(30px, 3.5vw, 44px); color: var(--blue); flex: 0 0 auto; }
#contacts.spin .flower { animation: turn 2.6s cubic-bezier(0.33, 0, 0.2, 1) both; }
@keyframes turn { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ---------- Pages travaux (photography / videography / 3d) ---------- */
.band {
  height: var(--band); display: flex; align-items: center;
  justify-content: space-between; gap: 1rem; padding: 0 var(--edge);
}
.band h1 {
  margin: 0; font-family: var(--display); font-weight: 600;
  font-size: clamp(1.8rem, 6vw, 4rem); letter-spacing: 0.01em; text-transform: lowercase;
}

/* L'encre est portee par le bandeau et non par le titre : le hamburger, trace
   en currentColor, prend alors la bonne couleur sur rose comme sur bleu sans
   qu'aucune regle ne la lui repete. */
.band--rose { background: var(--rose); color: var(--ink); }
.band--blue { background: var(--blue); color: var(--white); }

/* Menu de navigation des pages travaux. Bati sur <details>/<summary> : le
   navigateur fournit l'ouverture, l'etat `open`, l'activation clavier et
   l'annonce « developpe/reduit » aux lecteurs d'ecran. Le JS ne sert qu'au
   confort (Echap, clic exterieur) et son absence ne casse rien. */
.nav { position: relative; flex: 0 0 auto; }
.nav__toggle {
  list-style: none;                 /* retire le triangle de <summary> */
  /* 0.45rem et non 0.4 : autour d'un SVG de 30 px, cela donne 44,4 px de cote,
     juste au-dessus du minimum de 44 px pour une cible tactile. */
  cursor: pointer; line-height: 0; padding: 0.45rem; color: inherit;
}
.nav__toggle::-webkit-details-marker { display: none; }   /* idem, Safari */
.nav__toggle svg { display: block; width: 30px; height: 30px; }
.nav__toggle:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; border-radius: var(--radius-sm); }

/* Le panneau sort du bandeau : il est absolu dans .nav, et le bandeau ne coupe
   pas ses debordements. z-index le garde au-dessus des photos qui suivent. */
.nav__panel {
  position: absolute; z-index: 10; top: calc(100% + 0.5rem); right: 0;
  display: flex; flex-direction: column; min-width: 13rem;
  background: var(--white); border: 2px solid var(--blue);
  /* Les liens touchent le bord : sans le clip, leur fond au survol depasserait
     des coins arrondis. Plus sur que d'arrondir le premier et le dernier. */
  border-radius: var(--radius); overflow: hidden;
  box-shadow: 0 12px 28px rgb(20 20 43 / 0.18);
}
.nav__panel a {
  padding: 0.8rem 1.2rem; text-decoration: none; white-space: nowrap;
  font-family: var(--display); font-weight: 500; font-size: 1rem; letter-spacing: 0.02em;
  color: var(--blue); background: var(--white);
  transition: background 0.15s ease, color 0.15s ease;
}
.nav__panel a + a { border-top: 1px solid rgb(23 0 174 / 0.15); }
.nav__panel a:hover, .nav__panel a:focus-visible {
  background: var(--blue); color: var(--white); outline: none;
}

/* Page courante : signalee a l'oeil comme elle l'est deja aux lecteurs d'ecran
   par aria-current, plutot que masquee ou rendue non cliquable. */
.nav__panel a[aria-current="page"] { color: var(--caption); cursor: default; }
.nav__panel a[aria-current="page"]::before { content: "— "; }
.nav__panel a[aria-current="page"]:hover,
.nav__panel a[aria-current="page"]:focus-visible {
  background: var(--white); color: var(--caption);
}
/* Sans cette regle, le focus clavier heritait du bleu plein : --caption sur
   --blue tombe a 2,60:1, sous le seuil AA, et le contour etait desactive.
   Le survol se passe de contour, le focus non — il faut situer le curseur. */
.nav__panel a[aria-current="page"]:focus-visible {
  outline: 2px solid var(--blue); outline-offset: -2px;
}

/* Presentation en tete de page travaux. Elle vit AU-DESSUS de `gallery:start` /
   `videos:start` : tout ce qui suit ce marqueur est reecrit par les builders, et
   un paragraphe pose la disparaitrait au prochain build.
   Le padding porte la mesure plutot que le max-width, box-sizing etant border-box :
   la ligne fait bien 54ch, gouttieres en plus. */
.intro {
  margin: var(--gap-cap) auto;
  padding-inline: var(--edge);
  max-width: calc(54ch + 2 * var(--edge));
}

/* Une serie de photos, et le seul endroit d'ou vient l'espacement vertical de la
   galerie : le gap separe ses lignes, la marge la separe de la serie suivante.
   Meme valeur pour les deux, et la meme qu'entre deux photos d'une ligne — la
   galerie se lit d'un trait, filet constant dans les deux directions.

   La mise en page d'origine espaçait les lignes de --gap-cap et separait les
   series par la hauteur du libelle. Les libelles partis, ce blanc n'avait plus
   rien a porter. Attention a ne pas le reintroduire ailleurs : deux mecanismes
   d'espacement vertical s'additionnent en silence. */
.serie {
  margin-top: var(--photo-gap);
  display: flex; flex-direction: column; gap: var(--photo-gap);
}

/* Lignes justifiees : chaque ligne remplit la largeur, formats preserves.
   flex-grow = ratio de la photo -> hauteur commune, largeurs proportionnelles.
   Le gap est retire de l'espace libre avant la repartition, donc la ligne fait
   toujours exactement 100 % : le filet ne pousse rien dehors. */
.row { display: flex; gap: var(--photo-gap); }
.ph { flex-basis: 0; flex-shrink: 1; }

/* Photos reelles : meme calcul, mais le ratio arrive du manifeste via --ar au
   lieu d'une classe de format figee. Le flex porte sur le <picture> : c'est lui
   l'enfant direct de .row, l'<img> ne fait que remplir. */
picture.ph {
  display: block; flex-grow: var(--ar); min-width: 0;
  aspect-ratio: var(--ar); background: #f2f2f5;
}
picture.ph img { width: 100%; height: 100%; object-fit: cover; }

/* 3d works ne porte qu'une image : seule sur sa ligne, la justification lui
   donne 100 % de la largeur, et elle ecrase les lecteurs qui la suivent, cales
   sur --col-max. On la ramene dans la meme colonne. Les galeries photo restent
   pleine largeur, c'est leur parti pris. */
.page-3d .serie { max-width: var(--col-max); margin-inline: auto; }

/* Sous 640px, trois photos cote a cote deviennent illisibles : une par ligne,
   toujours collees. Doit rester aligne sur STACK_BREAKPOINT (tools/build-gallery.py),
   qui s'en sert pour calculer l'attribut sizes. */
/* Telephone : une photo par ligne. Le gap de .row vaut alors pour les deux axes,
   c'est lui qui empeche les photos empilees de se toucher. */
/* Ecran bas — telephone en paysage, petite tablette. Les trois bandes du volet
   menu (presentation, boutons, bande vide de meme part) reclament alors plus que
   la feuille : mesure a 320x568 le contenu depassait de 71 px, a 812x375 de 129.
   On rend au bloc sa hauteur naturelle et c'est .slide qui le centre, comme les
   deux autres volets ; la bande vide et l'un des deux gaps disparaissent avec. */
@media (max-height: 620px) {
  .menu__wrap {
    align-self: center;
    grid-template-rows: auto auto;
    gap: clamp(0.75rem, 2.5vh, 1.5rem);
  }
  .menu__about { font-size: clamp(0.8rem, 1.3vw, 0.95rem); }
  .menu__nav { gap: clamp(0.6rem, 1.8vh, 1rem); }
}

@media (max-width: 640px) {
  .row { flex-wrap: wrap; }
  .row > picture.ph { flex-basis: 100%; }

  /* A cette largeur la presentation tient 37 caracteres par ligne : justifiee
     et sans cesure (voir plus haut), elle se troue de lezardes entre les mots.
     Centree, les blancs redeviennent reguliers. */
  .menu__about { text-align: center; }
}

/* Videos : le lecteur YouTube est dans la page. Bloc partage par les pages
   travaux qui en portent — videography et 3d works. */
.videos {
  /* Plafond du lecteur : au-dela, un 16:9 en pleine largeur ecrase le reste de
     la page sur un grand ecran. Il suit --col-max, partage avec la galerie de
     3d works ; le Short se resserre davantage plus bas. */
  --video-max: var(--col-max);
  padding: var(--gap-cap) var(--edge) 0;
  display: flex; flex-direction: column; gap: var(--gap-cap);
}

/* `margin: 0 auto` centre la figure dans la colonne — en flex, les marges auto
   absorbent l'espace libre de l'axe transverse, sans toucher au conteneur. La
   legende, etant dans la figure, reste alignee sur le bord gauche du lecteur. */
.video { margin: 0 auto; width: min(100%, var(--video-max)); }

/* Un Short vertical au meme plafond ferait pres de deux hauteurs d'ecran : il
   herite de la meme mecanique, avec sa propre valeur. */
.video--portrait { --video-max: 420px; }

/* La boite reserve le format avant que l'iframe se charge : pas de saut de mise
   en page quand un lecteur en lazy s'initialise. */
.video__frame {
  position: relative; aspect-ratio: var(--ar, 16/9); width: 100%;
  overflow: hidden; background: #000;
}
.video__frame iframe, .video__frame img {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}
.video__frame img { object-fit: cover; }

/* Fin de page : navigation croisee vers les autres domaines */
.cross { padding: var(--gap-cap) var(--edge); display: flex; flex-direction: column; align-items: center; gap: 1.2rem; }
.cross__label {
  font-family: var(--subtitle); color: var(--caption);
  font-size: clamp(0.85rem, 1.4vw, 1rem); letter-spacing: 0.06em;
}
.cross__btns { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; }
.btn {
  font-family: var(--display); font-weight: 500; font-size: clamp(1rem, 2.2vw, 1.25rem);
  text-decoration: none; padding: 0.8rem 1.6rem;
  background: var(--blue); color: var(--white); border: 2px solid var(--blue); border-radius: var(--radius); cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.btn:hover, .btn:focus-visible, .btn:active { background: var(--white); color: var(--blue); outline: none; }

/* Pied de page nom / annee */
/* min-height : les spans sont poses a `bottom: var(--edge)`, or --edge monte a
   56 px. Sur un ecran large et bas, 12vh descendait sous cette valeur et le
   texte sortait par le haut du bloc, jusque dans la navigation croisee. */
.foot {
  position: relative;
  height: clamp(70px, 12vh, 120px); min-height: calc(var(--edge) + 2.5em);
  font-family: var(--subtitle); font-size: clamp(0.95rem, 1.7vw, 1.2rem);
}
.foot .left { position: absolute; left: var(--edge); bottom: var(--edge); }
.foot .right { position: absolute; right: var(--edge); bottom: var(--edge); }

@media (prefers-reduced-motion: reduce) {
  .scroller { scroll-behavior: auto; }
  .social { transition: background 0.2s ease, color 0.2s ease; }
  .social:hover, .social:focus-visible { transform: none; }
  /* `bob` est posee sur .home__arrow svg, et `animation` ne s'herite pas :
     sans le troisieme selecteur la fleche continue de tressauter. */
  #home .home__logo, #home .home__arrow, #home .home__arrow svg {
    opacity: 1 !important; animation: none !important;
  }
  #contacts.spin .flower { animation: none !important; }
}
