/* ==========================================================================
   css/liquide.css — LA PEAU « LIQUIDE »
   --------------------------------------------------------------------------
   POURQUOI CE FICHIER EXISTE

   identite.css a donné à l'app son caractère : noir absolu, angles droits,
   la couleur ne sert qu'à signaler. C'est juste, et rien ici ne le renie.
   Mais un instrument, ça se consulte ; ça ne se retouche pas trente fois
   par jour pour le plaisir. Or c'est exactement ce qu'on demande maintenant :
   que l'app donne ENVIE d'être ouverte, et qu'on ait envie de la montrer.

   Ce qui manque n'est pas une fonction de plus — aucune n'est ajoutée ici,
   aucune n'est retirée. Ce qui manque, c'est la MATIÈRE. Une surface plate
   ne renvoie rien au doigt qui la touche. Une surface de verre, si : elle a
   une épaisseur, une arête qui capte la lumière, et elle s'allume à
   l'endroit exact où on appuie.

   L'IDENTITÉ NE CHANGE PAS, LE MATÉRIAU CHANGE.

     • le noir reste le sol                 — l'OLED garde sa raison d'être
     • le bleu et le rouge restent          — mais comme LUMIÈRE, pas comme encre
     • le mono en capitales reste           — c'est la signature de la marque
     • aucune couleur nouvelle n'est admise — la palette est celle du tricolore

   CE QUI ARRIVE

     1. le fond n'est plus mort : deux lueurs tricolores dérivent très
        lentement derrière tout. C'est ce qui donne au verre quelque chose à
        réfracter — sans elles, une carte translucide sur du noir pur est
        juste une carte grise.
     2. les angles reviennent — trois rayons, pas six (cf. coherence.test.js)
     3. les cartes deviennent des panneaux de verre : arête éclairée en haut,
        ombre portée en bas, et un reflet qui SUIT LE DOIGT
     4. la barre d'onglets flotte, et sa pastille GLISSE d'un onglet à l'autre
     5. tout ce qui se touche s'enfonce, avec un ressort

   ⚠️  CHARGÉ APRÈS mobile.css, ET C'EST VOLONTAIRE.
       mobile.css garde le dernier mot sur les MESURES (zones sûres, cibles
       de 44 px, gouttières, escamotage au clavier). Ce fichier ne touche à
       AUCUNE mesure : ni padding, ni margin, ni height, ni width, ni
       position, ni z-index de la barre ou de l'en-tête. Il ne pose que de la
       couleur, de la forme, de la lumière et du mouvement.
       test/liquide.test.js vérifie cette frontière à chaque exécution.

   ⚠️  UNE LIGNE DANS index.html LE RETIRE. On retombe alors exactement sur
       l'identité « Instrument », sans rien casser d'autre — c'est la même
       promesse qu'identite.css tient vis-à-vis de l'ancienne peau.

   ⚠️  PAS DE THÈME CLAIR ICI, ET CE N'EST PAS UN OUBLI : identite.css impose
       déjà le noir aux deux thèmes. Le verre suit son sol.
   ========================================================================== */


/* --------------------------------------------------------------------------
   0. LES VALEURS
   --------------------------------------------------------------------------
   Trois épaisseurs de verre, une arête, trois rayons, deux ressorts. Tout le
   reste du fichier ne fait que les distribuer.
   -------------------------------------------------------------------------- */

:root {
    /* --- Le verre, du plus posé au plus creusé ---------------------------
       Ce sont des BLANCS TRANSPARENTS, pas des gris opaques : c'est la
       différence entre une surface qui laisse passer la lumière du fond et
       une surface qui la bloque. Un gris #111 sur du noir donne toujours le
       même #111 ; un blanc à 5,5 % donne une teinte différente selon ce qui
       dérive derrière. C'est tout le sujet.                              */
    --l-verre-1:  rgba(255, 255, 255, 0.065);  /* une carte posée sur le sol */
    --l-verre-2:  rgba(255, 255, 255, 0.028);  /* un champ creusé dans la carte */
    --l-verre-3:  rgba(255, 255, 255, 0.095);  /* un contrôle : bouton secondaire, pastille */

    /* --- L'arête, et l'ombre ---------------------------------------------
       Une plaque de verre posée sur une table se reconnaît à deux choses :
       le liseré clair sur sa tranche du haut, et l'ombre courte en dessous.
       Rien d'autre n'est nécessaire pour lire l'épaisseur.               */
    --l-arete:      rgba(255, 255, 255, 0.20);
    --l-arete-fine: rgba(255, 255, 255, 0.10);
    --l-fond-arete: rgba(0, 0, 0, 0.55);
    --l-ombre:      0 12px 32px -14px rgba(0, 0, 0, 0.95);
    --l-ombre-haute:0 24px 60px -20px rgba(0, 0, 0, 1);

    /* --- Les trois rayons, et un sens pour chacun -------------------------
       Repris tels quels de coherence.test.js : xl = un contrôle,
       2xl = une carte ou une modale, full = une pastille. On ne fait que
       leur redonner une valeur non nulle.                                */
    --l-r-controle: 14px;
    --l-r-carte:    22px;

    /* --- Le flou, réservé à ce qui FLOTTE --------------------------------
       Voir la section 4 : il coûte cher, il n'est posé que là où il paie. */
    --l-flou: 24px;

    /* --- LE RESSORT, ET IL EST UNIQUE ------------------------------------
       L'app en comptait TROIS, relevées une par une :

         cubic-bezier(.4, 0, .2, 1)      la valeur par défaut de Tailwind,
                                         sur 205 éléments sans que personne
                                         l'ait choisie ;
         cubic-bezier(0.16, 1, 0.3, 1)   le vrai ressort de l'app — la barre
                                         d'onglets qui s'escamote au clavier,
                                         l'en-tête qui se range, l'écran de
                                         fin qui se compose ;
         cubic-bezier(0.22, 1, 0.36, 1)  celle que cette peau avait apportée.

       Deux courbes presque identiques mais pas tout à fait, c'est le genre
       d'écart qu'on ne voit jamais isolément et qui rend une app « bizarre » :
       la barre glissait sur l'une, sa pastille sur l'autre.

       On garde celle de l'app. Elle était là avant, elle est mesurée, et
       elle est plus décidée — elle atteint 90 % de sa course en un tiers du
       temps, ce qui donne un objet qui PART vite et se pose doucement.

       Le second ressort dépasse légèrement : réservé à ce qui doit se sentir
       comme une matière qu'on relâche.                                   */
    --l-ressort:       cubic-bezier(0.16, 1, 0.3, 1);
    --l-ressort-appui: cubic-bezier(0.34, 1.42, 0.64, 1);

    /* --- QUATRE DURÉES, ET PAS UNE DE PLUS -------------------------------
       Le relevé en trouvait onze, de 60 ms à 1 s, choisies au fil de l'eau.
       Une échelle courte suffit, et chaque cran a un emploi :

         vif    ce qui répond au doigt — au-delà, on doute d'avoir appuyé
         court  une couleur, un survol, un état qui bascule
         moyen  un objet qui se DÉPLACE : la pastille, une feuille qui monte
         long   une entrée d'écran, la seule qu'on ait le temps de regarder  */
    --l-vif:   90ms;
    --l-court: 180ms;
    --l-moyen: 320ms;
    --l-long:  400ms;

    /* --- La lumière de la marque -----------------------------------------
       Le bleu et le rouge du tricolore, en LUMIÈRE. Ils n'écrivent rien,
       donc ils ne disent rien : la règle « la couleur ne sert qu'à
       signaler » est préservée — un fond n'est pas un signal.            */
    --l-lueur-bleue:  rgba(37, 99, 235, 0.17);
    --l-lueur-rouge:  rgba(229, 50, 45, 0.13);
    --l-lueur-blanche:rgba(255, 255, 255, 0.035);
}

/* La force du reflet sous le doigt. Déclarée avec @property pour qu'elle
   puisse être TRANSITIONNÉE : une variable CSS ordinaire saute d'une valeur
   à l'autre, et le reflet s'allumerait d'un bloc au lieu de monter. Là où
   @property n'existe pas, le reflet saute — c'est la seule dégradation, et
   elle est invisible pour qui ne la cherche pas. */
@property --l-eclat {
    syntax: '<number>';
    initial-value: 0;
    inherits: false;
}


/* --------------------------------------------------------------------------
   1. LE FOND N'EST PLUS MORT
   --------------------------------------------------------------------------
   C'EST LA PIÈCE MAÎTRESSE, ET ELLE TIENT EN UN PSEUDO-ÉLÉMENT.

   Du verre translucide posé sur du noir pur ne donne pas du verre : il donne
   un gris. Il faut quelque chose DERRIÈRE à laisser passer. Deux lueurs très
   sombres — le bleu et le rouge de l'app — dérivent donc en permanence
   derrière tout le contenu, sur 42 secondes. Assez lentement pour qu'on ne
   les surprenne jamais en train de bouger ; assez pour que deux captures
   d'écran prises à dix secondes d'écart ne soient pas identiques.

   L'OLED N'Y PERD PRESQUE RIEN : au plus fort, le fond monte à 17 % d'un
   bleu déjà sombre, et sur les deux tiers de l'écran il reste du noir exact.

   `inset: -25%` : la couche est plus grande que l'écran, sinon le `scale`
   de la dérive ferait entrer un bord vide dans le champ.

   `z-index: -1` fonctionne parce que le fond du <body> passe en transparent
   juste en dessous : la peinture du <html> devient alors celle de la page,
   et la couche se glisse entre les deux. C'est le seul endroit du fichier
   qui touche à un empilement — et il ne concerne que le <body>.
   -------------------------------------------------------------------------- */

html { background-color: #000000 !important; }
body { background-color: transparent !important; }

body::before {
    content: "";
    position: fixed;
    inset: -25%;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(38% 30% at 18% 10%, var(--l-lueur-bleue),   transparent 70%),
        radial-gradient(36% 30% at 88% 64%, var(--l-lueur-rouge),   transparent 72%),
        radial-gradient(50% 38% at 62% 44%, var(--l-lueur-blanche), transparent 70%);
    animation: l-derive 42s ease-in-out infinite;
    /* La dérive est une transformation pure : le GPU la compose, aucun
       pixel n'est recalculé. Un `background-position` animé aurait repeint
       tout l'écran soixante fois par seconde. */
    will-change: transform;
}

@keyframes l-derive {
    0%   { transform: translate3d(0, 0, 0) scale(1); }
    50%  { transform: translate3d(-5%, -3.5%, 0) scale(1.14); }
    100% { transform: translate3d(0, 0, 0) scale(1); }
}


/* --------------------------------------------------------------------------
   2. LES ANGLES REVIENNENT
   --------------------------------------------------------------------------
   identite.css remet TOUT rayon à zéro (`[class*="rounded"]`). On le défait,
   mais on ne rouvre pas la porte aux six rayons d'avant : les trois classes
   que coherence.test.js autorise reçoivent trois valeurs, et rien d'autre
   n'en reçoit.

   Le rayon n'est pas une coquetterie ici : c'est lui qui fait qu'une arête
   éclairée se lit comme une TRANCHE. Sur un angle droit, le liseré du haut
   s'arrête net et ressemble à une bordure ; sur un angle rond, il tourne et
   s'éteint — exactement comme la lumière sur une vitre.
   -------------------------------------------------------------------------- */

.rounded-xl   { border-radius: var(--l-r-controle) !important; }
.rounded-2xl  { border-radius: var(--l-r-carte)    !important; }
.rounded-full { border-radius: 9999px              !important; }

/* Les cartes qui n'ont pas de classe de rayon — l'app en compte beaucoup,
   dessinées avant que le système existe. Elles rejoignent l'échelle. */
.pro-card,
.exo-block,
#recovery-heatmap {
    border-radius: var(--l-r-carte) !important;
}


/* --------------------------------------------------------------------------
   3. LES TROIS ÉPAISSEURS DE VERRE
   --------------------------------------------------------------------------
   On reprend, une fois de plus, les classes que l'app emploie déjà : c'est ce
   qui permet de changer toute la matière sans toucher une ligne de balisage,
   donc sans risquer de perdre une fonction. systeme.css les avait rangées en
   trois niveaux, identite.css leur avait donné trois gris ; on leur donne
   trois verres.

   POURQUOI PAS DE `backdrop-filter` SUR LES CARTES, ET C'EST UN CHOIX MESURÉ.

   Un flou d'arrière-plan coûte une passe de rendu par élément flouté. Sur un
   écran d'historique qui aligne douze cartes, le défilement tombe sous les
   trente images par seconde — sur les téléphones que cette app vise en
   premier, c'est visible à l'œil nu.

   Et il n'apporterait RIEN ici : ce qu'il y a derrière une carte, c'est la
   dérive de la section 1, un dégradé déjà parfaitement lisse. Or le flou
   d'un dégradé lisse est ce même dégradé. On paie donc le prix fort pour un
   résultat identique. Le flou est réservé à ce qui passe DEVANT du contenu
   réel — barre, en-tête, modales : section 4.
   -------------------------------------------------------------------------- */

/* Niveau 1 — ce qui est POSÉ sur le sol : une carte, un bloc. */
.bg-zinc-900,
.bg-zinc-900\/30,
.bg-zinc-900\/40,
.bg-zinc-900\/50,
.bg-zinc-900\/60,
.bg-zinc-900\/80,
.pro-card,
.exo-block,
#recovery-heatmap {
    background-color: var(--l-verre-1) !important;
    border-color: var(--l-arete-fine) !important;
    /* L'arête. Trois traits, et ils disent trois choses :
         1. le liseré clair du haut  → la tranche prend la lumière
         2. le liseré sombre du bas  → la plaque a une épaisseur
         3. l'ombre portée           → elle est POSÉE, pas peinte */
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        inset 0 -1px 0 0 var(--l-fond-arete),
        var(--l-ombre) !important;
}

/* Niveau 2 — ce qui est CREUSÉ dans une carte : un champ, un encart.
   Plus sombre que le niveau 1, et son arête est inversée : la lumière frappe
   le BAS d'un creux, jamais le haut. C'est ce détail, et lui seul, qui fait
   qu'un encart se lit comme enfoncé plutôt que comme une carte plus petite. */
.bg-zinc-950,
.bg-zinc-950\/30,
.bg-zinc-950\/40,
.bg-zinc-950\/50,
.bg-zinc-950\/80 {
    background-color: var(--l-verre-2) !important;
    border-color: var(--l-arete-fine) !important;
    box-shadow:
        inset 0 1px 2px 0 rgba(0, 0, 0, 0.6),
        inset 0 -1px 0 0 var(--l-arete-fine) !important;
}

/* Les CONTRÔLES — un bouton secondaire, une pastille d'état. Ils ne sont pas
   des surfaces : ils se posent SUR une carte, ils sont donc plus clairs
   qu'elle, sinon ils s'y dissolvent. */
.bg-zinc-600,
.bg-zinc-700,
.bg-zinc-800 {
    background-color: var(--l-verre-3) !important;
    border-color: var(--l-arete-fine) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        0 2px 8px -4px rgba(0, 0, 0, 0.9) !important;
}

/* LES CARTES SANS FOND, ET C'EST LE PIÈGE DE CETTE APP.

   Les cartes de séance ne portent aucune classe `bg-` : leur fond était un
   dégradé Tailwind (`bg-gradient-to-br from-blue-900 to-zinc-950`), et
   identite.css retire TOUS les dégradés d'un `background-image: none`. Elles
   se retrouvent donc à `background-color: transparent` — mesuré, pas
   supposé — et ne tenaient plus que par leur bordure `border-white/10`.

   C'était cohérent avec « Instrument » : un cadre, pas une surface. Ça ne
   l'est plus ici — ce sont les objets les plus touchés de l'app, ils doivent
   être ce qu'il y a de plus tangible à l'écran. On leur rend un fond, celui
   du niveau 1, sans toucher au dégradé lui-même.

   L'anneau tricolore du partenaire n'est pas concerné : identite.css lui
   repose son dégradé avec une spécificité plus forte (0,2,0 contre 0,1,0),
   et il continue donc de le peindre par-dessus ce fond. MARQUE-TIERCE. */
/* `:not(.bg-clip-text)` — MESURÉ SUR LE LOGO, PAS DEVINÉ.

   Le mot-symbole « SMARTLIFT » est un dégradé DÉCOUPÉ DANS LE TEXTE
   (`bg-gradient-to-r … bg-clip-text text-transparent`) : ce n'est pas une
   surface, c'est de l'encre. La règle lui posait un cadre et une ombre —
   le logo de l'app se retrouvait dans une boîte, en haut de chaque écran. */
[class*="bg-gradient"]:not(.bg-clip-text) {
    background-color: var(--l-verre-1) !important;
    border-color: var(--l-arete-fine) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        inset 0 -1px 0 0 var(--l-fond-arete),
        var(--l-ombre) !important;
}

/* Les séparations reprennent l'arête : un trait blanc très transparent se
   fond dans le verre, là où un gris plein le troue. */
.border-zinc-600,
.border-zinc-700,
.border-zinc-800,
.border-zinc-800\/60 {
    border-color: var(--l-arete-fine) !important;
}


/* --- CE QUI NE PEUT PAS ÊTRE DU VERRE -------------------------------------
   Un VOILE plein écran n'est pas une surface : c'est ce qui coupe l'app en
   deux. Le rendre translucide laisserait lire l'écran précédent au travers —
   `.bg-black` sert exactement à ça dans trois modales, et `#auth-screen`
   masque toute l'app derrière lui. Ils restent donc pleins, et ils prennent
   simplement le noir légèrement remonté du verre pour ne pas trancher avec
   le reste.

   ⚠️  NE PAS AJOUTER `.bg-black` À LA LISTE DU NIVEAU 1 CI-DESSUS. C'est
       l'erreur qui rend l'écran de connexion transparent sur le contenu de
       la session précédente. */
.bg-black,
#auth-screen,
.fixed.inset-0.bg-zinc-950,
.fixed.inset-0.bg-zinc-900 {
    background-color: #08080A !important;
    /* Un voile n'a ni tranche ni coin : il n'est posé sur rien, il REMPLACE.
       Sans ces deux lignes, l'arête du niveau 2 traçait un liseré clair en
       bas de l'écran de connexion — une plaque de verre grande comme le
       téléphone, ce qui ne veut rien dire. */
    border-radius: 0 !important;
    box-shadow: none !important;
}

/* L'ÉCRAN DE CONNEXION GARDE SA LUMIÈRE, ET C'EST LE SEUL QUI LA PORTE
   LUI-MÊME.

   C'est le premier écran que voit quelqu'un qui arrive d'Instagram, et le
   seul qu'il jugera avant de décider. Il doit donc rester opaque — sinon,
   après une déconnexion, on lirait la session précédente au travers — mais
   il n'a aucune raison d'être le seul écran mort de l'app. La dérive de la
   section 1 est donc repeinte ICI, en dégradé plein, un cran plus haut :
   derrière un voile opaque, il n'y a plus de fond à laisser passer. */
#auth-screen {
    background-image:
        radial-gradient(48% 32% at 14% 4%,  rgba(37, 99, 235, 0.26), transparent 70%),
        radial-gradient(52% 34% at 90% 72%, rgba(229, 50, 45, 0.16), transparent 72%),
        radial-gradient(60% 40% at 50% 42%, rgba(255, 255, 255, 0.03), transparent 70%);
    background-attachment: fixed;
}

/* Le chrono flottant, lui, EST du verre : il passe devant la séance en
   cours, et voir la série continuer dessous fait partie de son intérêt. */
#floating-timer {
    /* 0,97 et pas 0,94 : mesuré à l'écran, « Notes, WOD, AMRAP » se devinait
       derrière le compte à rebours. Un chrono se lit d'un coup d'œil, entre
       deux séries, souvent à bout de bras — rien n'a le droit de passer
       derrière ses chiffres. */
    background-color: rgba(8, 8, 10, 0.97) !important;
    border-color: var(--l-arete-fine) !important;
    border-radius: var(--l-r-carte) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        var(--l-ombre-haute) !important;
}


/* --------------------------------------------------------------------------
   3 BIS. LA CARTE DESSINÉE AU TRAIT
   --------------------------------------------------------------------------
   TOUTE LA SECTION 3 REPOSE SUR UNE CLASSE `bg-`. TRENTE-QUATRE GABARITS DE
   L'APP N'EN ONT AUCUNE.

   « Prochaine charge », le bandeau de séance en cours, les notes de l'agenda,
   les cartes de quêtes, les réponses du questionnaire d'arrivée, les boutons
   secondaires : ils ne sont pas dessinés comme des surfaces, mais comme des
   CADRES — `border border-zinc-800 p-5`, et rien d'autre. C'était juste sous
   l'identité « Instrument », où le relief venait du trait. Sur du verre, ce
   sont les seuls rectangles vides de l'app, et ils se voient.

   Le relevé automatique des fonds ne les avait pas trouvés : il cherchait des
   couleurs de fond, et ceux-là n'en ont pas. Il a fallu relire les gabarits
   de la source, écran par écran — la plupart de ces cartes ne se dessinent
   qu'avec des données, donc jamais sur une app vide.

   ⚠️  `[class~="border"]` ET PAS `[class*="border"]`. Le premier exige le
       mot ENTIER dans la liste de classes : il attrape `border border-zinc-800`
       et laisse tranquilles `border-t`, `border-b`, `border-zinc-700` seuls —
       c'est-à-dire un trait de séparation, qui n'est pas une carte. Avec
       `*=`, chaque ligne soulignée de l'app serait devenue une plaque de
       verre.

   ⚠️  AUCUN `border-color` ICI, ET C'EST DÉLIBÉRÉ. La réponse choisie du
       questionnaire d'arrivée se signale par `border border-white` : une
       couleur d'arête imposée l'aurait effacée, et on ne saurait plus ce
       qu'on a coché. Les bordures neutres sont déjà reprises par la
       section 3 ; les autres gardent ce qu'elles ont à dire.
   -------------------------------------------------------------------------- */

[class~="border"]:not([class*="bg-"]):not(input):not(select):not(textarea) {
    background-color: var(--l-verre-1) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        inset 0 -1px 0 0 var(--l-fond-arete),
        var(--l-ombre) !important;
}

/* Le rayon, quand le gabarit n'en demande aucun. Deux valeurs, choisies sur
   le REMPLISSAGE : `p-4`, `p-5`, `p-6` est une carte ; tout le reste est un
   contrôle. C'est la seule information de taille qu'une feuille de style
   puisse lire dans une classe Tailwind, et elle suffit. */
[class~="border"]:not([class*="bg-"]):not([class*="rounded"]):not(input):not(select):not(textarea) {
    border-radius: var(--l-r-controle);
}

[class~="border"][class~="p-4"]:not([class*="bg-"]):not([class*="rounded"]),
[class~="border"][class~="p-5"]:not([class*="bg-"]):not([class*="rounded"]),
[class~="border"][class~="p-6"]:not([class*="bg-"]):not([class*="rounded"]) {
    border-radius: var(--l-r-carte);
}

/* `bg-transparent` DIT L'INVERSE DE CE QUE LE GARDE-FOU CROYAIT.

   `:not([class*="bg-"])` visait « cet élément a déjà son fond, ne le touche
   pas ». Mais `bg-transparent` déclare exactement le contraire : « je n'en ai
   aucun ». Trois gabarits l'emploient, et le plus visible est le pire —
   les quatre réponses de CHAQUE question du questionnaire d'arrivée. Le
   premier écran que voit un nouvel inscrit restait donc quatre rectangles
   vides à angles droits, au milieu d'une app de verre.

   On les rattrape nommément. (0,2,0) contre (0,1,0) : ces règles-ci passent
   devant le garde-fou sans avoir à le récrire. */
[class~="border"][class~="bg-transparent"] {
    background-color: var(--l-verre-1) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        inset 0 -1px 0 0 var(--l-fond-arete),
        var(--l-ombre) !important;
}

[class~="border"][class~="bg-transparent"]:not([class*="rounded"]) {
    border-radius: var(--l-r-controle);
}

[class~="border"][class~="bg-transparent"][class~="p-4"]:not([class*="rounded"]),
[class~="border"][class~="bg-transparent"][class~="p-5"]:not([class*="rounded"]),
[class~="border"][class~="bg-transparent"][class~="p-6"]:not([class*="rounded"]) {
    border-radius: var(--l-r-carte);
}


/* Deux cartes collées l'une à l'autre — le questionnaire d'arrivée empile un
   en-tête et son explication en `border-t-0`. Arrondir les quatre coins
   ouvrait une encoche entre les deux : la jointure doit rester droite. */
[class~="border-t-0"] {
    border-top-left-radius: 0 !important;
    border-top-right-radius: 0 !important;
}

[class~="border-b-0"] {
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}


/* --- LES PLEINS SANS RAYON ------------------------------------------------
   Même angle mort, l'autre moitié : une poignée de boutons et de pastilles
   pleines ne portent aucune classe de rayon — « Reprendre » du bandeau de
   séance, « Répondre aux questions », « Terminer » de l'écran de fin, la
   pastille « +2,5 » de la prochaine charge. Ils restaient carrés au milieu
   de boutons arrondis.

   Les voiles plein écran (`bg-black/90` et compagnie) ne sont PAS concernés :
   ils n'ont ni l'une ni l'autre de ces classes, et un voile arrondi laisserait
   voir quatre coins de l'écran précédent. */
.bg-white:not([class*="rounded"]),
.bg-zinc-100:not([class*="rounded"]),
.bg-emerald-400:not([class*="rounded"]),
.bg-emerald-500:not([class*="rounded"]),
.bg-emerald-600:not([class*="rounded"]) {
    border-radius: var(--l-r-controle) !important;
}


/* --------------------------------------------------------------------------
   4. LE FLOU, LÀ OÙ IL PAIE
   --------------------------------------------------------------------------
   Quatre familles d'éléments passent DEVANT du contenu qui défile. Pour
   elles, et pour elles seules, le flou est ce qui fait la profondeur : on
   voit le contenu continuer dessous, déformé. C'est la sensation qu'aucune
   ombre ne remplace — et c'est ce qui distingue une app d'une page web.

   `-webkit-backdrop-filter` n'est pas décoratif : sans lui, iOS — donc la
   moitié du parc installé de cette PWA — n'affiche aucun flou du tout.
   -------------------------------------------------------------------------- */

/* LE REPLI D'ABORD, LE VERRE ENSUITE — ET L'ORDRE EST LE SUJET.

   Un fond translucide SANS flou n'est pas un effet dégradé : c'est un défaut.
   La barre laisse alors lire, net, le texte qui passe dessous. Les valeurs
   posées hors du `@supports` sont donc presque opaques ; elles ne s'ouvrent
   que là où le flou existe pour rattraper la lisibilité.

   Concerné : les vieux WebView Android, et tout navigateur où l'utilisateur
   a coupé l'accélération matérielle. */
.bg-zinc-950\/95,
.bg-zinc-900\/95,
.bg-zinc-950\/90 {
    background-color: rgba(8, 8, 10, 0.96) !important;
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .bg-zinc-950\/95,
    .bg-zinc-900\/95,
    .bg-zinc-950\/90 {
        background-color: rgba(8, 8, 10, 0.62) !important;
        -webkit-backdrop-filter: blur(var(--l-flou)) saturate(180%);
        backdrop-filter: blur(var(--l-flou)) saturate(180%);
    }
}


/* --------------------------------------------------------------------------
   4 BIS. LES DEUX CENTS TRANSITIONS QUE PERSONNE N'A CHOISIES
   --------------------------------------------------------------------------
   `transition-colors` et `transition-all` sont posées 205 fois dans l'app.
   Elles arrivent avec la valeur PAR DÉFAUT de Tailwind — 150 ms sur
   cubic-bezier(.4, 0, .2, 1) — c'est-à-dire une courbe symétrique qui
   accélère autant qu'elle ralentit. Ce n'est pas un choix, c'est ce qui
   restait dans la boîte.

   Sur du verre, ça s'entend : la carte s'enfonce sur le ressort de l'app,
   sa bordure change de couleur sur une autre courbe, et les deux ne finissent
   pas ensemble. On les ramène toutes sur le même ressort.

   DEUX DURÉES, PAS UNE. Une couleur peut basculer vite — l'œil ne suit pas
   un dégradé de teinte. Un objet qui BOUGE a besoin de sa course, sinon le
   ressort n'a pas la place de s'exprimer et redevient un fondu.
   -------------------------------------------------------------------------- */

.transition-colors,
.transition-opacity {
    transition-timing-function: var(--l-ressort);
    transition-duration: var(--l-court);
}

.transition-all,
.transition-transform {
    transition-timing-function: var(--l-ressort);
    transition-duration: 240ms;
}


/* --------------------------------------------------------------------------
   5. LE REFLET QUI SUIT LE DOIGT
   --------------------------------------------------------------------------
   C'EST LE DÉTAIL QUI FAIT REVENIR.

   Une surface plate ne répond pas : on appuie, l'écran change, mais rien
   n'a eu lieu ENTRE les deux. Ici, l'endroit exact où le doigt se pose
   s'éclaire, et la lumière s'éteint quand il se lève. Le geste a un effet
   avant même que l'action n'aboutisse.

   js/liquide.js n'écrit que deux nombres — `--l-x` et `--l-y`, la position
   du doigt dans l'élément. Tout le reste est ici. Si le script ne tourne
   pas, le reflet se contente d'être centré : rien ne casse.

   `background-image` et pas `background`, et c'est ce qui rend la règle
   sûre : la COULEUR de fond posée par la section 3 n'est jamais touchée.
   Les deux propriétés se superposent sans se connaître.
   -------------------------------------------------------------------------- */

button,
[role="button"],
[onclick],
.pro-card,
.exo-block {
    --l-eclat: 0;
    /* `!important` EST OBLIGATOIRE, ET PAS PAR CONFORT.

       identite.css porte `[class*="bg-gradient"] { background-image: none
       !important }` pour effacer les dégradés de l'ancienne peau. Face à un
       `!important`, une déclaration normale perd quelle que soit sa
       spécificité : sans lui, le reflet n'existait sur AUCUNE carte de
       séance — c'est-à-dire exactement là où il compte le plus. */
    background-image:
        radial-gradient(
            circle 120px at var(--l-x, 50%) var(--l-y, 50%),
            rgb(255 255 255 / calc(var(--l-eclat) * 22%)),
            transparent 100%
        ) !important;
    /* Le raccourci `transition` remplace celui de `transition-colors` pour
       ces éléments-là — ils portent les deux — donc il doit reprendre la
       même échelle, sinon un bouton et le texte à côté ne finissent pas
       ensemble. La lumière monte un peu plus vite que l'objet ne se déplace :
       c'est elle qui doit répondre en premier. */
    transition:
        --l-eclat 240ms ease-out,
        transform 240ms var(--l-ressort),
        border-color var(--l-court) ease-out,
        box-shadow 240ms var(--l-ressort);
}

/* L'appui. Deux choses en même temps, et il en faut deux : la lumière dit
   « je t'ai vu », l'enfoncement dit « j'ai pris ». L'une sans l'autre laisse
   un doute d'un dixième de seconde, et c'est ce doute qu'on retire. */
button:active,
[role="button"]:active,
[onclick]:active,
.pro-card:active,
.exo-block:active {
    --l-eclat: 1;
    transform: scale(0.975);
    transition-duration: var(--l-vif);
}

/* Une carte est plus grande qu'un bouton : à 0,975 elle bougerait trop.
   L'échelle se lit sur la DISTANCE parcourue par le bord, pas sur le
   facteur — d'où deux valeurs. */
.pro-card:active,
.exo-block:active,
[onclick]:active {
    transform: scale(0.99);
}

/* Rien de tout cela ne doit rester accroché après le clic sur un appareil
   sans écran tactile : le survol allume le reflet à mi-force, la sortie
   l'éteint. Sur téléphone, aucune de ces deux règles ne se déclenche. */
@media (hover: hover) {
    button:hover,
    [role="button"]:hover,
    .pro-card:hover,
    .exo-block:hover { --l-eclat: 0.45; }
}


/* --- LE RETOUR TACTILE EST REMPLACÉ, PAS RETIRÉ --------------------------

   mobile.css fait tomber à 62 % d'opacité tout ce qu'on touche, sur les
   appareils sans souris. C'était la bonne réponse quand rien d'autre ne
   confirmait l'appui — et c'est la mauvaise maintenant : mesuré à l'écran,
   une carte qui S'ALLUME sous le doigt et qui PÂLIT en même temps ne dit
   plus rien de net, et une surface qui se ternit se lit comme désactivée
   plutôt que comme pressée.

   Les deux signaux de la section 5 la remplacent, et ils sont plus précis :
   la lumière naît à l'endroit exact du contact, l'enfoncement dit que
   l'appui est pris. On rend donc son opacité pleine à l'élément touché.

   ⚠️  L'EXCEPTION DE mobile.css EST REPRISE MOT POUR MOT. Un bouton
       désactivé ne doit ni s'allumer ni s'enfoncer : c'est son absence de
       réaction qui dit qu'il est désactivé. Sans ces trois lignes,
       « Valider la séance » pendant l'enregistrement répondait au doigt
       comme s'il était encore actif. */
@media (hover: none) {
    button:active,
    a[onclick]:active,
    [role="button"]:active,
    summary:active {
        opacity: 1;
    }
}

button:disabled:active,
button[aria-disabled="true"]:active,
button:disabled,
button[aria-disabled="true"] {
    --l-eclat: 0 !important;
    transform: none !important;
}


/* --------------------------------------------------------------------------
   6. LES BOUTONS PLEINS
   --------------------------------------------------------------------------
   L'action principale de l'app est un aplat blanc sur du noir. Il est
   parfait pour ça — on ne le remplace pas, on lui donne du volume : un
   dégradé très court du haut vers le bas, et une lueur sous la plaque.
   Un bouton blanc pur reste un rectangle ; celui-ci a une face.

   `background-image` à nouveau : l'aplat blanc de l'app reste la couleur de
   fond, on ne fait que poser une lumière dessus.
   -------------------------------------------------------------------------- */

/* IL Y A TROIS FAÇONS D'OBTENIR UN BOUTON BLANC DANS CETTE APP, ET ELLES
   DOIVENT RENDRE LE MÊME OBJET.

   `.bg-white` et `.bg-zinc-100` sont les deux évidentes. La troisième ne se
   voit pas dans le balisage : identite.css repeint en blanc TOUTES les
   surfaces emerald (« une action principale : fond blanc, texte noir »).
   « Gérer mon abonnement », « Enregistrer », les actions du portail coach
   sont donc des boutons blancs écrits `bg-emerald-600`.

   Sans cette liste, ils restaient plats à côté d'un `.bg-white` verni — deux
   boutons blancs identiques, deux matières différentes, sur le même écran.
   Relevé à la mesure sur les onglets Profil, Coach, Data et BodyScan. */
.bg-white,
.bg-zinc-100,
.bg-emerald-400,
.bg-emerald-500,
.bg-emerald-600,
.bg-emerald-500\/10,
.bg-emerald-500\/20,
.bg-emerald-600\/20 {
    /* Deux couches, et l'ordre compte : le reflet du doigt passe DEVANT le
       vernis, sinon l'appui ne se verrait pas sur un bouton déjà clair. */
    background-image:
        radial-gradient(
            circle 100px at var(--l-x, 50%) var(--l-y, 50%),
            rgb(255 255 255 / calc(var(--l-eclat) * 55%)),
            transparent 100%
        ),
        linear-gradient(
            to bottom,
            rgba(255, 255, 255, 0.55),
            rgba(255, 255, 255, 0) 55%
        ) !important;
    box-shadow:
        inset 0 1px 0 0 rgba(255, 255, 255, 0.85),
        0 8px 24px -10px rgba(255, 255, 255, 0.30),
        0 2px 8px -3px rgba(0, 0, 0, 0.8) !important;
}

/* Sous le doigt, la lueur se resserre : la plaque s'approche de son ombre.
   C'est ce que fait un vrai bouton qu'on enfonce. */
.bg-white:active,
.bg-zinc-100:active,
.bg-emerald-400:active,
.bg-emerald-500:active,
.bg-emerald-600:active,
.bg-emerald-500\/10:active,
.bg-emerald-500\/20:active,
.bg-emerald-600\/20:active {
    box-shadow:
        inset 0 1px 0 0 rgba(255, 255, 255, 0.85),
        0 3px 10px -6px rgba(255, 255, 255, 0.22),
        0 1px 3px -1px rgba(0, 0, 0, 0.9) !important;
}

/* Les surfaces de signal gardent leur couleur et prennent la même arête. Le
   rouge dit toujours « attention », le vert toujours « valide » : la matière
   change, jamais le sens.

   PAS DE HALO COLORÉ, ET C'EST UN CHOIX. Un `box-shadow` en `currentColor`
   aurait donné une lueur à la teinte de chaque bouton — mais `currentColor`
   est la couleur de l'ENCRE, pas du fond : un bouton rouge à texte blanc
   rayonnait blanc, et une pastille verte pâle rayonnait vert. Deux
   comportements pour une même règle. Et sur les vingt et quelques pastilles
   teintées de l'app, ces auréoles deviennent du bruit — la règle
   d'identité tient toujours : le relief vient du trait, pas de la couleur. */
.bg-red-500,
.bg-red-600,
.bg-orange-500,
.bg-amber-400,
.bg-amber-500,
.bg-amber-600 {
    box-shadow:
        inset 0 1px 0 0 rgba(255, 255, 255, 0.25),
        0 6px 18px -10px rgba(0, 0, 0, 0.9) !important;
}


/* --- LE VERRE TEINTÉ ------------------------------------------------------
   Une poignée de zones portent une couleur d'INFORMATION plutôt qu'une
   couleur d'action : l'encart d'échauffement en orange, celui d'exercice
   exotique en vert, quelques bandeaux d'alerte en rouge très pâle. Sur du
   verre, ces aplats sont les seuls endroits de l'app qui restent opaques —
   on les repère au premier coup d'œil, et pas pour la bonne raison.

   ON GARDE LE SIGNAL, ON CHANGE LA MATIÈRE : le FOND devient du verre, la
   BORDURE garde sa teinte. L'encart d'échauffement reste reconnaissable à
   son pointillé orange, l'exotique à son liseré. C'est exactement ce que
   identite.css fait déjà pour l'ambre et le rouge (« background-color:
   transparent », bordure conservée) — on l'étend aux teintes qu'elle avait
   laissées de côté, relevées une par une à l'écran.

   ⚠️  LISTE MESURÉE, PAS UN JOKER. Un sélecteur du genre
       `[class*="bg-orange-"]` aurait aussi attrapé les boutons pleins juste
       au-dessus, et les aurait vidés de leur couleur. */
.bg-orange-500\/5,
.bg-orange-500\/10,
.bg-orange-600\/10,
.bg-orange-600\/20,
.bg-orange-600\/30,
.bg-emerald-900\/10,
.bg-emerald-900\/20,
.bg-emerald-900\/30,
.bg-emerald-900\/40,
.bg-emerald-600\/40,
.bg-red-500\/5,
.bg-red-500\/10,
.bg-red-500\/20,
.bg-red-500\/40,
.bg-red-600\/10,
.bg-red-600\/20,
.bg-amber-500\/10,
.bg-yellow-500\/10 {
    background-color: var(--l-verre-1) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete-fine),
        var(--l-ombre) !important;
}


/* --------------------------------------------------------------------------
   7. LES CHAMPS
   --------------------------------------------------------------------------
   Un champ est un CREUX : il reçoit, il ne se propose pas. Même verre que le
   niveau 2, même arête inversée, et le focus l'allume au lieu de l'encadrer —
   un cadre blanc franc sur du verre fait tache, une lueur non.

   ⚠️  Aucune mesure n'est touchée : les 16 px qui empêchent iOS de zoomer et
       les 44 px de hauteur restent ceux de systeme.css et mobile.css.
   -------------------------------------------------------------------------- */

input:not([type="range"]):not([type="checkbox"]):not([type="radio"]),
select,
textarea {
    background-color: var(--l-verre-2) !important;
    border: 1px solid var(--l-arete-fine) !important;
    border-radius: var(--l-r-controle) !important;
    box-shadow: inset 0 1px 3px 0 rgba(0, 0, 0, 0.55) !important;
    transition: border-color 200ms ease-out, box-shadow 260ms var(--l-ressort);
}

input:focus,
select:focus,
textarea:focus {
    border-color: rgba(255, 255, 255, 0.45) !important;
    box-shadow:
        inset 0 1px 3px 0 rgba(0, 0, 0, 0.55),
        0 0 0 4px rgba(255, 255, 255, 0.07) !important;
}


/* --------------------------------------------------------------------------
   8. LA BARRE D'ONGLETS
   --------------------------------------------------------------------------
   LA PASTILLE GLISSE, ET C'EST LA PREMIÈRE CHOSE QUE L'ON REMARQUE.

   identite.css marque l'onglet ouvert par un trait blanc et un fond : c'est
   lisible, mais ça CLIGNOTE — l'ancien repère s'éteint pendant que le
   nouveau s'allume, et rien ne relie les deux. Ici une seule pastille de
   verre existe, et elle se déplace. L'œil la suit, donc l'œil comprend d'où
   il vient.

   ⚠️  ELLE N'EXISTE QUE SI js/liquide.js TOURNE. Toutes les règles qui la
       concernent sont donc enfermées sous `html[data-liquide]`, que le
       script pose lui-même au démarrage. Sans lui, l'app retombe sur le
       trait blanc d'identite.css — un repère de navigation ne doit jamais
       dépendre d'un script qui pourrait ne pas se charger.

   ⚠️  AUCUNE MESURE N'EST TOUCHÉE. Ni la hauteur de la barre (mesurerNav()),
       ni ses gouttières (ajusterNav() compare scrollWidth et clientWidth de
       la rangée), ni son escamotage au clavier — qui passe par un
       `transform` sur <nav> auquel ce fichier ne touche pas.
   -------------------------------------------------------------------------- */

nav {
    background-color: rgba(6, 6, 8, 0.96) !important;
    border-color: var(--l-arete-fine) !important;
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    nav {
        /* 0,62 et pas 0,55 : un fond plus ouvert laisse deviner les chiffres
           qui défilent sous la barre, et l'œil va les lire au lieu de lire
           les onglets. */
        background-color: rgba(6, 6, 8, 0.62) !important;
        -webkit-backdrop-filter: blur(28px) saturate(190%);
        backdrop-filter: blur(28px) saturate(190%);
    }
}

/* Le contenu glisse SOUS la barre, et la tranche du haut le prouve. */
@media (max-width: 767px) {
    nav {
        border-top-left-radius: var(--l-r-carte);
        border-top-right-radius: var(--l-r-carte);
        box-shadow:
            inset 0 1px 0 0 var(--l-arete),
            0 -18px 40px -24px rgba(0, 0, 0, 1);
    }
}

/* La rangée devient le repère de position de la pastille. `relative` sur un
   conteneur flex ne déplace rien et ne change aucune mesure. */
html[data-liquide] nav > div { position: relative; }

html[data-liquide] .l-pastille {
    position: absolute;
    left: 0;
    top: 0;
    width: 0;
    height: 0;
    z-index: 0;
    pointer-events: none;
    opacity: 0;
    border-radius: var(--l-r-controle);
    background-color: var(--l-verre-3);
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        0 4px 16px -8px rgba(255, 255, 255, 0.35);
    /* 420 ms : en dessous le glissement se lit comme un saut, au-dessus la
       barre traîne derrière le doigt. */
    /* 320 ms et non 420 : mesuré au doigt, la pastille arrivait APRÈS
       l'écran qu'elle annonce, ce qui la fait lire comme un retard plutôt
       que comme un déplacement. En dessous de 250 ms, le glissement
       redevient un saut. */
    transition:
        transform var(--l-moyen) var(--l-ressort),
        width     var(--l-moyen) var(--l-ressort),
        height    var(--l-moyen) var(--l-ressort),
        opacity   var(--l-court) linear;
}

/* Les onglets repassent devant la pastille. */
html[data-liquide] nav > div > button { position: relative; z-index: 1; }

/* ET L'ANCIEN REPÈRE S'EFFACE — SEULEMENT QUAND LA PASTILLE LE REMPLACE.

   `nav[data-pastille]` et pas `html[data-liquide]` : le drapeau du <html> dit
   que le script tourne, l'attribut du <nav> dit que la pastille est
   RÉELLEMENT VISIBLE. Les deux ne sont pas la même chose — la barre est
   masquée le temps de l'écran de fin de séance, et en paysage sur téléphone
   la colonne latérale rogne son premier onglet. Dans ces cas-là js/liquide.js
   retire l'attribut, et le trait blanc d'identite.css revient tout seul.

   L'onglet ouvert n'est donc jamais marqué par rien. C'est la seule chose de
   ce fichier dont dépend la navigation. */
nav[data-pastille] > div > button.opacity-100,
nav[data-pastille] > div > button.text-white.opacity-100,
nav[data-pastille] > div > button[aria-current="page"] {
    background-color: transparent !important;
    border-top-color: transparent !important;
}

nav[data-pastille] > div > button:active {
    background-color: transparent !important;
}

/* L'onglet appuyé s'enfonce un peu moins qu'un bouton : la barre entière
   semblerait bouger sinon. */
nav > div > button:active { transform: scale(0.94); }


/* --------------------------------------------------------------------------
   9. L'EN-TÊTE
   --------------------------------------------------------------------------
   Même verre que la barre. Le filet tricolore reste : c'est la signature de
   l'app, et sur du verre il devient la seule ligne franche de l'écran — donc
   il signe encore mieux qu'avant.

   ⚠️  Pas de `transform` ici : mobile.css s'en sert pour escamoter l'en-tête
       au défilement (`translateY(-101%)`). Une transformation posée à côté
       écraserait la sienne, et l'en-tête ne se rangerait plus.
   -------------------------------------------------------------------------- */

header {
    background-color: rgba(6, 6, 8, 0.96) !important;
    border-color: var(--l-arete-fine) !important;
}

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    header {
        background-color: rgba(6, 6, 8, 0.62) !important;
        -webkit-backdrop-filter: blur(28px) saturate(190%);
        backdrop-filter: blur(28px) saturate(190%);
    }
}


/* --------------------------------------------------------------------------
   10. LES MODALES
   --------------------------------------------------------------------------
   Une modale de verre demande deux choses : que le FOND s'éloigne, et que la
   feuille arrive de quelque part.

   Le fond s'éloigne par un flou profond posé sur les voiles que l'app dessine
   déjà (`bg-black/80`, `bg-black/60`…). C'est le geste natif : ce qui est
   derrière n'a pas disparu, il est simplement hors de portée.
   -------------------------------------------------------------------------- */

@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    .bg-black\/95,
    .bg-black\/90,
    .bg-black\/85,
    .bg-black\/80,
    .bg-black\/70,
    .bg-black\/60,
    .bg-black\/50 {
        -webkit-backdrop-filter: blur(18px) saturate(140%);
        backdrop-filter: blur(18px) saturate(140%);
    }
}

/* La feuille de verre elle-même : elle monte de 12 px en se posant. */
.fixed.inset-0 > .bg-zinc-900,
.fixed.inset-0 > .bg-zinc-950,
.fixed.inset-0 > div > .bg-zinc-900 {
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        var(--l-ombre-haute) !important;
    animation: l-pose var(--l-moyen) var(--l-ressort-appui) both;
}

@keyframes l-pose {
    from { opacity: 0; transform: translate3d(0, 12px, 0) scale(0.97); }
    to   { opacity: 1; transform: none; }
}


/* --------------------------------------------------------------------------
   11. LE MESSAGE QUI PASSE
   --------------------------------------------------------------------------
   Le toast est le seul élément de l'app qui apparaît sans qu'on l'ait
   demandé. Il doit donc se lire d'un coup d'œil — d'où l'opacité que
   js/toast.js a choisie, et qu'on ne remet pas en cause. On lui donne
   seulement la tranche et l'ombre des autres verres.
   -------------------------------------------------------------------------- */

/* IL EST OPAQUE, ET CE N'EST PAS NÉGOCIABLE.

   js/toast.js le dit en toutes lettres dans son propre code : « Opaque : à
   95 % l'en-tête se lisait à travers le message, et le backdrop-blur ne
   rattrapait rien sur un fond noir. » La section 3 l'avait pourtant repeint
   en verre de niveau 2 — 2,8 % de blanc — parce qu'il porte la classe
   `bg-zinc-950` comme un champ de saisie. Résultat mesuré à l'écran :
   « SMARTLIFT », « DÉCONNEXION » et le titre de la séance se lisaient à
   travers le message, et le message ne se lisait plus du tout.

   Un toast n'est pas une surface posée dans un écran : c'est une phrase qui
   passe devant tout, qu'on lit une fois et vite. Il garde donc la tranche et
   l'ombre du verre — sa MATIÈRE — mais pas sa transparence. Et pas de flou
   non plus : derrière un fond plein, il ne ferait que coûter une passe de
   rendu pour rien. */
#toast-container > div {
    background-color: #0E0E12 !important;
    border-radius: var(--l-r-carte) !important;
    box-shadow:
        inset 0 1px 0 0 var(--l-arete),
        var(--l-ombre-haute) !important;
}


/* --------------------------------------------------------------------------
   11 BIS. CE QUI ARRIVE TOUT SEUL DOIT ARRIVER, PAS SURGIR
   --------------------------------------------------------------------------
   Deux choses apparaissent maintenant sans qu'on les ait ouvertes, et
   aucune n'avait de mouvement :

     • LE CHRONO. Depuis que chaque exercice lance le sien, le panneau
       s'ouvre au moment où l'on repose le téléphone. Il apparaissait d'un
       bloc, en bas de l'écran — assez pour croire à un défaut d'affichage.
     • LES VOILES DE MODALE. Le noir se posait d'un coup sur l'écran ; le
       flou d'arrière-plan, lui, n'a rien à quoi s'accrocher pour se faire
       remarquer. Un fondu de 180 ms suffit à dire « quelque chose passe
       devant ».

   ⚠️  `translateX(-50%)` DANS LES DEUX IMAGES DU CHRONO, ET IL EST
       OBLIGATOIRE. Le panneau est centré par `left-1/2 -translate-x-1/2`
       (Tailwind) : une animation qui écrit `transform` remplace CETTE
       transformation-là aussi. Sans le -50 % dans chaque image, le chrono
       part se coller au bord droit de l'écran pendant toute son arrivée.
   -------------------------------------------------------------------------- */

#floating-timer:not(.hidden) {
    animation: l-chrono-arrive var(--l-moyen) var(--l-ressort) both;
}

@keyframes l-chrono-arrive {
    from { opacity: 0; transform: translate3d(-50%, 14px, 0) scale(0.98); }
    to   { opacity: 1; transform: translate3d(-50%, 0, 0) scale(1); }
}

.fixed.inset-0.bg-black\/95,
.fixed.inset-0.bg-black\/90,
.fixed.inset-0.bg-black\/85,
.fixed.inset-0.bg-black\/80,
.fixed.inset-0.bg-black\/70,
.fixed.inset-0.bg-black\/60 {
    animation: l-fondu var(--l-court) ease-out both;
}


/* --------------------------------------------------------------------------
   12. LES ÉCRANS SE COMPOSENT
   --------------------------------------------------------------------------
   styles.css faisait apparaître un onglet d'un bloc, en 0,4 s. On garde la
   durée, on change la manière : les blocs de l'écran montent l'un après
   l'autre, à 45 ms d'intervalle. Six blocs, donc 270 ms au maximum — au-delà
   on attendrait l'app, ce qui est exactement le contraire du but.

   ⚠️  `:not(.fixed)` n'est pas une précaution de style. Un élément en
       `position: fixed` DANS un parent transformé se positionne par rapport
       à ce parent et non plus par rapport à l'écran : un bandeau collé en
       bas se serait retrouvé au milieu du contenu pendant l'animation. Le
       parent, lui, n'anime que son opacité — jamais sa transformation — pour
       la même raison.
   -------------------------------------------------------------------------- */

.tab-content.active {
    animation: l-fondu var(--l-moyen) ease-out both;
}

.tab-content.active > *:not(.fixed):not(.hidden) {
    animation: l-monte var(--l-long) var(--l-ressort) backwards;
}

/* LES DEUX `:not()` SONT REPRIS SUR CHAQUE RETARD, ET CE N'EST PAS DE LA
   RECOPIE POUR RIEN.

   `animation` est une PROPRIÉTÉ RACCOURCIE : elle repose `animation-delay: 0s`
   au passage. Écrits sans les deux `:not()`, les retards pesaient (0,3,0)
   contre (0,4,0) pour la règle ci-dessus — ils perdaient, et les cinq
   premiers blocs de chaque écran arrivaient tous ensemble. Mesuré : seul le
   retard des blocs MASQUÉS survivait, parce qu'eux seuls échappaient au
   raccourci. L'escalier existait dans la feuille et nulle part à l'écran.

   Le premier bloc n'a pas de ligne : son retard est zéro, c'est la valeur
   que le raccourci pose déjà. */
.tab-content.active > *:not(.fixed):not(.hidden):nth-child(2)   { animation-delay:  38ms; }
.tab-content.active > *:not(.fixed):not(.hidden):nth-child(3)   { animation-delay:  76ms; }
.tab-content.active > *:not(.fixed):not(.hidden):nth-child(4)   { animation-delay: 114ms; }
.tab-content.active > *:not(.fixed):not(.hidden):nth-child(5)   { animation-delay: 152ms; }
.tab-content.active > *:not(.fixed):not(.hidden):nth-child(n+6) { animation-delay: 190ms; }

@keyframes l-fondu { from { opacity: 0; } to { opacity: 1; } }
@keyframes l-monte {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: none; }
}


/* --------------------------------------------------------------------------
   13. LA CÉLÉBRATION
   --------------------------------------------------------------------------
   C'est le moment que l'app doit réussir : trois secondes, une fois par
   séance, et c'est la capture d'écran qu'on envoie à quelqu'un. Le chiffre
   y est déjà écrit en 7xl par js/workouts.js ; on ne touche ni à sa valeur,
   ni à sa taille, ni à son enchaînement.

   Deux ajouts, et deux seulement :
     • une aube derrière l'écran — la lueur du fond, montée en intensité
     • un éclat qui traverse le chiffre UNE fois, puis ne revient plus
   -------------------------------------------------------------------------- */

#volume-celebration-modal {
    /* PLEIN, PAS À 92 %. Essayé, et rejeté à l'écran : à 8 % de
       transparence, on lisait « PROGRAMME DE BASE » derrière le chiffre de
       la séance. C'est le seul écran de l'app qu'on prend en photo pour
       l'envoyer à quelqu'un — rien n'a le droit de traverser. La couleur
       vient de l'aube ci-dessous, peinte PAR-DESSUS ce noir. */
    background-color: #000000 !important;
    /* `isolation: isolate` OUI, `position: relative` SURTOUT PAS.

       L'aube ci-dessous est un `::before` en `z-index: -1` : il lui faut un
       contexte d'empilement sur le parent, et c'est `isolation` qui le crée,
       sans toucher au positionnement. Un `position: relative` posé ici
       écrasait le `fixed` de la classe Tailwind `.fixed` : l'écran de fin de
       séance repassait dans le flux, à 2 400 px sous le pli — le moment le
       plus important de l'app ne s'affichait plus du tout.

       Le `::before` n'a besoin de rien de plus : `.fixed` est déjà un
       ancêtre positionné. */
    isolation: isolate;
}

#volume-celebration-modal::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(60% 40% at 50% 0%,  rgba(37, 99, 235, 0.28), transparent 70%),
        radial-gradient(70% 45% at 50% 100%, rgba(229, 50, 45, 0.18), transparent 72%);
    animation: l-fondu 900ms ease-out both;
}

/* L'éclat. `background-clip: text` : la lumière passe DANS le chiffre, elle
   ne glisse pas devant lui. C'est ce qui fait qu'on le lit comme du métal
   poli plutôt que comme du texte avec un effet. */
#volume-celebration-modal [data-chiffre] {
    background-image: linear-gradient(
        100deg,
        #FFFFFF 0%, #FFFFFF 40%,
        rgba(255, 255, 255, 0.55) 50%,
        #FFFFFF 60%, #FFFFFF 100%
    );
    background-size: 300% 100%;
    background-position: 100% 0;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: l-eclat-chiffre 1400ms var(--l-ressort) 600ms both;
}

@keyframes l-eclat-chiffre {
    from { background-position: 100% 0; }
    to   { background-position: 0% 0; }
}


/* --------------------------------------------------------------------------
   14. QUAND LE SYSTÈME DEMANDE MOINS
   --------------------------------------------------------------------------
   systeme.css et identite.css coupent déjà toutes les animations et les
   transitions par un sélecteur universel. Deux choses leur échappent, et il
   faut les nommer :

     • la dérive du fond, qui tourne en boucle infinie ;
     • le flou d'arrière-plan, qui n'est pas une animation mais qui reste la
       source d'inconfort la plus signalée par les personnes sensibles au
       mouvement — un contenu qui bouge derrière une surface trouble.

   L'app reste entière : on perd la lumière, pas une information.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    body::before {
        animation: none !important;
        transform: none !important;
    }

    #floating-timer:not(.hidden) {
        animation: none !important;
        /* Le -50 % de centrage n'est plus posé par l'animation : il faut le
           rendre à la main, sinon le chrono se colle au bord droit. */
        transform: translateX(-50%) !important;
    }

    #volume-celebration-modal [data-chiffre] {
        animation: none !important;
        background-image: none;
        -webkit-text-fill-color: #FFFFFF;
    }

    nav, header,
    .bg-zinc-950\/95, .bg-zinc-900\/95, .bg-zinc-950\/90,
    #toast-container > div {
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        background-color: rgba(8, 8, 10, 0.96) !important;
    }
}

/* Le même égard, pour une autre raison : « économiser les données » est le
   réglage que l'on active quand l'appareil peine. Le flou est ce qui coûte
   le plus cher à l'image près, on le retire en premier. */
@media (prefers-reduced-data: reduce) {
    nav, header,
    .bg-zinc-950\/95, .bg-zinc-900\/95, .bg-zinc-950\/90 {
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        background-color: rgba(8, 8, 10, 0.96) !important;
    }
}
