/* ==========================================================================
   css/mobile.css — LA COUCHE TÉLÉPHONE
   --------------------------------------------------------------------------
   POURQUOI UN QUATRIÈME FICHIER

   Les trois autres se partagent déjà le travail : styles.css porte
   l'historique, systeme.css ce qui a été gagné à la mesure (44 px de zone
   tactile, 16 px dans les champs, contrastes), identite.css l'apparence.
   Aucun ne s'occupe de ce que l'app devient QUAND ON LA TIENT DANS LA MAIN,
   sur un iPhone à encoche ou un Android à barre gestuelle.

   Ce fichier ne change aucune couleur, aucune forme, aucune structure. Il ne
   fait que trois choses, et il passe en dernier pour pouvoir les faire :

     1. LA ZONE SÛRE. `viewport-fit=cover` fait passer la page SOUS la barre
        d'état et SOUS la barre d'accueil. La barre du bas était traitée
        (styles.css) ; le haut, les côtés en paysage et les modales ne
        l'étaient pas. Un en-tête à `pt-12` — 48 px en dur — passe sous la
        barre d'état d'un iPhone 15 Pro, qui en réclame 59, et gaspille
        34 px dans un onglet de navigateur, où elle n'existe pas.

     2. CE QUE LE POUCE ATTEINT. systeme.css a relevé les boutons à 44 px.
        Restaient les listes déroulantes (35 px mesurés dans une séance), les
        cases à cocher, et le champ de photo de repas — 28 px, et le seul
        widget brut du navigateur encore visible dans l'app.

     3. CE QUI TRAHIT ENCORE LA PAGE WEB. Le reflet gris d'iOS sur les
        champs, la police qui grossit toute seule à la rotation, une modale
        calée sur `100vh` dont le bas passe sous la barre d'adresse, une
        liste qui entraîne la page derrière elle quand on arrive au bout.

   ⚠️  CHARGÉ APRÈS identite.css : c'est ce qui lui donne le dernier mot sur
       les mesures. Ne pas déplacer la balise dans index.html.

   ⚠️  POUR LE CODE NOUVEAU : rien à faire. Tout ici s'accroche à des
       sélecteurs d'éléments ou aux classes Tailwind déjà employées. Un
       écran ajouté demain en hérite sans une ligne de plus.

   test/mobile.test.js vérifie chacune de ces règles.
   ========================================================================== */


/* --------------------------------------------------------------------------
   0. LES QUATRE MARGES DE L'APPAREIL
   --------------------------------------------------------------------------
   `env(safe-area-inset-*)` ne vaut quelque chose que sous `viewport-fit=cover`
   ET quand l'appareil a de quoi mordre sur la page : encoche, île, barre
   d'accueil, coins arrondis, caméra frontale en paysage. Ailleurs — un
   Android sans encoche, un onglet de navigateur, un ordinateur — les quatre
   valent 0, et toutes les formules ci-dessous retombent sur leur plancher.

   On les nomme une fois ici pour ne plus jamais réécrire un `env()` ailleurs.
   -------------------------------------------------------------------------- */

:root {
    --marge-haut:   env(safe-area-inset-top, 0px);
    --marge-bas:    env(safe-area-inset-bottom, 0px);
    --marge-gauche: env(safe-area-inset-left, 0px);
    --marge-droite: env(safe-area-inset-right, 0px);
}


/* --------------------------------------------------------------------------
   1. LA POLICE NE GROSSIT PLUS TOUTE SEULE
   --------------------------------------------------------------------------
   iOS agrandit le texte d'un bloc étroit quand on tourne le téléphone —
   « text inflation ». Dans une app dont toute la hiérarchie repose sur la
   taille (identite.css, section 4), c'est la hiérarchie qui se déforme : un
   libellé de 11 px peut ressortir plus gros que le chiffre qu'il annonce.

   Tailwind pose déjà la version préfixée dans sa base ; on ajoute la
   propriété standard, que Chrome Android lit, et on la redit sur `body` où
   l'héritage diffère d'un moteur à l'autre.
   -------------------------------------------------------------------------- */

html, body {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}


/* --------------------------------------------------------------------------
   2. L'EN-TÊTE : NI SOUS LA BARRE D'ÉTAT, NI DÉCOLLÉ D'ELLE
   --------------------------------------------------------------------------
   MESURE. `pt-12` valait 48 px, en dur, partout. Trois situations, trois
   résultats, et un seul était bon :

     • iPhone à encoche installé → barre d'état de 59 à 62 px : le nom de
       l'app et les boutons « Profil / Déconnexion » passaient DESSOUS. Le
       titre était coupé par l'heure et la batterie.
     • Android installé          → barre d'état de 24 px : 24 px de noir en
       trop au-dessus du titre.
     • Onglet de navigateur      → la barre d'adresse occupe déjà le haut :
       48 px de vide sur un écran qui n'en a pas à donner.

   LA MARGE SE DÉDUIT DONC DE L'ENCOCHE. Reste à choisir ce qu'on ajoute, et
   une première version s'est trompée sur ce point — l'erreur est instructive,
   d'où ce paragraphe.

   ⚠️  CE QU'IL NE FAUT PAS FAIRE : COMPENSER LA ZONE TACTILE.
   systeme.css donne à chaque bouton d'en-tête un rectangle invisible de 44 px
   CENTRÉ sur lui (`header button::after`) ; le bouton mesurant 27 px, ce
   rectangle dépasse de 9 px au-dessus de ce qu'on voit. On avait cru devoir
   rendre ces 9 px à l'appareil, et on avait donc posé `encoche + 21px`.

   C'était un raisonnement faux. Avec `encoche + 14px` (mesuré sur une
   encoche de 59), la zone tactile commençait à 65 px — donc SIX PIXELS SOUS
   L'ENCOCHE, entièrement dans la zone sûre. Elle n'a jamais mordu sur la
   caméra : c'est précisément ce que `env(safe-area-inset-top)` garantit. La
   correction a simplement fait descendre l'en-tête de 7 px de plus que
   nécessaire, et ça se voyait.

   CE QU'ON FAIT, ET LA RÉFÉRENCE EST NATIVE. Une barre de navigation iOS
   commence à l'encoche et fait 44 pt : son titre est donc centré à
   `encoche + 22`. Notre rangée fait 29 px de haut ; pour retomber sur ce
   centre il faut `encoche + 7,5`. On pose 10 px — 2,5 px de marge de plus
   que le strict nécessaire, et la zone tactile commence alors 2,5 px sous
   l'encoche, comme celle d'un bouton natif.

   ET UN PLANCHER, QUAND L'APPAREIL N'ANNONCE RIEN. Beaucoup d'Android ne
   déclarent aucune marge alors qu'ils ont un poinçon de caméra, et un
   navigateur en annonce zéro. Sans plancher, la formule retombait à 10 px et
   les boutons se collaient au bord physique. 24 px les en écartent.

   ⚠️  Réservé au téléphone : au-delà de 768 px l'en-tête devient une bande
       fixe en haut à droite (md:pt-6), il n'y a pas de barre d'état à
       dégager et la règle n'a rien à y faire.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    header {
        padding-top: max(1.5rem, calc(var(--marge-haut) + 0.625rem)) !important;
        /* En paysage, l'encoche mord sur le CÔTÉ. `max()` et pas une
           addition : sans encoche, on garde exactement le px-3 d'origine. */
        padding-left:  max(0.75rem, var(--marge-gauche)) !important;
        padding-right: max(0.75rem, var(--marge-droite)) !important;
    }

    /* LA BARRE DU BAS : ON AJOUTE À SA GOUTTIÈRE, ON NE LA REMPLACE PAS.
       Une première version posait `padding-left: var(--marge-gauche)` — en
       `!important`, donc par-dessus la gouttière de 12 px qu'identite.css
       donne à la rangée d'onglets. En portrait `--marge-gauche` vaut 0 : la
       gouttière disparaissait, et le premier libellé revenait se coller au
       bord de l'écran. Exactement ce qu'identite.css avait corrigé, et son
       commentaire le dit en toutes lettres — « sans elle, SÉANCE commençait
       à 1 px du bord ».

       MESURÉ, à 402 px avec sept onglets : sans gouttière, « SÉANCE » se
       posait à 5,8 px du bord physique ; avec, à 17,8 px. C'est la
       différence entre un mot qui respire et un mot qu'on croit rogné — et
       elle ne se voit que sur le PREMIER et le DERNIER onglet, les seuls
       dont le voisin est le bord de l'écran.

       Le `calc()` fait donc les deux : la gouttière de la barre, plus ce que
       l'appareil réclame en paysage. */
    nav {
        padding-left:  calc(var(--nav-gouttiere, 12px) + var(--marge-gauche)) !important;
        padding-right: calc(var(--nav-gouttiere, 12px) + var(--marge-droite)) !important;
    }

    /* LE CONTENU RESPIRE MOINS SUR LES BORDS, ET C'EST LÀ QU'ON GAGNE.
       `main` portait `p-6` — 24 px des quatre côtés — hérité d'une mise en
       page pensée pour un écran large. Sur un téléphone de 390 px, c'est
       48 px de largeur perdus, soit un huitième de l'écran, sur des cartes
       et des lignes de série qui en manquent. Et les 24 px du haut se
       posaient juste sous un en-tête qui a déjà sa propre marge basse : deux
       respirations l'une sur l'autre.

       16 px sur les côtés, 12 px en haut : les cartes gagnent 16 px de large
       et l'écran 12 px de haut, sans que rien ne se touche.

       `max()` sur les côtés garde le plancher pour l'encoche en paysage. */
    main {
        /* `!important` comme pour l'en-tête et la barre : `p-6` est une classe
           Tailwind, elle l'emporte sur un sélecteur d'élément quel que soit
           l'ordre des fichiers. C'est la seule façon de la reprendre sans
           toucher au balisage. */
        padding-top:    0.75rem !important;
        padding-bottom: 0.75rem !important;
        padding-left:   max(1rem, var(--marge-gauche)) !important;
        padding-right:  max(1rem, var(--marge-droite)) !important;
    }
}


/* --------------------------------------------------------------------------
   3. LES MESSAGES NE PASSENT PLUS SOUS L'ENCOCHE
   --------------------------------------------------------------------------
   Les toasts sont posés à `top-4` — 16 px du bord HAUT DE LA PAGE, qui
   depuis `viewport-fit=cover` est le bord haut de l'ÉCRAN. Sur un iPhone
   installé, « Séance enregistrée » s'affichait donc derrière l'heure et la
   batterie. C'est le message le plus important de l'app, et c'était le seul
   qu'on ne pouvait pas lire.
   -------------------------------------------------------------------------- */

#toast-container {
    top: max(1rem, calc(var(--marge-haut) + 0.5rem)) !important;
    padding-left:  var(--marge-gauche);
    padding-right: var(--marge-droite);
}


/* --------------------------------------------------------------------------
   4. LES MODALES TIENNENT DANS L'ÉCRAN, ET RIEN QUE DANS L'ÉCRAN
   --------------------------------------------------------------------------
   Toutes les modales de l'app suivent le même patron : un `fixed inset-0`
   qui centre son contenu, avec `p-4`, `p-5` ou `p-6` pour marge. Sous
   `viewport-fit=cover`, ces marges ne dégagent plus rien : `inset-0` colle
   au bord physique de l'écran, la croix de fermeture d'une modale `p-4` se
   retrouve à 16 px du bord — donc sous l'encoche — et le bouton du bas sous
   la barre d'accueil.

   On ne remplace pas la marge, on lui donne un plancher : `max()` garde la
   valeur d'origine partout où l'appareil ne réclame rien.

   Trois règles plutôt qu'une : chaque modale garde EXACTEMENT sa marge. Une
   règle unique les aurait toutes ramenées à la même, et c'est le genre
   d'uniformisation qui se voit.
   -------------------------------------------------------------------------- */

.fixed.inset-0.p-4 {
    padding: max(1rem, var(--marge-haut)) max(1rem, var(--marge-droite))
             max(1rem, var(--marge-bas)) max(1rem, var(--marge-gauche));
}
.fixed.inset-0.p-5 {
    padding: max(1.25rem, var(--marge-haut)) max(1.25rem, var(--marge-droite))
             max(1.25rem, var(--marge-bas)) max(1.25rem, var(--marge-gauche));
}
.fixed.inset-0.p-6 {
    padding: max(1.5rem, var(--marge-haut)) max(1.5rem, var(--marge-droite))
             max(1.5rem, var(--marge-bas)) max(1.5rem, var(--marge-gauche));
}

/* Les écrans pleins — questionnaire d'arrivée, écran d'accueil, agenda,
   catalogue d'exercices — n'ont pas de marge à eux : ils défilent, et c'est
   leur contenu qui porte son propre espacement. Il leur manquait seulement
   de quoi passer sous la barre d'état et au-dessus de la barre d'accueil. */
.fixed.inset-0.overflow-y-auto {
    padding-top:    var(--marge-haut);
    padding-bottom: var(--marge-bas);
    padding-left:   var(--marge-gauche);
    padding-right:  var(--marge-droite);
}

/* UNE MODALE NE FAIT JAMAIS DÉFILER LA PAGE DERRIÈRE ELLE.
   Arrivé au bout d'une liste de modale, le geste continuait dans la page du
   dessous : on fermait la modale et on retrouvait l'app trente écrans plus
   bas. `contain` arrête la chaîne au bord de la modale. */
.fixed.inset-0,
.fixed.inset-0 .overflow-y-auto,
.fixed.inset-0 [class*="max-h-"] {
    overscroll-behavior: contain;
}

/* `90vh` sur iOS compte la barre d'adresse comme si elle n'était pas là :
   une modale à `max-h-[90vh]` déborde donc de l'écran visible, et ses
   derniers boutons sont inatteignables. `dvh` mesure ce qu'on voit vraiment,
   et se remesure quand la barre d'adresse glisse. */
@supports (height: 100dvh) {
    .max-h-\[90vh\] { max-height: 90dvh; }
    .max-h-\[80vh\] { max-height: 80dvh; }
}

/* TÉLÉPHONE COUCHÉ. Un iPhone en paysage offre 390 px de haut : une modale
   centrée verticalement y voit son haut ET son bas sortir de l'écran, sans
   moyen d'y revenir. Elle se cale alors en haut et défile. */
@media (max-height: 500px) and (orientation: landscape) {
    .fixed.inset-0.p-4,
    .fixed.inset-0.p-5,
    .fixed.inset-0.p-6 {
        align-items: flex-start;
        overflow-y: auto;
    }
}


/* --------------------------------------------------------------------------
   5. CE QUE LE POUCE ATTEINT ENCORE
   --------------------------------------------------------------------------
   systeme.css a relevé `button` et `summary` à 44 px. La mesure, refaite au
   navigateur sur les onze onglets, a trouvé ce qui restait dessous :

     • les listes déroulantes  — 35 px pour « Standard / Dégressive » dans
       une séance, 42 px pour le sélecteur d'exercice ;
     • les cases à cocher       — 16 px, la taille par défaut du navigateur ;
     • le champ de photo de repas — 28 px, et le seul widget brut de tout
       l'écran.

   Même principe que systeme.css : la zone grandit, le dessin ne bouge pas —
   sauf pour la case à cocher, où le dessin ÉTAIT le défaut.
   -------------------------------------------------------------------------- */

select {
    min-height: 44px;
}

/* `min-width` et pas `width` : les classes Tailwind `w-4` / `w-5` posées sur
   ces cases l'emportent sur un sélecteur d'élément, quel que soit l'ordre
   des fichiers. `min-width` n'a pas de concurrent, et donne le même
   résultat. */
input[type="checkbox"],
input[type="radio"] {
    min-width: 22px;
    min-height: 22px;
    flex-shrink: 0;
}

/* Le libellé qui enveloppe une case étend la zone à toute sa ligne : on
   coche en visant le mot, pas le carré. */
label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) {
    min-height: 44px;
}

/* Un lien ou un libellé qu'on a rendu cliquable est un bouton : il en prend
   la zone. `inline-flex` est nécessaire — `min-height` n'a aucun effet sur
   une boîte en ligne, et `a` comme `span` le sont par défaut.

   ⚠️  AUCUN ÉLÉMENT DE L'APP NE CORRESPOND AUJOURD'HUI : tout ce qui se
       clique est une balise `button`, et c'est très bien ainsi. Cette règle
       est un garde-fou pour le code à venir — le jour où un `<span onclick>`
       apparaîtra dans un écran, il naîtra déjà à la bonne taille plutôt que
       d'attendre la prochaine mesure. Ne pas la retirer en la croyant morte.

   On ne touche PAS aux liens ordinaires dans une phrase : la règle exige un
   `onclick` ou un `role="button"`, c'est-à-dire une déclaration explicite
   que l'élément est une commande. */
a[onclick],
span[onclick],
[role="button"] {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* Sauf ceux qui sont déjà des blocs : leur imposer `inline-flex` les ferait
   rétrécir sur leur contenu et casserait la mise en page. */
a[onclick].block, a[onclick].flex, a[onclick].grid, a[onclick].w-full,
span[onclick].block, span[onclick].flex, span[onclick].grid,
[role="button"].block, [role="button"].flex, [role="button"].grid,
[role="button"].w-full, [role="button"].absolute {
    display: revert;
}

/* UN `div` CLIQUABLE NE REÇOIT QUE LA HAUTEUR, JAMAIS L'AFFICHAGE.
   Il y en a cinq dans l'app, et ce sont tous des cartes ou des rangées qui
   ont déjà leur mise en page — dont la rangée des partenaires de l'écran
   Séances, mesurée à 32 px de haut. Leur imposer `inline-flex` les ferait
   rétrécir sur leur contenu ; `min-height` seul les grandit sans rien
   déplacer, parce qu'un bloc occupe déjà toute la largeur qu'on lui donne. */
div[onclick] {
    min-height: 44px;
}

/* LE CHAMP DE PHOTO DE REPAS.
   « Choisir un fichier » est dessiné par le système : gris clair sur les
   deux plateformes, minuscule, et le seul endroit de l'app où le navigateur
   se montre. On le rhabille comme un bouton secondaire — cadre, chasse
   fixe, capitales — et on lui donne ses 44 px. */
input[type="file"]:not(.hidden) {
    min-height: 44px;
    display: block;
    width: 100%;
    color: var(--i-encre-3, #8A8A8A);
    background: transparent !important;
    border: 0 !important;
    padding: 0;
}

input[type="file"]:not(.hidden)::file-selector-button {
    min-height: 44px;
    margin-right: 0.75rem;
    padding: 0 1rem;
    background: transparent;
    border: 1px solid var(--i-trait-fort, #3D3D3D);
    border-radius: 0;
    color: var(--i-encre, #FFFFFF);
    font-family: var(--i-mono, ui-monospace, monospace);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

input[type="file"]:not(.hidden)::file-selector-button:active {
    background: var(--i-surface, #0A0A0A);
}

/* Safari n'a compris `::file-selector-button` qu'en 16.4. Avant, il n'écoute
   que son nom préfixé — et un iPhone qui ne se met plus à jour est
   exactement le genre d'appareil sur lequel l'app doit rester correcte.
   Les deux règles ne peuvent pas être écrites ensemble : un sélecteur
   inconnu invalide toute la déclaration groupée. */
input[type="file"]:not(.hidden)::-webkit-file-upload-button {
    min-height: 44px;
    margin-right: 0.75rem;
    padding: 0 1rem;
    background: transparent;
    border: 1px solid var(--i-trait-fort, #3D3D3D);
    border-radius: 0;
    color: var(--i-encre, #FFFFFF);
    font-family: var(--i-mono, ui-monospace, monospace);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}


/* --------------------------------------------------------------------------
   6. LES CHAMPS SE RESSEMBLENT D'UNE PLATEFORME À L'AUTRE
   --------------------------------------------------------------------------
   iOS dessine ses propres champs par-dessus les nôtres : coins arrondis
   imposés, ombre intérieure grise, fond clair sur un `type="search"`. Sur
   Android, rien de tout ça. Le même écran ne se ressemblait donc pas d'un
   téléphone à l'autre — et sur iPhone, une app entièrement à angles droits
   (identite.css) affichait des champs aux coins ronds.

   `appearance: none` retire le dessin du système et laisse le nôtre. On ne
   le pose PAS sur `select` : ça effacerait aussi le chevron, et les listes
   déroulantes qui n'en dessinent pas un elles-mêmes deviendraient des
   rectangles muets.
   -------------------------------------------------------------------------- */

input[type="text"], input[type="number"], input[type="email"],
input[type="password"], input[type="search"], input[type="tel"],
input[type="url"], input:not([type]), textarea {
    -webkit-appearance: none;
    appearance: none;
    /* Le curseur reprend l'encre de l'app : le bleu système d'iOS était la
       seule chose bleue d'un écran qui n'en a plus. */
    caret-color: var(--i-encre, #FFFFFF);
}

/* Safari refuse de styler un champ rempli automatiquement : il le repeint en
   jaune pâle avec du texte noir, illisible sur le noir de l'app. L'ombre
   intérieure est le seul moyen connu de reprendre la main sur ce fond. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--i-encre, #FFFFFF);
    -webkit-box-shadow: 0 0 0 1000px var(--i-creux, #050505) inset !important;
    caret-color: var(--i-encre, #FFFFFF);
}

/* Un champ date sur iOS se dessine comme un bouton et rogne son propre texte
   quand la case est étroite : il lui faut la hauteur d'un contrôle. */
input[type="date"],
input[type="time"],
input[type="datetime-local"] {
    min-height: 44px;
}

/* Les flèches d'un `type="number"` ne servent à rien au doigt et volent
   16 px de large au chiffre — dans une ligne de série où le champ « KG »
   fait 80 px, c'est un cinquième de la place. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}


/* --------------------------------------------------------------------------
   7. LE RETOUR TACTILE
   --------------------------------------------------------------------------
   Sur un ordinateur, le survol dit « ce truc réagit » avant même le clic. Au
   doigt, il n'y a pas de survol : entre l'appui et le changement d'écran,
   rien ne confirme que l'appui a été pris. C'est le détail qui fait dire
   « l'app rame » d'une app qui ne rame pas.

   La barre d'onglets a déjà le sien (identite.css). On le donne à tout le
   reste, et uniquement là où il n'y a pas de souris — `(hover: none)`.

   Volontairement discret : une opacité, pas une animation. Un effet de
   150 ms sur un bouton « + Série » qu'on appuie quarante fois dans une
   séance devient une gêne.
   -------------------------------------------------------------------------- */

@media (hover: none) {
    button:active,
    a[onclick]:active,
    [role="button"]:active,
    summary:active {
        opacity: 0.62;
        transition: opacity 60ms linear;
    }

    /* Un bouton désactivé ne réagit pas : c'est ce qui dit qu'il est
       désactivé. Sans cette exception, « Valider la séance » pendant
       l'enregistrement clignotait comme s'il répondait encore. */
    button:disabled:active,
    button[aria-disabled="true"]:active {
        opacity: 1;
    }
}

button:disabled,
button[aria-disabled="true"] {
    cursor: not-allowed;
}


/* --------------------------------------------------------------------------
   8. LE CLAVIER LOGICIEL
   --------------------------------------------------------------------------
   Le clavier occupe la moitié basse de l'écran. La barre d'onglets, elle,
   est en `position: fixed` : selon le moteur, elle reste collée au bas de la
   page — donc cachée derrière le clavier, sans dommage — ou remonte AVEC lui
   et se pose juste au-dessus du champ qu'on est en train de remplir. Dans ce
   second cas, on tape « 80 » dans la case KG en voyant huit onglets à trois
   millimètres du doigt, et un appui manqué change d'écran au milieu d'une
   série.

   Aucune app native n'affiche sa barre d'onglets pendant la frappe.
   js/mobile.js pose `data-clavier` sur <html> dès que visualViewport se
   rétrécit ; la barre s'efface, et revient dès que le clavier se referme.

   Elle GLISSE, elle ne disparaît pas : `display: none` la ferait remesurer à
   zéro par mesurerNav(), le corps de page se relâcherait de 60 px sous le
   champ ouvert, et l'écran sauterait à chaque frappe.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    nav {
        transition: transform 180ms cubic-bezier(0.16, 1, 0.3, 1);
    }

    html[data-clavier] nav {
        transform: translateY(100%);
        pointer-events: none;
    }

    /* Le chrono flottant et les bandeaux du bas sont calés sur --nav-height.
       Pendant la frappe ils n'ont rien à faire au-dessus du clavier. */
    html[data-clavier] #floating-timer,
    html[data-clavier] #bandeau-maj,
    html[data-clavier] #bandeau-consentement {
        opacity: 0;
        pointer-events: none;
    }
}


/* --------------------------------------------------------------------------
   9. LA PAGE NE BOUGE PLUS DERRIÈRE UNE MODALE
   --------------------------------------------------------------------------
   Une modale ouverte, on fait défiler : c'est la page du dessous qui bouge.
   On la referme, et on ne sait plus où on était. js/mobile.js pose
   `data-modale` sur <html> et fige le corps de page à sa position — restituée
   à la fermeture, au pixel près.

   `position: fixed` sur <body> plutôt qu'`overflow: hidden` : c'est le seul
   verrou que Safari iOS respecte. `overflow: hidden` y laisse passer le
   défilement tactile.
   -------------------------------------------------------------------------- */

html[data-modale] body {
    position: fixed;
    top: var(--fige-a, 0px);
    left: 0;
    right: 0;
    width: 100%;
    overflow: hidden;
}

/* ET LA BARRE D'ONGLETS PASSE DERRIÈRE.
   MESURE : la barre est à `z-index: 9999` (styles.css, pour qu'elle reste
   cliquable quoi qu'il arrive) ; les modales de l'app sont à 60, 100, 200 ou
   300. Toutes passaient donc SOUS elle. Sur le bilan quotidien, la barre
   recouvrait la moitié basse du bouton « Envoyer » — l'action principale de
   la modale, à moitié cachée et à moitié cliquable.

   On ne la masque pas : `display: none` la ferait remesurer à zéro par
   mesurerNav(), et la page derrière se relâcherait de 60 px pendant que la
   modale est ouverte — un saut visible à la fermeture. On la fait juste
   passer derrière, le temps de la modale. */
html[data-modale] nav {
    z-index: 30 !important;
}


/* --------------------------------------------------------------------------
   10. LA CROIX DE FERMETURE
   --------------------------------------------------------------------------
   Les modales de l'app ferment par un `&times;` posé en `absolute top-4
   right-4`. systeme.css lui donne bien 44 px de HAUT — c'est une balise
   `button` — mais rien en LARGE : un glyphe de 12 px de large dans un
   rectangle de 12 × 44. On vise, et on rate vers l'extérieur, là où il n'y a
   rien à toucher.

   Le carré de 44 px est posé sur ce qui FERME, reconnu à son nom accessible
   (js/mobile.js le pose quand il manque) — pas sur tous les boutons absolus,
   dont un bouton d'action garde sa largeur.
   -------------------------------------------------------------------------- */

button[aria-label="Fermer"],
button[aria-label^="Fermer "] {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}


/* --------------------------------------------------------------------------
   11. CE QUI DÉFILE DE CÔTÉ
   --------------------------------------------------------------------------
   Quatre endroits : la rangée des partenaires (Séances), le tableau
   d'assiduité (Suivi), les filtres et les produits de la boutique
   partenaire. Sur Android, le geste poursuivi au-delà du bord ne s'arrêtait
   pas à la rangée — il déclenchait le retour arrière du navigateur, et
   faisait sortir de l'app au milieu d'une séance.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    .overflow-x-auto {
        -webkit-overflow-scrolling: touch;
        overscroll-behavior-x: contain;
    }
}


/* --------------------------------------------------------------------------
   12. L'EN-TÊTE NE MANGE PLUS CE VERS QUOI ON SAUTE
   --------------------------------------------------------------------------
   L'en-tête est collant. Quand le code fait défiler vers un exercice
   (scrollIntoView après « Ajouter un exercice », après une validation), le
   bloc se cale en haut de la fenêtre — donc SOUS l'en-tête, dont il perd les
   deux premières lignes.

   `scroll-margin-top` réserve la hauteur de l'en-tête sur la cible. C'est du
   CSS pur : aucun appel à scrollIntoView n'a besoin d'être retouché.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    .exo-block,
    .set-row,
    [id^="active-"] {
        scroll-margin-top: calc(var(--marge-haut) + 5rem);
    }
}


/* --------------------------------------------------------------------------
   12 bis. L'EN-TÊTE S'EFFACE QUAND ON LIT
   --------------------------------------------------------------------------
   LE CALCUL. Sur un iPhone 15 (844 px de haut), l'app dispose de :

       844  écran
     − 110  en-tête (encoche comprise)
     −  72  barre d'onglets (trait d'accueil compris)
     = 662  pour le contenu, soit 78 % de l'écran

   Les deux barres sont maintenant à la bonne taille — on ne peut plus rien
   leur prendre sans redonner au doigt les problèmes qu'on vient de régler.
   La place doit donc venir d'ailleurs, et elle vient du TEMPS : l'en-tête ne
   sert qu'entre deux gestes. Pendant qu'on descend une liste d'exercices ou
   un historique, « SMARTLIFT / Profil / Connecté / Déconnexion » n'a rien à
   dire ; il occupe 110 px pour être lu une fois par séance.

   Il s'efface donc dès qu'on descend, et il revient au premier geste vers le
   haut — comme dans toutes les apps où l'on fait défiler du contenu long.
   Le contenu passe alors à 772 px, soit 91 % de l'écran : 110 px gagnés
   là où on regarde, zéro pixel pris à ce que le doigt doit atteindre.

   ⚠️  LA BARRE D'ONGLETS, ELLE, NE BOUGE PAS. C'est la navigation : elle doit
       être là quand on la cherche, et une barre qui apparaît et disparaît
       sous le pouce est le contraire d'une app qui inspire confiance. Seul le
       clavier logiciel l'escamote (section 8), parce qu'à ce moment-là elle
       est un piège et non un service.

   js/mobile.js pose `data-entete="rangee"` ; le sens du geste, le seuil et
   les cas où l'en-tête doit revenir de force y sont expliqués.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    header {
        transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
    }

    html[data-entete="rangee"] header {
        /* 101 % et pas 100 : le filet tricolore est un ::after posé à
           `bottom: -1px`, hors de la boîte. À 100 % il restait une ligne
           bleu-blanc-rouge d'un pixel collée en haut de l'écran. */
        transform: translateY(-101%);
    }
}


/* --------------------------------------------------------------------------
   12 BIS. L'ÉTAT DE SYNCHRO PERD SON MOT, PAS SON SENS
   --------------------------------------------------------------------------
   MESURÉ, PAS SUPPOSÉ. « Connecté » demande 66 px dans l'en-tête. Il en
   reçoit 14 sur un écran de 320 px et 54 sur un écran de 360 — la largeur
   Android la plus répandue. Le mot sortait donc coupé au milieu, et « ■ C »
   ne veut rien dire. À 390 px il tient tout juste, mais « Connexion… », plus
   long de quatre caractères, y est coupé lui aussi.

   identite.css a déjà dit ce qu'est cet élément : « un état se signale, il ne
   se réclame pas : un carré de 5 px et un mot ». La couleur du carré porte
   déjà l'information à elle seule — encre pour connecté, rouge pour une
   erreur, bleu pour hors ligne. Sur un téléphone, où la place manque, on
   garde le carré et on rend les 60 px au reste de l'en-tête ; sur un écran
   large, le mot revient.

   ⚠️  `font-size: 0` PLUTÔT QUE `display: none` SUR LE TEXTE, et la
       différence compte : le mot reste dans le DOM, donc les lecteurs
       d'écran continuent de l'annoncer. On retire une information de l'œil,
       jamais de l'oreille. Le carré, lui, est un `::before` dimensionné en
       pixels : il ne rétrécit pas avec la police.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    #sync-status {
        font-size: 0 !important;
        gap: 0 !important;
        letter-spacing: 0 !important;
    }
}


/* --------------------------------------------------------------------------
   13. RIEN NE DÉBORDE DE CÔTÉ
   --------------------------------------------------------------------------
   `overflow-x: hidden` sur <body> (styles.css) cache le débordement mais ne
   l'empêche pas : le contenu reste coupé, simplement on ne le voit plus
   partir. Les deux causes mesurées sont un mot indivisible dans une carte
   étroite (une adresse e-mail, un nom d'exercice composé) et une rangée flex
   qui refuse de rétrécir.
   -------------------------------------------------------------------------- */

@media (max-width: 767px) {
    /* Un élément flex refuse par défaut de descendre sous la largeur de son
       contenu — et c'est la page qui déborde à sa place. */
    .tab-content .flex > .flex-1 {
        min-width: 0;
    }

    /* Une adresse e-mail de 40 caractères dans une carte de 280 px se coupe
       plutôt que de pousser la carte hors de l'écran. `break-word` et pas
       `anywhere` : `anywhere` change aussi la largeur minimale calculée des
       colonnes, et resserrerait des grilles qui vont très bien. */
    .tab-content p,
    .tab-content h2,
    .tab-content h3 {
        overflow-wrap: break-word;
    }
}

/* UN TITRE QUI PORTE UN CONTRÔLE PASSE À LA LIGNE.
   MESURE, sur un écran de 320 px : le titre « Mon Plan Alimentaire » et son
   sélecteur de date partagent un `<h2 class="flex justify-between">`. Un
   `input[type="date"]` a une largeur minimale imposée par le système — il ne
   se laisse pas comprimer — et la ligne réclamait 340 px pour 320
   disponibles. La page défilait de côté sur tout l'onglet Diète.

   Un titre et son contrôle sur deux lignes se lisent très bien ; une page qui
   glisse de côté, non. Le seuil est celui où ça ne rentre plus. */
@media (max-width: 380px) {
    .tab-content > h2 {
        flex-wrap: wrap;
        gap: 0.5rem;
    }
}
