/* CE FICHIER OUVRAIT SUR UN `:root` QUI NE SERVAIT A RIEN ET NUISAIT.
   Il declarait `--shadow` et `--radius`, dont AUCUN n'etait lu ici (le fichier utilise partout les
   jetons nommes : --radius-2xl, --radius-xl, --radius-card, --radius-control, --radius-full). Deux
   declarations mortes, donc — sauf que login.css est charge en DERNIER, apres les cinq feuilles
   globales : son `--radius: 18px` ecrasait l'alias `--radius: var(--radius-card)` d'app.css sur
   TOUTE la page. Une mine, pour rien. Supprimees.

   Ce qui reste ici est enregistre et non declare : `@property` type l'angle du lisere du bouton
   principal. Un `--x` non type n'INTERPOLE PAS — l'animation sauterait d'un tour a l'autre au lieu
   de tourner. L'enregistrement est global au document, quel que soit le fichier qui le porte. */
@property --auth-ring-angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}

/* La position du PLI sur la pastille, pendant son retournement. Enregistree pour la meme raison que
   l'angle ci-dessus : il faut qu'elle interpole.

   `initial-value: 150%` la place HORS de la pastille. Au repos, les deux bornes du degrade qui s'en
   sert valent donc la meme couleur et la pastille est d'un aplat uniforme — ce qui est indispensable,
   c'est cette uniformite qui rend son retournement a -180 degres invisible a l'arrivee. */
@property --auth-fold-crease {
    syntax: "<percentage>";
    inherits: false;
    initial-value: 150%;
}

.content:has(.auth-page) {
    padding-top: 0;
}

/* Conteneur de la page de connexion, pose par AuthLayout. Il ne borne rien et n'ajoute rien :
   toute la mise en page est portee par .auth-page. */
.auth-layout {
    display: block;
    width: 100%;
}

/* Le « - 1px » etait un pansement sur le debordement dont la cause reelle etait ailleurs (le padding
   vertical de .app-shell__content, retire d'AuthLayout) : la hauteur est franche maintenant.
   Plus de fond teinte non plus : le body porte deja le fond d'application, et un aplat legerement
   plus bleu par-dessus ne faisait que dessiner un rectangle au milieu de l'ecran. */
.auth-page {
    position: relative;
    isolation: isolate;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;

    /* 416 ms. Le pliage a CINQ temps (depart, torsion, pli, contre-torsion, pose) ; --duration-slow
       n'en tient qu'un.

       Deux allers-retours de recette sur cette seule valeur, et ils ne disaient pas la meme chose :
       440 ms d'abord, juge RIGIDE — mais la rigidite ne venait pas de la duree, elle venait de la
       courbe appliquee segment par segment (voir les keyframes plus bas). Une fois la courbe corrigee
       et l'arete de lumiere ajoutee, 520 ms laissaient le geste se lire ; le reglage a l'oeil l'a
       ensuite ramene 20 % plus bas. C'est le bon ordre : corriger la cause, puis regler la vitesse.

       Derive du jeton et non litteral, pour suivre le systeme s'il bouge. */
    --auth-fold-duration: calc(var(--duration-slow) * 1.6);

    /* La pointe irisee du lisere : deux litteraux ASSUMES. Aucun jeton de marque ne peut les
       fournir — l'irisation est par nature hors charte, c'est tout son propos. Portes par
       .auth-page et non par :root pour ne pas fuiter hors de cette page (cf. l'entete). */
    --auth-ring-spark-a: #ff8ec4;
    --auth-ring-spark-b: #6fd3ff;
}

/* HALO DE MARQUE DERRIERE LA CARTE. Elle flottait sur un aplat : deux voiles tres diffus tires de
   `--brand-glow` (jeton par marque, deja defini) l'ancrent dans la page.

   `absolute` et NON `fixed` : un ancetre transforme reduit un `fixed` a son bloc conteneur, et cette
   page anime desormais plusieurs elements. En absolute, la question ne se pose pas.
   Le halo est SOUS le contenu (z-index: -1) et `isolation: isolate` sur .auth-page borne cet index
   negatif a la page — il ne peut pas passer derriere le fond du document. */
.auth-page::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background:
        radial-gradient(58% 46% at 50% 34%, var(--brand-glow), transparent 70%),
        radial-gradient(38% 30% at 82% 78%, var(--brand-glow), transparent 72%);
    opacity: 0.5;
    pointer-events: none;
}

/* Le pont de compatibilite rend aux titres et aux paragraphes les marges du reboot Bootstrap, pour
   les ecrans pas encore refondus. Celui-ci l'est : ses marges viennent de ses gap et de ses padding,
   et les 8 px rendus sous chaque h1 s'ajoutaient a une page calee sur la hauteur de fenetre. */
.auth-page :is(h1, h2, h3, h4, h5, h6, p) {
    margin-block: 0;
}

/* Centre le shell verticalement ; le pied de page reste en bas.
   DEUX enfants maintenant : la carte, et le bandeau de migration SOUS elle. Il y etait dedans, ou il
   n'existait que sur l'onglet client — la carte changeait donc de taille d'un onglet a l'autre. */
.auth-stage {
    flex: 1;
    display: grid;
    place-items: center;
    align-content: center;
    gap: var(--space-4);
    padding: 28px 16px;
}

/* Bordure, fond et ombre passent sur les jetons du systeme — c'etaient trois litteraux.
   `--shadow-xl` est plus RESSERREE que le `0 28px 70px rgba(15,23,42,.14)` qu'elle remplace : la
   profondeur perdue est reprise par le halo pose sur .auth-page, et les deux vont ensemble — retirer
   l'un sans l'autre aplatit la scene. Benefice non evident : `--shadow-color` est chaud chez EUROPOLE
   (`30 25% 20%`), l'ombre devient donc juste par marque au lieu d'etre gris-bleu partout. */
.auth-shell {
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    width: min(960px, 100%);
    overflow: hidden;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-2xl);
    background: var(--color-surface);
    box-shadow: var(--shadow-xl);
}

/* Colonne gauche : panneau de marque (couleurs du tenant).

   LUMIERE RASANTE. Un aplat de marque pur lisait comme un rectangle de peinture. Deux couches de
   degrade par-dessus — un voile blanc en haut a gauche, une ombre en bas a droite — lui donnent une
   source de lumiere et un volume.

   Ce n'est PAS le degrade de marque que le systeme interdit (`--brand-gradient` est deprecie et
   resout sur un aplat) : la nuance est qu'on n'interpole pas DEUX couleurs de marque, on ECLAIRE une
   seule couleur avec du blanc et du noir neutres. La teinte du panneau reste exactement celle de la
   societe.

   Les couches sont dans le `background` et non dans un pseudo-element : un `::before` absolu se
   peindrait AU-DESSUS du contenu en flux (ordre de peinture), et il faudrait alors reprendre le
   z-index des trois enfants pour rien. */
.auth-brand {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 28px;
    padding: 44px 36px;
    color: var(--brand-on-primary);
    background:
        radial-gradient(120% 92% at 10% -12%, rgba(255, 255, 255, 0.10), transparent 62%),
        radial-gradient(92% 74% at 112% 108%, rgba(0, 0, 0, 0.13), transparent 62%),
        var(--brand-primary);
}

.auth-brand__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.auth-brand__eyebrow {
    margin: 0;
    font-size: var(--text-md);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    opacity: 0.85;
}

/* Bloc secondaire « Propulsé par Helios » + slogan, ancré en bas du panneau de marque. */
.auth-brand__powered {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    padding-top: 18px;
    border-top: 1px solid rgba(255, 255, 255, 0.18);
}

.auth-brand__powered-mark {
    width: 40px;
    height: 40px;
    padding: 6px;
    border-radius: var(--radius-card);
    background: rgba(255, 255, 255, 0.94);
    box-shadow: var(--shadow-md);
    object-fit: contain;
    flex-shrink: 0;
}

.auth-brand__powered-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.auth-brand__powered-label {
    font-size: var(--text-base);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.auth-brand__powered-tagline {
    font-size: var(--text-sm);
    font-weight: 400;
    line-height: 1.35;
    opacity: 0.78;
}

.auth-brand__logo {
    display: inline-grid;
    place-items: center;
    width: 104px;
    height: 104px;
    padding: 6px;
    border-radius: var(--radius-xl);
    background: rgba(255, 255, 255, 0.92);
    box-shadow: var(--shadow-lg);
}

    .auth-brand__logo img {
        width: 100%;
        height: 100%;
        object-fit: contain;
    }

.auth-brand__features {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 14px;
}

    .auth-brand__features li {
        display: flex;
        align-items: center;
        gap: 12px;
        font-size: var(--text-base);
        font-weight: 600;
    }

/* PLUS DE CADRE AUTOUR DE L'ICONE. Elle est passee par un aplat blanc a 16 %, puis par un filet
   blanc a 14 % cense « rendre l'icone a elle-meme » — le filet dessinait encore trois boutons
   inertes alignes le long du panneau, releve en recette. Il ne reste que l'icone.

   La boite de --control-h-sm disparait avec lui : sans cadre, elle ne servait plus qu'a eloigner
   l'icone de son libelle. L'alignement des trois lignes est desormais tenu par le `gap` de la liste,
   qui est ce qui doit le tenir. */
.auth-brand__icon {
    flex: 0 0 auto;
    color: inherit;
}

/* Europole : son primary (beige clair) génère un texte auto en noir qui rend mal.
   On bascule le panneau sur sa couleur secondaire (ardoise foncée #333F48) avec
   texte blanc, comme les autres sociétés, et un halo chaud tiré du primary beige.

   ATTENTION : cette regle remplace `background` EN ENTIER, donc elle doit reprendre les deux couches
   de lumiere rasante ci-dessus, sinon EUROPOLE serait la seule societe a garder un aplat plat. */
html[data-company="europole" i] .auth-brand {
    color: var(--color-text-inverse);
    background:
        radial-gradient(120% 92% at 10% -12%, rgba(255, 255, 255, 0.10), transparent 62%),
        radial-gradient(92% 74% at 112% 108%, rgba(0, 0, 0, 0.13), transparent 62%),
        var(--brand-secondary);
}

/* Colonne droite : zone onglets + formulaire.
   Déclarée comme conteneur pour que le titre s'adapte à SA largeur (cqi),
   et non à celle du viewport. */
.auth-panel-side {
    display: flex;
    flex-direction: column;
    background: var(--color-surface);
    container-type: inline-size;
}

/* DEUX COLONNES EGALES, et c'est ce qui rend le glissement exact. Le curseur est un SEUL element —
   le ::before de la liste — large d'une colonne : passer d'un onglet a l'autre est un
   `translateX(100%)`, donc aucune mesure a faire, aucun JavaScript, et le geste tient meme avant que
   le circuit Blazor soit etabli.

   `psTabIndicator`, le guetteur ecrit pour la frise du detail d'un ticket, ne convenait pas : il
   mesure un soulignement de largeur VARIABLE et doit relire le DOM a chaque bascule et a chaque
   redimensionnement. Ici la grille fixe la largeur ; la mesure n'apprendrait rien.

   Le systeme ne fournit pas ce motif : son `.ds-segmented` colore l'option active sans rien faire
   glisser. Ecart 57. La courbe est la sienne (`--ease`), et sous `prefers-reduced-motion` la
   transition est neutralisee par tokens/motion.css — rien a ecrire de plus. */
.auth-tabs {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    padding: var(--space-5) var(--space-5) var(--space-2);
    border-bottom: 1px solid var(--color-border);
}

.auth-tabs::before {
    content: "";
    position: absolute;
    z-index: 0;
    inset-block: var(--space-5) var(--space-2);
    inset-inline-start: var(--space-5);
    width: calc((100% - var(--space-5) * 2) / 2);
    border: 1px solid var(--brand-primary-subtle);
    border-radius: var(--radius-full);

    /* L'ARETE DE LUMIERE DU PLI. Les deux bornes valent la MEME couleur, si bien que tant que
       `--auth-fold-crease` est hors de [0 %, 100 %] — sa valeur au repos est 150 % — la pastille est
       d'un aplat parfaitement uniforme. Pendant le retournement, la variable traverse et une arete
       claire balaie la pastille : c'est la lumiere qui accroche le pli du papier.

       Sans elle, le geste se lisait comme une carte qui pivote — c'est le releve de recette. Une
       rotation seule ne fait pas un pli : il faut que la SURFACE change d'eclairement. */
    background:
        linear-gradient(100deg,
            var(--brand-primary-surface) 0 calc(var(--auth-fold-crease) - 4%),
            rgba(255, 255, 255, 0.95) var(--auth-fold-crease),
            var(--brand-primary-surface) calc(var(--auth-fold-crease) + 4%) 100%);
}

/* CETTE REGLE STATIQUE TIENT L'ETAT FINAL, et c'est pour cela qu'aucune des animations ci-dessous
   n'a besoin d'`animation-fill-mode`. Ne pas la supprimer en croyant que les keyframes suffisent. */
.auth-tabs[data-tab="support"]::before {
    transform: translateX(100%);
}

/* ---------------------------------------------------------------------------------------------
   LE PLIAGE. La pastille ne glisse plus : elle se TORD, se PINCE, traverse en tranche et se
   DEPLIE de l'autre cote — le geste d'un papier d'emballage qu'on entortille.

   POURQUOI DES KEYFRAMES ET PLUS UNE TRANSITION. Une transition ne sait aller que de A a B de
   facon monotone : elle ne peut pas pincer AU MILIEU du trajet. Le `transition: transform` d'avant
   est donc parti. Deux animations, une par sens, pour que le changement de `data-tab` RELANCE le
   geste : le nom d'animation change, donc l'animation redemarre — y compris sur des allers-retours
   rapides, ce qu'un simple redemarrage de la meme animation n'aurait pas donne.

   TROIS POINTS A NE PAS PERDRE :

   1. AUCUN `animation-fill-mode`, et ce n'est pas un oubli. L'image-cle 100 % vaut exactement la
      regle statique de l'etat d'arrivee, donc l'etat se tient tout seul. Le portail documente en
      80 lignes (ds-legacy-bridge.css) ce que coute un `both` mal place : `transform: none` s'y
      resout en matrice identite, qui cree DEFINITIVEMENT un bloc conteneur et un contexte
      d'empilement. Ici la question ne se pose meme pas.

   2. `rotateY(-180deg)` S'APPUIE SUR LA SYMETRIE DE LA PASTILLE. Un aplat uniforme, un filet
      uniforme et une forme symetrique sont IDENTIQUES a 0 et a -180 degres : le retour a la regle
      statique en fin d'animation ne produit donc aucun saut visible. SI UN JOUR ON DECORE LA
      PASTILLE de facon asymetrique (degrade, reflet, ombre interne), il faudra passer les deux
      keyframes a -360deg, sinon elle se retournera d'un coup a l'arrivee.

   3. `perspective()` EST UNE FONCTION DE TRANSFORM, PAS UNE PROPRIETE DU PARENT. Ne pas
      « simplifier » en posant `perspective` sur .auth-tabs : cela creerait un bloc conteneur et un
      contexte d'empilement au milieu de la carte. Et la scene 3D ne doit surtout pas remonter plus
      haut — `body { overflow-x: clip }` (app.css, bloc de durcissement mobile) APLATIT tout
      `preserve-3d` qui l'atteindrait. En gardant la perspective dans la liste de transform du
      pseudo-element lui-meme, la scene ne depasse jamais l'element anime.

   Sous `prefers-reduced-motion`, tokens/motion.css ramene la duree a 1 ms : la bascule redevient
   instantanee et se pose sur la regle statique. Rien a ecrire de plus ici. */
.auth-tabs[data-animate="true"][data-tab="support"]::before {
    animation: auth-fold-to-support var(--auth-fold-duration) var(--ease);
}

.auth-tabs[data-animate="true"][data-tab="client"]::before {
    animation: auth-fold-to-client var(--auth-fold-duration) var(--ease);
}

/* LA COURBE PAR SEGMENT, ET C'EST LA CAUSE DE LA RIGIDITE QU'ON A RELEVEE EN RECETTE.
   `animation-timing-function` posee sur l'animation s'applique ENTRE CHAQUE PAIRE d'images-cles, pas
   sur l'ensemble : avec cinq images-cles, la pastille decelerait et reaccelerait QUATRE fois en
   440 ms. On lisait quatre petits gestes saccades au lieu d'un seul.

   Chaque image-cle porte donc sa propre courbe : on part en accelerant (`--ease-in`, une courbe du
   systeme), on GLISSE en `linear` a travers la torsion — un plateau de vitesse, sans quoi la pastille
   marque un temps d'arret pile au moment du pli —, et on se pose sur `--ease`. `linear` n'est pas une
   courbe expressive de plus : c'est l'absence de courbe sur les segments intermediaires, la seule
   facon d'obtenir un mouvement continu en plusieurs temps.

   LA TORSION, elle, vient de `skewY` qui CHANGE DE SIGNE en passant par le milieu : les deux moities
   de la pastille penchent en sens contraire, ce qui est exactement ce que fait un papier qu'on
   entortille. Une rotation seule ne donnait qu'un pivot de carte. `scaleY` se resserre legerement au
   passage : le papier se pince aussi dans sa hauteur, pas seulement dans sa largeur. */
@keyframes auth-fold-to-support {
    0% {
        transform: perspective(680px) translateX(0) rotateY(0deg) skewY(0deg) scaleX(1) scaleY(1);
        border-radius: var(--radius-full);
        --auth-fold-crease: 0%;
        animation-timing-function: var(--ease-in);
    }

    30% {
        transform: perspective(680px) translateX(26%) rotateY(-58deg) skewY(-7deg) scaleX(0.42) scaleY(0.94);
        border-radius: 10px;
        animation-timing-function: linear;
    }

    50% {
        transform: perspective(680px) translateX(50%) rotateY(-90deg) skewY(0deg) scaleX(0.2) scaleY(0.88);
        border-radius: 6px;
        --auth-fold-crease: 50%;
        animation-timing-function: linear;
    }

    70% {
        transform: perspective(680px) translateX(74%) rotateY(-122deg) skewY(7deg) scaleX(0.42) scaleY(0.94);
        border-radius: 10px;
        animation-timing-function: var(--ease);
    }

    100% {
        transform: perspective(680px) translateX(100%) rotateY(-180deg) skewY(0deg) scaleX(1) scaleY(1);
        border-radius: var(--radius-full);
        --auth-fold-crease: 100%;
    }
}

@keyframes auth-fold-to-client {
    0% {
        transform: perspective(680px) translateX(100%) rotateY(-180deg) skewY(0deg) scaleX(1) scaleY(1);
        border-radius: var(--radius-full);
        --auth-fold-crease: 100%;
        animation-timing-function: var(--ease-in);
    }

    30% {
        transform: perspective(680px) translateX(74%) rotateY(-122deg) skewY(7deg) scaleX(0.42) scaleY(0.94);
        border-radius: 10px;
        animation-timing-function: linear;
    }

    50% {
        transform: perspective(680px) translateX(50%) rotateY(-90deg) skewY(0deg) scaleX(0.2) scaleY(0.88);
        border-radius: 6px;
        --auth-fold-crease: 50%;
        animation-timing-function: linear;
    }

    70% {
        transform: perspective(680px) translateX(26%) rotateY(-58deg) skewY(-7deg) scaleX(0.42) scaleY(0.94);
        border-radius: 10px;
        animation-timing-function: var(--ease);
    }

    100% {
        transform: perspective(680px) translateX(0) rotateY(0deg) skewY(0deg) scaleX(1) scaleY(1);
        border-radius: var(--radius-full);
        --auth-fold-crease: 0%;
    }
}

/* Au-dessus du curseur, et transparent : c'est lui qui porte le fond desormais. */
/* `white-space: nowrap` : les libelles TIENNENT SUR UNE LIGNE, a toutes les largeurs. Ils passaient
   sur deux des que la carte se resserrait, ce qui doublait la hauteur de la barre d'onglets et
   deformait la pastille — releve en recette. Le libelle a ete raccourci pour cela (cf. Login.razor) ;
   la phrase entiere reste annoncee aux lecteurs d'ecran par `aria-label`, rien n'est perdu. */
.auth-tab {
    position: relative;
    z-index: 1;
    padding: 10px 14px;
    border: 0;
    background: transparent;
    color: var(--muted);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease);
}

.auth-tab:hover {
    color: var(--text);
}

/* Anneau de focus explicite : la paire d'onglets est un seul arret de tabulation (tabindex glissant,
   cf. Login.razor), il faut donc voir OU l'on est. `box-shadow` convient ici — contrairement aux deux
   boutons plus bas, .auth-tab ne porte pas `overflow: hidden`, qui l'aurait rogne. Le rayon suit
   celui de la pastille pour que l'anneau epouse la forme et non la boite. */
.auth-tab:focus-visible {
    outline: none;
    border-radius: var(--radius-full);
    box-shadow: var(--shadow-focus);
}

/* `--brand-ink` et non `--text` : c'est le ton de marque lisible en texte, garanti AA sur blanc pour
   les quatre societes — la ou un aplat de marque ne l'est pas (EUROPOLE).

   LE DELAI EST LE POINT. Le libellé entrant passait au ton de marque en 130 ms alors que la pastille
   voyage 440 ms : le texte s'allumait dans le vide, puis la pastille arrivait sous un libellé deja
   colore. Avec 220 ms de delai — la moitie du pliage — le libellé s'allume au moment ou la pastille
   se deplie sous lui : il la RECOIT.

   Le libellé sortant, lui, n'a aucun delai : il repart sur la regle de base de .auth-tab, qui n'en
   declare pas. Il s'eteint donc tout de suite, ce qui est juste — c'est le depart qui doit etre net
   et l'arrivee qui doit etre attendue. */
.auth-tab[aria-selected="true"] {
    color: var(--brand-ink);
    transition-delay: 220ms;
}

.auth-header {
    padding: 26px 30px 10px;
}

    .auth-header h1 {
        margin: 0 0 8px;
        font-size: clamp(var(--text-xl), 6.5cqi, var(--text-3xl));
        line-height: 1.04;
        letter-spacing: -0.04em;
        color: var(--text);
    }

    .auth-header p {
        margin: 0;
        font-size: var(--text-base);
        line-height: 1.65;
        color: var(--muted);
    }

/* Gabarit + panneau actif empilés dans la même cellule de grille : la carte
   prend toujours la hauteur du gabarit (panneau le plus grand).
   `flex:1` fait remplir l'espace restant sous les onglets ; `grid-template-rows:1fr`
   étire la cellule pour que le contenu puisse être centré verticalement. */
.auth-stack {
    display: grid;
    grid-template-rows: 1fr;
    flex: 1 1 auto;
}

    .auth-stack > .auth-panel {
        grid-area: 1 / 1;
        display: flex;
        flex-direction: column;
        justify-content: center;
    }

    /* Le gabarit n'est jamais affiché ni basculé : il sert uniquement à réserver
       la hauteur. Seul le panneau actif (.auth-panel--live) est peint. */
    .auth-panel--sizer {
        visibility: hidden;
        pointer-events: none;
    }

/* LE CONTENU SUIT LA PASTILLE. Le panneau droit se remplacait sec, ce qui donnait deux gestes sans
   rapport : une pastille qui voyageait, et un contenu qui apparaissait. Il entre desormais depuis le
   cote vers lequel on va — convention du film qui defile, et celle du sens de lecture.

   Amplitude volontairement faible (14 px). Les keyframes du systeme ne convenaient pas :
   `ds-slide-in-right` part de `translateX(100%)`, soit une PLEINE largeur de panneau — juste pour un
   tiroir qui entre par le bord de l'ecran, absurde pour un contenu qui reste en place.

   `backwards` et non `both` : l'image-cle finale vaut l'etat de base (`transform: none`,
   `opacity: 1`), ce qui est exactement le critere de bascule fixe par le portail dans
   ds-legacy-bridge.css. Le fill sert ici a tenir l'etat de DEPART, pas celui d'arrivee.

   LE GABARIT EST EXCLU DU SELECTEUR, et ce n'est pas cosmetique : c'est lui qui tient la hauteur de
   la carte. S'il bougeait, la carte respirerait a chaque bascule — exactement ce que le gabarit
   existe pour empecher. */
    .auth-stack[data-animate="true"][data-tab="support"] > .auth-panel:not(.auth-panel--sizer) {
        animation: auth-panel-in-right var(--duration-slow) var(--ease) backwards;
    }

    .auth-stack[data-animate="true"][data-tab="client"] > .auth-panel:not(.auth-panel--sizer) {
        animation: auth-panel-in-left var(--duration-slow) var(--ease) backwards;
    }

@keyframes auth-panel-in-right {
    from {
        opacity: 0;
        transform: translateX(14px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes auth-panel-in-left {
    from {
        opacity: 0;
        transform: translateX(-14px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

        .auth-panel--sizer .ms-btn {
            width: 100%;
        }

.auth-form {
    display: grid;
    gap: 14px;
    padding: 14px 30px 30px;
}

    .auth-form form {
        margin: 0;
    }

        .auth-form form .primary-btn,
        .auth-form form .ms-btn {
            width: 100%;
        }

/* CORRECTION QUI NE VIENT PAS DE LA RECETTE : la couleur du libelle etait `#fff` en dur sur un aplat
   de marque. Chez EUROPOLE, dont le primaire est un beige clair, cela donnait du BLANC SUR BEIGE —
   exactement ce que le systeme interdit, et il fournit le jeton qui repond : `--brand-on-primary`,
   qui vaut #333f48 pour cette societe et du blanc pour les trois autres.

   PLUS D'OMBRE NON PLUS, ni au repos ni au survol, sur aucun des deux boutons : « Les boutons n'ont
   aucune ombre » (readme.md). Le survol change l'aplat, comme le fait `.ds-btn` du systeme. Le
   `translateY(-1px)` part avec : le systeme n'ecarte les boutons du fond a aucun moment. */
/* LES DEUX BOUTONS PARTAGENT DESORMAIS LA MEME SURFACE CLAIRE, et c'est une decision de recette.

   Le bouton client etait un aplat de marque. Le lisere lumineux qui tourne sur sa bordure est lui
   aussi en teintes de marque : du bleu sur du bleu, on ne le voyait pas. Les deux boutons passent
   donc sur la surface claire, ou l'arc de lumiere se detache franchement.

   CONSEQUENCE A CONNAITRE : il n'y a plus d'aplat de marque sur cette page cote formulaire, donc la
   hierarchie n'est plus portee par le REMPLISSAGE du bouton mais par le lisere en mouvement, qui est
   le seul element anime de l'ecran. C'est le choix retenu, pas un oubli.

   Regle commune plutot que deux blocs jumeaux : ils etaient deja identiques a la couleur pres, et
   deux blocs auraient divergé au premier ajustement.

   Ce qui remplace l'ancien `color: #fff` en dur reste acquis (ecart 59) : sur une surface claire, le
   libelle est `--text`, lisible pour les quatre societes sans avoir a raisonner par marque. */
.primary-btn,
.ms-btn {
    height: 52px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xl);
    background: var(--color-surface);
    color: var(--text);
    font-weight: 700;
    font-size: 14px;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease);
}

    .primary-btn:hover,
    .ms-btn:hover {
        background: var(--color-surface-hover);
    }

.primary-btn {
    margin-top: 4px;
}

/* LE REMPLISSAGE PENDANT LA REDIRECTION.

   Les deux boutons soumettent un formulaire qui QUITTE la page : la reponse ne revient pas, elle
   emmene ailleurs. Sans repere, on clique deux fois. Le systeme a bien un etat d'attente
   (`.ds-btn[data-loading]`), mais c'est un tourniquet : il tourne sans fin, ce qui se lit comme
   « bloque » alors qu'on est en train de partir. Le remplissage, lui, avance une fois et s'arrete.
   Ecart 58.

   C'est une TRANSITION et non une animation, et le choix est deliberé : une transition tient son
   etat final par construction, donc pas de `animation-fill-mode: forwards` ici — le seul du portail
   reste la sortie des notifications. L'attribut est pose par js/authForm.js, en phase de capture,
   pour que le remplissage parte avant que le navigateur ne commence sa navigation.

   `isolation: isolate` plus `z-index: -1` : le voile passe derriere le libelle sans sortir du bouton.
   `overflow: hidden` rogne le voile sur le rayon. */
.primary-btn,
.ms-btn {
    position: relative;
    overflow: hidden;
    isolation: isolate;
}

    .primary-btn::after,
    .ms-btn::after {
        content: "";
        position: absolute;
        inset: 0;
        z-index: -1;
        transform: scaleX(0);
        transform-origin: left;
        transition: transform 900ms var(--ease);
    }

    /* Un seul voile pour les deux, maintenant qu'ils partagent la meme surface. `--brand-primary-subtle`
       et non `--brand-primary-surface` : cette derniere est a deux doigts du blanc, le remplissage se
       voyait a peine sur un bouton blanc — le repere existait sans reperer. Le libelle reste
       largement lisible par-dessus (contraste mesure > 12:1 pour les quatre societes). */
    .primary-btn::after,
    .ms-btn::after {
        background: var(--brand-primary-subtle);
    }

    .primary-btn[data-submitting="true"]::after,
    .ms-btn[data-submitting="true"]::after {
        transform: scaleX(1);
    }

    /* Un second clic n'a plus rien a declencher : le formulaire est parti. */
    .primary-btn[data-submitting="true"],
    .ms-btn[data-submitting="true"] {
        pointer-events: none;
    }

/* ---------------------------------------------------------------------------------------------
   LE LISERE LUMINEUX DU BOUTON PRINCIPAL. Un arc de lumiere tourne en continu le long de sa
   bordure. C'est la seule action de la seule page qui n'a aucun autre contenu : le mouvement
   repond a « ou est-ce que je clique ». Ecart assume et consigne (ADHERENCE.md).

   `::after` est pris par le remplissage — d'ou `::before`. La recette est celle du lisere en
   degrade : un pseudo-element qui remplit le bouton, masque pour ne laisser que l'anneau.
   `mask-composite: exclude` retire du masque la zone `content-box`, ne laissant que le `padding`,
   soit exactement l'epaisseur du filet.

   `z-index: 1` ET SURTOUT PAS -1. Les deux pseudos a -1 se peindraient dans l'ordre du DOM, et le
   remplissage — opaque, `inset: 0` — recouvrirait l'anneau des le clic. A 1, le lisere passe
   au-dessus de tout, y compris du libelle : sans consequence, il vit sur 1,5 px de bordure ou il
   n'y a jamais de texte.

   `overflow: hidden` du bouton rogne l'anneau exactement sur le rayon. C'est voulu : aucun
   debordement possible, meme d'un demi-pixel.

   COUT REEL, PUISQUE L'ANIMATION EST INFINIE : animer une propriete ENREGISTREE repeint (elle
   n'est pas composee), ce qui s'ecarte du « on n'anime que transform et opacity ». Sur ~390 x 52 px,
   environ 20 000 px par image : negligeable, et mesurable si on en doute. Si un profilage disait le
   contraire, le repli est un <span> interieur portant un degrade STATIQUE qu'on fait tourner en
   `transform: rotate()` — compositeur pur, au prix d'un element de balisage.

   `@property` non supporte (Safari < 16.4, Firefox < 128) : l'angle ne s'interpole pas, l'anneau
   s'affiche STATIQUE. Degradation propre, rien ne casse.

   LES DEUX BOUTONS LE PORTENT. Il n'etait d'abord pose que sur le bouton client, au motif qu'une
   seule action se met en avant par panneau et que la charte Microsoft restreint la decoration d'un
   bouton SSO. Releve en recette : les deux panneaux sont EXCLUSIFS — on n'en voit jamais qu'un —,
   donc il n'y a jamais deux actions en concurrence, et l'onglet interne se retrouvait simplement
   moins soigne que l'autre sans raison visible pour l'utilisateur. Le lisere reste un filet de 1,5 px
   qui n'altere ni le libelle ni le symbole Microsoft. */
.primary-btn::before,
.ms-btn::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
    padding: 1.5px;
    background: conic-gradient(from var(--auth-ring-angle),
        transparent 0 55%,
        var(--brand-primary) 68%,
        var(--brand-accent) 78%,
        var(--auth-ring-spark-a) 84%,
        var(--auth-ring-spark-b) 89%,
        transparent 100%);
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    -webkit-mask-composite: xor;
    pointer-events: none;
    animation: auth-ring-spin 6s linear infinite;
}

@keyframes auth-ring-spin {
    to {
        --auth-ring-angle: 360deg;
    }
}

/* Pendant la redirection, l'anneau CESSE de tourner et se ferme sur un aplat. Le mouvement passe
   le relais au remplissage, qui est la vraie information : deux mouvements concurrents sur le meme
   bouton ne se lisent plus. */
.primary-btn[data-submitting="true"]::before,
.ms-btn[data-submitting="true"]::before {
    animation: none;
    background: var(--brand-accent);
}

/* ANNEAU DE FOCUS DES DEUX BOUTONS : `outline` et non `box-shadow`, et c'est la raison qui compte.
   Ces deux boutons portent `overflow: hidden` (il rogne le voile de remplissage sur le rayon), et
   `overflow` ROGNE UN BOX-SHADOW EXTERIEUR : `var(--shadow-focus)` y serait purement invisible.
   Un `outline` n'est jamais rogne par l'overflow de son propre element. */
.primary-btn:focus-visible,
.ms-btn:focus-visible {
    outline: 2px solid var(--brand-focus);
    outline-offset: 2px;
}

/* LE VRAI SYMBOLE MICROSOFT, plus la lettre « M » sur un carre noir — qui se lisait comme un gabarit
   qu'on aurait oublie de finir, et que la charte Microsoft n'admet pas sur un bouton SSO. Le carre
   noir, le rayon et la graisse partent avec : le SVG porte ses quatre couleurs, il ne reste qu'un
   dimensionnement. `DsIcon` ne pouvait pas servir — il ne sert que les 61 icones Lucide, pas les
   logos de marque. */
.ms-logo {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    display: block;
}

/* Bandeau d'information TEMPORAIRE (migration Jira → Helios) — à retirer vers septembre 2026.
   Cf. Login.razor : visible uniquement sur l'onglet client, et posé SOUS la carte, plus dedans.
   Il reprend donc la largeur de la carte au lieu de ses marges internes. */
.auth-notice {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: min(960px, 100%);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--brand-primary-subtle);
    border-radius: var(--radius-card);
    background: var(--brand-primary-surface);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.55;
}

/* Masque mais toujours la : il reserve sa place pour que la carte ne se deplace pas d'un onglet a
   l'autre. Cf. Login.razor. */
.auth-notice[data-hidden="true"] {
    visibility: hidden;
}

.auth-notice__icon {
    flex: 0 0 auto;
    margin-top: 1px;
    color: var(--primary);
    font-size: var(--text-md);
}

.auth-notice__text strong {
    display: block;
    margin-bottom: 2px;
}

.auth-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 24px;
    padding: 18px 28px;
    color: var(--muted);
    font-size: 12px;
}

.auth-footer__support {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 18px;
}

.auth-footer__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: inherit;
    text-decoration: none;
}

a.auth-footer__item:hover {
    text-decoration: underline;
}

.auth-footer__meta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.auth-footer__dot {
    opacity: 0.6;
}

@media (max-width: 880px) {
    .auth-shell {
        grid-template-columns: 1fr;
    }

    .auth-brand {
        padding: 30px 28px;
    }

    .auth-brand__head {
        align-items: center;
        text-align: center;
    }

    .auth-brand__features {
        justify-items: center;
    }
}

@media (max-width: 768px) {
    .auth-stage {
        padding: var(--space-4) var(--space-3);
    }

    .auth-shell {
        border-radius: var(--radius-2xl);
    }

    /* La grille reste a deux colonnes : le curseur ne glisse juste que sur une distance plus
       courte. Les regles de flex d'avant (`flex-wrap`, `flex: 1 1 calc(50% - .3rem)`) sont parties
       avec le flex — elles n'auraient plus rien pilote. */
    .auth-tabs {
        padding: var(--space-4) var(--space-4) var(--space-2);
    }

    .auth-tabs::before {
        inset-block: var(--space-4) var(--space-2);
        inset-inline-start: var(--space-4);
        width: calc((100% - var(--space-4) * 2) / 2);
    }

    .auth-header {
        padding: var(--space-5) var(--space-4) var(--space-2);
    }

    .auth-form {
        padding: var(--space-3) var(--space-4) var(--space-4);
    }

    .auth-footer {
        flex-direction: column;
        align-items: center;
        text-align: center;
        gap: 8px;
    }

    .auth-footer__support {
        justify-content: center;
    }
}

@media (max-width: 560px) {
    .auth-brand__features {
        display: none;
    }
}

@media (max-width: 480px) {
    /* Les onglets NE s'empilent plus : leur libelle passe a la ligne dans sa colonne. Empiles, le
       curseur aurait du glisser verticalement, soit une seconde regle a tenir pour un cas ou les
       deux options tiennent tres bien cote a cote sur deux lignes de texte. */
    .auth-tab {
        padding-inline: var(--space-2);
    }

    .auth-header h1 {
        font-size: var(--text-2xl);
    }
}

/* Le systeme traite `prefers-reduced-motion` globalement (tokens/motion.css) : durees d'animation et
   de transition a 1 ms, iterations a 1. Les animations de cette page n'ont donc rien a y ajouter.

   MAIS IL Y A UN TROU, ET IL EST ICI. Ce bloc global ecrase `transition-duration` et
   `animation-duration` — PAS `transition-delay`. Sans la regle ci-dessous, le libelle de l'onglet
   selectionne attendrait ses 220 ms avant une transition de 1 ms : un decalage franchement visible,
   et precisement pour l'utilisateur qui a demande moins de mouvement. */
@media (prefers-reduced-motion: reduce) {
    .auth-tab[aria-selected="true"] {
        transition-delay: 0s;
    }
}
