/* ==================================================================== */
/* Fiona Lebre — HOME <-> PROJET : LE PASSAGE                           */
/* ==================================================================== */
/*
 * CE PASSAGE EST D'UNE AUTRE FAMILLE QUE LES TROIS AUTRES, et il faut le
 * savoir avant de lire ce qui suit.
 *
 * home<->overview et home<->about ne rechargent RIEN : les trois vues sont
 * un seul document, et js/overview.js + js/about.js déplacent les éléments
 * à la main. Un projet, lui, est une vraie page — autre URL, autre
 * contenu, une planche qui n'existe pas sur la home. Reconstruire tout ça
 * sans navigation voudrait dire embarquer la liste d'images de CHAQUE
 * projet dans la home, pour n'en servir qu'une.
 *
 * On laisse donc le navigateur naviguer, et on lui dit quoi faire voyager.
 * Le site active déjà les View Transitions inter-pages (cf. theme.css,
 * @view-transition) : par défaut elles fondent la page entière. Nommer un
 * élément des DEUX côtés le sort de ce fondu et lui fait faire le trajet.
 *
 * Ce qu'on nomme, et ce que ça donne :
 *   fl-beige    le rectangle beige s'étend jusqu'au fond perdu ;
 *   fl-mount    le carton blanc grandit et se recentre ;
 *   fl-photo    la photo passe de l'un à l'autre ;
 *   fl-caption  la légende descend d'une ligne et recule d'une colonne ;
 *   fl-index    l'index s'en va (il n'existe pas sur un projet) ;
 *   fl-sheet    la planche arrive (elle n'existe pas sur la home) ;
 *   fl-head     le header ne bouge pas, et on le lui dit.
 * Tout le reste continue de fondre avec la racine.
 *
 * FICHIER SÉPARÉ, comme overview.css et about.css le sont de theme.css, et
 * pour la même raison très concrète : un geste se règle à l'œil, par
 * retouches successives, et republier 25 Ko de mise en page pour changer
 * une durée rend chaque essai coûteux.
 *
 * LES NOMS SONT PORTÉS PAR LES DEUX VUES SEULEMENT. Sur /overview/ et
 * /about/, le même markup existe mais masqué : un élément en display:none
 * ne produit pas d'instantané, donc ça marcherait quand même. On restreint
 * malgré tout aux deux classes de body concernées — un nom de vue en
 * double dans un même document ANNULE toute la transition, en silence, et
 * on ne veut pas que ça dépende d'un display hérité de trois fichiers.
 *
 * Firefox n'a pas encore les View Transitions inter-pages : il ignore tout
 * ce fichier et garde le fondu de secours de js/main.js. Rien ne casse, le
 * passage est simplement moins raconté.
 */

:root {
	/* Même durée et même courbe SYMÉTRIQUE que le pli vers About. La
	   symétrie (e(1-x) = 1-e(x)) est ce qui rend l'aller et le retour
	   identiques en vitesse — un point qui a demandé deux reprises sur le
	   pli, et qu'on ne va pas réapprendre ici. */
	--project-vt-duration: 0.9s;
	--project-vt-ease: cubic-bezier( 0.65, 0, 0.35, 1 );
	/* L'index et la planche ne voyagent pas, ils se croisent : celui qui
	   part s'efface tout de suite, celui qui arrive attend que la
	   géométrie soit posée. */
	--project-vt-fade: 0.4s;

	/* L'ÉCART ENTRE DEUX TITRES DE L'INDEX. Même valeur que le pli vers
	   About (--about-index-stagger dans css/about.css) et que les tuiles
	   de l'Overview (--overview-tile-stagger) : les trois vues emploient
	   le même vocabulaire, elles ont le même tempo. Le token est redéclaré
	   ici, sur :root, et pas emprunté à about.css — celui-là est posé sur
	   .home-index, or les pseudo-éléments de transition n'héritent QUE de
	   la racine. Un var() pointant vers .home-index y serait vide. */
	--project-index-stagger: 45ms;

	/* LE RETOUR PART UN POIL PLUS TARD, et ce n'est pas un rattrapage de
	   minutage : les deux sens ont la MÊME durée (--project-vt-duration) et
	   la même courbe, appliquées au groupe. Rien n'est plus rapide.

	   Ce qui diffère est la DISTANCE parcourue à l'écran. À l'aller le
	   carton s'étend, au retour il se contracte — et à durée égale, un
	   mouvement qui se referme se lit plus vite qu'un mouvement qui
	   s'ouvre. Les titres, partant à l'instant zéro, devançaient donc la
	   géométrie au retour alors qu'ils l'accompagnaient à l'aller.

	   0,1s suffit : c'est un décalage, pas une attente. L'ancienne version
	   faisait attendre TOUTE la durée du trajet (0,9s), et ça se lisait
	   comme un temps mort — cf. le bloc des titres plus bas.

	   À L'ALLER IL N'Y A PAS D'ÉQUIVALENT, volontairement : le texte est
	   déjà à l'écran, il n'a rien à rattraper, et le retarder ne ferait que
	   le laisser traîner sur une page qui part. */
	--project-index-in-delay: 0.1s;

	/* ------------------------------------------------------------------
	   LA PLANCHE DE CONTACT A SON PROPRE TEMPO, et c'est une correction.

	   Elle partageait --project-vt-fade (0,4s) et --project-vt-ease avec
	   l'index. Or les deux n'ont pas le même rôle : l'index est du TEXTE,
	   qui doit dégager vite ; la planche est un BLOC D'IMAGES, et un bloc
	   d'images qui apparaît en 0,4s sur une courbe en S se lit comme un
	   claquement. C'est ce qu'on entendait par « adoucir ».

	   Partager un token, c'est aussi s'interdire de régler l'un sans
	   l'autre. D'où deux tokens à elle.

	   LA DURÉE est presque doublée. Sur un fondu d'opacité, la douceur
	   vient beaucoup plus de la durée que de la courbe : l'œil ne perçoit
	   pas les 15 premiers pour cent d'opacité, donc une durée courte fait
	   commencer l'apparition « déjà en route ».

	   LA COURBE est douce aux DEUX bouts, contrairement à celle de la
	   géométrie (0.65, 0, 0.35, 1), qui est franche au milieu — bon pour
	   un déplacement qu'on veut décidé, mauvais pour une apparition.

	   L'ARRIVÉE ATTEND MOINS LONGTEMPS qu'avant. Le délai valait toute la
	   durée du trajet : la planche ne bougeait pas pendant 0,9s puis
	   arrivait. Avec un fondu plus long et plus doux, on peut le faire
	   chevaucher la fin du geste sans que la planche ne concurrence le
	   carton — ses premiers pour cent sont invisibles de toute façon.
	   Total inchangé à ~1,3s, mais l'apparition commence plus tôt et
	   monte plus lentement.

	   TROIS NOMBRES À TOUCHER, et ils sont indépendants : la durée, la
	   courbe, le retard d'arrivée. ------------------------------------ */
	--project-sheet-fade-duration: 0.7s;
	--project-sheet-fade-ease: cubic-bezier( 0.4, 0.05, 0.3, 1 );
	--project-sheet-in-delay: 0.62s;
}

/* ------------------------------------------------------------------ */
/* Ce qui voyage                                                       */
/* ------------------------------------------------------------------ */

body.fl-home .home-beige,
body.fl-project .home-beige {
	view-transition-name: fl-beige;
}

body.fl-home .fl-home-frame,
body.fl-project .fl-home-frame {
	view-transition-name: fl-mount;
}

/* LA COUCHE VISIBLE, ET ELLE SEULE. Les deux <img> du cadre existent
   toujours mais une seule porte .is-active — donc un seul nom, jamais
   deux. js/main.js et js/project.js retirent la classe de la sortante
   AVANT de la poser sur l'entrante ; l'invariant tient, et il DOIT tenir :
   deux fois le même nom dans un document annule toute la transition. */
body.fl-home .fl-home-img.is-active,
body.fl-project .fl-home-img.is-active {
	view-transition-name: fl-photo;
}

body.fl-home .home-caption,
body.fl-project .home-caption {
	view-transition-name: fl-caption;
}

/* L'INDEX COUVRE DÉSORMAIS TOUTE LA PAGE, et son instantané avec lui —
   chaque entrée se tend d'une marge à l'autre, et le bloc occupe les sept
   lignes (cf. theme.css). Ça reste sans conséquence ici : il n'existe pas
   sur une page projet, donc il n'y a pas de trajet, seulement une sortie
   en fondu. Un instantané pleine page dont le fond est transparent ne
   masque rien de ce qui voyage derrière. */
body.fl-home .home-index {
	view-transition-name: fl-index;
}

body.fl-project .project-sheet {
	view-transition-name: fl-sheet;
}

body.fl-home .site-header,
body.fl-project .site-header {
	view-transition-name: fl-head;
}

/* LA LÉGENDE RECULE D'UNE COLONNE — 2 à 4 ici contre 3 à 5 sur la home.
   Elle quitte donc le bord du tirage pour la bande vide de gauche, tout
   en restant à la même hauteur (ligne 7, cf. project.css).

   CE QUI DOIT COÏNCIDER, CE N'EST PAS LA POSITION, C'EST LA LARGEUR ET LE
   FER. Deux colonnes des deux côtés, ferré à gauche des deux côtés : la
   boîte capturée a exactement les mêmes dimensions au départ et à
   l'arrivée. Le navigateur n'a donc rien à mettre à l'échelle, et
   l'instantané se contente de SE DÉPLACER — un peu à gauche, un peu vers
   le bas. Un texte qui se déplace reste net ; c'est un texte qu'on ÉTIRE
   qui trahit la manœuvre, et l'étirement vient d'un écart de taille, pas
   d'un écart de place.

   La règle vit ICI et pas dans project.css parce que c'est cette
   contrainte-là — la largeur et le fer — qui appartient au passage. Le
   choix de la colonne, lui, est de la mise en page ; il est simplement
   écrit au même endroit pour que les deux se lisent ensemble.

   SI LA LARGEUR CHANGE D'UN CÔTÉ, elle doit changer de l'autre. C'est la
   seule chose à ne pas oublier ici. */
body.fl-project .home-caption {
	grid-column: 2 / 4;
	text-align: left;
}

/* ------------------------------------------------------------------ */
/* Comment ça voyage                                                   */
/* ------------------------------------------------------------------ */

/* Le trajet lui-même. Sans ça tout se ferait en 250ms — le défaut du
   navigateur, trois fois plus rapide que le moindre geste du site. */
::view-transition-group(fl-beige),
::view-transition-group(fl-mount),
::view-transition-group(fl-photo),
::view-transition-group(fl-caption),
::view-transition-group(fl-index),
::view-transition-group(fl-sheet),
::view-transition-group(fl-head) {
	animation-duration: var(--project-vt-duration);
	animation-timing-function: var(--project-vt-ease);
}

/* ==================================================================== */
/* LE BEIGE NE DOIT ARRIVER QU'UNE FOIS                                 */
/* ==================================================================== */
/*
 * Premier essai : le passage n'était pas fluide, et ça se voyait surtout
 * sur le rectangle beige. Deux causes, toutes deux invisibles dans le
 * code mais évidentes à l'écran.
 *
 * 1. LE FOND DU BODY CHANGEAIT AUSSI. La page projet peignait son body en
 *    beige, la home en blanc. Pendant le passage, la racine faisait donc
 *    son propre fondu blanc -> beige PENDANT que le rectangle grandissait.
 *    On voyait le beige arriver deux fois, par deux moyens différents et
 *    sur deux durées différentes. La règle plus bas rend les deux fonds
 *    identiques : le beige de la page projet vient désormais du seul
 *    élément .home-beige, qui couvre l'écran de toute façon.
 *
 * 2. LES DEUX INSTANTANÉS SE MÉLANGEAIENT EN "plus-lighter". C'est le
 *    défaut du navigateur, et c'est le bon choix quand deux contenus
 *    DIFFÉRENTS se croisent : l'addition évite le creux gris au milieu du
 *    fondu. Mais ici les deux instantanés sont le MÊME aplat beige. Les
 *    additionner pendant 0,9s produit une zone plus claire que le beige
 *    lui-même, qui se résorbe à la fin — exactement l'impression d'un
 *    fondu sale.
 *
 * D'où le traitement ci-dessous : pour les surfaces unies qui ne changent
 * PAS de couleur — le beige et le carton blanc — on ne fond rien du tout.
 * On jette l'instantané sortant, on garde l'entrant, et on laisse
 * seulement sa BOÎTE faire le trajet. Un rectangle uni qu'on redimensionne
 * ne peut pas mal fondre : il n'y a rien à fondre.
 */
::view-transition-old(fl-beige),
::view-transition-old(fl-mount) {
	display: none;
}

::view-transition-new(fl-beige),
::view-transition-new(fl-mount) {
	animation: none;
	/* block-size: 100% — CE N'EST PAS DÉCORATIF, voir le pavé ci-dessous. */
	inline-size: 100%;
	block-size: 100%;
	object-fit: fill;
}

/* ==================================================================== */
/* LE PIÈGE : block-size EST À "auto" PAR DÉFAUT                        */
/* ==================================================================== */
/*
 * C'est la cause du "rectangle beige qui part coupé, trop haut", et ça
 * vaut la peine de l'écrire en toutes lettres parce que rien dans le code
 * ne le laisse deviner.
 *
 * La feuille de style du navigateur pose ceci sur CHAQUE instantané :
 *
 *     ::view-transition-old(*), ::view-transition-new(*) {
 *         position: absolute;
 *         inset-block-start: 0;
 *         inline-size: 100%;
 *         block-size: auto;      <—
 *     }
 *
 * Autrement dit : l'instantané prend TOUTE LA LARGEUR de sa boîte, mais sa
 * hauteur suit SON PROPRE RAPPORT, et il est collé EN HAUT.
 *
 * Pour le beige, ça donne : l'instantané d'arrivée fait 1440x900 (rapport
 * 1,6). Au premier instant, la boîte fait 868x852 — celle du rectangle de
 * la home. L'image est donc dessinée en 868 de large sur 868/1,6 = 543 de
 * haut, calée en haut. Il manque 309 pixels de beige en bas, et le
 * rectangle paraît coupé et remonté. Exactement le défaut constaté.
 *
 * D'ordinaire ça ne se voit pas : le fondu par défaut démarre à opacité 0,
 * donc le pire moment est invisible. En supprimant ce fondu (animation:
 * none, pour éviter l'addition en plus-lighter) on a rendu le défaut
 * visible du premier au dernier instant. Une correction en a révélé une
 * autre.
 *
 * La règle, désormais partout ici : dès qu'on touche à un instantané, on
 * lui redonne block-size: 100%. La boîte de l'image redevient celle du
 * groupe, et object-fit reprend son sens — sans ça, "contain" ou "fill"
 * ne décident de rien, puisque la boîte a déjà le rapport de l'image.
 */

/* Le fond de la page projet repasse au blanc de la home. Il n'est JAMAIS
   visible au repos — .home-beige le couvre entièrement — donc son seul
   effet observable est pendant le passage, et c'est là qu'il faisait du
   dégât. Règle de transition, donc règle de ce fichier. */
body.fl-project {
	background: var(--color-page);
}

/* LA PHOTO NE S'ÉTIRE JAMAIS, MAIS ELLE DOIT VOYAGER. Les trois valeurs
   possibles ici donnent trois gestes très différents, et il a fallu se
   tromper deux fois pour trouver le bon.

   fill (le défaut)  l'instantané est tiré aux dimensions de la boîte. Le
                     carré de la home (487px) devient un portrait de
                     695x852 : 1,4 en largeur, 1,75 en hauteur. Une
                     déformation parfaitement visible sur un visage, et
                     contraire à la règle du site depuis le premier jour.

   none (essayé)     l'instantané est rendu à sa TAILLE NATURELLE, centré.
                     Aucune déformation — mais aucun voyage non plus : la
                     photo d'arrivée apparaît d'emblée à sa taille finale,
                     donc elle DÉBORDE du carton encore petit pendant
                     presque toute la durée. Très visible à l'aller, où
                     l'on va du petit vers le grand ; à peine au retour,
                     où l'on va du grand vers le petit. C'était le défaut
                     signalé.

   contain (retenu)  l'instantané est mis à l'échelle DANS la boîte, en
                     gardant ses proportions. La photo grandit donc avec
                     le carton, sans jamais se déformer ni le déborder.
                     C'est le seul des trois qui raconte le geste.

   ET SURTOUT : block-size: 100%. Sans lui, object-fit ne décide de RIEN
   ici — la boîte de l'image aurait déjà le rapport de l'image (cf. le
   pavé sur block-size plus haut), donc "contain" n'aurait rien à
   contenir, et la photo serait calée en haut au lieu d'être centrée.

   Pour la légende, le texte, la largeur ET le fer sont identiques des deux
   côtés (cf. la règle grid-column plus haut) : la boîte ne change pas de
   taille, donc contain ne met rien à l'échelle et l'instantané se contente
   de se déplacer — d'une colonne vers la gauche et d'une ligne vers le
   bas. Aucun sous-pixel, aucun flou en chemin. */
::view-transition-old(fl-photo),
::view-transition-new(fl-photo),
::view-transition-old(fl-caption),
::view-transition-new(fl-caption) {
	inline-size: 100%;
	block-size: 100%;
	object-fit: contain;
	object-position: center;
	animation-duration: var(--project-vt-duration);
	animation-timing-function: var(--project-vt-ease);
}

/* ==================================================================== */
/* LA BOÎTE DE L'IMAGE DOIT ÉPOUSER LA PHOTO                            */
/* ==================================================================== */
/*
 * C'est la cause du "on voit la différence de taille entre celle de la
 * home et celle du projet", et c'est une cause de fond : aucun réglage de
 * flou ou de courbe ne pouvait la corriger.
 *
 * UNE TRANSITION DE VUE CAPTURE DES BOÎTES D'ÉLÉMENTS, pas des pixels
 * peints. Or l'<img> est bien plus grande que la photo qu'elle affiche :
 * theme.css lui donne toute la surface du montage, 7% de padding, et
 * object-fit:contain. La photo n'occupe donc qu'un rectangle centré à
 * l'intérieur, variable selon son format.
 *
 * Chiffres sur un écran de 1440 x 900, avec une photo en 3:2 :
 *   home     boîte 487 x 487, photo 419 x 279 — 86% de la largeur
 *   projet   boîte 695 x 852, photo 452 x 301 — 65% de la largeur
 *
 * Le navigateur met à l'échelle LA BOÎTE. À chaque instant du fondu,
 * l'instantané sortant montre donc la photo à une taille et l'entrant à
 * une autre : 419 contre 258 au départ, 598 contre 452 à l'arrivée. Les
 * deux ne coïncident jamais, et l'œil lit ça comme deux images de tailles
 * différentes qui se superposent.
 *
 * LA CORRECTION : on redonne à l'<img> la taille de son contenu. Plus de
 * padding, plus de width/height à 100% — la boîte devient exactement la
 * photo, et les deux instantanés se recouvrent enfin.
 *
 * inset:0 + margin:auto + width/height:auto, c'est le centrage absolu
 * classique. On l'a choisi plutôt qu'un translate(-50%,-50%) pour une
 * raison précise : .fl-home-img porte DÉJÀ un transform, le scale de la
 * mise au point du carrousel (cf. --fl-scale dans theme.css). Un
 * deuxième transform l'aurait écrasé, et la mise au point aurait cessé
 * de fonctionner sur la home.
 *
 * RIEN NE BOUGE À L'ÉCRAN AU REPOS. La photo est peinte exactement au
 * même endroit et à la même taille qu'avant ; seule la boîte qui
 * l'entoure change. C'est une correction pour la transition, et c'est
 * pour ça qu'elle vit dans ce fichier.
 *
 * ET js/overview.js CONTINUE DE MARCHER. Il calcule le rectangle
 * réellement peint avec renderedRect() : boîte moins padding, puis
 * contenance. Avec une boîte qui a déjà le format de l'image et zéro
 * padding, ce calcul renvoie la boîte elle-même — donc la bonne valeur,
 * sans qu'on ait à toucher au module.
 */
body.fl-home .fl-home-img,
body.fl-project .fl-home-img {
	inset: 0;
	margin: auto;
	padding: 0;
	width: auto;
	height: auto;
}

/* Les retraits, repris à l'identique de ce qu'était le padding : 7% de
   chaque côté sur la home, une ligne de grille sur un projet. Le rendu
   est le même au pixel près, c'est la boîte qui a changé de rôle. */
body.fl-home .fl-home-img {
	max-width: 86%;
	max-height: 86%;
}

/* LARGEUR ET HAUTEUR NE SE BORNENT PAS DE LA MÊME FAÇON, et c'est le point.

   --project-mat-x sur la largeur : un POURCENTAGE du carton (7 % de
   chaque côté), donc un filet qui suit la largeur par construction. Sans
   ça une horizontale, qui est bornée par la LARGEUR, se faisait écraser
   dès qu'on réduisait la fenêtre — le retrait latéral était tiré d'une
   ligne de grille, donc de la HAUTEUR, et restait à 122px pendant que le
   carton passait de 731 à 525. Cf. son pavé dans css/project.css pour les
   mesures et pour le premier essai, en 9vw, qui n'allait pas assez loin.

   --project-mat sur la hauteur : inchangé. C'est lui qui donne ses 5
   lignes à une verticale et qui réserve la bande de blanc où la légende
   se pose.

   ET C'EST CE QUI GARDE LES VERTICALES INTACTES. Avec un retrait latéral
   assez fin, une verticale bute toujours sur son plafond de HAUTEUR et
   jamais sur celui de largeur — vérifié à 1728, 1200 et 1100 : 406x609
   dans les trois cas. Elle ne voit donc pas cette règle du tout. C'était
   faux avec la version en 9vw, où elle devenait bornée par la largeur en
   dessous de 1400 et se mettait à grandir. */
body.fl-project .fl-home-img {
	max-width: calc( 100% - 2 * var(--project-mat-x) );
	max-height: calc( 100% - 2 * var(--project-mat) );
}

/* ==================================================================== */
/* LE SURVOL DE L'INDEX RÉPOND À LA MAIN                                */
/* ==================================================================== */
/*
 * Survoler un titre dans l'index montre la photo d'ouverture de ce projet
 * au centre (cf. js/main.js). C'est ce qui rend le passage vers le projet
 * exact : la page d'arrivée affiche LA MÊME image, donc le navigateur fond
 * une photo dans elle-même à deux tailles au lieu de croiser deux images.
 *
 * MAIS LE CARROUSEL ET LE SURVOL NE PEUVENT PAS PARTAGER LE MÊME RYTHME.
 * La séquence de la home dure 3,8s — elle se déclenche toute seule, on la
 * contemple, et sa lenteur est le sujet. Au survol, la même séquence
 * paraîtrait cassée : on parcourt trois titres et le montage en est encore
 * au premier.
 *
 * js/main.js pose donc .fl-quick-swap sur le body pendant le survol, et
 * ces règles raccourcissent la séquence. On réutilise les tokens
 * --project-swap-* de la planche des pages projet, délibérément :
 * survoler un titre sur la home et survoler une vignette sur un projet
 * sont le même geste, ils doivent produire la même sensation.
 *
 * Les délais correspondants sont dans js/main.js (QUICK_TIMING). Les deux
 * DOIVENT rester d'accord — rien ne se recalcule tout seul. */
body.fl-quick-swap {
	--fl-blur: 10px;
	--fl-scale: 1.015;

	/* PAS DE PASSAGE PAR LE BLANC ICI NON PLUS, et c'était un oubli.
	   Ce délai valait --project-swap-out + --project-swap-gap, soit 460ms
	   de carton nu entre les deux photos : la grammaire du carrousel du
	   temps où il en avait un. Le carrousel l'a perdu (cf. carousel.css) et
	   le survol l'avait gardé — deux gestes voisins qui ne racontaient plus
	   la même chose. Zéro : les deux photos se croisent, comme partout
	   ailleurs sur ce site désormais. */
	--fl-gap-delay: 0s;

	/* Le carton refait le point avec sa photo, comme dans le carrousel,
	   mais au rythme de la main. Doit rester égal à --project-swap-in,
	   pour la même raison que dans carousel.css : sinon le bord redevient
	   net autour d'une image encore floue. */
	--fl-frame-refocus: var(--project-swap-in);

}

/* LA LÉGENDE SE FOND PLUS VITE AU SURVOL, dans le même rapport que le
   reste : au repos elle prend 1,1s (0,3 de sortie + 0,8 d'entrée), bien
   trop long quand l'image, elle, est en place en un demi-tour.

   Le total retombe sur 0,45s — exactement la valeur que portait
   --caption-reveal-duration ici du temps du rouleau. Le rapport entre les
   deux temps est conservé : la sortie reste le tiers de l'entrée.

   POSÉ SUR .home-caption ET NON SUR LE BODY, contrairement aux autres
   réglages de ce bloc. Les variables sont héritées, mais theme.css les
   déclare SUR .home-caption elle-même : une valeur posée plus haut dans
   l'arbre serait masquée par la sienne. Il faut donc viser le même
   élément, avec un sélecteur plus spécifique. */
body.fl-quick-swap .home-caption {
	--fl-cap-out: 0.15s;
	--fl-cap-in: 0.3s;
}

body.fl-quick-swap .fl-home-img {
	transition:
		opacity var(--project-swap-in) cubic-bezier( 0.4, 0, 0.2, 1 ),
		filter var(--project-swap-in) cubic-bezier( 0.2, 0.55, 0.35, 1 ),
		transform var(--project-swap-in) cubic-bezier( 0.2, 0.55, 0.35, 1 );
}

/* Règle plus spécifique que celle de theme.css, qui pose elle aussi un
   raccourci "transition" complet : à spécificité égale il l'aurait
   remplacée en silence. */
body.fl-quick-swap .fl-home-img.is-leaving {
	transition:
		opacity var(--project-swap-out) cubic-bezier( 0.4, 0, 1, 1 ),
		filter var(--project-swap-out) cubic-bezier( 0.4, 0, 1, 1 );
}

/* ==================================================================== */
/* PLUS DE FLOU : IL N'Y A PLUS RIEN À MASQUER                          */
/* ==================================================================== */
/*
 * Il y en a eu un, et il a servi. Trois raisons de l'avoir retiré, dans
 * l'ordre où elles sont apparues.
 *
 * 1. IL MASQUAIT LE CHANGEMENT D'IMAGE. La home montrait une photo tirée
 *    du carrousel, la page projet en montrait une autre : deux images
 *    sans rapport qui se croisaient. Depuis que survoler un titre affiche
 *    la photo d'OUVERTURE du projet (cf. js/main.js), et que la page
 *    projet ouvre sur la même (cf. $fl_opening_image dans
 *    template-parts/home-content.php), c'est la MÊME photo des deux
 *    côtés. Il n'y a plus de changement à cacher.
 *
 * 2. IL MASQUAIT LA MISE À L'ÉCHELLE. Elle était énorme tant que le nom
 *    de vue portait sur la boîte de l'<img> — 419px d'un côté, 258 de
 *    l'autre. Depuis que la boîte épouse la photo (cf. plus haut), le
 *    trajet réel ne fait plus que 419 -> 452, soit 8%. Un flou de 14px
 *    pour masquer 8% d'agrandissement, c'est le remède qui se voit plus
 *    que le mal.
 *
 * 3. IL COÛTAIT CHER. Un filtre animé sur une surface de 700 x 850, en
 *    même temps que sa boîte change de taille : aucune mise en cache
 *    possible, tout est recalculé à chaque image.
 *
 * Reste le fondu par défaut du navigateur, à notre durée et notre courbe
 * (posées plus haut avec la légende). Deux instantanés identiques, à la
 * même échelle au même instant : le fondu ne se voit pas, et on lit une
 * seule photo qui grandit.
 *
 * mix-blend-mode reste celui du navigateur (plus-lighter) : deux calques
 * semi-transparents superposés en mode normal creuseraient un gris au
 * milieu du fondu.
 *
 * SI LE FONDU REDEVIENT VISIBLE, c'est qu'on est arrivé sans survoler —
 * au doigt, au clavier, ou en cliquant depuis une autre photo du
 * carrousel. Là les deux images diffèrent à nouveau. Le remède ne serait
 * pas de remettre du flou partout mais de le poser CONDITIONNELLEMENT,
 * via les types de transition de vue (viewTransition.types).
 */
/* CELUI QUI PART S'EFFACE D'ABORD. La planche quand on quitte un projet :
   il n'y a pas de destination, donc pas de trajet — seulement une sortie.
   Rapide, pour dégager la place pendant que le carton rétrécit.

   L'INDEX N'EST PLUS ICI. Son instantané sortant est jeté plus bas — il
   arrivait rempli d'un aplat opaque, et ce fondu-là ne faisait donc que
   rendre le défaut visible pendant 0,4s. */
::view-transition-old(fl-sheet) {
	animation: fl-cross-out var(--project-sheet-fade-duration)
		var(--project-sheet-fade-ease) both;
}

/* ==================================================================== */
/* L'INDEX S'EFFACE TITRE PAR TITRE, PAS EN BLOC                        */
/* ==================================================================== */
/*
 * C'est l'animation du pli vers About, transportée sur le passage vers un
 * projet. Là-bas elle est simple : même document, chaque <a> porte une
 * transition CSS retardée de son rang (--fl-i), et les titres s'égrènent.
 * Ici on change de document, et une transition de vue capture des
 * INSTANTANÉS — .home-index n'en donnait qu'UN, donc tout l'index ne
 * pouvait que fondre d'un bloc. Aucun réglage de durée n'y changeait
 * rien : il fallait plusieurs instantanés.
 *
 * D'OÙ UN NOM PAR TITRE — view-transition-name: fl-idx-N, posé en ligne
 * par template-parts/home-content.php à côté du --fl-i qui y était déjà.
 * Chaque titre devient son propre groupe, avec son propre délai.
 *
 * ET UNE CLASSE POUR LES TRAITER ENSEMBLE — view-transition-class:
 * fl-idx. ::view-transition-old(.fl-idx) vise tous les groupes qui la
 * portent, donc l'animation s'écrit UNE fois, ici. Seul le délai diffère
 * d'un titre à l'autre, et il est écrit rang par rang plus bas : un
 * pseudo-élément de transition n'hérite PAS de l'élément dont il est
 * l'instantané (il est enfant de ::view-transition, rattaché à la
 * racine), donc le --fl-i du <a> lui est invisible.
 *
 * .home-index GARDE SON NOM. Son instantané devient vide — un élément
 * nommé exclut de son instantané les descendants qui ont leur propre nom,
 * et il n'a pas de fond — donc ses règles ne peignent plus rien. On le
 * laisse : il sert encore à EXCLURE le bloc du fondu de la racine.
 *
 * POUR REVENIR EN ARRIÈRE : retirer le view-transition-name en ligne dans
 * home-content.php. Tout ce qui suit redevient inerte et l'index refond
 * en un seul morceau, comme avant.
 */

/* PAS DE RACCOURCI "animation" DANS CES TROIS RÈGLES. Le délai est posé
   plus bas, nom par nom ; or un raccourci remet à leur valeur initiale
   toutes les sous-propriétés qu'il ne cite pas, animation-delay comprise.
   Il suffirait que la règle de classe gagne la cascade pour que tous les
   délais retombent à zéro et que l'index refonde en bloc, sans qu'aucune
   règle ne paraisse fausse. C'est le piège du "transition" sur
   .home-beige documenté dans css/overview.css, et il se rejoue ici.
   En propriétés longues, la classe dit QUOI jouer et les délais disent
   QUAND : leur ordre n'a plus d'importance. */
::view-transition-group(.fl-idx) {
	animation-duration: var(--project-vt-duration);
	animation-timing-function: var(--project-vt-ease);
}

/* AU RETOUR, LES TITRES REVIENNENT PENDANT LA GÉOMÉTRIE, pas après.

   Une première version leur donnait un délai de base valant TOUTE la
   durée du trajet : la home se refaisait, puis l'index se recomposait.
   C'est la règle qu'on suit pour la planche (fl-sheet) et elle est juste
   là-bas — la planche n'existe pas sur la home, elle ARRIVE, et on veut
   qu'elle arrive une fois le carton posé.

   L'index, lui, n'arrive pas : il REVIENT à une place qu'il occupait.
   Attendre la fin du trajet se lisait donc comme un temps mort après que
   tout le reste s'est posé. Il ne reste que --project-index-in-delay,
   0,1s — de quoi laisser le dézoom prendre une avance, sans faire
   attendre. Les titres se recomposent pendant que le carton rétrécit,
   comme ils s'effacent pendant qu'il grandit à l'aller. Les deux sens
   racontent la même chose, à un souffle près que le sens du mouvement
   justifie (cf. le token).

   animation-fill-mode: both est ce qui tient le titre invisible PENDANT
   son délai. Sans lui il serait à sa valeur finale — donc déjà visible —
   et l'égrenage ne se verrait pas à l'arrivée. */
::view-transition-new(.fl-idx) {
	animation-name: fl-cross-in;
	animation-duration: var(--project-vt-fade);
	animation-timing-function: var(--project-vt-ease);
	animation-fill-mode: both;
}

/* LA PLANCHE ATTEND, ET ELLE SEULE. Elle n'existe pas sur la home, elle
   ARRIVE, et on ne veut pas qu'elle concurrence le carton pendant qu'il
   grandit — même règle que sur l'Overview. Le retard ne vaut plus TOUTE
   la durée du trajet mais un peu moins (cf. --project-sheet-in-delay) :
   avec un fondu long et doux, les premiers pour cent sont invisibles, on
   peut donc chevaucher la fin du geste.

   L'INDEX N'EST PLUS ICI : son instantané est vide (les titres ont leurs
   propres noms) et, surtout, il ne doit plus attendre. Cf. le bloc des
   titres plus haut. */
::view-transition-new(fl-sheet) {
	animation: fl-cross-in var(--project-sheet-fade-duration)
		var(--project-sheet-fade-ease) var(--project-sheet-in-delay) both;
}

/* ==================================================================== */
/* L'INSTANTANÉ SORTANT DU TEXTE EST OPAQUE, ON LE JETTE                */
/* ==================================================================== */
/*
 * LE DÉFAUT : pendant le passage, une bande plus claire que le beige
 * apparaît à l'emplacement de l'index — rayée en autant de lignes qu'il y
 * a de titres — et un rectangle blanc entoure la légende.
 *
 * MESURÉ SUR UN ENREGISTREMENT D'ÉCRAN, image par image (8 im/s), avec la
 * toile du document peinte en magenta le temps du diagnostic :
 *
 *   im. 10-11  repos, marges blanches
 *   im. 12     TOUT est blanc, centre compris — le « flash » du début
 *   im. 13     le magenta apparaît dans les marges ; la bande de l'index
 *              et la boîte de la légende sont BLANC OPAQUE
 *   im. 14     ce blanc s'efface
 *   im. 15+    magenta franc dans les marges, beige sous la légende
 *
 * CE QUE ÇA ÉTABLIT. Le blanc s'efface en 2 à 3 images, soit ~0,3s — donc
 * exactement --project-vt-fade, la durée de fl-cross-out. Il est donc
 * DANS L'INSTANTANÉ SORTANT de ces groupes, et non vu à travers un trou :
 * un trou aurait montré le magenta dès la première image, comme les
 * marges. Aucune règle du thème ne peint de fond sur ces éléments —
 * vérifié sur toutes les feuilles — c'est donc le navigateur qui remplit
 * l'instantané d'un aplat opaque en le capturant.
 *
 * DEUX PISTES ÉCARTÉES EN CHEMIN, pour qu'on ne les reprenne pas :
 *   - l'addition en plus-lighter (mix-blend-mode: normal n'a rien changé) ;
 *   - le trou dans l'instantané de la racine (le magenta l'aurait montré).
 *
 * LE CORRECTIF est celui que ce fichier applique déjà au beige et au
 * carton, vingt lignes plus haut, et pour une raison voisine : quand un
 * instantané ne peut pas fondre proprement, on ne le fond pas. On jette
 * le sortant.
 *
 * CE QU'ON PERD, ET C'EST ASSUMÉ. À l'ALLER, les titres ne s'égrènent
 * plus en partant : ils disparaissent d'un coup pendant que le carton
 * grandit. Au RETOUR, l'égrenage est intact — c'est l'instantané ENTRANT
 * qui le porte, et lui n'a pas ce défaut puisqu'il arrive par-dessus une
 * page déjà peinte. Or c'est le retour qui compte : à l'aller le regard
 * suit la photo qui grandit, au retour il suit la home qui se recompose.
 *
 * SI ON VOULAIT L'ÉGRENAGE DANS LES DEUX SENS, il faudrait empêcher le
 * navigateur de remplir l'instantané sortant — et je n'ai pas trouvé
 * comment le lui demander. La piste la plus sérieuse serait de ne pas
 * nommer ces éléments à l'aller et de les animer en même-document avant
 * de naviguer, comme le fait le pli vers About.
 */
::view-transition-old(fl-index),
::view-transition-old(fl-caption) {
	display: none;
}

/* ==================================================================== */
/* ET DU COUP LA LÉGENDE NE FOND PLUS DU TOUT : ELLE SE DÉPLACE.         */
/* ==================================================================== */

/* CE QUI A ÉTÉ CASSÉ, ET PAR MOI. La règle ci-dessus jette l'instantané
   SORTANT de la légende, parce qu'il arrivait rempli de blanc opaque
   (mesuré 255,250,250) et déposait un cadre autour du texte. Jeter était
   la bonne décision. Ce qui ne l'était pas, c'est de s'arrêter là.

   Il restait l'ENTRANTE, et le navigateur la fait apparaître en fondu
   par défaut. Le résultat : la légende disparaissait, puis réapparaissait
   à sa nouvelle place en se dessinant. Le trajet était toujours là — le
   groupe l'anime — mais on ne le LISAIT plus, parce qu'un texte qui naît
   du vide ne se lit pas comme un texte qui bouge. C'est exactement ce
   qu'on avait gagné en alignant largeur et fer des deux côtés, et c'est
   exactement ce que ce fondu annulait.

   LA LÉGENDE N'A PAS BESOIN DE FONDU, ET C'EST UN FAIT SUR LE CONTENU :
   le texte est LE MÊME des deux côtés. La home nomme la photo du centre,
   on clique cette photo, la page projet nomme ce projet. Au retour,
   header.php repose le titre du projet quitté dans la légende de la home
   AVANT le premier rendu, donc l'égalité tient aussi dans ce sens.

   Deux instantanés identiques n'ont rien à se raconter en se croisant.
   On garde donc UNE SEULE légende, opaque du début à la fin, qui se
   déplace d'une colonne vers la gauche et d'une ligne vers le bas. Plus
   de cadre blanc, et le déplacement redevient lisible — ce n'est pas un
   compromis entre les deux défauts, c'est la sortie des deux.

   POURQUOI PAS UN FONDU PLUS DOUX, ou une opacité de départ à 0,5 : ça
   traiterait le symptôme. Un fondu sur du texte identique ne peut RIEN
   ajouter ; il ne peut que le rendre momentanément moins net.

   ON NE TOUCHE PAS AU GROUPE. C'est lui qui porte le trajet, avec la
   durée et la courbe du passage (cf. plus haut). On ne retire que le
   fondu de son contenu.

   SI UN JOUR LES DEUX TEXTES DIFFÈRENT — une légende qui dirait autre
   chose sur la page projet — cette règle devient fausse et il faudra
   remettre un croisement. C'est la seule condition à surveiller. */
::view-transition-new(fl-caption) {
	animation-name: none;
	opacity: 1;
}

/* LES TITRES, EUX, GARDENT LEUR SORTIE — et c'est le compromis retenu.

   MESURÉ sur l'enregistrement, à mi-fondu, sur une coupe horizontale au
   milieu de la bande : sur le beige, le voile ne fait que +3/+4/+10 en
   RGB (249,246,241 contre 246,242,231 au repos). C'est faible. Le blanc
   FRANC que j'avais cru voir traverser la page venait des marges, où la
   toile du document est légitimement visible, et de la légende — qui
   monte, elle, à 255,250,250, d'où son instantané jeté ci-dessus.

   Un voile de cette force pendant 0,4s, contre l'égrenage des titres en
   même temps que le carton grandit : l'échange vaut la peine. Si ça se
   voit trop à l'usage, la sortie se rejette en déplaçant .fl-idx dans la
   règle du dessus — une ligne.

   AUCUN DÉLAI AU CLIC. C'est tout l'intérêt de le jouer ici : la sortie
   se déroule PENDANT les 0,9s du zoom, pas avant. Une version précédente
   égrenait dans le document vivant avant de naviguer, et coûtait 336ms
   de clic qui ne répond pas. */
::view-transition-old(.fl-idx) {
	animation-name: fl-cross-out;
	animation-duration: var(--project-vt-fade);
	animation-timing-function: var(--project-vt-ease);
	animation-fill-mode: both;
}

/* Conservé : du texte sur fond transparent n'a pas besoin d'être
   additionné — plus-lighter n'existe que pour éviter le creux gris quand
   deux contenus DIFFÉRENTS se croisent, ce qui est le cas de fl-photo et
   pas le leur. Sans effet sur le défaut ci-dessus, mais juste sur le
   fond, et ça évite de le réessayer. */
::view-transition-new(fl-index),
::view-transition-new(fl-caption),
::view-transition-new(.fl-idx) {
	mix-blend-mode: normal;
}

/* LES DÉLAIS, UN PAR RANG. Écrits à la main et non générés : rien ici ne
   dépend du contenu, seul le NOMBRE de titres varie. Douze rangs couvrent
   le catalogue actuel — neuf projets — avec de la marge ; un treizième
   s'effacerait simplement sans retard, avec le premier. Dégradation
   visible mais inoffensive, et deux lignes à ajouter. C'est le seul
   endroit du thème où se répéter est la bonne réponse : cf. plus haut,
   un pseudo-élément de transition ne peut pas lire le rang de l'élément
   d'origine. */
::view-transition-old(fl-idx-0)  { animation-delay: calc( 0 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-1)  { animation-delay: calc( 1 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-2)  { animation-delay: calc( 2 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-3)  { animation-delay: calc( 3 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-4)  { animation-delay: calc( 4 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-5)  { animation-delay: calc( 5 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-6)  { animation-delay: calc( 6 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-7)  { animation-delay: calc( 7 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-8)  { animation-delay: calc( 8 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-9)  { animation-delay: calc( 9 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-10)  { animation-delay: calc( 10 * var(--project-index-stagger) ); }
::view-transition-old(fl-idx-11)  { animation-delay: calc( 11 * var(--project-index-stagger) ); }

::view-transition-new(fl-idx-0)  { animation-delay: calc( var(--project-index-in-delay) + 0 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-1)  { animation-delay: calc( var(--project-index-in-delay) + 1 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-2)  { animation-delay: calc( var(--project-index-in-delay) + 2 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-3)  { animation-delay: calc( var(--project-index-in-delay) + 3 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-4)  { animation-delay: calc( var(--project-index-in-delay) + 4 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-5)  { animation-delay: calc( var(--project-index-in-delay) + 5 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-6)  { animation-delay: calc( var(--project-index-in-delay) + 6 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-7)  { animation-delay: calc( var(--project-index-in-delay) + 7 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-8)  { animation-delay: calc( var(--project-index-in-delay) + 8 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-9)  { animation-delay: calc( var(--project-index-in-delay) + 9 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-10)  { animation-delay: calc( var(--project-index-in-delay) + 10 * var(--project-index-stagger) ); }
::view-transition-new(fl-idx-11)  { animation-delay: calc( var(--project-index-in-delay) + 11 * var(--project-index-stagger) ); }

/* Le header est identique des deux côtés : nommé uniquement pour qu'il
   soit EXCLU du fondu de la racine et reste parfaitement immobile.

   On JETTE l'instantané sortant au lieu de le fondre. Les deux sont
   identiques : les superposer ferait s'additionner l'antialiasing du
   texte, et "FIONA LEBRE" paraîtrait grossir puis maigrir. Un seul
   suffit. */
::view-transition-old(fl-head) {
	display: none;
}

::view-transition-new(fl-head) {
	animation: none;
	inline-size: 100%;
	block-size: 100%;
	object-fit: fill;
}

/* ------------------------------------------------------------------ */
/* Réduire les animations                                              */
/* ------------------------------------------------------------------ */

/* theme.css coupe déjà la racine sous ce réglage, mais nos groupes nommés
   ont leurs propres animations et ne sont pas concernés par cette
   règle-là. Chaque nom doit être cité. */
@media (prefers-reduced-motion: reduce) {
	::view-transition-group(fl-beige),
	::view-transition-group(fl-mount),
	::view-transition-group(fl-photo),
	::view-transition-group(fl-caption),
	::view-transition-group(fl-index),
	::view-transition-group(fl-sheet),
	::view-transition-group(fl-head),
	::view-transition-old(fl-index),
	::view-transition-new(fl-index),
	::view-transition-old(fl-sheet),
	::view-transition-new(fl-sheet),
	::view-transition-old(fl-photo),
	::view-transition-new(fl-photo),
	/* Les titres de l'index, nommés un par un : la classe les couvre tous,
	   y compris les projets à venir. Sans cette ligne l'égrenage jouerait
	   quand même — les groupes nommés ont leurs propres animations, et une
	   media query n'ajoute aucune spécificité. */
	::view-transition-group(.fl-idx),
	::view-transition-old(.fl-idx),
	::view-transition-new(.fl-idx) {
		animation: none;
	}
}
