/* ============================================================
   Jules Focone — feuille de style  ·  v2
   Charte : ../charte-graphique/CHARTE-GRAPHIQUE.md
   Références : ../Inspirations/

   Deux partis pris hérités du relevé des inspirations :
   · duo serif éditoriale / grotesque d'interface
   · monochrome strict — aucune couleur d'accent, la couleur
     ne vient que des photographies
   ============================================================ */

:root{
  /* --- Couleurs : noir, blanc, deux gris. Rien d'autre. -----
     Le rapport est inversé : le fond porte le noir, le texte le blanc.
     La règle, elle, ne bouge pas — monochrome strict, la couleur ne
     vient que des photographies. Le fond n'est pas noir : #171209 est un
     brun très foncé — une teinte en dessous du noir, assez sombre pour
     laisser aux noirs des tirages de quoi s'en détacher plutôt que d'y
     fondre, assez chaude pour qu'on la lise comme une matière et non
     comme une absence.

     Il a d'abord été le neutre #0A0A0A. Le virage chaud était déjà celui
     de l'encre, du gris et des filets ; le fond le rejoint, et le
     monochrome tient toujours — un brun et un ivoire ne font pas deux
     couleurs, ils font deux bouts de la même.

     L'encre non plus n'est pas pure : le blanc est un ivoire, tiré vers
     le chaud. Un #FFFFFF sur ce fond a l'éclat d'un écran ; l'ivoire a
     celui d'un papier, et il s'accorde aux argentiques qu'il légende.
     Le fond, lui, reste neutre — c'est ce qui fait lire la chaleur comme
     venant du texte. Le gris et le filet suivent le même virage, sans
     quoi le texte serait chaud et ses légendes froides. */
  --paper:#171209;   /* fond — brun très foncé                  */
  --ink:#F4F0E8;     /* texte — ivoire                          */
  --mist:#8F8880;    /* client, légendes, métadonnées           */
  --hair:#2C2926;    /* filets                                  */
  /* Le gris qui occupe la place d'une photo avant qu'elle n'arrive. Il
     suit le virage du fond : un gris neutre y ferait une tache froide,
     visible juste le temps du chargement mais visible quand même. */
  --attente:#241E15;

  /* Les éléments du navigateur — barres de défilement, champs — suivent :
     sans ça, ils restent clairs et trahissent la page. */
  color-scheme:dark;

  /* --- Typographie ----------------------------------------- */
  /* UN SEUL CARACTÈRE, UNE SEULE TAILLE sur tout le site.
     La hiérarchie ne tient plus qu'à la graisse, à la couleur et au blanc.
     Inter est servie depuis ce domaine : même rendu partout, et aucune
     requête vers un tiers. */
  --type:11px;
  --font:'Inter','Helvetica Neue',Helvetica,Arial,sans-serif;

  /* --- Mise en page ---------------------------------------- */
  --pad:30px;        /* marge desktop, relevée sur WFR          */
  --pad-m:15px;      /* marge mobile                            */

  /* Le cadre de la scène de Work — et, avant elle, des vignettes de la
     galerie, qui ont disparu quand la galerie est devenue une suite de
     photos au ratio natif. La photo remplit ce cadre en object-fit:cover.
     Le fonds est vertical — seize des dix-sept couvertures le sont, à 2:3
     (0,67), 4:5 (0,80) ou 0,82. Le cadre a donc été choisi en mesurant la
     coupe qu'il inflige aux dix-sept, pas au jugé :

         3/2   48,9 % de coupe moyenne   ·  0 couverture intacte
         1/1   25,8 %                    ·  0
         2/3   11,0 %                    ·  8   mais jusqu'à 45,5 %
         3/4   10,9 %                    ·  0
         4/5   10,5 %                    ·  4   la plus douce des deux côtés

     4:5 gagne sur la moyenne ET sur le pire cas. Une seule ligne à reprendre
     si le fonds change de forme. */
  --vignette-ratio:4/5;
  /* La porte fond en 1,5 s ; elle change toutes les 7 s et s'ouvre
     toute seule à 5 s (build.py) — le fondu est réglé pour l'œil, pas
     pour le compte. Sur Work, au survol, le lecteur a quitté la ligne
     bien avant : même courbe (linear), trois fois moins de temps. */
  --fondu-work:.45s;
  --fondu-index-m:.6s;   /* les cartes de l'Index au téléphone : un fondu
                            franc, bien plus court que leur temps de pose
                            (CADENCE_INDEX_MOBILE, 2 s) */
  --header:calc(var(--pad) * 2 + 1.2em);  /* marge, une ligne, marge */
  --ecart-pied:.45em;                     /* l'écart entre deux lignes de texte du bas de page : « 1 / 75 » au-dessus de Legal / Privacy (mesuré le 30/10/2026 : 4,5 px au bord des lignes à 11 px), et « Paris-Biarritz » au-dessus des droits */
  --pied:calc(var(--header) + 1.15em + var(--ecart-pied));   /* le pied fixe : la hauteur de l'en-tête + une ligne + l'écart (« Paris-Biarritz » au-dessus des droits) */
  --zone-h:calc(100dvh - var(--header) - var(--pied));   /* ce qui reste entre les deux */
  /* LE 16/9 CONTIENT LES PHOTOS (demande de Jules). Sur un écran large, aucune
     photo ne doit dépasser de la fenêtre : sa hauteur est bornée à ce qui
     reste sous l'en-tête, moins une marge. Les colonnes, elles, ne bougent
     pas — la photo se cale dans la sienne, au bord gauche, là où la ligne
     du projet l'attend. */
  --cadre-h:calc(100dvh - var(--header) - var(--pad));
  /* La course du nom quand le store se lève : du bas de la porte (une marge
     au-dessus du bord) au haut de l'en-tête (une marge sous le bord) — une
     hauteur d'écran moins deux marges et une ligne. `dvh` et non `vh` :
     la porte est un plein cadre fixe, et sur un téléphone c'est la hauteur
     entre les barres qu'il occupe. Voir « Le store ». */
  --nom-course:calc(100dvh - var(--pad) * 2 - var(--type) * 1.15);

  /* --- Mouvement ------------------------------------------- */
  --ease:cubic-bezier(0.895,0.03,0.685,0.22);  /* cascade de chargement (charte) */
  --ease-out:cubic-bezier(0.16,1,0.3,1);       /* apparitions au défilement      */
  /* Le store — une seule durée, une seule courbe, pour la porte qui monte
     ET pour le nom qui monte avec elle : c'est ce qui les garde du même pas.
     La courbe d'apparition (--ease-out) a été essayée : la moitié de la
     course en 90 ms, près de 5 000 px/s au plus vite — l'œil ne suit pas, on
     ne voit pas le nom monter, il est déjà là. Celle-ci part en douceur,
     tire franchement au milieu et se pose : mi-course à 0,45 s, le nom
     s'installe à 0,8 s, le store finit seul le demi-seconde qui reste. */
  --store-duree:1.3s;
  --store-courbe:cubic-bezier(.4,.05,.2,1);
}

/* ============================================================
   Inter, hébergée ici — aucune requête vers un tiers.
   Fichier variable : un seul woff2 porte tout l'axe de graisse, 100 à 900.
   La plage déclarée doit le dire : déclarée « 300 500 », toute demande
   au-dessus était rabattue à 500 sans un mot — le survol du menu, à 600,
   n'aurait rien changé. Le site n'en emploie que quatre crans (300, 400,
   500, 600), mais c'est ici qu'on saurait qu'un cinquième est possible.
   Copyright (c) 2016 The Inter Project Authors (github.com/rsms/inter).
   SIL Open Font License 1.1 : texte complet dans ../assets/fonts/OFL.txt
   ============================================================ */
@font-face{
  font-family:'Inter';
  font-style:normal;
  font-weight:100 900;
  font-display:swap;
  src:url('../assets/fonts/inter-latin.woff2') format('woff2');
  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;
}

*{box-sizing:border-box;margin:0;padding:0}

/* Une seule taille, vraiment : sans ça, les titres retombent sur les valeurs
   par défaut du navigateur (h1 = 2em, h2 = 1.5em) et les boutons sur les leurs. */
h1,h2,h3,h4,h5,h6{font-size:1em;font-weight:inherit}
button,input,select,textarea{font:inherit}
html{scroll-behavior:smooth}
html,body{width:100%}

body{
  font-family:var(--font);
  font-size:var(--type);
  font-weight:400;
  color:var(--ink);
  background:var(--paper);
  -webkit-font-smoothing:antialiased;
  line-height:1.5;
  min-height:100vh;
}
body.scrolls{overflow-x:hidden}

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

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

/* titre de section, réservé aux lecteurs d'écran : à l'écran, c'est le nom
   au centre de l'en-tête qui titre */
.sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
img{display:block;max-width:100%}

/* --- Cascade d'apparition au chargement (charte) ----------- */
/* Rien n'est masqué tant que le JS n'a pas signalé sa présence (.js sur <html>,
   posé dans <head> par build.py, avant le premier rendu) : si le script est
   coupé, le site reste entièrement lisible plutôt que blanc.
   Le nom de l'en-tête n'est PAS dans la cascade (build.py, HEAD) : il est au
   même endroit sur toutes les pages et ne doit jamais y disparaître — dans
   la cascade, il tombait à zéro à chaque navigation sans transition.
   La transition est sur l'état d'ARRIVÉE (body.ready), pas sur l'état
   caché : déclarée sur `.js .fade`, elle jouait aussi de 1 vers 0 quand
   `.js` se posait sur une page déjà peinte (Safari ignore rel=expect), et
   `ready`, quelques millisecondes après, l'inversait depuis 0,99 — tout
   était là d'un coup, un clic sur deux. Vu au journal des transitions le
   11 septembre 2026 (transitionrun, transitioncancel, transitionend à
   188 ms). */
.js .fade{opacity:0;transform:translateY(4px)}
body.ready .fade{opacity:1;transform:none;transition:opacity 1s var(--ease),transform 1s var(--ease)}
body.ready .fade.d1{transition-delay:.15s}
body.ready .fade.d2{transition-delay:.35s}
body.ready .fade.d3{transition-delay:.55s}

/* ============================================================
   En-tête fixe — le nom reste, les images glissent dessous
   ============================================================ */
.header{
  position:fixed;top:0;left:0;right:0;z-index:100;
  display:flex;justify-content:space-between;align-items:flex-start;
  padding:var(--pad);
  pointer-events:none;
  /* LA hauteur de ligne de l'en-tête, et la seule. Le nom la portait à
     1,15 quand la navigation héritait du 1,5 du corps : deux boîtes de
     hauteurs différentes, alignées par le haut, dont les textes ne
     tombaient donc pas sur la même ligne de base. Le décalage se voyait, et
     il n'avait aucune raison d'être. Posée ici, elle vaut pour le nom, la
     navigation et la ligne du projet — les trois blocs de la même ligne. */
  line-height:1.15;
}
/* Pas de fond derrière l'en-tête : ni bande, ni filet, ni dégradé.
   Le nom et la navigation flottent directement sur les images. */
.header a{pointer-events:auto}

.brand{font-family:var(--font);font-weight:400}
/* Le nom est un lien : au survol il s'épaissit comme les entrées du menu,
   et glisse vers sa graisse à la même vitesse qu'elles. Le double invisible
   du titre de projet (.lp-double) reste à 400 : il donne la LARGEUR au repos,
   et le titre ne doit pas bouger parce qu'on survole le nom. */
.brand a{transition:font-weight .3s}
.brand a:hover{font-weight:600}

/* Le nom a le même aspect aux deux bouts de son trajet : capitales et
   espacement de la porte, repris par l'en-tête de toutes les pages. C'est ce
   qui fait croire, quand le store se lève, que c'est le même JULES FOCONE
   qui s'est déplacé — voir « Le store », plus bas. UNE règle pour les trois
   porteurs : le lien de l'en-tête, la porte, et le double invisible du
   titre de projet, qui doit avoir la largeur exacte du nom pour caler le
   titre. */
.porte-nom,.lp-double,.brand a{text-transform:uppercase;letter-spacing:.16em}

/* Le titre, le client et l'année d'un projet.

   Au repos, la ligne prolonge l'en-tête : on lit « Jules Focone — Alexandre
   Kasproviez », puis le client, puis l'année, puis le menu, d'un bord à
   l'autre de la page. Mais elle N'EST PAS dans l'en-tête — elle est dans la
   page, et elle monte avec les photos quand on descend. Seuls le nom et la
   navigation restent.

   Elle vit DANS l'en-tête, qui est fixe : elle reste en vue du haut au bas
   de la page, et chacun de ses liens reste joignable. Elle a passé un temps
   dans le corps de la page, à monter avec les photos — mais un titre qui
   s'en va emmène le menu avec lui, hors de portée passé le premier écran.

   `position:absolute` la sort du flex de l'en-tête : le nom garde sa place
   à gauche et la navigation la sienne à droite, quelle que soit la longueur
   du titre. Ses bornes sont celles de la PAGE et non du reste disponible
   entre ses deux voisins — c'est ce qui lui permet de se caler sur les
   photos plutôt que sur eux.

   Les positions se calent sur les photos : le client est justifié sur le
   bord DROIT de la photo de gauche, l'année démarre au bord GAUCHE de celle
   de droite. Deux colonnes égales et le même écart que les rangées suffisent
   à le dire. */
.ligne-projet{
  position:absolute;
  top:var(--pad);
  left:var(--pad);right:var(--pad);
  display:grid;
  grid-template-columns:1fr 1fr;
  column-gap:var(--pad);
  pointer-events:none;
}

/* Le titre pousse à gauche, le client tombe à droite — sur le bord de la
   photo qui est dessous. */
.lp-gauche{display:flex;justify-content:space-between;gap:1.5rem;min-width:0}
.lp-titre{font-weight:500;white-space:nowrap}

/* Le double invisible du nom, qui donne au titre son point de départ exact.
   `visibility:hidden` et non `display:none` : il doit occuper sa largeur,
   c'est toute sa raison d'être. La graisse est celle de .brand — une graisse
   différente donnerait une largeur différente, et le titre serait décalé.

   Sa marge droite EST l'espace entre le nom du site et celui du projet. Une
   espace dans le texte ne se règle pas ; celle-ci vaut --pad, l'écart de la
   page, et se lit comme tel. */
.lp-double{visibility:hidden;font-weight:400;margin-right:var(--pad)}

/* À l'encre, comme le titre et l'année : toute la ligne du projet est d'une
   seule couleur, et c'est la graisse du titre qui y fait la hiérarchie. Le
   gris est resté ailleurs — sur les étiquettes de la galerie et du
   glossaire, où il sépare le client du titre au milieu de seize autres. */
.lp-client{white-space:nowrap}
/* Chiffres tabulaires : sans eux, un 1 et un 2 n'ont pas la même largeur et
   l'année ne démarre pas au même endroit d'un projet à l'autre. */
.lp-annee{font-variant-numeric:tabular-nums}



.nav{text-align:right;font-weight:400}
.nav a{transition:opacity .3s,font-weight .3s}
.nav a + a::before{content:", ";color:var(--ink);font-weight:400}
/* La page en cours se lit à 500. Le survol monte à 600 — un cran net, là où
   400 → 500 se voyait à peine. Il est écrit APRÈS `.current`, qui pèse
   autant : sinon l'entrée de la page en cours serait la seule sourde à la
   souris. Toujours la graisse, jamais la couleur. */
.nav a.current{font-weight:500}
.nav a:hover{font-weight:600}

/* ============================================================
   Pied de page — fixé en bas de la fenêtre, comme l'en-tête en haut
   ============================================================ */
.footer{
  /* Mêmes principes que l'en-tête : fixe, sans fond ni voile, il flotte sur
     les photos, à l'encre. À gauche « lieux » puis, juste dessous, les droits ;
     à droite, sur la ligne des droits, les pages juridiques ; il ne reçoit
     pas la souris sauf sur ses liens.
     Au-dessus de tout, visionneuses comprises. */
  position:fixed;left:0;right:0;bottom:0;z-index:230;
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"l l" "c r";align-items:end;align-content:end;row-gap:var(--ecart-pied);
  height:var(--pied);padding:0 var(--pad) var(--pad);line-height:1.15;
  pointer-events:none;
}
/* UNE définition de la typo de l'interface, partagée par le pied, la ligne
   Close / compteur des visionneuses, le compteur du défilé et la flèche :
   celle du corps (Inter, --type, 400), sans casse ni
   espacement. L'en-tête les hérite du corps, sans rien redire. */
.footer,.bas,.defile-compteur,.pv-fermer,.pv-compteur,.lb-bar,.lb-close{
  font-family:var(--font);font-size:var(--type);font-weight:400;font-style:normal;
  letter-spacing:normal;text-transform:none;line-height:1.15;
}
.foot-l{grid-area:l}
.foot-c{grid-area:c}
.foot-r{grid-area:r;margin-left:auto;text-align:right}
.foot-r a{pointer-events:auto}
/* Les pages qui défilent gardent une réserve sous leur dernière ligne : le
   pied fixe ne doit rien masquer. */
body.scrolls{padding-bottom:var(--pied)}

/* Flèche de descente — MOBILE UNIQUEMENT. Mène au bas de la page d'un
   geste. Elle s'efface quand on y est déjà, sinon elle recouvrirait le
   pied de page. */
.bas{
  display:none;
  position:fixed;right:var(--pad);bottom:var(--pad);z-index:95;
  border:0;background:none;cursor:pointer;
  font:inherit;line-height:1;color:var(--mist);
  padding:14px;margin:-14px;          /* zone de touche large, glyphe petit */
  transition:opacity .35s var(--ease-out),visibility .35s,color .3s;
}
.bas:hover{color:var(--ink)}
.bas.is-cache{opacity:0;visibility:hidden;pointer-events:none}
.footer a{transition:font-weight .3s}
.footer a:hover{font-weight:600}   /* comme le menu : la graisse, jamais la couleur */

/* ============================================================
   La porte — la page d'accueil
   Une photo plein cadre, un nom. Toute la page est un lien.
   ============================================================ */
.porte-page{margin:0;background:var(--paper)}

.porte{
  position:fixed;inset:0;display:block;
  background:var(--paper);cursor:pointer;
  text-decoration:none;
  view-transition-name:store;    /* voir « Le store », plus bas */
}

/* Pas d'équerres sur la porte — c'est une demande : à l'accueil, la photo
   est nue, plein cadre. La visionneuse fait de même (plus bas). */

.porte-img{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;              /* le seul recadrage du site, assumé */
  object-position:50% 50%;       /* écrasé photo par photo par build.py */
}

/* La suite — l'accueil montre plusieurs photos, l'une après l'autre.
   Elles sont empilées au même endroit et c'est l'opacité qui les échange :
   aucune ne bouge, aucune ne se recadre au passage.

   Tout est derrière `.js`, comme la cascade d'apparition plus haut. La
   première photo est dans le HTML, les suivantes sont fabriquées par le
   script : sans script, aucune des deux règles ne s'applique, la première
   reste pleine et seule, et la porte est celle d'avant. Rien n'est jamais
   masqué par une feuille de style en attendant un script qui ne viendrait
   pas.

   Le fondu est long : une seconde et demie sur trois de présence — la
   moitié du temps de pose, CADENCE_ACCUEIL dans build.py, et il doit
   suivre si elle change. Ce n'est pas une transition, c'est une
   dissolution — on ne doit pas voir le moment où l'image change,
   seulement s'apercevoir qu'elle a changé.

   UN SEUL SENS SE FOND, et c'est le mauvais qu'on croit. La photo qui ARRIVE
   devient opaque sur-le-champ, sous la pile ; c'est celle qui PART qui se
   fond, par-dessus elle, et qui la découvre en s'effaçant. Le script se
   charge de remettre la sortante au-dessus (z-index) à chaque passage, et de
   poser l'entrante sans fondu.

   Pourquoi pas les deux à la fois, ce qui serait l'évidence : parce que deux
   opacités qui se croisent ne font pas une pile opaque. À mi-fondu, deux
   images à 0,5 laissent passer la moitié du fond — et le fond est noir. Le
   milieu du fondu virait donc au sombre, à chaque changement. Ici la pile
   est pleine d'un bout à l'autre : l'une est toujours à 1.

   La transition est posée une fois pour toutes et ne bouge plus. Une règle
   `transition:none` sur `.is-vue` aurait dit la même chose plus court, mais
   passer de `none` à `1.5s` DANS le changement de style qui fait aussi
   varier l'opacité ne démarre aucun fondu : la sortante sautait à zéro.
   C'est le script qui rend l'entrante immédiate, par un reflow. */
.js .porte-img{opacity:0;transition:opacity 1.5s linear}
.js .porte-img.is-vue{opacity:1}

/* Le système demande qu'on réduise les animations : la rotation s'arrête
   net côté script, et le fondu avec elle ici. Un plein cadre qui change
   tout seul est du mouvement, et c'est tout l'écran. */
@media (prefers-reduced-motion:reduce){
  .js .porte-img{transition:none}
}

.porte-nom{
  /* Au-dessus de la pile, toujours. Les photos prennent les étages 1 et 2
     pendant un fondu (voir main.js) ; sans étage à elle, le nom serait
     recouvert par la sortante pendant la seconde et demie du passage,
     puis reparaîtrait d'un coup. */
  position:absolute;bottom:var(--pad);z-index:3;
  /* Un bloc posé au centre, et non un texte centré sur toute la largeur :
     c'est ce qui permet de le déplacer. `left` et `transform` glissent
     ensemble — même durée, même courbe — pour une droite. */
  left:50%;transform:translateX(-50%);
  white-space:nowrap;
  transition:left .35s var(--ease-out),transform .35s var(--ease-out);
  color:var(--ink);              /* le même ivoire que partout ailleurs */
  font-size:var(--type);         /* 12 px, comme tout le site */
  /* La hauteur de ligne de l'en-tête : les deux boîtes du nom doivent avoir
     la même hauteur, sans quoi le groupe de transition redimensionne l'une
     vers l'autre et le texte bouge à l'intérieur pendant le trajet. */
  line-height:1.15;
  /* Capitales et espacement : avec .brand, plus haut. */
  /* Pas de voile, pas d'ombre portée : l'image a été choisie pour que le nom
     tombe sur du noir. Si on change d'image, c'est l'ancrage qu'on règle. */
}
/* Quitté : le nom est au bord gauche, en bas — là d'où il montera. Posé au
   clic, avant de partir ; posé aussi à l'arrivée, avant la première image,
   quand on revient à la porte par la transition (voir main.js). */
.porte.is-quittee .porte-nom{left:var(--pad);transform:none}

/* Le focus clavier doit rester visible : c'est le seul lien de la page. */
.porte:focus-visible{outline:2px solid var(--ink);outline-offset:-8px}

/* ============================================================
   Le store — de la porte à l'index

   On clique : la porte monte comme un store qu'on lève, et derrière il y
   a l'index. Le nom, au centre de la porte, a d'abord glissé au bord
   gauche (`.is-quittee`, sur la porte elle-même) ; il monte avec le store
   et s'accroche en haut, à sa place dans l'en-tête. Deux documents, une
   transition de navigation entre les deux : le navigateur prend un
   instantané de la porte (`store`) et du nom (`nom`), affiche l'index
   dessous et anime le passage. Ce qu'on voit dessous est la page, pas une
   image de la page.

   `nom` est porté aux DEUX bouts — le nom de la porte, le nom de
   l'en-tête — sans quoi le navigateur ne saurait pas que c'est le même.
   Le nom est un descendant de la porte : il est peint au-dessus d'elle
   pendant le trajet.

   LE NOM NE FAIT PAS SON PROPRE TRAJET. Il reste en bas à gauche du store
   tant que le store le porte, puis s'installe quand il arrive à la hauteur
   de l'en-tête — et le store continue seul, derrière lui. Un premier
   dessin le faisait monter de son côté, même durée, même courbe : il
   arrivait quand le store était parti, et rien ne disait qu'il avait été
   porté. Ici, le groupe du nom se pose d'emblée à l'arrivée ; c'est la
   paire d'images qui part décalée vers le bas, de toute sa course
   (`--nom-course`), et qui la remonte du pas exact du store — la courbe du
   store, coupée à l'endroit où le nom arrive, voir plus bas — puis
   s'arrête. À la descente, l'inverse : il attend en haut que le bord du
   store le rejoigne, puis descend avec lui.

   Le CSS ne sait pas d'où l'on vient : c'est main.js qui nomme le sens —
   `leve` en arrivant de la porte, `baisse` en y revenant. Sans sens, le
   nom ne bouge pas du tout : d'un projet à Work, il est au même endroit
   sur les deux pages, et la transition est une coupure — l'ancienne image
   du nom cachée d'emblée, la nouvelle vivante et identique, aucun quart de
   seconde de page inerte.

   Les autres navigations du site ne changent pas non plus pour la racine.
   Par défaut, la transition fond l'ancienne racine dans la nouvelle en un
   quart de seconde, et le groupe de la racine s'anime autant ; retiré, les
   trois : la nouvelle page couvre l'ancienne sur-le-champ, la coupure
   d'aujourd'hui — et pas un quart de seconde de page inerte sous le voile
   de la transition.

   Un navigateur qui ne connaît pas les transitions de navigation ignore
   tout ce bloc, et le script ne retient pas le clic (main.js, « Le
   store ») : le lien reste un lien.
   ============================================================ */
@view-transition{navigation:auto}
.porte-nom,.brand{view-transition-name:nom}
::view-transition-group(root),::view-transition-old(root),::view-transition-new(root){animation:none}
::view-transition-old(store){animation:store-leve var(--store-duree) var(--store-courbe) both}
::view-transition-new(store){animation:store-baisse var(--store-duree) var(--store-courbe) both}
@keyframes store-leve{to{transform:translateY(-100%)}}
@keyframes store-baisse{from{transform:translateY(-100%)}}

/* Le nom : posé à l'arrivée, l'ancienne image cachée, et rien ne bouge —
   sauf quand un sens est nommé. */
::view-transition-group(nom),::view-transition-new(nom){animation:none}
::view-transition-old(nom){animation:none;opacity:0}
/* Et toujours AU-DESSUS du store. Le navigateur empile les groupes dans
   l'ordre de peinture de la page de départ, et y ajoute ceux qu'elle n'a
   pas : au retour vers la porte, l'index n'a pas de store, le store passe
   donc après le nom — dessus — et le nom qui attend en haut disparaissait
   sous le bord du store, pour reparaître d'un coup en bas à la fin. */
::view-transition-group(nom){z-index:1}
/* Le pas du store, coupé là où le nom s'arrête. Un premier dessin faisait
   le calcul en CSS à chaque image : une propriété enregistrée montait de 0
   à 1 au pas du store et le déplacement en découlait, buté à zéro par
   max(). Exact sur le papier, décalé à l'écran : une propriété s'anime sur
   le fil principal, une transformation sur le compositeur — deux horloges.
   Dès que la page dessous coûte (l'index et ses vignettes), le nom prenait
   jusqu'à vingt-cinq pixels d'avance sur le store, puis les rendait. Ici le
   nom n'a qu'une transformation à images clés, comme le store, et c'est
   main.js qui lui taille sa durée et sa courbe (« Le pas du nom ») : le
   morceau de la courbe du store qui va de zéro à la course du nom,
   renormalisé — point par point le même déplacement que le store, jusqu'à
   l'arrêt. À la descente, le morceau de la fin, après une attente. Sans le
   script, la durée et la courbe du store : le nom arrive avec lui. */
:root:active-view-transition-type(leve)::view-transition-image-pair(nom){
  animation:nom-leve var(--nom-duree,var(--store-duree)) var(--nom-courbe,var(--store-courbe)) both;
}
:root:active-view-transition-type(baisse)::view-transition-image-pair(nom){
  animation:nom-baisse var(--nom-duree,var(--store-duree)) var(--nom-courbe,var(--store-courbe)) var(--nom-delai,0s) both;
}
@keyframes nom-leve{from{transform:translateY(var(--nom-course))}to{transform:translateY(0)}}
@keyframes nom-baisse{from{transform:translateY(calc(-1 * var(--nom-course)))}to{transform:translateY(0)}}
/* Le nom de l'en-tête est plein dès la première image de toute page : il
   est hors de la cascade d'apparition (build.py, HEAD). Rien à tenir ici
   le temps du trajet — le nom qui arrive n'est jamais un vide. */

/* ============================================================
   La galerie — le défilé —, puis les photos de chaque projet.
   « .galerie » ne porte que le retrait : la galerie et une page
   de projet le partagent. Une page de projet pose ses photos en
   rangées (.row, plus bas) ; la galerie n'en montre qu'une.
   ============================================================ */
.galerie{padding:0 var(--pad)}

/* ============================================================
   Le défilé — la galerie : une photo à la fois

   Cinquante photos dans le HTML, une seule à l'écran : entière, jamais
   recadrée, au milieu de la fenêtre, avec de l'air autour. La
   moitié droite mène à la suivante, la gauche à la précédente — le
   geste de la visionneuse (plus bas), dont elle reprend la nudité :
   pas de cadre, la photo sur le fond.

   Tout est derrière `.js`. Sans script, les .vue coulent en colonne,
   pleine largeur, avec la respiration des rangées : la galerie reste
   lisible telle quelle, en défilant.

   Trois états, trois classes posées par main.js : la courante
   (`is-vue`, seule visible), ses deux voisines (`is-voisine` :
   présentes, à zéro — le navigateur les charge, et avec le dérivé que
   la page demande, puisque c'est la page qui les demande), et les
   autres, en display:none — qui ne charge rien. Une photo qu'on
   appelle est toujours une voisine, donc toujours déjà en place : le
   fondu a un point de départ, et deux photos qui se croisent font un
   fondu enchaîné, comme sur la porte.

   La première porte `is-vue` dès le HTML (build.py) : elle est là au
   premier rendu, et c'est sur elle que le store se lève.

   Au bureau, la photo est PLUS PETITE QUE LA FENÊTRE, ET CENTRÉE
   (demande de Jules — elle a d'abord touché le haut, au plus grand). La
   scène couvre toute la fenêtre : les deux moitiés restent deux moitiés
   d'écran, l'en-tête flotte dessus comme sur la porte, le compteur
   reste dans son coin. Chaque vue s'en retire d'une marge sur les
   quatre bords — le cadre —, et la photo, entière, se cale au milieu
   du cadre (object-fit:contain, position par défaut : le centre). Une
   verticale laisse du vide à gauche et à droite, une horizontale en
   haut et en bas ; dans les deux cas, autant de chaque côté.

   Au téléphone (plus bas), rien de tout ça : la photo prend toute la
   largeur, du premier pixel, calée en haut de sa scène. */
.defile .vue{margin:0 0 9vh}
.defile .vue img{width:100%;height:auto}

.js .defile{
  position:fixed;inset:0;   /* la scène : toute la fenêtre */
  padding:0;
}
.js .defile .vue{
  position:absolute;margin:0;
  inset:10vh 10vw;   /* le cadre : 80 % de la fenêtre en hauteur comme en largeur, centré */
  opacity:0;transition:opacity .3s;   /* la durée de la visionneuse */
}
.js .defile .vue img{width:100%;height:100%;object-fit:contain}
.js .vue.is-vue{opacity:1}
.js .defile .vue:not(.is-vue):not(.is-voisine){display:none}
.js body:has(.defile){overflow:hidden}

/* Les deux moitiés — mêmes curseurs que la visionneuse. Au toucher, iOS
   et Android teintent le bouton pressé, et le bouton est une moitié
   d'écran : la photo s'assombrissait d'un côté à chaque pas. Jules ne
   veut rien voir — la photo passe, c'est tout. */
.defile-zone{
  position:absolute;top:0;bottom:0;width:50%;
  border:0;background:transparent;padding:0;
  -webkit-tap-highlight-color:transparent;
}
.defile-zone.prev{left:0;cursor:w-resize}
.defile-zone.next{right:0;cursor:e-resize}
.defile-zone:focus{outline:none}
.defile-zone:focus-visible{outline:1px solid var(--mist);outline-offset:-1px}

.defile-compteur{
  position:fixed;right:var(--pad);bottom:calc(var(--pad) + 1.15em + var(--ecart-pied));   /* au-dessus du pied : la ligne Legal / Privacy, à l'écart commun */
  color:var(--mist);font-variant-numeric:tabular-nums;
  pointer-events:none;
}

/* ============================================================
   Work — le glossaire

   Deux moitiés : la liste à gauche, la photo à droite. La liste
   est au gris chaud et la ligne visée monte à l'ivoire — le fond
   noir retourne le geste de la référence, où c'est la liste qui
   s'assombrit.
   ============================================================ */

.work{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:var(--pad);
  align-items:start;
  padding:0 var(--pad) var(--pad);
}

/* Quatre colonnes : le titre, le client, la prestation, l'année. Les
   deux premières se partagent la place, les deux dernières prennent la
   leur — la prestation n'a que deux valeurs, l'année quatre chiffres.

   CHAQUE LIGNE EST SA PROPRE GRILLE, et c'est un piège : une colonne
   `auto` se mesure ligne par ligne. Tant que la dernière colonne ne
   portait que des années, quatre chiffres partout, rien ne bougeait. La
   prestation a deux longueurs — « Commission », « Personal series » — et
   les colonnes d'une ligne à l'autre ne tombaient plus au même endroit.
   La liste devient donc la grille, et chaque ligne s'y inscrit en
   sous-grille : les colonnes sont mesurées une fois, pour toutes les
   lignes. La grille par ligne reste en repli pour les navigateurs qui
   ignorent `subgrid`. */
.ligne{
  display:grid;
  grid-template-columns:1fr auto auto;
  column-gap:1.5rem;
  padding:.2rem 0;
  color:var(--mist);
  transition:color .3s;
}
/* L'Index a quatre colonnes : le titre (le nom de l'architecte ou du
   designer) prend la place qui reste, puis le type du projet, l'année, le
   lieu. */
/* Press en a six : le magazine, le support (Web / Print), le type du
   projet, le créateur ou la créatrice, le lieu, l'année. Le support prend la
   place qui reste : il colle au nom du magazine, et les quatre dernières se
   mesurent une fois pour toutes les lignes. Son
   repli est posé ICI, avant le @supports, et pas plus bas : `.presse
   .ligne` est plus spécifique que `.ligne`, et placé après le bloc il
   battrait la sous-grille — les colonnes redeviendraient mesurées ligne
   par ligne (constaté au navigateur). Dans le bloc, la même sélection
   redit `subgrid` pour la même raison. */
.presse .ligne{grid-template-columns:auto 1fr auto auto auto auto}
@supports (grid-template-columns:subgrid){
  .work-liste{display:grid;grid-template-columns:1fr auto auto;column-gap:1.5rem}
  .ligne{grid-column:1/-1;grid-template-columns:subgrid}
  .presse .work-liste{grid-template-columns:auto 1fr auto auto auto auto}
  .presse .ligne{grid-template-columns:subgrid}
}
/* Survol, focus clavier et ligne ACTIVE (celle dont la photo ou la bande est
   à droite, main.js ligneActive) : le même état, à l'encre, pour l'Index et
   Press. Que la couleur — la graisse ferait bouger les colonnes. */
.ligne:hover,.ligne:focus-visible,.ligne.is-active{color:var(--ink)}
/* Press — ligne VERROUILLÉE par un clic (main.js, « Press — la bande ») : elle reste à
   l'encre, comme survolée, et pendant le verrou le survol des autres lignes ne les
   éclaire plus (le focus clavier, lui, se voit toujours). Que la couleur. */
.presse.is-verrou .ligne:not(.is-verrou):not(:focus-visible):hover{color:var(--mist)}
.presse .ligne{cursor:pointer}

/* Press — les parutions, présentées comme l'Index. Le cadre n'est pas
   le 4:5 de l'Index : la page pose `--vignette-ratio` au ratio des
   couvertures, en style en ligne, pour que la couverture s'y voie
   entière — une couverture porte son titre en haut et son pied en bas,
   un cadre fixe en couperait un. La scène et les cartes lisent la même
   variable, rien d'autre à faire. Une ligne sans lien est un span :
   même gris, même passage à l'encre. */
span.ligne{cursor:default}

/* Chiffres tabulaires : sans eux, un 1 et un 2 n'ont pas la même
   largeur et la colonne des années ondule d'une ligne à l'autre. */
.l-annee{text-align:right;font-variant-numeric:tabular-nums}
/* L'Index n'a plus de colonne d'année (Jules, 30/10/2026) : la dernière colonne, le lieu, prend sa place et son alignement
   — à droite, collée au bord où les années finissaient (et où finit celle de Press) —, le type la précède. Rien ne dépasse
   de la liste, donc rien ne rencontre la scène à toutes les largeurs. */
.work:not(.presse) .l-lieu{text-align:right}

/* Collante : la liste peut dépasser l'écran, la photo reste en vue.
   Elle s'amarre sous l'en-tête fixe, dont --header donne la hauteur. */
.work{
  /* La place de la photo : la fenêtre, moins l'en-tête, moins le pied (fixe,
     même hauteur : --pied). La photo y tient entière, ratio gardé, et se
     CENTRE : la marge sous l'en-tête et la marge au-dessus du pied sont
     égales, sur tout écran. Bornée aussi par la largeur de sa colonne. */
  --scene-h:min(var(--zone-h),calc((50vw - 1.5 * var(--pad)) / (var(--vignette-ratio))));
  --scene-m:calc((var(--zone-h) - var(--scene-h)) / 2);   /* la marge de centrage, en haut comme en bas */
}
.work-scene{
  position:sticky;
  top:calc(var(--header) + var(--scene-m));
  margin-top:var(--scene-m);
  aspect-ratio:var(--vignette-ratio);
  height:var(--scene-h);
  width:auto;
  justify-self:end;
  overflow:hidden;
}
/* Pas de marge basse : le pied fixe et la réserve du corps (body.scrolls)
   la remplacent, la page ne défile pas pour rien. */
@media (min-width:901px){.work{padding-bottom:0}}

/* Toutes les photos occupent la même case et se superposent : c'est
   l'opacité seule qui décide de celle qu'on voit. Rien ne bouge dans
   le DOM — déplacer un nœud remettrait sa transition à zéro. */
.work-img{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
  opacity:0;
}
.js .work-img{transition:opacity var(--fondu-work) linear}
.work-img.is-vue{opacity:1}

@media (prefers-reduced-motion:reduce){
  .js .work-img{transition:none}
}

/* Press : la scène est justifiée à gauche de sa colonne — elle démarre au
   milieu de la page, là où l'Index, lui, se cale à droite. */
.presse .work-scene{justify-self:start}

/* La bande de Press : la parution elle-même, à la place de la scène — même
   colonne, même largeur (posées par main.js depuis la scène). Un panneau
   défilant par parution, couverture en tête puis l'article ; un seul est vu,
   les autres fondent. Elle s'arrête sous l'en-tête et au-dessus du pied, qui
   restent lisibles ; sans barre de défilement : la bande, pas un cadre. */
.bande{
  position:fixed;top:0;bottom:0;z-index:90;
  opacity:0;pointer-events:none;transition:opacity .35s linear;
}
.bande.is-open{opacity:1;pointer-events:auto}
.bande-pan{
  position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  scrollbar-width:none;
  padding-top:var(--haut,0);   /* la couverture démarre où la scène est centrée */
  opacity:0;visibility:hidden;transition:opacity .35s linear,visibility 0s .35s;
}
.bande-pan::-webkit-scrollbar{display:none}
.bande-pan.is-vue{opacity:1;visibility:visible;transition-delay:0s}
.bande-pan:focus{outline:none}
.bande-pan{cursor:pointer}   /* un clic mène à l'article en ligne */
.bande-page{display:block;width:100%;height:auto;background:var(--attente)}
@media (max-width:900px){.bande{display:none}}
@media (prefers-reduced-motion:reduce){.bande,.bande-pan{transition:none}}

/* L'Index au téléphone — une carte par projet : la photo dans le cadre
   de la scène (--vignette-ratio, object-fit:cover, pour que les huit
   photos du projet défilent sans que rien ne bouge), la ligne du projet
   dessous. Au bureau les cartes n'existent pas : display:none, et leurs
   images paresseuses ne sont jamais tirées. C'est la requête média, plus
   bas, qui échange la liste et la scène contre les cartes. */
.work-cartes{display:none}
.carte{display:block;color:var(--mist);margin-bottom:var(--ecart-m,30px)}
.carte-cadre{
  position:relative;display:block;
  aspect-ratio:var(--vignette-ratio);
  overflow:hidden;
  background:var(--attente);
}
.carte-img{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;
  opacity:0;
}
.js .carte-img{transition:opacity var(--fondu-index-m) linear}
.carte-img.is-vue{opacity:1}
/* Une carte qui se charge à l'approche (main.js, « Les cartes se chargent à l'approche ») n'a pas encore de photo : sans
   `src`, le navigateur écrirait son texte de remplacement dans le cadre. Le cadre reste le fond d'attente. */
.carte-img[data-src]{visibility:hidden}
/* La ligne sous la photo : les mêmes cellules que la liste, rangées comme
   la liste du téléphone — le titre et l'année en haut, le type et le lieu
   en bas. */
.carte-ligne{
  display:grid;grid-template-columns:1fr auto;column-gap:1.5rem;
  padding:.6rem 0 0;
}
.carte-ligne .l-titre{grid-column:1;grid-row:1;color:var(--ink)}
.carte-ligne .l-annee{grid-column:2;grid-row:1}
.carte-ligne .l-type{grid-column:1;grid-row:2}
.carte-ligne .l-lieu{grid-column:2;grid-row:2;text-align:right}
/* Press : trois rangs — le magazine et l'année, le type et le support, le
   créateur et le lieu. */
.presse .carte-ligne .l-support{grid-column:2;grid-row:2;text-align:right}
.presse .carte-ligne .l-createur{grid-column:1;grid-row:3}
.presse .carte-ligne .l-lieu{grid-row:3}

@media (prefers-reduced-motion:reduce){
  .js .carte-img{transition:none}
}

/* Sous 900 px la photo passe au-dessus, et la ligne se replie sur deux
   rangs : le titre et l'année en haut, le client et la prestation en bas
   — quatre colonnes serrées feraient perdre ses mots au titre avant que
   les autres n'y gagnent leur place. */
@media (max-width:900px){
  .work{grid-template-columns:1fr;padding:0 var(--pad-m) var(--pad-m)}
  /* La liste et sa scène s'effacent, les cartes prennent la page : une
     photo par projet, qui défile, et sa ligne dessous. La liste sous une
     photo unique a existé ici ; Jules l'a trouvée fausse au téléphone. */
  .work-liste,.work-scene{display:none}
  .work-cartes{display:block}
}


/* Les deux pages réservent la bande de l'en-tête. Une page de projet parce
   qu'elle écrit son nom au centre de cette bande, et ce nom-là se lit ; la
   galerie parce que ses photos prennent toute la largeur — la colonne 1
   toujours vide qui portait « Jules Focone » sur du blanc a disparu avec
   le mur. Pas de voile ni d'ombre portée pour s'en dispenser :
   la charte les exclut. */
.sous-entete{padding-top:var(--header)}

/* La légende d'un projet, sur sa page : en tête, au style des informations du projet (couleur et taille du type et du lieu). */
.legende-projet{color:var(--mist);margin:0 0 4vh;max-width:min(100%,46em)}

/* ============================================================
   Les rangées d'une page de projet
   ------------------------------------------------------------
   Deux photos de front, toute la largeur, un seul écart — la
   discipline de la galerie. Une seule différence, et c'est la
   bonne : la hauteur n'est JAMAIS imposée ici, elle suit le
   ratio natif du fichier. Aucun recadrage sur une page de
   projet.

   Comment les deux tiennent quand même la même hauteur : chaque
   photo reçoit un `flex-grow` égal à son rapport largeur/hauteur
   (posé en ligne par build.py, seule valeur qui y reste). Sur
   une base nulle, flexbox répartit alors la largeur au prorata
   des ratios — et w/ratio, la hauteur, sort identique pour les
   deux. À ratios égaux, c'est-à-dire seize projets sur dix-sept,
   le partage tombe exactement à la moitié : de vraies colonnes,
   droites du haut de la page au bas.

   Ce que ça remplace : un mur de 15 colonnes aux largeurs et
   aux décrochés verticaux tirés au sort, qui laissait la
   colonne 1 vide et creusait de grands blancs.
   ============================================================ */
.row{
  display:flex;
  gap:var(--pad);
  align-items:flex-start;  /* la figure épouse sa photo, elle ne s'étire pas :
                              sinon la légende au survol, ancrée sous la
                              figure, décrocherait du bas de l'image */
  margin-bottom:9vh;       /* respiration unique — celle de la galerie */
}

/* une image */
.shot{position:relative;cursor:zoom-in}
.js .shot{
  opacity:0;transform:translateY(10px);
  transition:opacity 1.1s var(--ease-out),transform 1.1s var(--ease-out);
}
.shot.is-in{opacity:1;transform:none}
/* La figure garde sa colonne ; la photo s'y cale au bord gauche, bornée par la
   hauteur utile de la fenêtre (--r, posé en ligne par build.py : largeur /
   hauteur du fichier). Une verticale 2:3 ne dépasse plus de l'écran. */
.shot img{width:min(100%,calc(var(--cadre-h) * var(--r,1000)));height:auto;background:var(--attente)}

.shot::after{
  content:attr(data-caption);
  position:absolute;left:0;top:calc(100% + 9px);color:var(--mist);
  opacity:0;transition:opacity .35s;pointer-events:none;white-space:nowrap;
}
.shot:hover::after{opacity:1}

/* ============================================================
   Pages éditoriales — À propos, Contact
   ============================================================ */
.page{
  /* La marge du site, à gauche comme partout ailleurs. Le texte démarrait
     trois colonnes plus loin pour laisser passer l'index fixe des séries ;
     cet index a quitté le site, et son retrait avec lui — il ne restait
     qu'un grand blanc à gauche du texte. */
  min-height:100vh;
  display:flex;flex-direction:column;justify-content:center;
  padding:22vh var(--pad) 16vh;
}
.page h1{font-family:var(--font);font-weight:300;margin-bottom:40px}
.page .lede{font-family:var(--font);max-width:640px;font-weight:300;line-height:1.45;margin-bottom:34px}
.page .prose{max-width:560px;font-family:var(--font);font-weight:400;line-height:1.6}
.page .prose p + p{margin-top:1.1em}
.page .prose h2{font-weight:500;margin:2.4em 0 .7em}
.page .prose h2:first-child{margin-top:0}
.page .prose strong{font-weight:500;color:var(--mist)}
.page .prose a{text-decoration:underline;text-decoration-thickness:.5px;text-underline-offset:.28em;text-decoration-color:var(--hair);transition:text-decoration-color .3s}
.page .prose a:hover{text-decoration-color:var(--ink)}

/* Les deux pages juridiques portent leurs deux langues, empilées.
   Le lien d'ancrage saute l'anglais ; le filet dit où l'on change de
   langue. Pas de commutateur : un texte qui engage doit se trouver au
   Ctrl+F et s'imprimer en entier. */
.page .vers-fr{margin-bottom:2rem;color:var(--mist)}
.page .vers-fr a{color:var(--mist);transition:color .3s}
.page .vers-fr a:hover{color:var(--ink)}
.page .filet-langue{border:0;border-top:1px solid var(--hair);margin:4rem 0 2rem}
.page .titre-fr{color:var(--mist)}

/* « À propos » — deux colonnes : la biographie à gauche, le portrait et les
   coordonnées à droite. Contact était une page à part ; elle tenait en trois
   lignes, et « À propos » finissait par un lien vers elle. Les deux disent
   maintenant la même chose au même endroit, sans aller-retour.

   Le portrait est POUSSÉ au bord droit — c'est space-between qui l'y met, pas
   une largeur de conteneur : il tombe sur la marge du site, exactement comme
   la dernière vignette d'une rangée de galerie. Le grand blanc du milieu est
   ce qui reste entre les deux, et c'est lui qui fait la page. */
.duo{
  display:grid;
  grid-template-columns:minmax(0,560px) auto;
  justify-content:space-between;
  align-items:center;   /* texte et photo centrés l'un par rapport à l'autre */
  column-gap:var(--pad);
}

/* La photo est un CADRE, pas une image posée : elle court du haut du titre au
   bas de la dernière ligne, et c'est le texte d'en face qui lui donne cette
   hauteur.

   D'où le position:absolute. Une image dans le flux aurait pesé de sa propre
   hauteur dans le calcul de la rangée : la rangée aurait pris la plus haute
   des deux colonnes, et la photo se serait mesurée à elle-même. Détachée, elle
   ne pèse rien — la rangée est celle du texte, et la photo la remplit.

   Ce que ça coûte : object-fit:cover rogne ce qui dépasse, comme les vignettes
   de la galerie. 430 px est choisi pour que ça ne coûte justement rien au
   point de dessin — 1440 × 900, trois paragraphes et trois lignes de
   coordonnées : le texte fait alors 522 px de haut, le cadre 430 × 522, soit
   0,824 contre 0,822 pour les scans de labo. Deux millièmes. Le rognage
   n'apparaît qu'aux largeurs où le texte se replie autrement, et il reste
   centré. */
/* La photo garde ses proportions : 430 px de large au plus, et si la hauteur
   manque elle se réduit pour tenir entre l'en-tête et le pied (--zone-h, comme
   la scène de l'Index). Jamais rognée, jamais étirée. */
.cote{display:flex;justify-content:flex-end}
.portrait{margin:0}
.portrait img{display:block;width:auto;height:auto;max-width:430px;max-height:var(--zone-h)}
/* Sur ordinateur, main.js mesure le texte (de « Photographer… » à « Social ») et
   donne cette hauteur à la photo ; la largeur suit le ratio du fichier. */
.duo.is-mesure .portrait img{height:var(--texte-h);max-width:none;object-fit:contain;object-position:right center}

/* Une page à deux colonnes est HAUTE : le portrait fait à lui seul le double
   du texte qui lui fait face. Les 22vh de réserve qui aèrent une page de
   texte court y enverraient les coordonnées sous la ligne de flottaison — la
   page garde donc la réserve d'en-tête, et rien de plus. Écrit en :has() et
   pas en classe : c'est la présence de la colonne qui change la réserve, et
   c'est une affaire de mise en page, pas de gabarit. Un navigateur qui ne
   connaîtrait pas :has() rend la page d'avant, plus aérée — jamais cassée. */
/* Le contenu est centré dans ce qui reste entre l'en-tête et le pied (le pied
   fixe est déjà compté dans le padding du body). */
.page:has(.duo){min-height:calc(100dvh - var(--pied));padding-top:var(--header);padding-bottom:0}

/* Les coordonnées ferment la colonne de texte : le libellé en gris, la valeur
   en ivoire, une ligne chacune. Les trois colonnes de l'ancienne page Contact
   n'ont plus lieu d'être — il n'y a plus de page à remplir. */
.joindre{margin-top:2.4em}
.joindre p{display:flex;gap:1.2em;line-height:1.9}
.joindre span{flex:0 0 5.4em;color:var(--mist)}
.joindre a{transition:opacity .3s}
.joindre a:hover{opacity:.55}

/* Sous 900 px, deux colonnes ne tiennent plus : le portrait passe sous le
   texte, à la largeur de la page. */
@media (max-width:900px){
  .duo{grid-template-columns:minmax(0,1fr);row-gap:44px}
  /* Une seule colonne : il n'y a plus de rangée à remplir, la photo revient
     dans le flux et reprend le ratio de son fichier. */
  .cote{max-width:430px}
  .portrait img{width:100%;max-width:none;max-height:none}
}

/* ============================================================
   Un projet de l'Index — la visionneuse (clic sur une ligne, la
   couverture ou une carte)

   La photo de la scène (ou de la carte) VOLE jusqu'au centre de
   l'écran, les informations du projet se lèvent à gauche, puis on
   parcourt le projet : moitié droite / gauche de l'écran, flèches,
   balayage. Les photos, leur position et leur taille sont posées par
   main.js (bloc « 5 quater ») ; ici, seulement l'habillage.
   Sous l'en-tête (z-index 100), au-dessus du reste ; le pied (230) reste au-dessus.
   ============================================================ */
.pv{position:fixed;inset:0;z-index:90;overscroll-behavior:contain;touch-action:none}
.pv[hidden]{display:none}
.pv:focus{outline:none}
.pv-fond{position:absolute;inset:0;background:var(--paper);opacity:0;transition:opacity .4s linear}
.pv.is-ouvert .pv-fond{opacity:1}

/* Les photos : chacune posée à sa taille exacte (left/top/width/height en
   px, par le script), donc sans bande ni rognage. L'opacité seule les
   fait passer de l'une à l'autre — même fondu que le défilé. */
.pv-img{
  position:absolute;display:block;object-fit:cover;
  background:var(--attente);opacity:0;pointer-events:none;
  transition:opacity .3s;
}
.pv-img.is-vue{opacity:1}

.pv-info{
  position:absolute;left:var(--pad);top:var(--pv-haut,var(--header));
  width:min(240px,calc(50vw - var(--pad) * 2));
  pointer-events:none;z-index:2;   /* au-dessus des zones précédente/suivante : Close doit recevoir le clic */
  opacity:0;transform:translateY(8px);
  transition:opacity .5s var(--ease-out) .25s,transform .7s var(--ease-out) .25s;
}
.pv.is-ouvert .pv-info{opacity:1;transform:none}
.pv-titre{font:inherit;font-weight:500;color:var(--ink)}
.pv-meta{color:var(--mist);margin-top:.2rem}
.pv-meta span{display:block}
/* La légende facultative d'un projet : la taille et la couleur du type et du lieu, sous eux. Vide, elle n'occupe rien. */
.pv-legende{color:var(--mist);margin-top:.6rem}
.pv-legende:empty{display:none}

.pv-zone{
  position:absolute;top:var(--header);bottom:var(--pied);width:50%;
  border:0;background:transparent;padding:0;
  -webkit-tap-highlight-color:transparent;
}
.pv-zone.prev{left:0;cursor:w-resize}
.pv-zone.next{right:0;cursor:e-resize}
.pv-zone:focus{outline:none}
.pv-zone:focus-visible{outline:1px solid var(--mist);outline-offset:-1px}

/* Dans la colonne d'informations (main.js les y range), sous le texte du
   projet : un vide de trois lignes, le compteur, puis Close, collés. */
.pv-compteur{
  display:block;margin-top:3em;
  color:var(--mist);font-variant-numeric:tabular-nums;pointer-events:none;
}
.pv-fermer{
  display:block;width:fit-content;margin-top:.2rem;pointer-events:auto;text-align:left;
  border:0;background:none;padding:0;
  color:var(--mist);cursor:pointer;transition:color .3s;
}
.pv-fermer:hover,.pv-fermer:focus-visible{color:var(--ink)}
/* Close reste où il est, mais sa zone cliquable est le dernier huitième de
   l'écran à gauche (entre l'en-tête et le pied) : facile à atteindre. C'est un
   pseudo-élément du bouton : un clic dessus est un clic sur Close, et le survol
   allume le mot. Écran à souris seulement : au toucher, ce bord sert à revenir
   à la photo précédente. */
@media (min-width:901px) and (hover:hover){
  .pv-fermer{position:static}
  .pv-fermer::before{
    content:"";position:absolute;z-index:-1;cursor:pointer;
    left:calc(var(--pad) * -1);width:12.5vw;
    top:calc(var(--header) - var(--pv-haut, var(--header)));height:var(--zone-h);
  }
}
.pv-fermer,.pv-compteur{opacity:0;transition:opacity .4s linear .2s,color .3s}
.pv.is-ouvert .pv-fermer,.pv.is-ouvert .pv-compteur{opacity:1}

/* Les lignes et la scène se prêtent au clic : curseur main sur la photo. */
.work-scene{cursor:pointer}

/* Téléphone : la photo tient la largeur, l'information passe SOUS elle,
   compteur et Close en dernier, juste au-dessus du pied. */
@media (max-width:900px){
  .pv-info{
    top:auto;bottom:calc(var(--pad) + env(safe-area-inset-bottom,0px));
    width:auto;right:var(--pad);
    /* Deux lignes, basses et fixes : le titre et le compteur, puis les
       détails et Close. Le moins de hauteur possible, pour que la photo garde
       TOUTE la largeur de l'écran. */
    display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:1rem;align-items:baseline;
  }
  .pv-titre{grid-column:1;grid-row:1}
  .pv-compteur{grid-column:2;grid-row:1;margin:0;text-align:right}
  .pv-meta{grid-column:1;grid-row:2;margin-top:.2rem}
  .pv-fermer{grid-column:2;grid-row:2;margin:.2rem 0 0;justify-self:end;text-align:right}
  .pv-fermer{display:none}   /* au téléphone : plus de « Close », on touche en dehors de la photo ou en bas */
  .pv-legende{grid-column:1 / -1;grid-row:3;margin-top:.2rem}
  .pv-meta span{display:inline}
  .pv-meta span + span::before{content:", "}
  /* Si une photo haute passe sous le bloc d'informations, un dégradé sombre
     derrière le texte le garde lisible ; sur le fond sombre, on ne le voit pas. */
  .pv-info::before{
    content:"";position:absolute;z-index:-1;pointer-events:none;
    left:calc(var(--pad) * -1);right:calc(var(--pad) * -1);
    top:-28px;bottom:calc(var(--pad) * -1 - env(safe-area-inset-bottom,0px));
    background:linear-gradient(to top,rgba(20,16,12,.82),rgba(20,16,12,0));
  }
}

@media (prefers-reduced-motion:reduce){
  .pv-fond,.pv-img,.pv-info,.pv-fermer,.pv-compteur{transition:none}
  .pv-info{transform:none}
}

/* ============================================================
   Visionneuse plein écran — le geste hérité de Bastian Achard
   (clic gauche / droite, flèches clavier, Échap)
   ============================================================ */
.lightbox{
  position:fixed;inset:0;z-index:200;background:var(--paper);
  display:flex;align-items:center;justify-content:center;
  opacity:0;transition:opacity .4s var(--ease-out);
}
.lightbox[hidden]{display:none}
.lightbox.is-open{opacity:1}

/* La scène ne réserve que ce que l'interface occupe VRAIMENT, mesuré, pas
   estimé en vh. La photo prend tout le reste.

   En bas — la légende et le compteur commencent à `var(--pad)` du bord et
   font une ligne. La réserve vaut donc exactement leur hauteur : le bas de
   la photo vient toucher le haut du texte, sans un pixel perdu.

   En haut — RIEN. La croix de fermeture est dans le coin, et la marge
   latérale de 60 px suffit à l'en tenir à l'écart : une photo ne peut pas
   dépasser 1320 px de large là où la croix commence à 1391. Une verticale
   monte donc jusqu'au bord de l'écran.

   Ce que ça change, à 1440 × 900 : 850 px de haut pour la photo au lieu de
   720. Une 2:3 y passe de 480 × 720 à 567 × 850 — plus grande que sa
   vignette de galerie (834), ce qu'elle n'était pas. Elle reste plus courte
   que sur la page du projet (1013), et c'est irréductible : une photo de
   1013 px ne tient pas entière dans une fenêtre de 900. La page laisse
   défiler, la visionneuse doit la montrer d'un bloc. */
.lb-stage{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  padding:0 60px calc(var(--pad) + 3.1em);
}
.lb-stage img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;transition:opacity .3s}
.lb-stage img.is-loading{opacity:0}

/* Pas d'équerres dans la visionneuse — c'est une demande : ouverte en plein
   écran, la photo est nue, comme celle de l'accueil. Le cadre y a existé,
   porté par un span qui n'existait que pour lui ; il est parti avec. */

.lb-zone{position:absolute;top:0;bottom:0;width:50%;z-index:210;border:0;background:transparent;padding:0;
  -webkit-tap-highlight-color:transparent}   /* même raison que .defile-zone */
.lb-zone.prev{left:0;cursor:w-resize}
.lb-zone.next{right:0;cursor:e-resize}
.lb-zone:focus{outline:none}
.lb-zone:focus-visible{outline:1px solid var(--mist);outline-offset:-1px}   /* comme les moitiés de la galerie et de la visionneuse de l'Index */

.lb-close{
  position:absolute;top:calc(var(--pad) - 12px);right:calc(var(--pad) - 6px);z-index:220;
  border:0;background:transparent;cursor:pointer;
  font-family:var(--font);line-height:1;color:var(--ink);
  padding:8px;transition:opacity .3s;
}
.lb-close:hover{opacity:.5}

.lb-bar{
  position:absolute;left:0;right:0;bottom:0;z-index:215;
  display:flex;justify-content:space-between;
  padding:var(--pad) var(--pad) calc(var(--pad) + 1.6em);color:var(--mist);pointer-events:none;
}
.lb-counter{font-variant-numeric:tabular-nums}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width:900px){
  .row{margin-bottom:7vh}
}

@media (max-width:700px){
  :root{
    --pad:var(--pad-m);
    --ecart-m:30px;      /* LE seul écart entre deux photos, partout */
  }

  .header{padding:var(--pad-m);align-items:baseline;gap:10px}
  .brand{white-space:nowrap}   /* jamais le nom coupé en deux */

  /* Sous 700 px, « Jules Focone » et le menu tiennent déjà toute la
     largeur : la ligne du projet passe DESSOUS plutôt que de la prolonger,
     et son double invisible du nom n'a plus lieu d'être. Le client et
     l'année se rangent à droite du titre, sur une colonne unique. */
  .ligne-projet{
    top:calc(var(--pad) + 1.2em);
    grid-template-columns:1fr auto;
    column-gap:1rem;
  }
  .lp-double{display:none}
  .lp-titre{white-space:normal}
  body:has(.ligne-projet){--header:calc(var(--pad) * 2 + 2.4em)}
  .nav{flex:1 1 auto;text-align:right}

  /* À 375 px, le milieu de l'en-tête est déjà pris par la navigation : le nom
     du projet descend d'un rang plutôt que de passer dessous. */
  .header{flex-wrap:wrap}
  .titre-projet{position:static;order:3;flex:1 0 100%;text-align:center}
  /* deux lignes d'en-tête au lieu d'une : la réserve suit — marge, deux
     lignes pleines, la gouttière qui les sépare, marge. */
  .sous-entete{padding-top:calc(var(--pad-m) * 2 + 3em + 10px)}

  /* ----------------------------------------------------------------
     Les photos de série et celles de la galerie : même discipline, toute
     la largeur et TOUJOURS le même écart. Les !important écrasent le
     partage de largeur posé en ligne pour les rangées du bureau.
     ---------------------------------------------------------------- */
  .row{display:block;margin:0 !important}
  .row .shot{
    flex:none !important;      /* écrase le partage de largeur posé en ligne */
    width:100% !important;
    margin:0 0 var(--ecart-m) !important;
  }
  .shot::after{white-space:normal}

  /* Le défilé au téléphone : ni cadre ni marge — la photo prend toute
     la largeur, du premier pixel, calée en haut de sa scène, et l'en-tête
     flotte dessus (les pages de projet gardent leur marge et leur
     réserve). La scène s'arrête au-dessus de la ligne du compteur : une
     verticale bornée par la hauteur ne le recouvre pas. Sans script,
     même chose en colonne : .galerie et .sous-entete défaits pour cette
     page seule. */
  .js .defile{top:0;left:0;right:0;bottom:var(--pied)}
  .js .defile .vue{inset:0}
  .js .defile .vue img{object-position:50% 0}   /* le vide, s'il y en a, en bas */
  .defile{padding:0}

  .page{padding:16vh var(--pad-m) 10vh}
  .cols{gap:30px}

  /* Pied de page : deux lignes sobres — « lieux — droits » (elle passe à la
     ligne si l'écran est trop étroit), puis les pages juridiques à droite.
     Le pied est plus haut (--pied). Le compteur du défilé prend le bas
     gauche, à côté des pages juridiques ; la flèche « ↓ » monte au-dessus
     du pied. */
  :root{--pied:calc(var(--pad-m) * 2 + 2.4em)}
  .footer{flex-wrap:wrap;align-content:flex-end;row-gap:var(--ecart-pied);padding:0 var(--pad-m) var(--pad-m)}
  .foot-r{flex:1 0 100%}
  .defile-compteur{right:auto;left:var(--pad-m);bottom:var(--pad-m);line-height:1.15}

  .bas{display:block;right:var(--pad-m);bottom:calc(var(--pied) + .5em)}

  /* Même règle : rien en haut, la ligne de texte en bas. Au téléphone la
     photo est toujours bornée par la largeur, donc elle ne touche jamais
     la croix — elle reste centrée dans ce qu'on lui laisse. */
  .lb-stage{padding:0 var(--pad-m) calc(var(--pied) + 1.5em)}
  .lb-bar{padding-bottom:calc(var(--pied) + .2em)}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .fade,.shot{opacity:1!important;transform:none!important;transition:none!important}
  .defile .vue{transition:none!important}   /* le défilé : coupe franche */
  /* Le store : un plein cadre qui monte est du mouvement, et c'est tout
     l'écran. Rien ne bouge — la nouvelle page couvre l'ancienne, le nom de
     la porte ne glisse pas. */
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}
  .porte-nom{transition:none}
}

/* Zones cliquables un peu plus grandes (rien ne change à l'œil) : un pseudo-
   élément invisible prolonge chaque lien et bouton de quelques pixels, pour
   les atteindre plus vite. Un clic dessus est un clic sur le lien. */
.brand a,.nav a,.footer a{position:relative}
.brand a::after,.nav a::after,.footer a::after{content:"";position:absolute}
.brand a::after{inset:-10px -8px}
.nav a::after{inset:-10px -6px}
.footer a::after{inset:-10px -4px}

/* ============================================================
   Téléphone — passe d'accessibilité (demande de Jules). Rien ici ne touche
   la version bureau : tout est sous 700 px, sauf les cartes de l'Index (900,
   comme la requête qui les affiche).
   ============================================================ */
/* Les photos ne sont JAMAIS rognées : dans le cadre de la carte, elles
   s'adaptent (contain) ; le cadre garde son ratio, rien ne bouge d'une
   photo à l'autre. */
@media (max-width:900px){
  .carte-img{object-fit:contain}
  /* Index : la photo d'un projet remplit tout son cadre (elle est rognée, voulu
     par Jules) et le cadre prend toute la largeur de l'écran. Press garde ses
     couvertures entières. */
  .work:not(.presse) .carte-img{object-fit:cover}
  .work:not(.presse) .carte-cadre{margin-left:calc(var(--pad-m) * -1);margin-right:calc(var(--pad-m) * -1)}
}

@media (max-width:700px){
  /* Pied de page : « Paris, Biarritz » puis, juste dessous, les droits ;
     les pages juridiques à droite, sur la ligne des droits. */
  .footer{
    display:grid;grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"l l" "c r";align-items:end;align-content:end;row-gap:var(--ecart-pied);
  }
  .foot-r{margin-left:0;flex:none;text-align:right}

  /* Les pages qui défilent : l'en-tête et le pied ont le fond de la page, les
     photos passent dessous sans se mêler au texte. Le défilé garde son
     en-tête flottant sur la photo : un léger voile en haut suffit à le lire. */
  body.scrolls:not(:has(.defile)) .header,
  body.scrolls:not(:has(.defile)) .footer{background:var(--paper)}
  body:has(.defile) .header{background:linear-gradient(to bottom,rgba(20,16,12,.55),rgba(20,16,12,0))}
  body:has(.defile) .footer{background:var(--paper)}

  /* Le compteur du défilé et la flèche montent au-dessus du pied (qui a
     maintenant deux lignes à gauche). */
  .defile-compteur{bottom:calc(var(--pied) + var(--ecart-pied))}

  /* Tout se touche du doigt : les zones cliquables dépassent du texte
     (invisibles), vers 44 px de haut. */
  .brand a::after{inset:-16px -10px}
  .nav a::after{inset:-16px -8px}
  .footer a::after{inset:-14px -8px}
  .bas{padding:18px;margin:-18px}
  .pv-fermer{position:relative}
  .pv-fermer::before{content:"";position:absolute;inset:-14px -28px -14px -10px}

  /* About : le texte ne colle plus à l'en-tête ; une colonne, la photo dessous,
     entière. */
  .page:has(.duo){min-height:0;padding:calc(var(--header) + 44px) var(--pad-m) 6vh}
}

/* Téléphone — le pied n'est plus fixé : il ferme la page, tout en bas du
   contenu (et en bas de l'écran quand la page est courte). Il reste sur chaque
   page, donc Legal et Privacy restent atteignables. Le défilé d'accueil, dont
   la photo est fixe et plein écran, garde son pied en bas de l'écran. */
@media (max-width:700px){
  body.scrolls:not(:has(.defile)){display:flex;flex-direction:column;min-height:100dvh;padding-bottom:0}
  body.scrolls:not(:has(.defile)) > main{flex:1 0 auto}
  body.scrolls:not(:has(.defile)) .footer{
    position:static;height:auto;margin-top:20px;padding-top:var(--pad-m);
    pointer-events:auto;background:none;z-index:auto;
  }
  body.scrolls:not(:has(.defile)) .bas{bottom:calc(var(--pad-m) + .5em)}
}

/* Téléphone — le pied ne garde que le légal (demande de Jules) : « Legal » d'un
   côté, « Privacy » de l'autre. Le lieu et les droits, non obligatoires,
   disparaissent d'ici (ils restent au bureau). La virgule du HTML est masquée
   (font-size:0 sur le conteneur, rendu aux liens). */
@media (max-width:700px){
  :root{--pied:calc(var(--pad-m) * 2 + 2.3em + var(--ecart-pied))}   /* deux lignes et leur écart : « Paris-Biarritz » puis les droits */
  .footer{grid-template-columns:minmax(0,1fr);grid-template-areas:"l" "c"}
  .foot-r{display:flex;justify-content:space-between;width:100%;font-size:0;margin-left:0}
  .foot-r a{font-size:var(--type)}
}

/* Téléphone — la phrase d'intro d'About a exactement la typo du texte : même
   graisse (la taille était déjà la même, 11 px). */
@media (max-width:700px){
  .page .lede{font-weight:400}
}

/* ============================================================
   Téléphone — le menu (demande de Jules)
   Bouton à trois traits en haut à gauche, comme le site de l'Atelier Virginie
   Morel (icône « menu » de 22 px, trait de 1,2, zone de 40 px). Le menu qui
   s'ouvre est celui de julesmessad.com sur mobile : un voile qui descend du
   haut, flou, les liens blancs au centre qui apparaissent en cascade, la
   croix en haut à droite. Il contient Index, Press, About, Legal, Privacy ;
   main.js le construit à partir des liens du header et du pied.
   Au bureau, ni le bouton ni le menu n'existent.
   ============================================================ */
.menu-btn,.menu{display:none}

@media (max-width:700px){
  /* Le pied ne garde que le lieu et les droits : le légal est dans le menu. */
  .foot-r{display:none}

  /* L'en-tête : le nom à gauche, le bouton à droite (comme julesmessad.com et le site de l'Atelier). Sans script, le menu de
     navigation reste là où il était. */
  .js .nav{display:none}
  .js .header{justify-content:flex-start}
  .menu-btn{
    display:inline-flex;align-items:center;justify-content:center;
    position:absolute;right:6px;top:50%;transform:translateY(-50%);
    width:40px;height:40px;padding:0;border:0;background:none;
    color:var(--ink);cursor:pointer;pointer-events:auto;   /* blanc, comme le nom : lisible sur les photos */
    -webkit-tap-highlight-color:transparent;
    transition:color .3s,transform .3s;
  }
  .menu-btn:hover{color:var(--ink)}
  .menu-btn:active{transform:translateY(-50%) scale(.95)}
  .menu-btn svg{display:block;transition:rotate 1.8s cubic-bezier(.22,1,.36,1)}
  .menu-btn[aria-expanded="true"] svg{rotate:90deg}

  .menu{
    display:flex;position:fixed;inset:0;z-index:300;
    background:rgba(17,17,16,.42);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
    flex-direction:column;justify-content:center;align-items:center;gap:26px;
    transform:translateY(-100%);visibility:hidden;
    transition:transform .6s cubic-bezier(.22,1,.36,1),visibility 0s .6s;
    color:#fff;
  }
  .menu.open{transform:translateY(0);visibility:visible;transition:transform .6s cubic-bezier(.22,1,.36,1)}
  .menu a,.menu-close{
    opacity:0;transform:translateY(12px);
    transition:opacity .5s cubic-bezier(.22,1,.36,1),transform .5s cubic-bezier(.22,1,.36,1);
  }
  .menu.open a,.menu.open .menu-close{opacity:1;transform:translateY(0)}
  .menu.open a:nth-of-type(1){transition-delay:.10s}
  .menu.open a:nth-of-type(2){transition-delay:.16s}
  .menu.open a:nth-of-type(3){transition-delay:.22s}
  .menu.open a:nth-of-type(4){transition-delay:.28s}
  .menu.open a:nth-of-type(5){transition-delay:.34s}
  .menu a{position:relative;color:#fff;font-size:var(--type)}
  .menu a::before{content:"";position:absolute;inset:-14px -16px}   /* zone du doigt, invisible */
  .menu a::after{
    content:"";position:absolute;left:0;right:0;bottom:-4px;height:1px;background:currentColor;
    transform:scaleX(0);transform-origin:left;transition:transform .4s cubic-bezier(.22,1,.36,1);
  }
  .menu a:hover::after,.menu a[aria-current="page"]::after{transform:scaleX(1)}
  .menu a:active{opacity:.5}
  .menu-close{
    position:absolute;top:0;right:5px;width:2.4em;height:2.4em;   /* la croix tombe exactement où était le bouton */
    display:flex;align-items:center;justify-content:center;
    background:none;border:0;border-radius:50%;color:#fff;font:inherit;font-size:1.6em;line-height:1;
    padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent;
  }
  .menu.open .menu-close:hover{transform:rotate(90deg)}
  .menu.open .menu-close:active{transform:scale(.82)}
  body.menu-ouvert{overflow:hidden}
}
@media (prefers-reduced-motion:reduce){
  .menu,.menu a,.menu-close,.menu-btn svg{transition:none}
}

/* Pas de cadre bleu sur le site (demande de Jules) : ni le flash bleu du toucher,
   ni l'anneau de focus autour d'une photo, d'une carte ou d'un nom. Sur écran
   tactile aucun anneau ; à la souris, le navigateur n'en montre qu'au clavier. */
html,a,button,[tabindex]{-webkit-tap-highlight-color:transparent}
/* Le clic à la souris ne montre rien (`:focus` sans `:focus-visible`), comme avant. */
a:focus:not(:focus-visible),button:focus:not(:focus-visible),[tabindex]:focus:not(:focus-visible){outline:none}
/* FOCUS CLAVIER (31/10/2026) : avant, `a:focus{outline:none}` partout effaçait tout signe au clavier — le nom, la
   navigation, le pied, les coordonnées d'About n'avaient AUCUN signe (mesuré : 14 arrêts de Tab sur 15 sans
   rien). Désormais un contour fin à l'encre (la couleur du texte), à 3 px du mot, sans animation, uniquement au
   clavier (`:focus-visible`) : à la souris et au doigt, l'aspect ne change pas d'un pixel. Les éléments qui ont
   déjà leur signe (la porte, les moitiés de la galerie et de la visionneuse, Close) le gardent, plus bas. */
:focus-visible{outline:1px solid var(--ink);outline-offset:3px}
/* Une ligne de liste (Index, Press) : le contour ne déborde pas sur le texte des lignes voisines. */
.ligne:focus-visible{outline-offset:1px}
@media (hover:none),(pointer:coarse){
  a:focus-visible,button:focus-visible,[tabindex]:focus-visible,
  .porte:focus-visible,.pv-zone:focus-visible,.defile-zone:focus-visible{outline:none}
}
