/* =====================================================================
   Mouvement — couche commune des transitions d'interface
   =====================================================================
   Ce que cette feuille adoucit (relevé image par image le 11/10/2026) :
     1. changement d'écran par le conteneur : l'ancien contenu disparaissait
        d'un coup, la pastille de chargement clignotait même sur une réponse en
        150 ms, et le nouvel écran « sautait » en place ;
     2. contenu d'onglet, de sous-onglet, de tuile : apparition d'un bloc, et
        tableau visible une fraction de seconde SANS sa mise en forme, le temps
        que bootstrap-table s'initialise ;
     3. sous-menus de la barre latérale : ouverts en une image, les entrées du
        dessous décalées de 130 px sans trajet ;
     4. menus (déroulants, listes de choix, menu du compte, volet du rail) :
        apparition sèche ;
     5. boutons : aucun retour à l'appui ;
     6. chargement d'une page complète (lien hors conteneur, enregistrement
        suivi d'une redirection, connexion) : une image vide entre deux pages.
   L'entrée des fenêtres (swal) est pilotée par js/eb_motion.js : la parade
   `animation: none !important` de dashboard-modern.css reste en place.

   TROIS RÈGLES, à tenir pour tout ajout :

   a) Tout vit sous `html.eb-motion`. Cette classe n'est posée par
      js/eb_motion.js que si l'onglet est AFFICHÉ et que la personne n'a pas
      demandé moins d'animations. Dans un onglet en arrière-plan le temps des
      animations ne s'écoule pas : une entrée qui part de 0 d'opacité y
      resterait figée — contenu présent, invisible. Sans la classe, chaque
      élément est d'emblée dans son état final.
      Seule exception, le point 6 : une transition entre deux documents ne
      peut pas dépendre d'une classe posée par un script (la page d'arrivée
      n'a encore rien exécuté). C'est le navigateur qui tient ce rôle — il
      l'abandonne de lui-même si l'onglet est masqué ou si la page d'arrivée
      tarde — et la préférence « moins d'animations » passe par la requête média.

   b) Une entrée ne doit jamais pouvoir laisser un contenu invisible. Les
      classes `.eb-motion-in*` sont posées PUIS RETIRÉES par js/eb_motion.js,
      sur `animationend` ou, à défaut, sur un délai de secours. Aucune
      animation d'ici ne garde son état final (`forwards`) : la fin d'une
      animation, c'est le style normal de l'élément.

   c) Seules l'opacité et les propriétés `translate` / `scale` sont animées.
      Jamais `transform` sur un conteneur : il deviendrait le repère de ses
      descendants en `position: fixed` (barre « Sauver », volets) pour la
      durée du trajet, et Popper écrit déjà `transform` sur les menus qu'il
      place. `translate` et `scale` se composent avec lui sans l'écraser.

   Les sélecteurs passent par `:where()` chaque fois qu'ils AJOUTENT une
   animation : spécificité nulle, donc toute règle du produit qui anime déjà
   l'élément (feuille d'actions mobile, toasts, SITAC…) garde la main.
   ===================================================================== */

:root {
  --eb-motion-fast: 110ms;   /* menus, appui                          */
  --eb-motion-base: 180ms;   /* apparition d'un contenu               */
  --eb-motion-slow: 240ms;   /* ce qui déplace ses voisins (accordéon) */
  --eb-ease-out: cubic-bezier(0.2, 0, 0, 1);    /* arrivée : vite puis posé */
  --eb-ease-std: cubic-bezier(0.4, 0, 0.2, 1);  /* départ doux, arrivée douce */
}

/* Une seule image clé : le point d'arrivée est le style normal de l'élément,
   quel qu'il soit (une opacité de 0,6 posée ailleurs reste 0,6). */
@keyframes eb-motion-fade-in { from { opacity: 0; } }
@keyframes eb-motion-rise-in { from { opacity: 0; translate: 0 6px; } }
@keyframes eb-motion-drop-in { from { opacity: 0; translate: 0 -4px; } }
@keyframes eb-motion-slide-in { from { opacity: 0; translate: -6px 0; } }

/* ---------------------------------------------------------------------
   1. Contenu qui arrive (js/eb_motion.js → ebMotion.entree)
   ---------------------------------------------------------------------
   Départ doux (--eb-ease-std) et non rapide : pendant les premières dizaines
   de millisecondes le fragment est encore brut (tableau sans bootstrap-table,
   listes de choix natives). À 50 ms il n'est qu'à ~10 % d'opacité. */
:where(html.eb-motion) :where(.eb-motion-in) {
  animation: eb-motion-fade-in var(--eb-motion-slow) var(--eb-ease-std);
}
/* Variante « enfants » pour #space-left : la barre du haut est remplacée sur
   place et ne doit pas clignoter, la pastille de chargement a sa propre
   entrée. La pastille « mode » (.test-label-container, MAIL DESACTIVE…) pend
   sous la barre du haut : elle fait partie du cadre, pas de l'écran qui arrive.
   Elle est écartée ici et dans le fondu de sortie ci-dessous. */
:where(html.eb-motion) :where(.eb-motion-in-enfants) > :where(:not(.eb-topbar):not(.eb-spa-loader):not(.test-label-container)) {
  animation: eb-motion-fade-in var(--eb-motion-slow) var(--eb-ease-std);
}

/* ---------------------------------------------------------------------
   2. Changement d'écran par le conteneur
   ---------------------------------------------------------------------
   L'ancien contenu reste MASQUÉ pendant le chargement (décision du
   28/07/2026, 67ab21c2b : une vue lourde ne doit pas rester sous la
   pastille). Il s'efface seulement en 90 ms au lieu d'être coupé net :
   `visibility` bascule à la fin du fondu, les clics sont coupés tout de
   suite par le `pointer-events: none` de la règle d'origine. */
html.eb-motion body.eb-has-topbar-v2 #space-left.eb-container-is-loading > :not(.eb-topbar):not(.eb-spa-loader):not(.test-label-container),
html.eb-motion body.eb-has-topbar-v2 .space-left.eb-container-is-loading > :not(.eb-topbar):not(.eb-spa-loader):not(.test-label-container),
html.eb-motion body.eb-has-topbar-v2 #space-left.eb-spa-is-loading > :not(.eb-topbar):not(.eb-spa-loader):not(.test-label-container),
html.eb-motion body.eb-has-topbar-v2 .space-left.eb-spa-is-loading > :not(.eb-topbar):not(.eb-spa-loader):not(.test-label-container) {
  transition: opacity 90ms ease-out, visibility 0s linear 90ms;
}
/* La pastille n'apparaît que si l'attente dure. Mesuré en local : la réponse
   arrive en ~150 ms, la pastille s'affichait donc une ou deux images avant de
   disparaître — un clignotement, pas une information. Sous 220 ms, rien ;
   au-delà, elle entre en fondu. `backwards` : invisible pendant le délai. */
:where(html.eb-motion body.eb-has-topbar-v2) :where(.eb-spa-loader) {
  animation: eb-motion-fade-in var(--eb-motion-base) ease-out 220ms backwards;
}

/* ---------------------------------------------------------------------
   3. Sous-menus de la barre latérale
   ---------------------------------------------------------------------
   Hauteur animée de 0 à `auto` : il faut `interpolate-size` (Chromium 129+).
   Ailleurs le bloc entier est ignoré et le sous-menu s'ouvre comme avant —
   aucune régression, seulement pas de trajet.
   L'état fermé reste `display: none` (règle d'origine) : rien ne change pour
   le clavier, les lecteurs d'écran ni les `:visible` de jQuery. C'est
   `transition-behavior: allow-discrete` qui retarde ce `display: none` à la
   fin de la fermeture, et `@starting-style` qui donne son point de départ à
   l'ouverture.
   Hors rail étroit / replié : ces deux modes masquent les sous-menus par un
   `display: none !important` qui ne doit pas être différé de 240 ms. */
@supports (interpolate-size: allow-keywords) {
  html.eb-motion:not(.eb-sidebar-is-narrow) .eb-sidebar:not(.collapsed) .eb-nav-sub {
    interpolate-size: allow-keywords;
    height: 0;
    padding-top: 0;
    padding-bottom: 0;
    opacity: 0;
    /* `clip` et non `hidden` : pas de zone de défilement créée, et l'axe
       horizontal reste libre. La marge laisse passer le contour de focus de
       la première et de la dernière entrée. */
    overflow-y: clip;
    overflow-clip-margin: 3px;
    transition:
      height var(--eb-motion-slow) var(--eb-ease-out),
      padding var(--eb-motion-slow) var(--eb-ease-out),
      opacity var(--eb-motion-base) ease,
      display var(--eb-motion-slow) allow-discrete;
  }
  html.eb-motion:not(.eb-sidebar-is-narrow) .eb-sidebar:not(.collapsed) .eb-nav-sub.show {
    height: auto;
    padding-top: 2px;
    padding-bottom: 4px;
    opacity: 1;
  }
  @starting-style {
    html.eb-motion:not(.eb-sidebar-is-narrow) .eb-sidebar:not(.collapsed) .eb-nav-sub.show {
      height: 0;
      padding-top: 0;
      padding-bottom: 0;
      opacity: 0;
    }
  }
}

/* ---------------------------------------------------------------------
   4. Menus
   ---------------------------------------------------------------------
   Déroulants Bootstrap et listes de choix : fondu seul. Popper les place par
   un `transform` en ligne et peut les recalculer pendant l'ouverture — on ne
   déplace rien. `.inner` exclu : bootstrap-select pose `.dropdown-menu.show`
   sur l'enveloppe ET sur sa liste intérieure, le fondu jouerait deux fois. */
:where(html.eb-motion) :where(.dropdown-menu.show:not(.inner)) {
  animation: eb-motion-fade-in var(--eb-motion-fast) ease-out;
}
/* Menu du compte : ancré au-dessus de l'avatar, il monte de quelques pixels. */
:where(html.eb-motion) :where(.eb-user-menu.show) {
  animation: eb-motion-rise-in var(--eb-motion-base) var(--eb-ease-out);
}
/* Volet du rail étroit : il sort de la barre, vers la droite. */
:where(html.eb-motion) :where(.eb-nav-flyout.show) {
  animation: eb-motion-slide-in var(--eb-motion-fast) ease-out;
}

/* ---------------------------------------------------------------------
   5. Boutons : retour à l'appui
   ---------------------------------------------------------------------
   `scale` (propriété) et non `transform: scale()` : un bouton flottant centré
   par `transform: translateX(-50%)` garde sa place. Même geste que les
   boutons du SITAC (`:active { transform: scale(.96) }`), étendu au produit.
   Exclus : le bouton d'une liste de choix (il occupe toute la largeur du
   champ), les liens habillés en bouton, les boutons inertes ou désactivés, et
   les interrupteurs bootstrap-toggle — leur enveloppe ET leurs deux libellés
   sont des `.btn`, l'effet s'y multiplierait pendant que le curseur glisse. */
:where(html.eb-motion) .btn:active:not(.dropdown-toggle):not(.btn-link):not(.eb-btn-inert):not(.disabled):not(:disabled):not(.toggle):not(.toggle *) {
  scale: 0.97;
}

/* ---------------------------------------------------------------------
   6. D'une page complète à la suivante
   ---------------------------------------------------------------------
   Le conteneur ne prend pas tout : le formulaire d'activité, un
   enregistrement suivi d'une redirection, la connexion, tout `location.href`
   rechargent le document. Entre les deux, l'écran passait par une image vide
   (relevé du 11/10/2026 : zone de contenu blanche à 84-95 ms, nouvelle page à
   120 ms). Avec cette règle le navigateur garde l'image de la page quittée
   jusqu'au premier rendu de la suivante, puis fond l'une dans l'autre : la
   barre latérale, identique des deux côtés, ne bouge plus.

   Rien à programmer, et rien qui puisse rester figé : c'est le navigateur qui
   tient l'ancienne image, la rend dès que la nouvelle page peut être peinte,
   et abandonne la transition si l'onglet est masqué ou si l'arrivée tarde.
   Il faut que les DEUX pages portent la règle — donc cette feuille : une page
   sans load_css() (document servi, page de traitement nue) se charge comme
   avant. Ne s'applique ni au rechargement, ni à une adresse saisie, ni à une
   autre origine. Chromium 126+, Safari 18.2+ ; ailleurs la règle est ignorée.

   160 ms et non les 250 ms d'usage : pendant le fondu la page d'arrivée ne
   reçoit pas les clics.

   Cette règle a une garde, servie dans le <head> avec la feuille :
   eb_transition_page_script() (fonctions_menu.php). Elle écoute la promesse
   que le navigateur rejette quand il abandonne une transition — sans quoi ce
   rejet remonterait au journal d'erreurs comme une panne — et abandonne
   d'elle-même la transition vers ce qui n'est pas un écran (sortie de session,
   page de traitement, export). Ne pas poser l'une sans l'autre. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
  ::view-transition-group(root),
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 160ms;
  }
}
