/* ═══════════════════════════════════════════════════════════════
   Constance Dugauguez — Portfolio
   Composé en Instrument Serif. Trois couleurs, rien d’autre.

   01 · Tokens
   02 · Reset & base
   03 · Typographie
   04 · Liens & focus
   05 · En-tête
   06 · Hero
   07 · Révélations (motion)
   08 · Impression
   ═══════════════════════════════════════════════════════════════ */

/* ── 01 · Tokens ────────────────────────────────────────────── */

:root {
  /* Ivoire chaud et bordeaux profond. Les deux mêmes rôles qu’avant
     (--ink sur --paper), donc tout le site bascule d’un coup.
     Contraste mesuré : 14,3:1 — très au-delà du AA exigé. */
  --ink:        #580119;   /* bordeaux profond, pour le texte */
  --paper:      #FFFDEF;   /* ivoire crème, le fond */
  --paper-soft: #F2E9DE;   /* l’ivoire teinté de 8 % de bordeaux */

  /* Sur le panneau bordeaux, le texte secondaire s’éteint par la
     couleur et non par l’opacité : une ivoire à 60 % d’opacité sur
     du bordeaux tombe sous le seuil de lisibilité. */
  --paper-dim:  #E2C9CF;   /* ivoire rosé, 8,9:1 sur --ink */
  --ink-dim:    #925964;   /* son symétrique : 5,4:1 sur --paper */

  /* Instrument Serif : une seule graisse, romaine et italique. La
     contrainte est l’intérêt — plus de gras ni de light à doser,
     le seul contraste qui reste est celui de l’italique, et il
     porte toute la voix. Montserrat tient les petits textes — la
     même que le CV, pour que les deux se lisent comme un seul jeu. */
  --font-serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --font-sans:  "Montserrat", -apple-system, "Helvetica Neue", Arial, sans-serif;

  /* un seul token pour l’arrondi : passe-le à 0 et tout le site
     revient à l’angle vif d’avant */
  --radius: 8px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* décélération douce : vitesse de départ raisonnable, arrivée
     posée — pour les mouvements longs, où --ease claque */
  --ease-soft: cubic-bezier(0.33, 1, 0.68, 1);
  --dur:  800ms;

  /* L’échelle était trouée : 5 et 7 manquaient, et toute déclaration
     qui les appelait était jetée en bloc par le navigateur — une
     gouttière écrite var(--space-5) valait zéro, sans rien signaler. */
  --space-1: 0.5rem;   /*   8px */
  --space-2: 1rem;     /*  16px */
  --space-3: 1.5rem;   /*  24px */
  --space-4: 2rem;     /*  32px */
  --space-5: 2.5rem;   /*  40px */
  --space-6: 3rem;     /*  48px */
  --space-7: 3.5rem;   /*  56px */
  --space-8: 4rem;     /*  64px */
  --section-pad: clamp(6rem, 12vw, 12rem);

  --measure: 34ch;
}

/* ── 02 · Reset & base ──────────────────────────────────────── */

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

html {
  hanging-punctuation: first last;
  scroll-behavior: smooth;
}

/* le texte courant passe en sans, comme dans la référence : le
   serif est désormais réservé aux titres, où son contraste porte */
body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 1.0625rem;               /* 17px */
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;                   /* le nom déborde volontairement */
  min-height: 100svh;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

::selection {
  background: var(--ink);
  color: var(--paper);
}

/* ── 03 · Typographie ───────────────────────────────────────── */

/* Instrument Serif n’a qu’une graisse. Déclarer 300 n’allégerait
   rien — aucun navigateur ne synthétise un Light — alors on dit 400
   et le contraste se joue ailleurs : romain contre italique. */
h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: 1.04;
  text-wrap: balance;
}

/* l’italique comme seul accent disponible, et c’est volontaire */
.italic {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
}

/* le motif des parenthèses, en plus clair que le texte */
/* Pleine opacité : la parenthèse est un signe à part entière, pas
   une ponctuation qu’on s’excuse d’avoir mise. */
.paren {
  opacity: 1;
}

/* Grotesque neutre pour les petits textes : contrepoids au Fraunces,
   qui reste réservé aux titres et au corps de texte. */
.small-caps {
  font-family: var(--font-sans);
  font-size: 0.6875rem;               /* 11px */
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-feature-settings: "lnum";
}

/* ── 04 · Liens & focus ─────────────────────────────────────── */

a {
  color: inherit;
}

.link {
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: left calc(100% - 0.05em);
  background-size: 0% 1px;
  transition: background-size 600ms var(--ease);
  padding-bottom: 0.25em;
}

.link:hover {
  background-size: 100% 1px;
}

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: 10;
  padding: var(--space-1) var(--space-2);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  font-size: 0.8125rem;
  transform: translateY(-300%);
  transition: transform 300ms var(--ease);
}

.skip-link:focus {
  transform: translateY(0);
}

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

.site-head {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 3;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: var(--space-3) var(--space-4);
}

.site-head__links {
  display: flex;
  gap: var(--space-4);
  list-style: none;
  padding: 0;
}

/* — Navigation centrée, logo au milieu —
   trois colonnes égales : les deux groupes de liens poussent le
   logo pile au centre optique, quelle que soit leur longueur */
/* Deux groupes de liens tenant les deux bords, sur toutes les pages.
   Plus de nom au milieu : il est déjà dans le titre de l’onglet, le
   pied de page et le colophon. */
.site-nav {
  width: 100%;
  display: grid;
  grid-template-columns: auto auto;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
}

.site-nav__group {
  display: flex;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav__group--end {
  justify-content: flex-end;
}

/* la flèche reste collée à son mot : sinon elle tombe seule à la ligne */
.site-nav__group .link {
  white-space: nowrap;
}

@media (max-width: 44rem) {
  /* Deux colonnes figées forçaient les trois liens de droite à
     s’empiler l’un sous l’autre. En flex, le second groupe passe
     entier à la ligne suivante : deux rangées au lieu de quatre. */
  .site-nav {
    display: flex;
    flex-wrap: wrap;
    column-gap: var(--space-3);
    row-gap: var(--space-2);
  }

  .site-nav__group {
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
  }

  /* plus de bord droit à tenir : tout s’aligne sur la marge gauche */
  .site-nav__group--end {
    justify-content: flex-start;
  }
}

/* ── 06 · Hero ──────────────────────────────────────────────── */

.hero {
  position: relative;
  /* L’écran entier, désormais : la bande de travaux qui dépassait
     sous le pli servait à dire qu’il y avait une suite. Le rappel
     cliquable le dit mieux, le hero peut donc respirer en entier. */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(5rem, 11vh, 8rem) var(--space-4) clamp(2.5rem, 5vh, 3.5rem);
}

/* — Hero conversationnel —
   un corps bien plus modeste que l’ancien nom géant : ici c’est la
   phrase qui porte, pas la taille. */
/* 24ch et non 20 : Instrument Serif est plus étroite que Cormorant,
   et à 20ch le plus long des mots cyclés faisait passer le bloc de
   trois à quatre lignes — toute la phrase sautait à chaque mot. */
.hero__hello {
  max-width: 24ch;
  font-size: clamp(2.25rem, 6.5vw, 5.5rem);
  line-height: 1.12;
  text-wrap: balance;
}

/* Le curseur est un bloc en ligne : il ouvre donc une césure possible
   juste avant lui, et se retrouvait rejeté seul en début de ligne.
   On soude le mot et son curseur. */
[data-typewriter] {
  white-space: nowrap;
}

/* Curseur : une barre droite, posée sur la ligne de base et montant
   à hauteur de capitale. */
.typewriter-caret {
  display: inline-block;
  width: 0.05em;
  height: 0.72em;
  margin-left: 0.07em;
  vertical-align: -0.04em;
  background: currentColor;
  /* un fondu et non un `steps(1)` : le battement carré hachait la
     ligne, celui-ci respire au même rythme que la frappe */
  animation: caret-blink 1.4s var(--ease-soft) infinite;
}

@keyframes caret-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

/* Le rappel est devenu l’action principale du hero : il descend vers
   le bas de l’écran — là où le regard va chercher la suite — et
   grossit assez pour se lire comme une invitation, pas comme une
   note de bas de page. */
.hero__sub {
  margin-top: clamp(var(--space-6), 13vh, 8.5rem);
  font-size: clamp(1.25rem, 2.4vw, 2rem);
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
}

/* Cliquable sans rien autour : ni cadre, ni aplat, ni soulignement.
   Le point qui suit la souris grossit déjà sur tout lien ; il ne
   reste à faire qu’un signe, et ce sont les parenthèses qui le font
   — elles s’écartent, comme pour laisser passer. */
.hero__sub {
  display: inline-block;
  text-decoration: none;
}

.hero__sub .paren {
  display: inline-block;
  transition: transform 600ms var(--ease);
}

.hero__sub:hover .paren:first-child,
.hero__sub:focus-visible .paren:first-child {
  transform: translateX(-0.22em);
}

.hero__sub:hover .paren:last-child,
.hero__sub:focus-visible .paren:last-child {
  transform: translateX(0.22em);
}

/* Cette règle doit suivre le `padding` raccourci de `.hero` ci-dessus,
   sinon il la recouvre : à spécificité égale, c’est la dernière qui
   l’emporte. La nav tient sur deux rangées ici, le hero commence donc
   plus bas pour ne pas venir buter dans les liens. */
@media (max-width: 44rem) {
  .hero {
    padding-top: clamp(10rem, 21vh, 13rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .typewriter-caret { animation: none; }
}

.hero__eyebrow {
  /* pousse le bloc (nom + paragraphes) vers le bas de l’écran —
     valeur fixe plutôt qu’auto, car le nom occupe déjà presque
     tout l’espace vertical disponible sur la plupart des écrans */
  margin-top: clamp(2rem, 7vh, 5rem);
  margin-bottom: var(--space-3);
}

.hero__name-wrap {
  position: relative;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
}

.hero__name-wrap > h1 {
  width: 100%;
  min-width: 0;
}

.hero__name {
  /* Cormorant est nettement plus large que Fraunces à corps égal
     (5,94 em pour « DUGAUGUEZ » contre ~5,5) : le facteur vw est
     recalculé pour que le nom tienne, décalage compris.
     Toujours borné par la hauteur pour que la flèche reste visible. */
  font-size: clamp(3rem, min(13vw, 24vh), 14rem);
  text-transform: uppercase;
  white-space: nowrap;                /* autorisé à saigner hors cadre */
  line-height: 0.86;
  display: grid;
  justify-items: center;              /* les lignes débordent symétriquement */
}

.hero__name .line {
  width: max-content;
}

/* Décalage éditorial, à la Regina Musi. Symétrique et franc : une
   ligne poussée à gauche, l’autre à droite d’une même valeur, pour
   que le décalage se lise comme un parti pris et non comme un
   défaut d’alignement. La marge décale de la moitié de sa valeur,
   d’où un écart total entre les deux lignes de --stagger. */
.hero__name {
  /* environ une lettre et demie d’écart entre les deux lignes */
  --stagger: 1em;
  /* le bloc entier recule d’un cheveu vers la gauche : le décalage
     et le bord droit étant liés, c’est le seul moyen de garder
     l’écart voulu sans que le « Z » ne touche la marginalia */
  transform: translateX(-1rem);
}

.hero__name .line:not(.line--shift) {
  margin-right: var(--stagger);
}

.hero__name .line--shift {
  margin-left: var(--stagger);
}

.hero__intro {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--space-3);
  /* davantage d’air sous le nom, pour que les deux blocs respirent */
  margin-top: clamp(4rem, 12vh, 7rem);
  font-size: 0.9375rem;               /* 15px */
}

.hero__intro p {
  max-width: 26ch;
  text-align: left;
  text-wrap: pretty;
}

.hero__intro p:first-child {
  grid-column: 1 / 5;
}

.hero__intro p:last-child {
  grid-column: 9 / 13;
  justify-self: end;
}





.marginalia {
  position: fixed;                    /* épinglée au bord, ne défile pas */
  top: 50%;
  right: var(--space-2);
  z-index: 4;
  writing-mode: vertical-rl;
  transform: translateY(-50%);
  font-size: 0.5625rem;
  pointer-events: none;
}

@media (max-width: 44rem) {
  .hero__intro p:first-child { grid-column: 1 / 8; }
  .hero__intro p:last-child  { grid-column: 5 / 13; }

  /* sur petit écran : pas de décalage, débord symétrique et discret —
     donc pas de compensation à droite non plus */
  .hero__name .line:not(.line--shift) { margin-right: 0; }
  .hero__name .line--shift { margin-left: 0; }
  .hero__name { transform: none; }

  .site-head { padding: var(--space-2) var(--space-2); }
}

/* ── 06b · Travaux sélectionnés (grille) ────────────────────── */

/* Le carrousel cachait les projets : il fallait cliquer une flèche
   pour savoir qu’il en restait. Une grille les montre tous d’un
   coup, et accepte d’en recevoir d’autres sans rien changer. */

/* Marge latérale resserrée et plafond relevé : la grille tient
   presque toute la largeur, comme dans la référence. Ce sont ces
   deux valeurs, et elles seules, qui décident de la taille des
   cartes — le reste de la section suit. */
/* Haut volontairement court : c’est ce qui fait que l’intitulé de la
   section affleure sous le pli du hero. Le bas garde sa respiration. */
.works {
  /* on ne tombe plus sur les cartes à la seconde où le hero finit :
     le défilement arrive sur un blanc, puis sur le titre */
  padding: clamp(var(--space-5), 9vh, 7rem)
           clamp(var(--space-2), 1.8vw, var(--space-5))
           var(--section-pad);
}

.works__head {
  max-width: 96rem;
  margin: 0 auto clamp(var(--space-7), 7vw, 6rem);
  text-align: center;
}

.works__eyebrow {
  margin-bottom: var(--space-2);
}

.works__title {
  font-size: clamp(2.5rem, 6vw, 5rem);
}

/* auto-fit : trois cartes de front sur un grand écran, deux puis une
   quand la place manque, sans média-requête à tenir à jour. */
.works__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  /* une seule gouttière, identique en largeur et en hauteur : la
     grille se lit comme une trame régulière, sans direction
     privilégiée */
  gap: clamp(var(--space-4), 3.5vw, var(--space-7));
  max-width: 96rem;
  margin: 0 auto;
  list-style: none;
  padding: 0;
}

@media (min-width: 64rem) {
  /* au-delà de 1024 px on fige à trois : auto-fit en mettrait quatre
     et les cartes perdraient leur présence */
  .works__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.work {
  display: flex;
}

.work__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--paper-soft);
  border-radius: calc(var(--radius) * 2);
  text-decoration: none;
  transition: border-color 600ms var(--ease), background-color 600ms var(--ease);
}

.work__link:hover,
.work__link:focus-visible {
  border-color: var(--ink);
  background-color: var(--paper-soft);
}

.work__figure {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
}

.work__figure img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform 900ms var(--ease);
}

.work__link:hover .work__figure img {
  transform: scale(1.03);
}

.work__name {
  margin-top: var(--space-3);
  font-size: clamp(1.375rem, 2vw, 1.75rem);
  line-height: 1.1;
}

/* `margin-top: auto` pousse l’étiquette tout en bas de la carte :
   qu’un titre tienne sur une ou deux lignes, les étiquettes
   s’alignent toutes sur une même ligne d’une carte à l’autre. */
.work__tag {
  align-self: flex-start;
  margin-top: auto;
  padding-top: var(--space-2);
}

/* Pastille en contour, et non pleine : c’est une étiquette, pas une
   action. Le bouton plein reste réservé au seul vrai bouton de la
   page (.btn), qui le perdrait si cinq cartes l’imitaient. Au survol
   elle se remplit — l’inverse exact du .btn, même vocabulaire. */
.work__tag span {
  display: inline-block;
  padding: 0.55em 1.15em;
  border: 1px solid var(--ink);
  border-radius: 999px;
  color: var(--ink);
  transition: background-color 600ms var(--ease), color 600ms var(--ease);
}

.work__link:hover .work__tag span,
.work__link:focus-visible .work__tag span {
  background: var(--ink);
  color: var(--paper);
}

.works__more {
  display: flex;
  justify-content: center;
  margin-top: clamp(var(--space-7), 7vw, 6rem);
}

/* Bouton-pastille, le même objet que l’étiquette des cartes : la
   page n’invente pas une nouvelle forme pour une action de plus. */
.btn {
  display: inline-block;
  padding: 0.9em 1.8em;
  border: 1px solid var(--ink);
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color 600ms var(--ease), color 600ms var(--ease);
}

.btn:hover,
.btn:focus-visible {
  background: transparent;
  color: var(--ink);
}


/* ── 06c · Page About (curriculum) ──────────────────────────── */

/* La page entière s’inverse : bordeaux en fond, ivoire en texte.
   Le CV avait sa place dans une colonne de l’accueil, où il fallait
   tout rapetisser pour tenir — c’est précisément ce qui le rendait
   illisible. Une page à lui seul lui rend sa taille de lecture. */
.page-about {
  background: var(--ink);
  color: var(--paper);
}

.page-about ::selection {
  background: var(--paper);
  color: var(--ink);
}

.page-about :focus-visible {
  outline-color: var(--paper);
}

/* Sur la page About, tout est déjà bordeaux : c’est le pied de page
   qui s’inverse, en ivoire. Le contraste fait seul la séparation,
   sans filet — et l’inversion s’annule donc deux fois ici. */
.page-about .site-footer {
  background: var(--paper);
  color: var(--ink);
}

.page-about .site-footer ::selection {
  background: var(--ink);
  color: var(--paper);
}

.page-about .site-footer :focus-visible {
  outline-color: var(--ink);
}

.page-about .site-footer__colophon {
  color: var(--ink-dim);
}

/* — Titre — */

.about-hero {
  padding: clamp(7rem, 16vh, 11rem) var(--space-4) var(--space-6);
  text-align: center;
}

.about-hero__title {
  font-size: clamp(2.5rem, 7vw, 5.5rem);
}

/* — Portrait et biographie — */

.about-intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: clamp(var(--space-4), 5vw, var(--space-6));
  max-width: 62rem;
  margin: 0 auto;
  /* la pleine --section-pad creusait un trou sous le bouton du
     résumé : le curriculum paraissait détaché de la biographie */
  padding: 0 var(--space-4) clamp(var(--space-7), 7vw, 5.5rem);
}

.about-portrait {
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--paper-dim);
}

.about-portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

/* Les deux paragraphes côte à côte : une seule colonne centrée dans
   une page large laissait des marges béantes. Chaque colonne porte
   sa propre justification, donc plus besoin de --measure. */
.about-bio {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: clamp(var(--space-4), 4vw, var(--space-6));
  font-size: clamp(1.0625rem, 1.5vw, 1.3125rem);
  line-height: 1.55;
  text-wrap: pretty;
}

/* l’espacement vient de la gouttière, dans les deux dispositions */
.about-bio p {
  margin: 0;
}

/* -2 / -1, c’est-à-dire la dernière colonne quelle qu’elle soit :
   le lien s’aligne sur le texte, qu’il y ait un portrait ou non. */
.about-bio__resume {
  grid-column: -2 / -1;
  justify-self: start;
}

/* Dès qu’un portrait est fourni, il prend la colonne de gauche et
   le texte repasse sur une seule colonne à sa droite. */
.about-intro:has(.about-portrait) {
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
}

.about-intro:has(.about-portrait) .about-bio {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
}

/* — Le curriculum — */

.cv {
  max-width: 62rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--section-pad);
}

/* Trois colonnes : rubrique, date, entrée. La date garde sa colonne
   à elle parce qu’un CV se parcourt par les dates — les fondre dans
   le texte ferait perdre ce repère. */
.cv-group {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
  gap: var(--space-5);
  border-top: 1px solid var(--paper-dim);
  padding-top: var(--space-4);
}

.cv-group + .cv-group {
  margin-top: var(--space-6);
}

/* L’intitulé de rubrique était un label de 14 px, au même corps que
   les dates : rien ne disait que c’était un titre. Il passe au
   sérif, au corps d’un vrai titre de section. */
/* L’intitulé suit sa rubrique pendant le défilement. Sans cela, sur
   une rubrique longue comme Experience, il disparaissait en haut et
   on ne savait plus ce qu’on lisait — c’est là que la colonne de
   gauche paraissait vide et la page illisible. */
.cv-group__label {
  position: sticky;
  top: var(--space-4);
  align-self: start;
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 2.2vw, 2rem);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.005em;
  margin: 0;
}

.cv-group ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cv-item {
  display: grid;
  grid-template-columns: minmax(0, 9.5rem) minmax(0, 1fr);
  gap: var(--space-1) var(--space-4);
  align-items: baseline;
}

.cv-item + .cv-item {
  margin-top: var(--space-5);
}

/* La date passe devant l’intitulé dans le flux pour que la lecture
   à la voix annonce « 2024 — 2027, BFA… » et non l’inverse ; à
   l’écran elle reste à gauche, alignée sur sa colonne. */
/* Trois corps nettement distincts — 21 / 17 / 16 px — là où il n’y
   avait que 18 / 16 / 14 : l’œil ne hiérarchisait plus rien. */
.cv-item__when {
  font-size: 1rem;
  line-height: 1.4;
  color: var(--paper-dim);
  margin: 0;
}

.cv-item__what {
  font-size: 1.3125rem;
  font-weight: 500;
  line-height: 1.3;
  margin: 0;
}

.cv-item__where {
  font-size: 1.0625rem;
  line-height: 1.5;
  color: var(--paper-dim);
  margin-top: 0.3em;
  max-width: 48ch;
}

@media (max-width: 52rem) {
  /* Deux colonnes de texte sous 832 px donneraient des lignes de
     trente signes : on empile, et la mesure redevient lisible. */
  .about-bio,
  .about-intro:has(.about-portrait) {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
  }

  .about-bio {
    max-width: 44ch;
  }
}

@media (max-width: 44rem) {
  /* Plus de colonnes : l’intitulé coiffe ses entrées, la date
     coiffe la sienne. Rien ne rétrécit pour tenir. */
  .cv-group,
  .cv-item {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2);
  }

  .cv-group {
    gap: var(--space-3);
  }

  /* collant sur une seule colonne, l’intitulé recouvrirait ses
     propres entrées */
  .cv-group__label {
    position: static;
  }
}

/* ── 06d · Colophon (pied de page) ──────────────────────────── */

/* Aplat bordeaux : le pied de page ferme le document par un bloc de
   couleur plutôt que par un filet. Plus de bordure — c’est le
   changement de fond qui fait la séparation. */
.site-footer {
  background: var(--ink);
  color: var(--paper);
  padding: var(--space-6) var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

/* tout ce qui se repérait sur --ink doit s’inverser ici */
.site-footer ::selection {
  background: var(--paper);
  color: var(--ink);
}

.site-footer :focus-visible {
  outline-color: var(--paper);
}


.site-footer__row {
  width: 100%;
  max-width: 64rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
}

.site-footer__links {
  display: flex;
  gap: var(--space-4);
  list-style: none;
  padding: 0;
}

/* Le nom est le retour à l’accueil. Les parenthèses le signalent
   comme cliquable avant même le survol — c’est le seul lien du
   site qui n’a pas de flèche ni d’intitulé pour le dire. */
.site-footer__home {
  text-decoration: none;
}

/* par la couleur et non par l’opacité : une ivoire translucide sur
   du bordeaux tombe sous le seuil de lisibilité */
.site-footer__colophon {
  color: var(--paper-dim);
  text-align: center;
}

/* ── 06e · Lien retour à l’index ─────────────────────────────── */

.back-link {
  display: inline-block;
  margin: var(--space-6) var(--space-4) 0;
}

/* ── 09 · Pages projet ───────────────────────────────────────── */
/* Même grille 12 colonnes, même rythme que la page d’accueil —
   déclinés en gabarit éditorial pour chaque étude de cas. */

.project-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--space-3);
  row-gap: var(--space-6);
  /* évite que le texte et les images ne s’étirent démesurément
     sur les très grands écrans */
  max-width: 84rem;
  margin-inline: auto;
}

.span-1-12 { grid-column: 1 / -1; }
.span-1-9  { grid-column: 1 / 10; }
.span-1-7  { grid-column: 1 / 8; }
.span-1-6  { grid-column: 1 / 7; }
.span-1-5  { grid-column: 1 / 6; }
.span-1-4  { grid-column: 1 / 5; }
.span-5-8  { grid-column: 5 / 9; }
.span-2-8  { grid-column: 2 / 9; }
.span-6-12 { grid-column: 6 / 13; }
.span-7-12 { grid-column: 7 / 13; }
.span-8-9  { grid-column: 8 / 10; }
.span-8-12 { grid-column: 8 / 13; }
.span-9-12 { grid-column: 9 / 13; }
.span-10-12 { grid-column: 10 / 13; }

@media (max-width: 44rem) {
  .span-1-12, .span-1-9, .span-1-7, .span-1-6, .span-1-5, .span-1-4,
  .span-5-8, .span-2-8, .span-6-12, .span-7-12, .span-8-9, .span-8-12, .span-9-12, .span-10-12 {
    grid-column: 1 / -1;
  }
}

/* — Hero de projet — */

.project-hero {
  padding: clamp(6rem, 13vh, 8.5rem) var(--space-4) clamp(2rem, 4vw, 3.5rem);
}

/* Le titre et son chapô forment un seul bloc. Séparés, ils tombaient
   dans deux rangées de grille distinctes : la première prenait la
   hauteur du bloc d’infos voisin, et le chapô se retrouvait rejeté
   deux cents pixels sous le titre qu’il accompagne. */
.project-hero__lead {
  align-self: start;
}

.project-hero__eyebrow {
  margin-bottom: var(--space-3);
}

.project-hero__title {
  font-size: clamp(3rem, 8vw, 7.5rem);
  line-height: 0.96;
  text-wrap: balance;
}

.project-hero__meta {
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Ces quatre lignes portent les faits du projet — année, nature,
   rôle, outils. À 11 et 16 px elles se lisaient comme une mention
   légale. */
.meta-item__label {
  display: block;
  margin-bottom: 0.45em;
  font-size: 0.8125rem;
  opacity: 0.7;
}

.meta-item__value {
  font-family: var(--font-serif);
  font-size: clamp(1.1875rem, 1.5vw, 1.4375rem);
  line-height: 1.25;
}

/* même correction que pour le corps de texte : 34ch étranglait le
   chapô dans une colonne deux fois plus large */
.project-hero__intro {
  margin-top: var(--space-3);
  font-size: clamp(1.125rem, 1.6vw, 1.375rem);
  line-height: 1.5;
  max-width: 52ch;
  text-align: left;
  text-wrap: pretty;
}

/* — Couverture — */
/* une image d’ouverture, pas une bannière : cadrée avec ses propres
   marges, jamais bord à bord — c’est un portfolio, pas un mur d’images */

.project-cover {
  max-width: 56rem;
  margin: 0 auto;
  padding: 0 var(--space-4);
}

.project-cover img {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 26rem;
  object-fit: cover;
}

.project-cover figcaption {
  margin-top: var(--space-1);
}

/* — Sections du cas (brief / process / results…) — */
/* Pas de filet horizontal entre les sections : le blanc du papier
   suffit à les distinguer, comme les chapitres d’un livre. Marge
   plus resserrée que --section-pad : ce padding s’additionne en
   haut ET en bas de chaque section, donc entre deux sections
   consécutives l’écart réel est déjà le double de cette valeur. */

/* Haut raccourci : la section 01 suivait le hero à sept rem de
   distance, en plus du bas du hero — le projet mettait un écran
   entier à commencer. */
.project-section {
  padding: clamp(2.5rem, 5vw, 4.5rem) var(--space-4);
}

.project-section:first-of-type {
  padding-top: clamp(1.5rem, 3vw, 2.5rem);
}

/* le titre reste attaché à son texte — un seul bloc, jamais un
   bandeau séparé qui s’éloigne des paragraphes qu’il annonce */
.project-section__text {
  display: flex;
  flex-direction: column;
}

.project-section__index {
  display: block;
  margin-bottom: var(--space-2);
  opacity: 0.7;
}

.project-section__title {
  font-size: clamp(1.75rem, 3.5vw, 3rem);
  margin-bottom: var(--space-4);
}

/* --measure vaut 34ch : une mesure de titre, pas de corps de texte.
   Appliquée ici, elle bridait le paragraphe à 301 px dans une
   colonne de 660 — d’où 359 px de blanc que personne n’avait voulu,
   et des lignes de 35 signes. 66ch remplit la colonne et reste dans
   la fourchette lisible. */
.project-section__text p {
  font-size: 1.125rem;
  line-height: 1.6;
  max-width: 66ch;
}

.project-section__text p + p {
  margin-top: var(--space-3);
}

/* — Figures — */
/* toujours en retrait dans la grille (jamais bord à bord) : l’image
   illustre le texte, elle ne le remplace pas — l’un ne va pas sans
   l’autre. Plus grande seule, plus discrète à deux, pour que la
   grille respire sans jamais devenir un mur d’images. */

.media {
  align-self: start;
  overflow: hidden;
}

/* hauteur fixe plutôt qu’un simple plafond : deux photos source de
   proportions différentes, côte à côte, doivent quand même s’aligner
   pile sur la même ligne — un max-height seul laisse l’image la
   plus « carrée » plus courte que sa voisine */
.media img {
  width: 100%;
  height: 24rem;
  object-fit: cover;
}

/* seule dans sa section : elle peut prendre toute sa place */
.media--lg img {
  height: 32rem;
}

/* garde les proportions d’origine de la photo : aucun recadrage,
   l’image est montrée telle qu’elle a été cadrée à la prise de vue */
.media--natural img {
  height: auto;
}

/* plus étroite et plus haute : pour une image dont le cadrage
   serré coupe autrement une part importante du contenu — alignée
   à droite, sur le même bord que ses voisines plus larges */
.media--tall {
  align-self: flex-end;
  width: min(75%, 28rem);
}

.media--tall img {
  height: 30rem;
}

/* cadré carré plutôt qu’en bande — largeur plafonnée à la hauteur
   d’origine (24rem) pour que le carré ne soit qu’un réarrangement
   de la forme, jamais un agrandissement */
.media--square {
  width: 24rem;
  max-width: 100%;
}

.media--square img {
  height: auto;
  aspect-ratio: 1 / 1;
}

/* petite planche de quatre visuels, deux par deux, carrés — pour
   une section qui documente plusieurs images plutôt qu’une ou deux */
.media-grid {
  align-self: start;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}

.media-grid .media img {
  height: auto;
  aspect-ratio: 1 / 1;
}

/* paire d’images empilées, proches l’une de l’autre — dans son
   propre conteneur flex pour ne pas hériter de la hauteur de la
   colonne de texte voisine (sinon le second visuel se retrouve
   collé au bas du texte, loin du premier) */
.media-stack {
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* dans une grille, align-self contrôle l’axe vertical ; dans ce
   conteneur flex (colonne), c’est l’axe horizontal — sans ce reset,
   chaque figure se réduirait à la largeur de son image plutôt que
   de remplir la pile */
.media-stack .media {
  align-self: stretch;
}

/* .media--tall doit garder son alignement à droite même empilée —
   sinon la règle ci-dessus (plus spécifique) l’emporterait */
.media-stack .media--tall {
  align-self: flex-end;
}

.media figcaption {
  margin-top: var(--space-1);
}

@media (max-width: 44rem) {
  .media img {
    height: 20rem;
  }

  .media--lg img {
    height: 22rem;
  }

  .media--tall {
    width: 100%;
  }

  .media--tall img {
    height: 24rem;
  }

  .media-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-2);
  }
}

/* — Navigation entre projets (fin de page) — */
/* Trois vignettes cliquables plutôt qu’un seul lien « projet
   suivant » : le lecteur voit où il va et choisit. */

.project-nav {
  padding: var(--section-pad) var(--space-4) clamp(4rem, 8vw, 7rem);
}

.project-nav__inner {
  max-width: 84rem;
  margin-inline: auto;
}

.project-nav__head {
  margin-bottom: var(--space-6);
  opacity: 0.7;
}

/* Index en texte seul : le nom du projet et son type, rien d’autre.
   Des vignettes ici répétaient les images de la page qu’on vient de
   lire, sans rien ajouter. */
.project-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--paper-soft);
}

.project-nav__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: clamp(var(--space-3), 2.5vw, var(--space-5)) 0;
  border-bottom: 1px solid var(--paper-soft);
  text-decoration: none;
  transition: padding-left 600ms var(--ease), border-color 600ms var(--ease);
}

.project-nav__item:hover,
.project-nav__item:focus-visible {
  /* la ligne se décale : le survol se voit sans rien colorer */
  padding-left: var(--space-3);
  border-color: var(--ink);
}

.project-nav__name {
  font-family: var(--font-serif);
  font-size: clamp(1.75rem, 3.5vw, 3rem);
  line-height: 1.05;
  letter-spacing: -0.005em;
}

.project-nav__label {
  color: var(--ink);
  opacity: 0.65;
  text-wrap: pretty;
}

@media (max-width: 44rem) {
  .project-nav__item {
    /* le type passe sous le nom plutôt que de le comprimer */
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ── 06f · Curseur ──────────────────────────────────────────── */

/* La flèche système ne disparaît que lorsque le JS a posé son point
   et ajouté cette classe. Si le script ne tourne pas, le curseur
   d’origine reste : on ne laisse jamais la page sans pointeur. */
html.has-cursor,
html.has-cursor * {
  cursor: none;
}

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;      /* le point ne doit rien intercepter */
  will-change: transform;
}

/* L’échelle vit sur l’enfant : le parent porte déjà la position, et
   une transformation ne peut pas servir deux maîtres. */
.cursor span {
  display: block;
  width: 0.6875rem;
  height: 0.6875rem;
  margin: -0.34375rem 0 0 -0.34375rem;
  border-radius: 999px;
  background: var(--ink);
  transition:
    transform 450ms var(--ease-soft),
    opacity 250ms linear,
    background-color 250ms linear;
}

/* sur un lien, le point s’ouvre et s’éclaircit : on voit à travers
   ce qu’on s’apprête à cliquer */
.cursor.is-link span {
  transform: scale(2.6);
  opacity: 0.4;
}

.cursor.is-down span {
  transform: scale(0.75);
}

.cursor.is-out span {
  opacity: 0;
}

/* Sur un aplat bordeaux, un point bordeaux n’existe pas. */
.cursor.is-inverted span,
.page-about .cursor span {
  background: var(--paper);
}

/* …et la page About inverse une fois de plus : son pied de page est
   ivoire, le point y redevient bordeaux. */
.page-about .cursor.is-inverted span {
  background: var(--ink);
}

/* ── 07 · Révélations (motion) ──────────────────────────────── */

.line {
  display: block;
  overflow: hidden;
}

.line-inner {
  display: block;
}

@media (prefers-reduced-motion: no-preference) {
  /* Le hero a son propre tempo, plus lent que les révélations au
     défilement — c’est la première chose qu’on voit du site, elle
     n’a pas à être expédiée.

     Et surtout sa propre courbe : --ease est quasi exponentielle,
     elle part à toute vitesse puis rampe. Sur 110 % de course en
     620 ms, ça se lisait comme un claquement suivi d’un traînage.
     --ease-soft démarre doucement et se pose. L’opacité accompagne
     la montée pour que le bord du masque ne tranche pas net. */
  html.js .line-inner {
    opacity: 0;
    transform: translateY(110%);
    transition:
      transform 1100ms var(--ease-soft),
      opacity 700ms var(--ease-soft);
    transition-delay: calc(var(--i, 0) * 110ms);
  }

  html.js.is-loaded .line-inner {
    opacity: 1;
    transform: translateY(0);
  }

  /* la ligne de rappel entre à la suite des deux lignes du titre,
     au même tempo qu’elles plutôt qu’à celui du défilement */
  html.js .hero__sub {
    opacity: 0;
    transform: translateY(0.75rem);
    transition:
      opacity 900ms var(--ease-soft) 620ms,
      transform 900ms var(--ease-soft) 620ms;
  }

  html.js.is-loaded .hero__sub {
    opacity: 1;
    transform: none;
  }

  html.js .hero__intro {
    opacity: 0;
    transition: opacity 700ms var(--ease-soft) 300ms;
  }

  html.js.is-loaded .hero__intro {
    opacity: 1;
  }

  html.js [data-reveal] {
    opacity: 0;
    transform: translateY(1.25rem);
    transition:
      opacity 520ms var(--ease),
      transform 520ms var(--ease);
    transition-delay: calc(var(--i, 0) * 45ms);
  }

  html.js [data-reveal].is-visible {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* ── 08 · Impression ────────────────────────────────────────── */

@media print {
  body {
    background: #fff;
    color: #000;
  }

  .marginalia,
  .skip-link,
  .site-footer__top {
    display: none;
  }
}
