/* ===========================================================================
   LOT 09 — MOUVEMENT (ESSAI, 2026-09-30, branche essai/mouvement-interface)

   Ce que cette feuille fait : donner un RYTHME COMMUN à ce qui bouge déjà, et
   un retour visuel à ce qui ne réagissait pas — sans toucher une seule page.
   Tout passe par des classes partagées (gabarit .mx-*, liste .tbl-*, coquille
   .et-*, Bootstrap .btn), si bien que les ~900 écrans en héritent d'un coup.

   Ce qu'elle ne fait pas : aucune couleur (les jetons --et-* seulement, donc le
   thème sombre suit), aucune géométrie de mise en page, aucun chiffre qui bouge
   (« un chiffre qui bouge se lit mal », easypaie.css §16 — la règle vaut ici :
   une tuile peut se soulever, sa valeur ne s'anime jamais).

   Les durées et courbes sont des jetons (easytech-theme.css, bloc MOUVEMENT) ;
   sous prefers-reduced-motion ils valent 0 et tout ce qui suit s'éteint.

   Les animations d'ENTRÉE sont en « backwards », jamais en « both » : une valeur
   d'animation conservée après la fin l'emporte sur toute transition — le
   soulèvement d'une tuile au survol restait bloqué (mesuré, pas supposé).

   Chargée EN DERNIER (App.razor) : elle reprend des animations que modele-erp.css
   pose déjà (.mx-modale-cadre, .mx-page, .mx-panneau) — à spécificité égale,
   l'ordre tranche. Les sélecteurs passent par :where() pour garder la
   spécificité au plus bas : une feuille d'écran qui voudrait autre chose gagne
   sans lutter.

   ⚠ Contredit la passation design-7 (« Rien d'autre ne bouge ») : PROPOSITION.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   0. IMAGES-CLÉS — préfixe mvt-, libre dans tout le dépôt (vérifié).
   --------------------------------------------------------------------------- */
@keyframes mvt-voile   { from { opacity: 0; } to { opacity: 1; } }
@keyframes mvt-fenetre { from { opacity: 0; transform: translateY(16px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes mvt-monte   { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes mvt-glisse  { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes mvt-reflet  { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* ---------------------------------------------------------------------------
   1. RÉACTIVITÉ — tout ce qui se clique change d'état en douceur.
   Avant : la plupart des boutons et liens basculaient sèchement, quelques-uns
   avaient une transition écrite à la main (.12s ici, .15s là).
   --------------------------------------------------------------------------- */
.appgrid :where(.mx-btn, .btn, .et-navmain, .mx-onglet, .tbl-etat-opt, .tbl-vider, a) {
    transition-property: background-color, border-color, color, box-shadow, transform, opacity;
    transition-duration: var(--et-duree-courte);
    transition-timing-function: var(--et-courbe-standard);
}

/* L'appui se SENT : le bouton s'enfonce d'un rien, et revient vite. */
.appgrid :where(.mx-btn, .btn):active:not(:disabled) {
    transform: scale(.965);
    transition-duration: var(--et-duree-instant);
}

/* Le focus d'un champ s'installe au lieu d'apparaître : un halo doux de la
   couleur d'action, en plus de la bordure — angles droits conservés (charte :
   « les champs de saisie n'ont JAMAIS d'arrondi »). */
.appgrid :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea) {
    transition: border-color var(--et-duree-courte) var(--et-courbe-standard),
                box-shadow var(--et-duree-courte) var(--et-courbe-standard);
}
.appgrid :where(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea):focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--et-form-blue) 18%, transparent);
}

/* Le menu latéral : le libellé glisse d'un pas vers la droite au survol,
   comme une main qui l'effleure. */
.appgrid .et-navmain .lb {
    transition: transform var(--et-duree-courte) var(--et-courbe-sortie);
}
.appgrid .et-navmain:hover .lb { transform: translateX(3px); }

/* Les lignes de liste : le fond de survol arrive au lieu de clignoter. */
.appgrid .tbl tbody td {
    transition: background-color var(--et-duree-courte) var(--et-courbe-standard);
}

/* ---------------------------------------------------------------------------
   2. FENÊTRES — elles arrivent de quelque part.
   Avant : 160 ms, glissement de 8 px (mxUp). Après : un peu plus long, une
   courbe qui décélère longuement, et une légère mise à l'échelle — la fenêtre
   « se pose » au lieu de glisser.
   --------------------------------------------------------------------------- */
.mx-voile {
    animation: mvt-voile var(--et-duree-moyenne) var(--et-courbe-standard) backwards;
}
.mx-modale-cadre,
.modal.show > .modal-dialog {
    animation: mvt-fenetre var(--et-duree-longue) var(--et-courbe-sortie) backwards;
    transform-origin: 50% 30%;
}
/* Un léger flou derrière le voile donne de la profondeur. Réservé aux écrans
   larges : sur un Android d'entrée de gamme, un backdrop-filter coûte cher. */
@media (min-width: 1024px) {
    .mx-voile { backdrop-filter: blur(2px); }
}

/* ---------------------------------------------------------------------------
   3. PAGES, PANNEAUX, TUILES — l'arrivée se fait en cascade.
   --------------------------------------------------------------------------- */
.mx-page {
    animation: mvt-monte var(--et-duree-longue) var(--et-courbe-sortie) backwards;
}
.mx-panneau {
    animation: mvt-glisse var(--et-duree-moyenne) var(--et-courbe-sortie) backwards;
}

/* Tuiles d'indicateur : elles montent l'une après l'autre. Huit pas au plus —
   au-delà, la cascade deviendrait une attente. */
.mx-ind-grille > .mx-ind {
    animation: mvt-monte var(--et-duree-longue) var(--et-courbe-sortie) backwards;
}
.mx-ind-grille > .mx-ind:nth-child(2) { animation-delay: calc(var(--et-cascade-pas) * 1); }
.mx-ind-grille > .mx-ind:nth-child(3) { animation-delay: calc(var(--et-cascade-pas) * 2); }
.mx-ind-grille > .mx-ind:nth-child(4) { animation-delay: calc(var(--et-cascade-pas) * 3); }
.mx-ind-grille > .mx-ind:nth-child(5) { animation-delay: calc(var(--et-cascade-pas) * 4); }
.mx-ind-grille > .mx-ind:nth-child(6) { animation-delay: calc(var(--et-cascade-pas) * 5); }
.mx-ind-grille > .mx-ind:nth-child(7) { animation-delay: calc(var(--et-cascade-pas) * 6); }
.mx-ind-grille > .mx-ind:nth-child(n+8) { animation-delay: calc(var(--et-cascade-pas) * 7); }

/* Une tuile qui MÈNE quelque part se soulève au survol ; une tuile qui ne fait
   qu'afficher ne bouge pas — un soulèvement promet un clic. La tuile cliquable EST
   le lien (<a class="mx-ind">, 16 écrans ; <button>, 5) : 237 tuiles d'affichage
   sont des <div> et restent immobiles. */
.appgrid :is(a, button).mx-ind {
    transition: transform var(--et-duree-courte) var(--et-courbe-sortie),
                box-shadow var(--et-duree-courte) var(--et-courbe-standard);
}
.appgrid :is(a, button).mx-ind:hover {
    transform: translateY(-2px);
    box-shadow: var(--et-ombre-flottant);
}

/* État vide : il apparaît en douceur plutôt que d'être « déjà là, vide ». */
.mx-etat-vide,
.appgrid tr.tbl-etat:not(:has(.spinner-border)) > td {
    animation: mvt-monte var(--et-duree-moyenne) var(--et-courbe-sortie) backwards;
}

/* ---------------------------------------------------------------------------
   4. CHARGEMENT — un squelette plutôt qu'une roue.
   Tableau.razor émet la même roue partout (tr.tbl-etat en mode tableau,
   .tbl-bloc-etat dans les autres modes). On la remplace EN CSS par des lignes
   grises à la forme d'une liste, traversées d'un reflet : l'œil voit déjà la
   liste arriver. Le texte « Chargement… » reste lu par un lecteur d'écran.
   --------------------------------------------------------------------------- */
.appgrid tr.tbl-etat:has(> td > .spinner-border) > td,
.appgrid .tbl-bloc-etat:has(> .spinner-border) {
    position: relative; overflow: hidden;
    padding: 16px 14px; text-align: left;
}
.appgrid tr.tbl-etat:has(> td > .spinner-border) > td > .spinner-border,
.appgrid .tbl-bloc-etat:has(> .spinner-border) > .spinner-border { display: none; }
.appgrid tr.tbl-etat:has(> td > .spinner-border) > td > .mt-2 {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}
/* Cinq lignes, chacune de deux barres de longueurs différentes : une liste
   « vraisemblable », pas une grille de rectangles identiques. Une ligne = UN
   dégradé à segments, pleine largeur : les arrêts de couleur se lisent en % de
   la largeur, alors qu'un background-position en % se calcule sur (conteneur −
   image) et faisait chevaucher les deux barres. */
.appgrid tr.tbl-etat:has(> td > .spinner-border) > td::before,
.appgrid .tbl-bloc-etat:has(> .spinner-border)::before {
    --mvt-os: var(--et-rang);
    content: ""; display: block; height: 156px;
    background-image:
        linear-gradient(90deg, var(--mvt-os) 0 14%, transparent 14% 18%, var(--mvt-os) 18% 54%, transparent 54%),
        linear-gradient(90deg, var(--mvt-os) 0 10%, transparent 10% 18%, var(--mvt-os) 18% 62%, transparent 62%),
        linear-gradient(90deg, var(--mvt-os) 0 16%, transparent 16% 18%, var(--mvt-os) 18% 46%, transparent 46%),
        linear-gradient(90deg, var(--mvt-os) 0 12%, transparent 12% 18%, var(--mvt-os) 18% 58%, transparent 58%),
        linear-gradient(90deg, var(--mvt-os) 0 14%, transparent 14% 18%, var(--mvt-os) 18% 40%, transparent 40%);
    background-repeat: no-repeat;
    background-size: 100% 12px;
    background-position: 0 0, 0 36px, 0 72px, 0 108px, 0 144px;
}
.appgrid tr.tbl-etat:has(> td > .spinner-border) > td::after,
.appgrid .tbl-bloc-etat:has(> .spinner-border)::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent 0%,
                color-mix(in srgb, var(--et-surface) 65%, transparent) 50%, transparent 100%);
    animation: mvt-reflet 1.4s var(--et-courbe-standard) infinite;
}

/* Les autres roues (boutons « Enregistrer… », blocs isolés) : même roue, mais
   qui respire — elle ralentit et accélère au lieu de tourner comme une
   machine. */
.appgrid .spinner-border {
    animation-timing-function: cubic-bezier(.55, .15, .45, .85);
    animation-duration: .9s;
}

/* ---------------------------------------------------------------------------
   5. MOUVEMENT RÉDUIT — les jetons valent déjà 0 ; restent les deux boucles
   infinies (reflet, roue), qu'un « 0 ms » ferait clignoter : on les arrête.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .appgrid tr.tbl-etat:has(> td > .spinner-border) > td::after,
    .appgrid .tbl-bloc-etat:has(> .spinner-border)::after { animation: none; opacity: 0; }
    .appgrid .et-navmain:hover .lb,
    .appgrid :is(a, button).mx-ind:hover,
    .appgrid :where(.mx-btn, .btn):active:not(:disabled) { transform: none; }
    @keyframes mvt-voile   { from, to { opacity: 1; } }
    @keyframes mvt-fenetre { from, to { opacity: 1; transform: none; } }
    @keyframes mvt-monte   { from, to { opacity: 1; transform: none; } }
    @keyframes mvt-glisse  { from, to { opacity: 1; transform: none; } }
}
