/* =========================================================================
   ESDI MANAGER — 13-mouvement.css
   LA COUCHE DE MOUVEMENT du design system (2026-09-06).

   Kevin : « des animations qui donnent un sentiment de premium, de satisfaction
   dans l'utilisation quotidienne et de fluidité ».

   Ce fichier ne pose AUCUNE couleur ni dimension : seulement du temps. Chargé
   après 06-dark.css, il l'emporte à spécificité égale sur les transitions
   déclarées avant lui — c'est voulu, il les REMPLACE en reprenant chaque
   propriété qu'elles couvraient (une transition raccourcie n'est pas une
   transition perdue).

   Quatre lois, et rien d'autre :
   1. Ce qu'on voit cent fois par jour ne bouge pas, ou à peine (≤ 220 ms).
      Aucune action clavier n'est animée.
   2. Une entrée démarre vite (ease-out fort, l'œil regarde le premier tiers) ;
      une sortie est plus courte que l'entrée.
   3. Seuls opacity, transform, translate et scale bougent — jamais la mise en
      page. `translate`/`scale` (propriétés individuelles) plutôt que `transform`
      partout où une autre règle — survol, Popper — pose déjà un transform.
   4. prefers-reduced-motion coupe tout : la règle universelle de 05-utilities
      ramène transitions et animations à 0,01 ms ; les pseudo-éléments de
      transition de page, hors de portée de `*`, sont neutralisés ici.

   Deux mécaniques vérifiées dans dmxAppConnect.js avant d'écrire une ligne :
   - `dmx-show` pose `display:none !important` en ligne → `@starting-style`
     couvre CHAQUE apparition (onglet, alerte, pastille, carte) sans JS ;
   - `dmx-repeat` accepte `key="…"` → les nœuds inchangés sont réutilisés, la
     liste ne re-cascade pas à chaque rechargement.
   ========================================================================= */

/* =========================================================================
   1. D'UNE PAGE À L'AUTRE — le contenu se fond, la barre reste
   Le produit est multi-pages (Express + EJS) : chaque clic de la barre
   latérale rechargeait l'écran d'un coup sec (Kevin, 2026-09-06 : « un
   rechargement de page brutal et sec »). Ici :
   - au clic sur un lien de la barre (script esdi-sidebar.js), le contenu de
     la page qu'on quitte s'assombrit en 140 ms (.esdi-sortie) ; le
     navigateur garde cette image jusqu'à la première peinture de la
     suivante (paint holding) ;
   - le contenu de la nouvelle page arrive en fondu de 180 ms
     (@starting-style sur main) ; barre latérale et barre du haut, identiques
     d'une page à l'autre, ne bougent pas — c'est ce qui donne la sensation
     d'application.
   Opacité seule : un translate sur <main> en ferait le bloc conteneur des
   éléments fixes du chat pendant 180 ms, et ils sauteraient.

   🪤 ESSAYÉ ET RETIRÉ LE MÊME JOUR : @view-transition { navigation: auto }
   (fondu natif inter-documents). Mesuré à l'écran (PrintWindow, 25 i/s,
   Chrome 152) : une image ENTIÈREMENT NOIRE pendant ~80 ms avant le fondu,
   puis un damier à la place de l'ancienne barre latérale nommée — les
   instantanés de l'ancien document manquent au moment de composer. C'est
   précisément le clignotement que Kevin a vu. Le Chromium de Playwright,
   lui, gèle carrément la page (il est lancé avec --disable-features=
   PaintHolding). Trop fragile pour un geste fait cent fois par jour.
   Le fondu de VUE reste utilisé dans le MÊME document (changement de thème,
   esdi-init.js) : là, les captures sont fiables.
   ========================================================================= */
main.shell-main {
    transition: opacity 180ms var(--ease-out-strong);
}
@starting-style {
    main.shell-main { opacity: 0; }
}
main.shell-main.esdi-sortie {
    opacity: 0.35;
    transition: opacity 140ms linear;
    pointer-events: none;
}

/* Changement de thème (document.startViewTransition dans esdi-init.js) :
   un fondu croisé se compose en mix-blend-mode plus-lighter, les deux
   images S'ADDITIONNENT — même durée, même courbe LINÉAIRE des deux côtés,
   sinon la somme dépasse 100 % et l'écran éclaire quelques images. */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 160ms;
    animation-delay: 0ms;
    animation-timing-function: linear;
}
@media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-image-pair(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
    main.shell-main.esdi-sortie { opacity: 1; }
}

/* =========================================================================
   2. LES LISTES ARRIVENT EN CASCADE
   Une carte qui « pop » d'un coup dit « données chargées » ; huit cartes qui
   se posent à 30 ms d'intervalle disent « voilà votre équipe ». Le décalage
   ne vaut que pour l'ENTRÉE (opacity + translate) : survol, enfoncement et
   changement d'état gardent leur réponse immédiate — d'où la liste de
   propriétés explicite et le délai porté par --entree-delai, consommé par
   les deux premières seulement.
   ========================================================================= */
.esdi-employee-card,
.esdi-auto-carte,
.esdi-validation-card,
.esdi-rapport-card,
.esdi-propo-carte,
.esdi-kpi-card,
.pl-tuile {
    transition:
        opacity var(--duration-entree) var(--ease-out-strong) var(--entree-delai, 0ms),
        translate var(--duration-entree) var(--ease-out-strong) var(--entree-delai, 0ms),
        transform 180ms var(--ease-out-strong),
        box-shadow var(--duration-micro) var(--ease-out-strong),
        border-color var(--duration-micro) var(--ease-out-strong),
        background-color var(--duration-micro) var(--ease-out-strong);
}
@starting-style {
    .esdi-employee-card,
    .esdi-auto-carte,
    .esdi-validation-card,
    .esdi-rapport-card,
    .esdi-propo-carte,
    .esdi-kpi-card,
    .pl-tuile {
        opacity: 0;
        translate: 0 8px;
    }
}
/* Le décalage, par rang. Huit rangs suffisent : au-delà, la carte est sous la
   ligne de flottaison et personne ne la regarde arriver. Les cartes d'équipe et
   les KPI vivent dans une colonne Bootstrap : c'est la colonne qui compte. */
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(2) { --entree-delai: 30ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(3) { --entree-delai: 60ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(4) { --entree-delai: 90ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(5) { --entree-delai: 120ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(6) { --entree-delai: 150ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(7) { --entree-delai: 180ms; }
:is(.esdi-auto-carte, .esdi-validation-card, .esdi-rapport-card, .esdi-propo-carte, .pl-tuile, .row > [class*="col"]):nth-child(8) { --entree-delai: 210ms; }

/* L'écran vide (« Rien à valider ») arrive comme une carte. */
.esdi-empty-state {
    transition: opacity var(--duration-entree) var(--ease-out-strong),
                translate var(--duration-entree) var(--ease-out-strong);
}
@starting-style {
    .esdi-empty-state { opacity: 0; translate: 0 6px; }
}

/* =========================================================================
   3. LES ONGLETS — le panneau qui arrive
   Le clic sur un onglet est fréquent : 160 ms, 4 px, pas plus. Assez pour
   que le contenu ne « claque » pas, pas assez pour qu'on l'attende.
   ========================================================================= */
[role="tabpanel"] {
    transition: opacity 160ms var(--ease-out-strong),
                translate 160ms var(--ease-out-strong);
}
@starting-style {
    [role="tabpanel"] { opacity: 0; translate: 0 4px; }
}

/* =========================================================================
   4. CE QUI S'OUVRE — menus, modales, tiroirs, dépliants
   ========================================================================= */

/* --- Menu déroulant Bootstrap : il pousse depuis son déclencheur ----------
   Popper pose un `transform: translate(…)` EN LIGNE sur le menu : un
   `transform: scale()` en CSS serait écrasé. `scale` (propriété
   individuelle) se compose avec, sans conflit. */
.dropdown-menu {
    transform-origin: top left;
    transition: opacity 140ms var(--ease-out-strong),
                scale 140ms var(--ease-out-strong);
}
.dropdown-menu-end,
.dropdown-menu[data-popper-placement="bottom-end"] { transform-origin: top right; }
.dropdown-menu[data-popper-placement^="top"]        { transform-origin: bottom left; }
.dropdown-menu[data-popper-placement="top-end"]     { transform-origin: bottom right; }
@starting-style {
    .dropdown-menu.show { opacity: 0; scale: 0.96; }
}

/* --- Modale Bootstrap : une carte qui se pose, pas un rideau qui tombe ----
   Bootstrap la fait descendre de 50 px en 300 ms ease-out. Ici : 12 px et
   2 % d'échelle en 220 ms, centrée (une modale n'a pas de déclencheur
   spatial, elle garde son origine au centre). Le voile suit en 160 ms. */
.modal.fade { transition: opacity 160ms linear; }
.modal-backdrop.fade { transition: opacity 160ms linear; }
.modal.fade .modal-dialog {
    transform: translateY(12px) scale(0.98);
    transition: transform 220ms var(--ease-out-strong);
}
.modal.show .modal-dialog { transform: none; }

/* --- Tiroirs (offcanvas) : la courbe iOS des tiroirs ----------------------
   Bootstrap glisse en ease-in-out (le départ traîne). Portée aux tailles
   de téléphone seulement pour .offcanvas-md : dès 768 px c'est la barre
   latérale, qui a SA transition de largeur — un `transition` posé ici la
   remplacerait. */
.offcanvas { transition: transform 280ms var(--ease-drawer); }
@media (max-width: 767.98px) {
    .offcanvas-md { transition: transform 280ms var(--ease-drawer); }
}
.offcanvas-backdrop.fade { transition: opacity 200ms linear; }

/* --- Dépliants <details> (rapports, appels d'outils du chat, journaux) ----
   Le contenu apparaît d'un bloc à l'ouverture : on le laisse descendre de
   3 px en 180 ms. Fermé, le contenu n'a pas de boîte — rien à animer. */
details > :not(summary) {
    transition: opacity 180ms var(--ease-out-strong),
                translate 180ms var(--ease-out-strong);
}
@starting-style {
    details[open] > :not(summary) { opacity: 0; translate: 0 -3px; }
}

/* =========================================================================
   5. CE QUI RÉPOND — interrupteurs, pastilles, retours d'action
   ========================================================================= */

/* --- Cases et interrupteurs : le curseur glisse, la couleur suit ------- */
.form-check-input {
    transition: background-color 160ms var(--ease-out-strong),
                border-color 160ms var(--ease-out-strong),
                box-shadow 160ms var(--ease-out-strong),
                scale 120ms var(--ease-out-strong);
}
.form-switch .form-check-input {
    transition: background-position 200ms var(--ease-out-strong),
                background-color 200ms var(--ease-out-strong),
                border-color 200ms var(--ease-out-strong),
                box-shadow 160ms var(--ease-out-strong),
                scale 120ms var(--ease-out-strong);
}
.form-check-input:active:not(:disabled) { scale: 0.92; }

/* --- Pastilles de compte (barre latérale, onglets) : elles éclosent ------
   Un compteur qui apparaît a le droit à un léger rebond : rare, petit, et
   c'est une bonne nouvelle (« un rapport vous attend »). */
.esdi-nav__item .badge,
.esdi-nav-tabs__tab .badge,
.esdi-pv-compteur {
    transition: scale 220ms var(--ease-pop),
                opacity 160ms var(--ease-out-strong);
}
@starting-style {
    .esdi-nav__item .badge,
    .esdi-nav-tabs__tab .badge,
    .esdi-pv-compteur { scale: 0.5; opacity: 0; }
}

/* --- Alertes et toasts : ils se posent, ils ne surgissent pas -------------
   Les deux keyframes historiques de .esdi-toast (une glissait de haut,
   l'autre de droite) laissent place à UNE transition : interruptible, même
   loi que les alertes en ligne. */
.shell-main .alert,
.esdi-toast {
    animation: none;
    transition: opacity 200ms var(--ease-out-strong),
                translate 200ms var(--ease-out-strong);
}
@starting-style {
    .shell-main .alert,
    .esdi-toast { opacity: 0; translate: 0 -6px; }
}

/* --- Le geste principal : un halo au survol, seulement à la souris -------
   Réservé au CTA dégradé (Embaucher, Lui parler) : c'est le bouton qu'on
   veut voir, pas celui qu'on presse cent fois. */
@media (hover: hover) and (pointer: fine) {
    .esdi-btn-gradient:hover:not(:disabled):not(.disabled) {
        box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--accent-500) 55%, transparent);
    }
}

/* =========================================================================
   6. À VALIDER — la carte tranchée s'en va
   L'action la plus satisfaisante du produit ne donnait AUCUN retour : on
   cliquait « Confirmer », rien ne bougeait, puis la liste entière se
   redessinait. Maintenant : le bouton montre qu'il travaille (spinner à la
   place du libellé), la carte s'estompe pendant l'appel, puis glisse vers
   la droite quand la décision est enregistrée — et seulement alors la liste
   se recharge (dmx-repeat keyed : les voisines ne bougent pas).
   Les classes sont posées par le script de views/pending-validations.ejs.
   ========================================================================= */
.esdi-validation-card.is-envoi {
    opacity: 0.6;
    pointer-events: none;
    transition-delay: 0ms;
}
.esdi-validation-card.is-partie {
    opacity: 0;
    translate: 32px 0;
    pointer-events: none;
    transition-delay: 0ms;
    transition-duration: 200ms;
}
.btn.is-attente {
    position: relative;
    color: transparent !important;
    pointer-events: none;
}
.btn.is-attente > * { visibility: hidden; }
.btn.is-attente::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--btn-attente, #fff);
    border-right-color: transparent;
    animation: esdi-spin-rot 700ms linear infinite;
}
.btn-outline-secondary.is-attente,
.btn-secondary.is-attente,
.btn-ghost.is-attente { --btn-attente: var(--ink-soft); }

/* =========================================================================
   7. AU DOIGT, PAS DE SURVOL
   Un écran tactile déclenche :hover au premier contact et le garde jusqu'au
   tap suivant : une carte « soulevée » resterait en l'air. On ne soulève
   qu'avec un vrai curseur.
   ========================================================================= */
@media (hover: none) {
    .esdi-kpi-card:hover,
    .esdi-preset-card:hover,
    .esdi-oauth-btn:hover,
    .esdi-employee-card:hover,
    .pl-tuile:hover:not(:disabled) { transform: none; }
}

/* =========================================================================
   8. FLUIDE EN TOUTE CIRCONSTANCE — mesuré le 2026-09-06 dans Chrome 152
   Kevin : « tester l'optimisation de l'app aussi, que ce soit fluide en toute
   circonstance ». Huit pages sondées, processeur normal puis ralenti 4×
   (un portable fatigué). Le serveur répond en 20 ms, le DOM est prêt en 90 à
   210 ms ; ce qui restait à régler n'était pas la vitesse mais les SAUTS :
   - ce qui se charge après coup poussait ce qui était déjà à l'écran
     (CLS 0,20 sur Plannings, 0,09 sur l'Espace de travail, 0,08 sur Recruter,
     et un petit saut sur TOUTES les pages : la pilule d'abonnement qui arrive
     dans la barre latérale) ;
   - les longues listes (rapports, automatisations) coûtaient 280 ms de
     travail d'un bloc à l'ouverture, 730 ms sur processeur lent.
   ========================================================================= */

/* --- Les longues listes ne rendent que ce qui est à l'écran -------------
   content-visibility: auto — le navigateur saute la mise en page et la
   peinture des cartes hors écran, et s'en souvient (auto). Elles arrivent
   avec leur cascade quand on descend jusqu'à elles. Vérifié : aucune de ces
   cartes n'héberge de menu ou d'élément fixe, que le confinement clipperait. */
.esdi-rapport-card,
.esdi-auto-carte {
    content-visibility: auto;
    contain-intrinsic-size: auto 150px;
}

/* --- Les icônes réservent leur place avant que Lucide ne les dessine ------
   Un <i data-lucide> est une boîte en ligne VIDE (0 px) jusqu'à ce que le
   script le remplace par un svg de 1 em : chaque icône décalait son texte de
   16 px au chargement, sur toutes les pages. */
i[data-lucide] { display: inline-block; }

/* --- La barre latérale : la pilule d'abonnement n'agrandit plus la ligne --
   Elle arrive par une requête après le rendu et faisait grandir la ligne du
   profil de 15 à 21 px, parfois 37 quand elle passait à la ligne. La ligne a
   désormais SA hauteur, pilule ou pas, et la pilule ne se replie jamais. */
.esdi-profil__sous { min-height: 20px; flex-wrap: nowrap; }
.esdi-profil__sous .esdi-palier-pilule { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* --- Recruter : la grille réserve sa place pendant qu'elle se remplit ---- */
#catalogGrid:not(:has(*)) { min-height: 60vh; }

/* --- Plannings : le sélecteur de planning a une largeur fixe --------------
   Vide au premier rendu, il s'élargissait avec le nom du planning et faisait
   passer la barre d'outils sur deux lignes (la carte grandissait de 53 px). */
#pl-select { min-width: 15rem; max-width: 15rem; }
