/* ===========================================================================
   LES FINITIONS — feuille additive, chargée EN DERNIER
   ---------------------------------------------------------------------------
   Écrite le 09/10/2026. Elle se retire en commentant ses DEUX lignes
   d'index.html (celle-ci et finitions.js) ; rien d'autre ne dépend d'elle.

   POURQUOI ELLE EXISTE. animations.css a été écrite le 12/09 sur la grammaire
   « .btn / .carte / .tuile ». Les écrans refaits depuis parlent une autre
   langue — « -pilule- », « -rond », « -coin », « -sous » — et la mesure du
   09/10 (vrai survol, vrai appui, style calculé comparé, sur les quinze
   écrans) le dit sans appel :

     aucun retour à l'appui     pilules, ronds, coins, sous-cartes, rangs de
                                l'Accueil, « .ag-b », interrupteurs
     aucune transition          les mêmes : le survol existait, mais il sautait

   Ce sont les boutons les plus visibles de l'application — « Fixer
   l'objectif », « Visite », « Filtres », « Satellite » — et ils étaient les
   seuls à ne rien répondre sous le doigt.

   LA DOCTRINE EST CELLE D'ANIMATIONS.CSS, INCHANGÉE : le mouvement explique,
   il est court, il ne touche pas ce qu'on manipule en masse ; toute durée
   suit « --anim-vit », toute amplitude « --anim-amp » ; « backwards » jamais
   « forwards ».

   ⚠️ UNE RÈGLE D'ÉCRITURE PROPRE À CETTE FEUILLE : ELLE N'ÉCRIT JAMAIS
   « transform ». Tout passe par les propriétés séparées « scale »,
   « translate » et « rotate », qui SE COMPOSENT avec le transform déjà posé
   au lieu de le remplacer. C'est ce qui lui permet de venir après quatre
   feuilles de mouvement sans défaire un seul de leurs gestes : le
   « translateY(-2px) » d'un .btn survolé reste, l'appui s'y ajoute.

   ⚠️ ET LES SURVOLS QUI DÉPLACENT SONT SOUS « (hover: hover) ». Sur un
   téléphone, un survol reste collé après le doigt : le bouton garderait son
   soulèvement jusqu'au prochain toucher ailleurs. L'appui, lui, vaut partout.
   =========================================================================== */

/* --- 0. Les jetons ----------------------------------------------------------
   Ceux d'animations.css, avec un repli : cette feuille doit survivre au
   retrait de l'autre — même raison que dans fluide.css. */
:root {
  --fi-vif:    var(--m-vif,   calc(.14s * var(--anim-vit, 1)));
  --fi-court:  var(--m-court, calc(.22s * var(--anim-vit, 1)));
  --fi-moyen:  var(--m-moyen, calc(.34s * var(--anim-vit, 1)));
  --fi-ressort: var(--ressort, cubic-bezier(.16, 1, .3, 1));
  --fi-amp:    var(--anim-amp, 1);
}

/* ===========================================================================
   1. LES BOUTONS DE LA REFONTE
   ---------------------------------------------------------------------------
   Visés par leur FAMILLE et non par écran : « acc-pilule-verre »,
   « cli-pilule-blanche », « gr-pilule-verre » sont le même bouton sous trois
   préfixes, et le prochain écran en apportera un quatrième. Le sélecteur
   d'attribut les prend tous, et prendra le suivant.

   L'identifiant en tête n'est pas un périmètre, c'est un POIDS : « .acc
   .acc-rond » pose déjà sa propre liste de transitions avec deux classes, et
   une liste de transitions ne s'ajoute pas — la dernière remplace. Il faut
   donc l'emporter, et reprendre dans la nôtre ce que la leur portait
   (couleur de fond, liseré).
   =========================================================================== */
:is(#vue, #calques) :is(button, a)[class*="-pilule-"],
:is(#vue, #calques) button:is([class*="-rond"], [class*="-coin"]),
:is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]),
:is(#vue, #calques) :is(.ag-b, .ag-ici, .gr-puce, .lag-prendre, .lag-genre, .lag-jour, .meteo-badge) {
  transition: background-color var(--fi-court), border-color var(--fi-court),
              color var(--fi-court), box-shadow var(--fi-moyen),
              opacity var(--fi-court), filter var(--fi-court),
              scale var(--fi-vif) var(--fi-ressort),
              translate var(--fi-court) var(--fi-ressort);
}

/* L'APPUI. Le seul geste de cette section qui vaille aussi au téléphone, et
   celui qui manquait partout. La durée d'entrée est écourtée : un bouton qui
   met un dixième de seconde à s'enfoncer paraît mou, c'est le RETOUR qui a le
   droit de prendre son temps. */
:is(#vue, #calques) :is(button, a)[class*="-pilule-"]:active:not(:disabled),
:is(#vue, #calques) :is(.ag-b, .ag-ici, .gr-puce, .lag-prendre, .lag-genre, .meteo-badge):active:not(:disabled) {
  scale: calc(1 - .04 * var(--fi-amp));
  translate: 0 0;
  transition-duration: calc(.05s * var(--anim-vit, 1));
}
/* Un rond est petit : quatre centièmes ne s'y voient pas, il lui en faut dix. */
:is(#vue, #calques) button:is([class*="-rond"], [class*="-coin"]):active:not(:disabled) {
  scale: calc(1 - .1 * var(--fi-amp));
  transition-duration: calc(.05s * var(--anim-vit, 1));
}
/* Une sous-carte et un jour d'agenda sont grands : deux centièmes et demi. */
:is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]):active,
:is(#vue, #calques) .lag-jour:active {
  scale: calc(1 - .025 * var(--fi-amp));
  translate: 0 0;
  transition-duration: calc(.05s * var(--anim-vit, 1));
}

@media (hover: hover) and (pointer: fine) {
  /* La pilule se lève d'un pixel et demi et s'éclaire : même geste que le
     « .btn », un cran plus discret — elles vont souvent par trois, côte à
     côte, et trois boutons qui sautent de deux pixels font une vague. */
  :is(#vue, #calques) :is(button, a)[class*="-pilule-"]:hover:not(:disabled):not(:active),
  :is(#vue, #calques) :is(.ag-b, .lag-prendre):hover:not(:disabled):not(:active) {
    translate: 0 calc(-1.5px * var(--fi-amp));
    filter: brightness(1.1);
  }
  /* Une pilule BLANCHE ne peut pas s'éclairer — elle est déjà au maximum.
     C'est l'ombre qui dit le survol. */
  :is(#vue, #calques) :is(button, a):is([class*="-pilule-blanche"], [class*="-pilule-sombre"]):hover:not(:disabled):not(:active) {
    filter: none;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .22);
  }
  :is(#vue, #calques) button:is([class*="-rond"], [class*="-coin"]):hover:not(:disabled):not(:active) {
    scale: calc(1 + .08 * var(--fi-amp));
  }
  /* La sous-carte de la fiche — « Boîte connectée », le téléphone, « Y
     aller ». Elle se lève, et son icône de coin répond : c'est elle qui dit
     que la carte est un bouton. */
  :is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]):hover:not(:active) {
    translate: 0 calc(-2px * var(--fi-amp));
  }
  :is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]):hover > i svg {
    scale: 1.18;
    rotate: -8deg;
  }
}

/* La flèche d'un coin part dans sa direction. Elle est déjà tournée de 45°
   par « rotate » dans app.css : « translate » est une propriété à part, qui
   s'exprime dans le repère du bouton et non dans celui de la flèche — deux
   pixels vers le haut et la droite, quel que soit l'angle. */
:is(#vue, #calques) button:is([class*="-coin"], .acc-rond.sombre) svg,
:is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]) > i svg {
  transition: translate var(--fi-court) var(--fi-ressort),
              scale var(--fi-court) var(--fi-ressort),
              rotate var(--fi-court) var(--fi-ressort);
}
@media (hover: hover) and (pointer: fine) {
  :is(#vue, #calques) button:is([class*="-coin"], .acc-rond.sombre):hover svg {
    translate: calc(2px * var(--fi-amp)) calc(-2px * var(--fi-amp));
  }
  /* Le « + » de « Ajouter une note » fait un quart de tour : il ouvre. */
  :is(#vue, #calques) [class*="-ajout"] svg { transition: rotate var(--fi-moyen) var(--fi-ressort); }
  :is(#vue, #calques) [class*="-ajout"]:hover svg { rotate: 90deg; }
}

/* ===========================================================================
   2. LES RANGS — la couleur seule, comme le veut la doctrine
   ---------------------------------------------------------------------------
   « .acc-rang » (trente lignes sur l'Accueil) et « .sous-rang » changeaient
   de fond sans transition : le survol sautait d'une ligne à l'autre. Rien ne
   se déplace au survol — on vise ces lignes en série. À l'appui seulement,
   un tassement de huit millièmes : assez pour que le doigt sente la ligne au
   téléphone, trop peu pour décaler celle d'à côté.
   =========================================================================== */
.acc .acc-rang,
.sous-rang,
/* « .cli-rang » et « .gr-rang » : animations.css leur a donné la couleur ;
   on reprend sa liste mot pour mot et on y ajoute l'appui. Rien au survol —
   ces lignes portent une poignée de glisser-déposer, et sa règle tient. */
.cli .cli-rang,
.gr-page .gr-rang {
  transition: background-color var(--fi-court), color var(--fi-court),
              scale var(--fi-vif) var(--fi-ressort);
}
.acc .acc-rang:active,
.sous-rang:active,
.cli .cli-rang:active,
.gr-page .gr-rang:active {
  scale: calc(1 - .008 * var(--fi-amp));
  transition-duration: calc(.05s * var(--anim-vit, 1));
}

/* Les cartes d'alerte des Statistiques se cliquent : elles se soulevaient au
   survol (animations.css) sans rien dire à l'appui. « scale » s'ajoute à leur
   « translateY » au lieu de le défaire. */
.st-alerte { transition: transform var(--fi-court) var(--glisse, ease-out),
                         box-shadow var(--fi-moyen) var(--glisse, ease-out),
                         border-color var(--fi-court),
                         scale var(--fi-vif) var(--fi-ressort); }
.st-alerte:active { scale: calc(1 - .02 * var(--fi-amp)); transition-duration: calc(.05s * var(--anim-vit, 1)); }

/* Le micro de l'écran Larry : il se penchait au survol, rien à l'appui. */
.micro { transition: background-color var(--fi-vif), border-color var(--fi-vif),
                     transform calc(.1s * var(--anim-vit, 1)),
                     scale var(--fi-vif) var(--fi-ressort); }
.micro:active:not(:disabled) { scale: calc(1 - .1 * var(--fi-amp)); transition-duration: calc(.05s * var(--anim-vit, 1)); }

/* ===========================================================================
   3. LES CHAMPS
   ---------------------------------------------------------------------------
   Le champ lui-même s'anime déjà (app.css : liseré, fond, halo). Ce qui
   restait muet, c'est ce qui l'accompagne : la loupe et le libellé posés
   dans le champ. Ils prennent l'accent quand on écrit — on voit où l'on est
   sans chercher le curseur.
   =========================================================================== */
.acc .acc-champ > svg, .cli .cli-recherche > svg,
.acc .acc-champ.acc-date > span {
  transition: color var(--fi-court), scale var(--fi-court) var(--fi-ressort);
}
.acc .acc-champ:focus-within > svg,
.cli .cli-recherche:focus-within > svg {
  color: var(--acc-vif);
  scale: 1.12;
}
.acc .acc-champ.acc-date:focus-within > span { color: var(--acc-vif); }

/* ===========================================================================
   4. L'INTERRUPTEUR
   ---------------------------------------------------------------------------
   Le pouce glissait déjà. Deux choses manquaient : rien ne disait qu'on le
   SURVOLAIT, et rien ne répondait pendant qu'on appuyait — le geste ne
   commençait qu'au relâchement.

   Sous le doigt, le pouce s'étire de quatre pixels vers l'intérieur : coché,
   il recule d'autant pour que son bord extérieur ne bouge pas. C'est le
   geste des interrupteurs d'iOS, et il dit « je vais partir de ce côté »
   avant qu'on lâche.
   =========================================================================== */
.interrupteur .piste {
  transition: background-color var(--fi-court), border-color var(--fi-court),
              box-shadow var(--fi-court);
}
.interrupteur .piste::after {
  transition: translate var(--fi-moyen) var(--fi-ressort),
              width var(--fi-court) var(--fi-ressort),
              background-color var(--fi-court);
}
.interrupteur:active .piste::after { width: 18px; }
.interrupteur:active input:checked + .piste::after { translate: 10px 0; }
.interrupteur input:focus-visible + .piste {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 30%, transparent);
}
@media (hover: hover) and (pointer: fine) {
  .interrupteur:hover .piste {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--acc) 14%, transparent);
  }
}

/* Le choix rond des Portefeuilles : même famille de geste que les ronds. */
.pf-choix.pf-rond .pf-rond-visuel {
  transition: scale var(--fi-vif) var(--fi-ressort), box-shadow var(--fi-court),
              border-color var(--fi-court), background-color var(--fi-court);
}
.pf-choix.pf-rond:active .pf-rond-visuel { scale: calc(1 - .1 * var(--fi-amp)); }

/* ===========================================================================
   5. L'ONDE D'APPUI — le dessin ; finitions.js la pose
   ---------------------------------------------------------------------------
   Sur les boutons PLEINS seulement : là où un fond uni laisse voir une onde.
   Elle part du point touché, ce qui est toute l'information : « c'est ICI que
   tu as appuyé, et c'est bien ce bouton qui l'a reçu ».

   ⚠️ ELLE NE VIT PAS DANS LE BOUTON. Une onde classique exige « position:
   relative » et « overflow: hidden » sur sa cible ; ici ce serait retoucher
   seize familles de boutons, dont deux sont en « absolute » — fluide.css a
   payé cette leçon sur les rails. Le cadre est donc posé dans <body>, en
   « fixed », aux coordonnées et au rayon relevés sur le bouton. Rien de ce
   qui existe n'est touché.

   « currentColor » : blanche sur un bouton bleu, sombre sur une pilule
   blanche — elle se voit sur les deux sans qu'une couleur soit écrite.
   =========================================================================== */
.fi-onde {
  position: fixed;
  z-index: 95;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
}
.fi-onde > i {
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  animation: fi-onde calc(.6s * var(--anim-vit, 1)) cubic-bezier(.2, .6, .3, 1) backwards;
}
@keyframes fi-onde {
  from { scale: 0; opacity: .24; }
  to   { scale: 1; opacity: 0; }
}

/* ===========================================================================
   6. NOTIFICATIONS ET MENUS
   =========================================================================== */

/* L'icône d'une notification se pose après son cadre : la coche d'une vente
   enregistrée est la seule chose qu'on lit, autant qu'elle arrive. */
@keyframes fi-pose {
  from { scale: .3; opacity: 0; }
  to   { scale: 1; opacity: 1; }
}
.toast .ico {
  animation: fi-pose calc(.42s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.12s * var(--anim-vit, 1));
}
.toast .fermer {
  transition: color var(--fi-court), rotate var(--fi-moyen) var(--fi-ressort),
              scale var(--fi-vif) var(--fi-ressort);
}
.toast .fermer:hover { color: var(--txt); rotate: 90deg; }
.toast .fermer:active { scale: .8; }
.toast .annuler {
  transition: border-color var(--fi-court), color var(--fi-court),
              scale var(--fi-vif) var(--fi-ressort);
}
.toast .annuler:active { scale: calc(1 - .05 * var(--fi-amp)); }

/* La croix d'une fenêtre fait un quart de tour au survol : c'est le seul
   bouton d'une modale qui ne dit pas ce qu'il fait par un mot. « .btn svg »
   porte déjà une transition sur « transform » (animations.css) ; on la
   reprend et on y ajoute « rotate ». */
.modale > header .btn.carre svg {
  transition: transform var(--fi-court) var(--fi-ressort),
              rotate var(--fi-moyen) var(--fi-ressort);
}
@media (hover: hover) and (pointer: fine) {
  .modale > header .btn.carre:hover svg { rotate: 90deg; }
}

/* Le choix du personnage, dans les Préférences : dix pastilles côte à côte,
   aucune ne répondait à l'appui. */
.personnage {
  transition: border-color calc(.12s * var(--anim-vit, 1)),
              transform calc(.12s * var(--anim-vit, 1)) var(--fi-ressort),
              background-color var(--fi-court),
              scale var(--fi-vif) var(--fi-ressort);
}
.personnage:active { scale: calc(1 - .1 * var(--fi-amp)); transition-duration: calc(.05s * var(--anim-vit, 1)); }

/* Les entrées d'un menu flottant : le fond se posait d'un coup. */
.flottant button {
  transition: background-color var(--fi-court), color var(--fi-court),
              translate var(--fi-vif) var(--fi-ressort),
              scale var(--fi-vif) var(--fi-ressort);
}
.flottant button:active { scale: calc(1 - .02 * var(--fi-amp)); }
@media (hover: hover) and (pointer: fine) {
  .flottant button:hover { translate: calc(3px * var(--fi-amp)) 0; }
}

/* ===========================================================================
   7. CE QUI SE REMPLIT À L'ARRIVÉE
   ---------------------------------------------------------------------------
   Une barre de progression posée d'emblée à sa valeur ne dit pas qu'elle
   mesure quelque chose. Elle part de zéro et va à sa place — une fois, à
   l'arrivée sur l'écran (« entrant », posée par rendre()), jamais aux rendus
   qui suivent : une jauge qui se revide à chaque rafraîchissement donnerait
   l'impression que la donnée a été perdue.
   =========================================================================== */
@keyframes fi-remplit {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

/* ===========================================================================
   8. LE REFUS — l'écran de connexion
   ---------------------------------------------------------------------------
   Un mot de passe faux posait une phrase rouge sous le bouton, sans un
   geste : on recliquait « Entrer » en croyant que rien n'était parti. Le
   message se secoue une fois en arrivant — le « non » de la tête, que tout
   le monde lit sans le lire.

   « :not(:empty) » : l'animation part quand le texte arrive, et ne rejoue
   pas tant qu'il reste. app.js le vide avant chaque tentative.
   =========================================================================== */
@keyframes fi-refus {
  0%   { translate: 0 0; opacity: 0; }
  18%  { translate: calc(-7px * var(--fi-amp)) 0; opacity: 1; }
  38%  { translate: calc(6px * var(--fi-amp)) 0; }
  58%  { translate: calc(-4px * var(--fi-amp)) 0; }
  78%  { translate: calc(2px * var(--fi-amp)) 0; }
  100% { translate: 0 0; }
}
#cx-souci:not(:empty) {
  animation: fi-refus calc(.46s * var(--anim-vit, 1)) ease-out backwards;
}

/* ===========================================================================
   9. LE CLAVIER
   ---------------------------------------------------------------------------
   Le trait de focus arrivait sec. Il se resserre maintenant sur sa cible —
   de six pixels à deux — ce qui le fait VOIR arriver : au clavier, c'est le
   seul curseur qu'on ait.

   « :where() » : poids nul. Toute animation déjà déclarée sur l'élément
   l'emporte — le rebond d'un onglet pris, l'entrée d'un pied de modale.
   Celle-ci ne joue que là où il n'y avait rien.
   =========================================================================== */
@keyframes fi-focus {
  from { outline-offset: 7px; outline-color: transparent; }
}
:where(button, a, summary, [tabindex]):focus-visible {
  animation: fi-focus calc(.26s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
}

/* ===========================================================================
   10. LA BARRE DU BAS, AU TÉLÉPHONE
   ---------------------------------------------------------------------------
   L'icône de l'écran qu'on vient de prendre rebondit une fois. La classe est
   posée par finitions.js après le rendu, sur le bouton devenu actif — même
   mécanisme que « anim-onglet-pris ». En CSS seul, elle rejouerait à chaque
   rendu de la barre.
   =========================================================================== */
@keyframes fi-pris {
  0%   { scale: 1; translate: 0 0; }
  35%  { scale: 1.22; translate: 0 calc(-3px * var(--fi-amp)); }
  100% { scale: 1; translate: 0 0; }
}
.nav-bas button.fi-pris svg {
  animation: fi-pris calc(.44s * var(--anim-vit, 1)) var(--fi-ressort);
}

/* ===========================================================================
   10 bis. LE TIROIR ET « PLUS », AU TÉLÉPHONE
   ---------------------------------------------------------------------------
   Le tiroir glissait déjà (app.css). Trois gestes l'accompagnent.

   LES ENTRÉES ARRIVENT EN CASCADE. animations.css a retiré les cascades des
   changements d'écran — douze blocs, cinquante fois par jour, « ça
   grouille ». Ici le cas est l'inverse : une liste de quinze lignes
   identiques, qu'on ouvre quelques fois par jour, et qui suit le sens du
   tiroir. Vingt-deux millisecondes entre deux lignes, dix lignes au plus :
   le tout est posé en trois dixièmes.

   « fi-ouvre » est posée par finitions.js quand « ouvert » apparaît, et
   retirée sept dixièmes plus tard. En CSS seul, sur « .lateral.ouvert », la
   cascade rejouerait chaque fois qu'app.js redessine le menu tiroir ouvert —
   un compteur qui change suffirait.
   =========================================================================== */
@keyframes fi-tiroir {
  from { opacity: 0; translate: calc(-16px * var(--fi-amp)) 0; }
  to   { opacity: 1; translate: 0 0; }
}
@media (max-width: 980px) {
  .lateral.fi-ouvre .nav button {
    animation: fi-tiroir calc(.34s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
    animation-delay: calc(.26s * var(--anim-vit, 1));
  }
  .lateral.fi-ouvre .nav button:nth-child(1) { animation-delay: calc(.06s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(2) { animation-delay: calc(.082s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(3) { animation-delay: calc(.104s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(4) { animation-delay: calc(.126s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(5) { animation-delay: calc(.148s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(6) { animation-delay: calc(.17s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(7) { animation-delay: calc(.192s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(8) { animation-delay: calc(.214s * var(--anim-vit, 1)); }
  .lateral.fi-ouvre .nav button:nth-child(9) { animation-delay: calc(.236s * var(--anim-vit, 1)); }
}

/* LE « + » DE « PLUS » DEVIENT UNE CROIX quand le tiroir est ouvert : le même
   bouton le referme, et rien ne le disait. Le bouton se reconnaît à ce qu'il
   n'ouvre aucun écran — pas de « data-vue ». La transition reprend celle
   d'app.css (transform, filter) et y ajoute « rotate ». */
#nav-bas button:not([data-vue]) svg {
  transition: transform calc(.26s * var(--anim-vit, 1)) var(--fi-ressort),
              filter calc(.26s * var(--anim-vit, 1)),
              rotate var(--fi-moyen) var(--fi-ressort);
}
body:has(.lateral.ouvert) #nav-bas button:not([data-vue]) svg { rotate: 45deg; }

/* LA PASTILLE ORANGE de « Plus » — « il y a du neuf derrière ». Elle était
   posée, immobile, et on ne la voyait qu'une fois qu'on la connaissait. Un
   anneau s'en échappe deux fois puis s'arrête : un signal, pas un gyrophare.
   Une animation sans fin sur la barre qu'on a sous le pouce toute la journée
   serait exactement ce que la doctrine interdit. */
@keyframes fi-point {
  from { box-shadow: 0 0 0 0 rgba(250, 178, 25, .6); }
  to   { box-shadow: 0 0 0 calc(8px * var(--fi-amp)) rgba(250, 178, 25, 0); }
}
#nav-bas button:not([data-vue])::after {
  animation: fi-point calc(1.3s * var(--anim-vit, 1)) ease-out calc(.5s * var(--anim-vit, 1)) 2 backwards;
}

/* ===========================================================================
   10 ter. CE QUE LA SECONDE MESURE A TROUVÉ MUET
   ---------------------------------------------------------------------------
   Seconde passe du 09/10, sur tout ce qui porte un curseur « main » et non
   plus sur les seules balises de bouton. Restaient sans retour d'appui : les
   cases de calendrier (Agenda, Phoning, Équipe), le menu de compte, les deux
   gestes de l'écran Larry au téléphone, les onglets.

   Chaque liste de transitions REPREND celle d'app.css et y ajoute « scale » :
   une liste ne s'ajoute pas, elle remplace.
   =========================================================================== */
.mois-case, .jour-appels {
  transition: background-color calc(.12s * var(--anim-vit, 1)),
              border-color calc(.12s * var(--anim-vit, 1)),
              scale var(--fi-vif) var(--fi-ressort);
}
.case-jour {
  transition: border-color calc(.14s * var(--anim-vit, 1)),
              background-color calc(.14s * var(--anim-vit, 1)),
              transform calc(.1s * var(--anim-vit, 1)),
              scale var(--fi-vif) var(--fi-ressort);
}
.mois-case:active, .jour-appels:active, .case-jour:active {
  scale: calc(1 - .035 * var(--fi-amp));
  transition-duration: calc(.05s * var(--anim-vit, 1));
}

.cm-bouton, .lag-geste {
  transition: background-color var(--fi-court), border-color var(--fi-court),
              color var(--fi-court), box-shadow var(--fi-court),
              scale var(--fi-vif) var(--fi-ressort);
}
.cm-bouton:active, .lag-geste:active {
  scale: calc(1 - .035 * var(--fi-amp));
  transition-duration: calc(.05s * var(--anim-vit, 1));
}

/* Un bandeau qui fait la largeur de l'écran ne rétrécit pas — cinq millièmes
   de 1350 px, c'est sept pixels qui rentrent de chaque bord. Il s'assombrit. */
.veille-bandeau {
  transition: background-color calc(.15s * var(--anim-vit, 1)), filter var(--fi-vif);
}
.repli > summary, details > summary {
  transition: color var(--fi-court), background-color var(--fi-court), filter var(--fi-vif);
}
.veille-bandeau:active, .repli > summary:active, details > summary:active {
  filter: brightness(.86);
}

/* LES ONGLETS. animations.css leur a donné le creux qui se déplace et le
   rebond de l'onglet PRIS — après le rendu. Entre le doigt posé et le rendu,
   rien : l'onglet visé s'enfonce maintenant d'un rien.

   « :not(.actif) » : même règle que pour la barre du haut — cliquer l'onglet
   où l'on est déjà ne fait rien, il ne promet donc rien. */
[class*="onglets"] button,
[class*="encoche"] button {
  transition: background-color var(--fi-moyen) var(--fi-ressort),
              border-color var(--fi-moyen) var(--fi-ressort),
              color var(--fi-court),
              transform var(--fi-vif) var(--fi-ressort),
              scale var(--fi-vif) var(--fi-ressort);
}
[class*="onglets"] button:not(.actif):active,
[class*="encoche"] button:not(.actif):active {
  scale: calc(1 - .04 * var(--fi-amp));
  transition-duration: calc(.05s * var(--anim-vit, 1));
}

/* ===========================================================================
   10 quater. LES DÉTAILS DE MISE EN PAGE
   ---------------------------------------------------------------------------
   Troisième passe du 09/10 : les quinze écrans photographiés au bureau et au
   téléphone, puis un détecteur passé sur chacun (pilules devenues bulles,
   textes coupés, cibles trop petites). Rien ici ne bouge — ce sont des
   défauts de pose, et chacun est borné à l'endroit où il a été vu.
   =========================================================================== */

/* LES TEXTES QUI SE COUPENT MAL. « Rien à appeler ce jour-là. Donne un jour à
   ceux qui n'en / ont pas. » : deux mots seuls sur la dernière ligne d'un
   message centré. « balance » répartit les lignes d'un titre ou d'un message
   court ; « pretty » évite l'orphelin au bout d'un paragraphe. Les
   navigateurs qui ne les connaissent pas les ignorent. */
h1, h2, h3,
.vide .titre, .vide p,
[class*="-vide"], [class*="vide-"] { text-wrap: balance; }
p, .aide, .muet { text-wrap: pretty; }

/* UN TITRE DE SECTION NE CÈDE PAS SA PLACE À SON EXPLICATION. Sur
   Statistiques, « Ce qui bloque » était écrasé en deux lignes — trois au
   téléphone — par les trois cents caractères posés à sa droite : dans un
   flex, c'est le plus court qui cède. Le titre garde sa largeur, et
   l'explication passe dessous quand il ne lui reste pas de quoi tenir. */
.section > .chapeau:has(> .aide) { flex-wrap: wrap; }
.section > .chapeau:has(> .aide) > h2 { flex: 0 0 auto; max-width: 100%; }
.section > .chapeau:has(> .aide) > .aide { flex: 1 1 280px; min-width: 0; }

@media (max-width: 700px) {
  /* LA PILULE QUI DEVIENT UNE BULLE. Un rail de segments a un rayon de
     999 px : parfait sur une ligne, mais au téléphone il passe à la ligne, et
     un bloc de 204 × 196 arrondi à 999 px est un disque — vu sur les périodes
     de Statistiques, les vues de Clients, les tris de Conversations. À 24 px,
     un rail d'une ligne reste une pilule (il fait 47 px de haut) et un rail
     de trois lignes devient un cadre arrondi. */
  #vue .segmente { border-radius: 24px; }

  /* Les deux dates des filtres de l'Accueil tenaient chacune sa ligne, à
     demi-largeur, avec un vide à droite. Elles partagent maintenant la
     leur. */
  .acc .acc-filtres .acc-champ.acc-date { flex: 1 1 calc(50% - 6px); }
  /* Le champ qui précède la première date prend sa ligne entière : sans
     cela c'est LUI qui s'apparie avec « Du », et « Au » reste seul — premier
     essai, photographié. */
  .acc .acc-filtres .acc-champ:not(.acc-date):has(+ .acc-champ.acc-date) { flex-basis: 100%; }
  /* À demi-largeur, « jj/mm/aaaa » touchait l'icône du calendrier : on
     resserre le libellé et le corps d'un point. */
  .acc .acc-filtres .acc-champ.acc-date > span { left: 12px; }
  .acc .acc-filtres .acc-champ.acc-date input { padding-left: 34px; padding-right: 6px; font-size: 13.5px; }

  /* LA SEMAINE DU PHONING : sept jours sur quatre colonnes laissaient un trou
     en bas à droite. Quatre colonnes étaient voulues — « 12 appels » ne tient
     pas dans un septième d'écran — donc on les garde, et la seconde rangée
     s'élargit pour finir au même bord : quatre cases de trois douzièmes, puis
     trois de quatre. */
  .bande-semaine { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .bande-semaine > .jour-appels { grid-column: span 3; }
  .bande-semaine > .jour-appels:nth-child(n+5) { grid-column: span 4; }

  /* LE BANDEAU DE LA VEILLE : trois textes serrés côte à côte, chacun sur deux
     lignes. Le titre et l'état sur la première, la cadence dessous. */
  .veille-bandeau { flex-wrap: wrap; row-gap: 2px; }
  .veille-bandeau > .espace { display: none; }
  .veille-bandeau > b { flex: none; }
  .veille-bandeau > .chevron { margin-left: auto; }
  .veille-bandeau > .chevron ~ *,
  .veille-bandeau > .espace + .petit { order: 5; flex: 1 0 100%; text-align: left; }
}

/* « Ce mois-ci », dans l'Agenda : un lien de 19 px de haut. On ne le grossit
   pas — il est discret exprès — on agrandit ce que le doigt attrape. */
.ag-ici { position: relative; }
.ag-ici::after { content: ""; position: absolute; inset: -12px -8px; }

/* ===========================================================================
   10 quinquies. CE QUI SE DESSINE — graphiques, jauges, tableau de liège
   ---------------------------------------------------------------------------
   Quatrième passe du 09/10. Les chiffres montaient déjà ; les DESSINS, eux,
   étaient posés d'un bloc — la courbe d'une tuile, les barres de la semaine,
   l'entonnoir des Statistiques. Un graphique qui se trace dit dans quel sens
   il se lit ; posé tout fait, c'est une image.

   ⚠️ TOUT TIENT EN SIX DIXIÈMES, ET CE N'EST PAS UN GOÛT. app.js retire
   « entrant » 600 ms après le rendu (rendre(), « _jetonEntree ») : une
   animation qui dure plus longtemps est coupée net à cet instant. Les durées
   ci-dessous finissent avant, ou sont si près de leur terme — la courbe
   « ressort » fait 97 % du chemin dans sa première moitié — que la coupe ne
   se voit pas.

   « anim-panneau-actif » (animations.js) vaut pour un onglet interne : les
   six volets de Statistiques redessinent leurs graphiques sans que l'écran
   change, et sans elle ils resteraient immobiles cinq fois sur six.
   =========================================================================== */

/* LA COURBE D'UNE TUILE se découvre de gauche à droite. Un masque et non un
   tracé : « stroke-dashoffset » exigerait la longueur de chaque chemin, donc
   du JavaScript sur 846 points par courbe aux Statistiques. Le masque ne
   coûte qu'une couche. Il déborde de dix pixels pour ne pas rogner le point
   du jour, qui dépasse le cadre du dessin. */
@keyframes fi-trace {
  from { clip-path: inset(-10px 100% -10px -10px); }
  to   { clip-path: inset(-10px -10px -10px -10px); }
}

/* ===========================================================================
   LA NAISSANCE DU TABLEAU DE BORD — demande d'Enzo, 09/10/2026
   ---------------------------------------------------------------------------
   « Au moment où je lance l'appli et où je les vois pour la première fois, ou
   que je clique sur l'accueil : je veux que ça pousse, que ça naisse. Que le
   fil apparaisse, qu'il traverse l'écran. »

   Le balayage d'une demi-seconde écrit plus haut ne répondait pas à ça, pour
   deux raisons mesurées : app.js retire « entrant » 600 ms après le rendu, et
   animations.js retient la page jusqu'à 220 ms avant de la montrer — il
   restait moins de quatre dixièmes de geste visible. Un tracé qu'on veut VOIR
   traverser a besoin d'une seconde.

   D'où un déclencheur à part : finitions.js pose « fi-nait » sur les dessins
   de l'Accueil quand on Y ARRIVE — après la connexion, ou depuis un autre
   écran — et la retire quand tout est posé. Pas à un simple rafraîchissement
   de l'écran où l'on est déjà : un graphique qui se redessine pendant qu'on
   le lit donnerait l'impression que les chiffres ont changé.

   LE FIL EST UN VRAI TRACÉ. finitions.js pose « pathLength="1" » sur le
   chemin : sa longueur vaut alors 1 quelle que soit sa forme, et un pointillé
   de 1 qu'on décale de 1 à 0 le dessine d'un bout à l'autre — sans mesurer
   aucun chemin. Une lueur l'accompagne et s'éteint à l'arrivée.

   L'AIRE sous le fil se découvre au même rythme, de gauche à droite : elle
   suit le fil au lieu d'apparaître d'un bloc derrière lui.

   LE POINT du jour se pose en dernier, quand le fil l'atteint.

   Les retards partent à trois dixièmes — le temps que la page soit montrée —
   et s'échelonnent d'une tuile à l'autre (« --fi-rang », posé par le script).
   =========================================================================== */
@keyframes fi-fil {
  0%   { stroke-dashoffset: 1; filter: drop-shadow(0 0 5px var(--acc-vif, #6ea8ff)); }
  75%  { filter: drop-shadow(0 0 5px var(--acc-vif, #6ea8ff)); }
  100% { stroke-dashoffset: 0; filter: drop-shadow(0 0 0 transparent); }
}
@keyframes fi-aire {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes fi-bout {
  0%   { scale: 0; opacity: 0; }
  60%  { scale: 1.7; opacity: 1; }
  100% { scale: 1; opacity: 1; }
}
/* Le balayage générique s'efface devant le tracé : même poids, plus bas dans
   la feuille. */
#vue .courbe-tuile.fi-nait { animation: none; overflow: visible; }
#vue .courbe-tuile.fi-nait .fi-fil {
  /* « 1 1.5 » et non « 1 » : avec un motif de période 2, le bout du chemin
     tombe pile sur le début du trait suivant, et un trait de longueur nulle
     à bout rond dessine quand même un point — filmé le 09/10, un fragment de
     fil posé à droite de la tuile avant que le tracé n'ait commencé. */
  stroke-dasharray: 1 1.5;
  stroke-dashoffset: 0;
  animation: fi-fil calc(1.15s * var(--anim-vit, 1)) cubic-bezier(.6, .05, .3, 1) backwards;
  animation-delay: calc((.3s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue .courbe-tuile.fi-nait .fi-aire {
  animation: fi-aire calc(1.15s * var(--anim-vit, 1)) cubic-bezier(.6, .05, .3, 1) backwards;
  animation-delay: calc((.3s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue .courbe-tuile.fi-nait .fi-bout {
  transform-box: fill-box;
  transform-origin: center;
  animation: fi-bout calc(.5s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((1.3s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* ---------------------------------------------------------------------------
   LA MÊME NAISSANCE, PARTOUT OÙ QUELQUE CHOSE SE DESSINE — 09/10, sur
   « ajoute des animations similaires sur à peu près tout ce qui est
   similaire ».

   finitions.js ne vise plus le seul Accueil : il marque les dessins à
   l'arrivée sur N'IMPORTE QUEL écran, et à chaque volet des Statistiques (qui
   redessine ses graphiques sans changer d'écran). Relevé du 09/10, écran par
   écran, de ce qui se dessine :

     courbes        Accueil, Statistiques (Pilotage, Vendeurs)
     jauges         Statistiques (entonnoir, vendeurs, ventes, portefeuilles),
                    Formation
     répartitions   Conversations, Statistiques, Portefeuilles
     anneaux        Statistiques (objectifs des vendeurs)
     calendriers    Agenda, Équipe, la semaine du Phoning
     cartes de      la fiche de l'Accueil, de Clients, d'Équipe, de
     chiffres       Portefeuilles

   Les règles « entrant » de la passe précédente (jauges, balayage des
   courbes, anneau) sont RETIRÉES : deux déclencheurs sur le même élément, et
   l'animation repartait de zéro à 600 ms, quand « entrant » tombait et que
   l'autre règle prenait la main.

   Le rang (« --fi-rang ») suit l'ordre des dessins dans la page : chacun part
   sept centièmes après le précédent, douze au plus.
   --------------------------------------------------------------------------- */

/* UNE JAUGE SE REMPLIT depuis son bord gauche. */
#vue :is(.progression, .st-etage-barre, .fo-jauge).fi-nait > * {
  transform-origin: left center;
  animation: fi-remplit calc(.9s * var(--anim-vit, 1)) cubic-bezier(.3, .7, .2, 1) backwards;
  animation-delay: calc((.3s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* UNE RÉPARTITION SE DÉCOUVRE d'un seul balayage, de gauche à droite : ses
   segments sont les parts d'UN total, les faire pousser chacun de son côté
   montrerait six barres au lieu d'une. Sa légende la suit, une entrée après
   l'autre. */
#vue :is(.barre-statuts, .pf-repartition).fi-nait {
  animation: fi-aire calc(.9s * var(--anim-vit, 1)) cubic-bezier(.6, .05, .3, 1) backwards;
  animation-delay: calc((.3s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue :is(.legende, .pf-legende).fi-nait > * {
  animation: fi-leve calc(.4s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.9s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(1) { animation-delay: calc((.5s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(2) { animation-delay: calc((.56s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(3) { animation-delay: calc((.62s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(4) { animation-delay: calc((.68s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(5) { animation-delay: calc((.74s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(6) { animation-delay: calc((.8s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.legende, .pf-legende).fi-nait > :nth-child(7) { animation-delay: calc((.86s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* L'ANNEAU D'UN OBJECTIF se pose en tournant d'un sixième de tour. */
#vue .st-anneau.fi-nait {
  animation: fi-anneau calc(.8s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.3s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* LES CARTES DE CHIFFRES d'une fiche — « Boîte connectée », le téléphone,
   « 4 portefeuilles » — montent l'une après l'autre, comme les cases de
   l'objectif. */
#vue [class*="-sous-cartes"].fi-nait > * {
  animation: fi-leve calc(.5s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.6s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue [class*="-sous-cartes"].fi-nait > :nth-child(1) { animation-delay: calc((.3s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue [class*="-sous-cartes"].fi-nait > :nth-child(2) { animation-delay: calc((.38s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue [class*="-sous-cartes"].fi-nait > :nth-child(3) { animation-delay: calc((.46s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue [class*="-sous-cartes"].fi-nait > :nth-child(4) { animation-delay: calc((.54s + var(--fi-rang, 0) * .07s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* UN CALENDRIER SE POSE EN VAGUE, du coin haut gauche au coin bas droit.
   finitions.js donne à chaque case « --fi-i », sa colonne plus sa rangée :
   les cases d'une même diagonale partent ensemble, et la vague traverse la
   grille en un tiers de seconde. Trente-cinq cases qui apparaîtraient d'un
   bloc sont un tableau ; en vague, c'est un mois qui s'ouvre. */
@keyframes fi-case {
  from { opacity: 0; scale: .8; }
  to   { opacity: 1; scale: 1; }
}
#vue :is(.mois, .mois-jours, .bande-semaine).fi-nait > * {
  animation: fi-case calc(.42s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.24s + var(--fi-i, 0) * .028s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* LES BARRES DE LA SEMAINE poussent depuis leur pied, l'une après l'autre —
   samedi d'abord, c'est le sens de lecture —, et leurs lettres les suivent.
   « fill-box » : sans lui l'origine d'un <rect> est le coin du dessin, et la
   barre arriverait de travers. */
@keyframes fi-pousse {
  0%   { scale: 1 0; }
  70%  { scale: 1 1.12; }
  100% { scale: 1 1; }
}
.acc-barres .acc-barre { transform-box: fill-box; transform-origin: 50% 100%; }
#vue .acc-barres.fi-nait .acc-barre {
  animation: fi-pousse calc(.62s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
}
#vue .acc-barres.fi-nait .acc-axe {
  animation: fi-pose calc(.4s * var(--anim-vit, 1)) ease-out backwards;
  transform-box: fill-box; transform-origin: center;
}
#vue .acc-barres.fi-nait g:nth-child(1) > * { animation-delay: calc((.3s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(2) > * { animation-delay: calc((.37s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(3) > * { animation-delay: calc((.44s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(4) > * { animation-delay: calc((.51s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(5) > * { animation-delay: calc((.58s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(6) > * { animation-delay: calc((.65s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-barres.fi-nait g:nth-child(7) > * { animation-delay: calc((.72s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* LES TROIS CASES DE L'OBJECTIF montent l'une après l'autre, et la pilule
   « Fixer l'objectif » arrive en dernier — c'est le geste qu'on attend de
   toi, il se présente quand le reste est lu. */
@keyframes fi-leve {
  from { opacity: 0; translate: 0 calc(14px * var(--fi-amp)); }
  to   { opacity: 1; translate: 0 0; }
}
#vue .acc-cartes-mini.fi-nait > * {
  animation: fi-leve calc(.5s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.86s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue .acc-cartes-mini.fi-nait > :nth-child(1) { animation-delay: calc((.3s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-cartes-mini.fi-nait > :nth-child(2) { animation-delay: calc((.4s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .acc-cartes-mini.fi-nait > :nth-child(3) { animation-delay: calc((.5s + var(--fi-rang, 0) * .14s) * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* L'ENTONNOIR ET LES JAUGES des Statistiques se remplissent — même geste que
   les barres de progression plus haut, étendu aux volets internes. */
/* L'anneau d'un objectif se pose en tournant d'un sixième de tour. */
@keyframes fi-anneau {
  from { opacity: 0; rotate: -60deg; scale: .86; }
  to   { opacity: 1; rotate: 0deg; scale: 1; }
}

/* LE FILIGRANE D'UNE TUILE — son icône de coin — se pose après la tuile, les
   quatre l'une après l'autre. Le conteneur seul : l'icône qu'il porte a déjà
   ses gestes à elle (« g-frappe », « g-trace »…), et ils restent. */
#vue.entrant .tuile > .filigrane {
  animation: fi-pose calc(.36s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.14s * var(--anim-vit, 1));
}
#vue.entrant .tuile:nth-child(2) > .filigrane { animation-delay: calc(.18s * var(--anim-vit, 1)); }
#vue.entrant .tuile:nth-child(3) > .filigrane { animation-delay: calc(.22s * var(--anim-vit, 1)); }
#vue.entrant .tuile:nth-child(4) > .filigrane { animation-delay: calc(.26s * var(--anim-vit, 1)); }

/* LES POST-IT DE L'AIDE tombent sur le liège : ils arrivent d'un peu plus
   haut et d'un peu plus près, puis se posent. Leur penchant (« transform:
   rotate(var(--penche)) », app.css) n'est pas touché — « translate » et
   « scale » s'y ajoutent. Huit au plus en cascade : au-delà ils sont sous le
   pli. */
@keyframes fi-punaise {
  from { opacity: 0; translate: 0 calc(-22px * var(--fi-amp)); scale: calc(1 + .1 * var(--fi-amp)); }
  to   { opacity: 1; translate: 0 0; scale: 1; }
}
#vue.entrant .postit {
  animation: fi-punaise calc(.4s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.2s * var(--anim-vit, 1));
}
#vue.entrant .postit:nth-child(1) { animation-delay: calc(.06s * var(--anim-vit, 1)); }
#vue.entrant .postit:nth-child(2) { animation-delay: calc(.085s * var(--anim-vit, 1)); }
#vue.entrant .postit:nth-child(3) { animation-delay: calc(.11s * var(--anim-vit, 1)); }
#vue.entrant .postit:nth-child(4) { animation-delay: calc(.135s * var(--anim-vit, 1)); }
#vue.entrant .postit:nth-child(5) { animation-delay: calc(.16s * var(--anim-vit, 1)); }
#vue.entrant .postit:nth-child(6) { animation-delay: calc(.18s * var(--anim-vit, 1)); }
.postit { transition: transform .18s ease, box-shadow .18s ease, scale var(--fi-vif) var(--fi-ressort); }
.postit:active { scale: calc(1 - .03 * var(--fi-amp)); transition-duration: calc(.05s * var(--anim-vit, 1)); }

/* ===========================================================================
   10 sexies. LES REPLIS QUI S'OUVRENT POUR DE BON
   ---------------------------------------------------------------------------
   animations.css l'écrivait : « la hauteur d'un <details> n'est pas animable
   […] interpoler auto demande interpolate-size, dont on ne dépend pas ici ».
   On n'en dépend toujours pas — on s'en SERT quand il est là. Les étapes du
   parcours de Formation et les replis s'ouvrent en hauteur ; sur un moteur
   qui ne connaît pas « ::details-content », la règle entière est ignorée et
   le repli s'ouvre comme hier, son contenu montant de seize pixels.

   Bornée à deux familles nommées, pas à tout <details> : « overflow: clip »
   rognerait un menu flottant posé dans un repli, et il faut avoir regardé
   ce que chacun contient.
   =========================================================================== */
@supports selector(::details-content) {
  details:is(.fo-etape, .repli) { interpolate-size: allow-keywords; }
  details:is(.fo-etape, .repli)::details-content {
    block-size: 0;
    overflow-y: clip;
    transition: block-size var(--fi-moyen) var(--fi-ressort),
                content-visibility var(--fi-moyen) allow-discrete;
  }
  details:is(.fo-etape, .repli)[open]::details-content { block-size: auto; }
}

/* ===========================================================================
   10 septies. CE QUI ARRIVE EN DÉFILANT
   ---------------------------------------------------------------------------
   Les longues pages — le parcours de Formation, les sections des
   Statistiques — : un bloc qui entre par le bas de l'écran monte de seize
   pixels en s'éclaircissant, sur ses soixante premiers pixels de course.
   L'animation est attachée au DÉFILEMENT, pas à une horloge : elle avance
   avec le doigt, recule avec lui, et un bloc déjà à l'écran à l'arrivée est
   dans son état final — rien ne joue tant qu'on ne défile pas.

   ⚠️ UNE DURÉE N'ARRÊTE PAS UNE ANIMATION DE DÉFILEMENT. « --anim-vit: 0 »
   n'y peut rien : c'est finitions.js qui pose « fi-sans » sur <html> quand
   le mouvement est coupé, et toute la section est sous « :not(.fi-sans) ».
   =========================================================================== */
@keyframes fi-defile {
  from { opacity: 0; translate: 0 calc(16px * var(--fi-amp)); }
  to   { opacity: 1; translate: 0 0; }
}
@supports (animation-timeline: view()) {
  html:not(.fi-sans) #vue:not(.entrant) :is(.fo-etape, .gr-stats > .section, .gr-stats > .carte) {
    animation: fi-defile linear both;
    animation-timeline: view();
    /* 64 px et non 120 : mesuré sur Formation, une étape fait 86 px de haut,
       et à 120 elle était ENTIÈREMENT visible en bas d'écran en n'étant
       encore qu'aux trois quarts de son opacité. */
    animation-range: entry 0px entry 64px;
  }
}

/* ===========================================================================
   10 octies. LE COMPTEUR QUI CHANGE
   ---------------------------------------------------------------------------
   « 3 » devient « 4 » à côté d'une entrée du menu : le chiffre bat une fois.
   finitions.js compare les compteurs d'un rendu au suivant et pose la classe
   sur ceux qui ont bougé — jamais au premier rendu, où tous seraient
   « nouveaux ».
   =========================================================================== */
@keyframes fi-compte {
  0%   { scale: 1; }
  35%  { scale: calc(1 + .32 * var(--fi-amp)); }
  100% { scale: 1; }
}
/* « .fi-change » seule, et plus « .compteur.fi-change » : la même classe sert
   maintenant aux grands chiffres d'un écran — la valeur d'une tuile qui
   change pendant qu'on la regarde (finitions.js, « comparerLesChiffres »). */
.fi-change {
  animation: fi-compte calc(.5s * var(--anim-vit, 1)) var(--fi-ressort);
}

/* ===========================================================================
   10 nonies. LES ICÔNES QUI SE REDESSINENT
   ---------------------------------------------------------------------------
   Cinquième passe du 09/10. Les icônes de Larry sont des tracés — des
   contours, pas des aplats. Elles peuvent donc se DESSINER, par le même
   procédé que le fil d'une courbe : finitions.js donne à chaque forme une
   longueur de 1, et un pointillé qu'on décale la trace d'un bout à l'autre.

   AU MENU : l'entrée survolée redessine son icône. Pas l'entrée ouverte —
   même règle que pour le reste de la barre, elle ne répond pas à un geste
   qui ne mène nulle part. Au téléphone, c'est l'icône de l'écran qu'on vient
   de prendre qui se trace, avec son rebond.

   DANS UN ÉCRAN VIDE : la coche de « Tout est à jour », le dossier de
   « Choisissez un portefeuille » se tracent à l'arrivée. Un écran vide n'a
   que son icône à montrer ; autant qu'elle arrive.
   =========================================================================== */
@keyframes fi-redessine {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}
@media (hover: hover) and (pointer: fine) {
  .nav button:not(.actif):hover svg [pathLength] {
    stroke-dasharray: 1 1.5;
    animation: fi-redessine calc(.5s * var(--anim-vit, 1)) cubic-bezier(.5, 0, .3, 1) backwards;
  }
}
.nav-bas button.fi-pris svg [pathLength] {
  stroke-dasharray: 1 1.5;
  animation: fi-redessine calc(.5s * var(--anim-vit, 1)) cubic-bezier(.5, 0, .3, 1) backwards;
}
#vue .fi-nait .fi-trait {
  stroke-dasharray: 1 1.5;
  stroke-dashoffset: 0;
  animation: fi-redessine calc(.8s * var(--anim-vit, 1)) cubic-bezier(.5, 0, .3, 1) backwards;
  animation-delay: calc(.35s * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue .fi-nait .fi-trait:nth-child(2) { animation-delay: calc(.55s * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue .fi-nait .fi-trait:nth-child(n+3) { animation-delay: calc(.7s * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* ===========================================================================
   10 decies. LE SUCCÈS ET LE SEUIL
   =========================================================================== */

/* « Vente enregistrée » : c'est LE moment de la journée d'un vendeur, et il
   arrivait avec la même coche que « Filtre retiré ». Un anneau vert s'échappe
   de la coche une fois — rien de plus : un feu d'artifice à chaque vente
   serait drôle trois jours. */
@keyframes fi-succes {
  from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bon, #22c55e) 60%, transparent); }
  to   { box-shadow: 0 0 0 calc(11px * var(--fi-amp)) transparent; }
}
.toast.bon .ico {
  border-radius: 50%;
  animation: fi-pose calc(.42s * var(--anim-vit, 1)) var(--fi-ressort) calc(.12s * var(--anim-vit, 1)) backwards,
             fi-succes calc(.9s * var(--anim-vit, 1)) ease-out calc(.4s * var(--anim-vit, 1)) backwards;
}

/* LA CARTE DE CONNEXION : son titre, ses deux champs et son bouton montent
   l'un après l'autre. Le logo avait toute une séquence ; la carte, elle,
   était posée d'un bloc à côté. « #cx-souci » est exclu — il a son propre
   geste, le refus, et les deux se marcheraient dessus. */
.cx-volet .carte > :not(#cx-souci) {
  animation: fi-leve calc(.5s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.5s * var(--anim-vit, 1));
}
.cx-volet .carte > .chapeau { animation-delay: calc(.18s * var(--anim-vit, 1)); }
.cx-volet .carte > .champ { animation-delay: calc(.28s * var(--anim-vit, 1)); }
.cx-volet .carte > .champ ~ .champ { animation-delay: calc(.36s * var(--anim-vit, 1)); }
.cx-volet .carte > .rangee { animation-delay: calc(.46s * var(--anim-vit, 1)); }
.cx-volet .carte > .rangee ~ .rangee { animation-delay: calc(.58s * var(--anim-vit, 1)); }

/* ===========================================================================
   10 undecies. CE QUI ACCOMPAGNE LA NAISSANCE
   ---------------------------------------------------------------------------
   Sixième passe du 09/10. Les graphiques et les chiffres naissaient ; autour
   d'eux, le reste de l'écran était posé. Tout ce qui suit joue dans la même
   fenêtre et sous le même déclencheur (finitions.js, « ACCOMPAGNE » et
   « LISTES ») : à l'arrivée sur un écran, jamais à un simple rafraîchissement.
   =========================================================================== */

/* LES PREMIÈRES LIGNES D'UNE LISTE montent l'une après l'autre — dix au plus.
   Voir la note de finitions.js sur ce que animations.css avait retiré, et
   pourquoi ceci n'est pas le même geste. */
@keyframes fi-rangee {
  from { opacity: 0; translate: 0 calc(12px * var(--fi-amp)); }
  to   { opacity: 1; translate: 0 0; }
}
#vue .fi-rangee {
  animation: fi-rangee calc(.42s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc((.28s + var(--fi-i, 0) * .042s) * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* L'AVATAR D'UNE FICHE se pose en tournant d'un quinzième de tour, puis ses
   pastilles — statut, motif, portefeuille — arrivent une à une. */
@keyframes fi-avatar {
  from { opacity: 0; scale: .4; rotate: -24deg; }
  to   { opacity: 1; scale: 1; rotate: 0deg; }
}
#vue :is(.acc-avatar, .cli-avatar, .gr-avatar, .ava).fi-nait {
  animation: fi-avatar calc(.6s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.3s * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue :is(.cli-jetons, .gr-jetons, .gr-larry-exemples).fi-nait > * {
  animation: fi-leve calc(.45s * var(--anim-vit, 1)) var(--fi-ressort) backwards;
  animation-delay: calc(.74s * var(--anim-vit, 1) - var(--fi-avance, 0s));
}
#vue :is(.cli-jetons, .gr-jetons, .gr-larry-exemples).fi-nait > :nth-child(1) { animation-delay: calc(.46s * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.cli-jetons, .gr-jetons, .gr-larry-exemples).fi-nait > :nth-child(2) { animation-delay: calc(.55s * var(--anim-vit, 1) - var(--fi-avance, 0s)); }
#vue :is(.cli-jetons, .gr-jetons, .gr-larry-exemples).fi-nait > :nth-child(3) { animation-delay: calc(.64s * var(--anim-vit, 1) - var(--fi-avance, 0s)); }

/* LE MICRO DE L'ÉCRAN LARRY appelle deux fois : un anneau s'en échappe, puis
   un second, et il se tait. C'est le seul geste de l'écran — « appuie et
   parle » — et rien ne le désignait. Deux fois, pas en boucle : un bouton qui
   pulse sans fin finit par être celui qu'on ne voit plus. */
@keyframes fi-appel {
  from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--acc-vif, #6ea8ff) 55%, transparent); }
  to   { box-shadow: 0 0 0 calc(16px * var(--fi-amp)) transparent; }
}
#vue .micro.fi-nait {
  animation: fi-appel calc(1s * var(--anim-vit, 1)) ease-out 2 backwards;
  animation-delay: calc(.6s * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* LE CIEL DE LA MÉTÉO — le petit nuage du badge — arrive d'en bas et se
   balance une fois avant de se poser. */
@keyframes fi-ciel {
  0%   { opacity: 0; translate: 0 calc(7px * var(--fi-amp)); rotate: -14deg; }
  40%  { opacity: 1; translate: 0 calc(-2px * var(--fi-amp)); rotate: 9deg; }
  70%  { rotate: -5deg; }
  100% { opacity: 1; translate: 0 0; rotate: 0deg; }
}
#vue .meteo-badge.fi-nait .ciel {
  display: inline-block;
  animation: fi-ciel calc(1.1s * var(--anim-vit, 1)) ease-in-out backwards;
  animation-delay: calc(.45s * var(--anim-vit, 1) - var(--fi-avance, 0s));
}

/* ===========================================================================
   11. LE RETRAIT
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  :is(#vue, #calques) :is(button, a)[class*="-pilule-"],
  :is(#vue, #calques) button:is([class*="-rond"], [class*="-coin"]),
  :is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]),
  :is(#vue, #calques) :is(.ag-b, .ag-ici, .gr-puce, .lag-prendre, .lag-genre, .lag-jour, .meteo-badge),
  :is(#vue, #calques) button:is([class*="-coin"], .acc-rond.sombre) svg,
  :is(#vue, #calques) :is(a, button)[class*="-sous"]:not([class*="-sous-"]) > i svg,
  :is(#vue, #calques) [class*="-ajout"] svg,
  .acc .acc-rang, .sous-rang,
  .acc .acc-champ > svg, .cli .cli-recherche > svg,
  .interrupteur .piste, .interrupteur .piste::after,
  .pf-choix.pf-rond .pf-rond-visuel,
  .toast .fermer, .toast .annuler, .flottant button,
  .cli .cli-rang, .gr-page .gr-rang, .st-alerte, .micro, .personnage,
  .modale > header .btn.carre svg,
  .mois-case, .jour-appels, .case-jour, .cm-bouton, .lag-geste,
  [class*="onglets"] button, [class*="encoche"] button,
  #nav-bas button:not([data-vue]) svg {
    transition: none;
    scale: none !important;
    translate: none !important;
  }
  .interrupteur input:checked + .piste::after { translate: 14px 0 !important; }
  .interrupteur:active .piste::after { width: 14px; }
  :is(#vue, #calques) [class*="-ajout"]:hover svg, .toast .fermer:hover,
  .modale > header .btn.carre:hover svg { rotate: none; }
  .fi-onde { display: none; }
  #vue .fi-rangee, #vue .fi-nait, #vue .fi-nait > *, #vue .meteo-badge .ciel,
  .fi-change, .cx-volet .carte > :not(#cx-souci), .toast.bon .ico,
  .nav button svg [pathLength], .nav-bas button svg [pathLength],
  #vue .fi-trait {
    animation: none !important;
    stroke-dasharray: none !important;
  }
  .postit { transition: none; }
  details:is(.fo-etape, .repli)::details-content { transition: none; }
  body:has(.lateral.ouvert) #nav-bas button:not([data-vue]) svg { rotate: none; }
  /* « !important » : les règles de la passe 4 portent deux ou trois classes
     de plus que celles-ci (« #vue:is(.entrant, …) »), et sans lui elles
     l'emporteraient jusque dans le mouvement réduit. */
  #vue .courbe-tuile, #vue .acc-barres .acc-barre, #vue .st-anneau,
  #vue .tuile > .filigrane, #vue .postit, .compteur.fi-change,
  #vue :is(.st-etage-barre, [class*="jauge-barre"], [class*="part-barre"], .progression, .barre-statuts) > i,
  #vue :is(.fo-etape, .gr-stats > .section, .gr-stats > .carte) {
    animation: none !important;
  }
  .lateral.fi-ouvre .nav button, #nav-bas button:not([data-vue])::after,
  .toast .ico, #cx-souci:not(:empty), .nav-bas button.fi-pris svg,
  #vue.entrant .progression > i, #vue.entrant .barre-statuts > i,
  #vue.entrant .fo-jauge > *,
  :where(button, a, summary, [tabindex]):focus-visible {
    animation: none;
  }
}

/* ---------------------------------------------------------------------------
   LES LISTES DÉROULANTES — celle que pose finitions.js à la place de Windows
   --------------------------------------------------------------------------- */
.fi-liste {
  position: fixed; left: 0; top: 0; z-index: 10000;
  max-width: min(560px, calc(100vw - 16px));
  overflow-y: auto; overscroll-behavior: contain;
  padding: 6px;
  background: var(--surf-2);
  border: 1px solid var(--bord-fort);
  border-radius: var(--r-s);
  box-shadow: var(--ombre-flottante);
  color: var(--txt);
  font: inherit; font-size: 14px;
  scrollbar-width: thin; scrollbar-color: var(--bord-fort) transparent;
  transform-origin: top center;
  animation: fi-liste-ouvre calc(160ms * var(--anim-vit, 1)) var(--ressort) both;
}
.fi-liste.fi-en-haut { transform-origin: bottom center; animation-name: fi-liste-ouvre-haut; }
@keyframes fi-liste-ouvre { from { opacity: 0; translate: 0 -4px; scale: .98; } }
@keyframes fi-liste-ouvre-haut { from { opacity: 0; translate: 0 4px; scale: .98; } }
.fi-liste-ligne {
  position: relative;
  padding: 8px 12px 8px 30px;
  border-radius: var(--r-xs);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer; user-select: none;
}
.fi-liste-ligne.fi-actif { background: var(--acc-sourd); }
.fi-liste-ligne.fi-pris { color: var(--acc-vif); font-weight: 600; }
.fi-liste-ligne.fi-pris::before {
  content: ""; position: absolute; left: 11px; top: 50%;
  width: 9px; height: 5px; margin-top: -4px;
  border: solid currentColor; border-width: 0 0 2px 2px;
  rotate: -45deg;
}
.fi-liste-ligne.fi-eteint { opacity: .4; pointer-events: none; }
.fi-liste-groupe {
  padding: 10px 12px 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--txt-3);
}
.fi-liste-groupe:first-child { padding-top: 4px; }
select.fi-sel-ouvert { border-color: var(--acc-bord); }
@media (prefers-reduced-motion: reduce) { .fi-liste { animation: none; } }
