/* of-mobile.css — socle de la version mobile (chantier « responsive + PWA », 2026-08).
 *
 * POURQUOI UN FICHIER SÉPARÉ plutôt qu'une section de of-commun.css :
 *   1. of-commun.css n'a aucune @media et sa vocation reste « règles transversales de bureau » ;
 *   2. un fichier isolé se désactive d'un seul <link> pour bissecter une régression d'affichage
 *      sur toute l'application — outil de diagnostic gratuit ;
 *   3. la frontière est nommable : quand une règle mobile ne mord pas, on sait où regarder.
 *
 * ⚠ CE FICHIER NE BAT PAS LE CSS DE PAGE. Vérifié : app/finance/factures.php charge son
 *   css/finance.css APRÈS l'include du shell. Toute surcharge d'une règle de PAGE va donc dans la
 *   @media du CSS de cette page, pas ici. Ici vivent le socle et les patrons (classes neuves,
 *   sans concurrent). Quand une règle « ne fait rien », c'est la première hypothèse à tester.
 *
 * ⚠ Variables : toujours --ct-* (thème), JAMAIS --bs-* — ces dernières se résolvent en
 *   transparent, sans le moindre signal.
 *
 * Chargé en dernier par include/utils/head.inc.php.
 */

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   1. Réglages globaux — valables à toutes les largeurs
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* Safari iOS grossit de lui-même le corps des textes au passage en paysage (« text inflation »).
   La mise en page calculée en portrait se disloque alors sans qu'aucune règle ne l'ait demandé. */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* Un média plus large que son conteneur est la première cause de débordement horizontal. La règle
   est volontairement générale : elle ne coûte rien là où la largeur est déjà maîtrisée.
   ⚠ On ne pose PAS `body { overflow-x: hidden }` : ce pansement masque le débordement au lieu de
   le montrer, et rendrait aveugle le contrôle de non-régression (verify-mobile-matrice.js). */
img,
video,
iframe,
canvas {
    max-width: 100%;
}

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   2. Sous 768 px — le seuil déjà retenu par of-shell.js (ajusterAuGabarit)
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

@media (max-width: 767.98px) {

    /* ── Zoom au focus : le défaut le plus visible d'un portage bâclé ─────────────────────────
       Sous 16 px de corps, Safari iOS zoome la page à chaque entrée dans un champ — et ne dézoome
       jamais en sortant. L'utilisateur finit par naviguer dans une page agrandie qu'il doit
       repincer à la main. La seule parade est de tenir 16 px, ce que le thème ne fait pas
       (form-control est à .875rem). */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select,
    textarea,
    .form-control,
    .form-select,
    .select2-search__field {
        font-size: 16px;
    }

    /* ⚠ Les variantes « petites » du thème sont à DEUX classes (`.input-group-sm > .form-select`,
       0-2-0) et l'emportaient donc sur la règle ci-dessus (0-1-0) : le sélecteur de portée de la
       recherche des emails restait à 14 px, et Safari zoomait. Il faut réécrire les mêmes
       sélecteurs pour gagner par l'ORDRE de chargement, à spécificité égale — plutôt qu'un
       !important qui rendrait le corps de ces champs inajustable par les pages. */
    .form-control-sm,
    .form-select-sm,
    .input-group-sm > .form-control,
    .input-group-sm > .form-select,
    .input-group-sm > .input-group-text {
        font-size: 16px;
    }

    /* ── Cibles tactiles ─────────────────────────────────────────────────────────────────────
       44 px est le minimum au doigt. La règle est CIBLÉE et non globale : un `min-height` posé
       sur tout élément cliquable ferait exploser la hauteur des tableaux denses (une liste de 50
       emails deviendrait un rouleau de 2 200 px) et des menus déroulants longs.
       `align-items: center` accompagne le min-height, sinon le libellé reste collé en haut. */
    .topbar-menu .nav-link,
    .side-nav-link,
    .dropdown-item,
    .page-link,
    .nav-tabs .nav-link,
    .btn-close {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* Les boutons gardent leur mise en page d'origine (inline-flex) : les passer en `flex`
       les étirerait sur toute la largeur de leur conteneur. */
    .btn,
    .btn-sm {
        min-height: 44px;
    }

    /* Une case à cocher de 1 em est intouchable au doigt. On agrandit la case elle-même plutôt
       que sa zone de clic, pour que le retour visuel corresponde à ce qu'on vise. */
    .form-check-input {
        width: 1.25em;
        height: 1.25em;
        margin-top: .15em;
    }

    /* ── Barre supérieure : ce qui la faisait déborder ───────────────────────────────────────
       Mesuré à 320 px : groupe de gauche 176 px (hamburger 36 + horloge **134**), groupe de droite
       196 px — soit 372 px pour 320 disponibles. C'est la seule cause de débordement horizontal
       qui restait sur TOUTES les pages, y compris celles déjà adaptées.

       L'horloge part la première, et sans regret : un téléphone affiche l'heure en permanence dans
       sa propre barre d'état. La redonner coûtait 134 px, soit 42 % de la largeur d'un petit
       écran, pour une information que l'utilisateur a déjà sous les yeux.

       ⚠ `!important` OBLIGATOIRE, et pas par confort : l'élément porte la classe utilitaire
       `d-flex`, que Bootstrap déclare elle-même en `!important`. Un `#datetime { display: none }`
       — pourtant un sélecteur d'IDENTIFIANT, la spécificité la plus forte — reste sans effet.
       Mesuré : l'horloge conservait ses 134 px et la barre débordait toujours de 28 px à 320.
       La spécificité ne peut rien contre `!important` ; seul un autre `!important` le peut. */
    #datetime { display: none !important; }

    /* `gap-3` (16 px) entre quatre icônes, c'est 48 px de vide pour 92 px d'icônes. */
    .topbar-menu { gap: .5rem !important; }

    /* ── Encoche et barre d'accueil iOS ──────────────────────────────────────────────────────
       env() vaut 0 partout ailleurs : la règle est sans effet hors iPhone à encoche, elle n'a
       donc pas à être conditionnée. */
    .navbar-custom > .topbar {
        padding-top: env(safe-area-inset-top);
    }

    .leftside-menu {
        padding-bottom: env(safe-area-inset-bottom);
    }

    /* ⚠ CONTREPARTIE DE `viewport-fit=cover`. Depuis le lot M9, la balise viewport est unique et
       porte `viewport-fit=cover` sur TOUTES les pages — sans quoi les `env(safe-area-inset-*)`
       ci-dessus valent zéro et tout ce bloc est inerte (c'était le cas sur 46 pages sur 54).
       Mais `cover` a un revers : la page cesse d'être encadrée par iOS et s'étend sous l'encoche.
       En PAYSAGE sur un iPhone à encoche, les retraits gauche/droite deviennent non nuls et le
       contenu passerait dessous. On les rend donc au contenu, ce qui est un no-op partout
       ailleurs — env() vaut 0 sur tout autre appareil.
       ⚠ Sur le conteneur, pas sur `body` : `body` porte les fonds pleine largeur, qu'on veut voir
       aller jusqu'au bord. */
    .content-page,
    .of-page-contenu {
        padding-left: env(safe-area-inset-left);
        padding-right: env(safe-area-inset-right);
    }

    /* ── Bande d'identité du module ──────────────────────────────────────────────────────────
       Le ruban coûte 76 px pour redire le nom du module — que la barre supérieure et l'onglet
       actif de la barre du bas indiquent déjà. Sur un écran où l'on se bat pour chaque ligne de
       liste, il ne les vaut pas.

       ⚠ Uniquement quand il est PUREMENT décoratif : `:has(> .module-ribbon:only-child)` vise la
       bande dont le seul contenu est la pastille. Certaines pages (Temps passé) logent des
       commandes dans la même boîte via `.page-title-right` — les masquer les rendrait
       inatteignables, ce qui est exactement le genre de dégât qu'une règle trop large provoque
       sans qu'on le remarque avant longtemps. */
    .module-ribbon-box:has(> .module-ribbon:only-child) { display: none; }

    /* ⚠ Conséquence à ne pas manquer : le thème tire CHAQUE conteneur DataTables de `-.625rem`
       (`.dt-container { margin-top: -.625rem }`) pour le rapprocher de l'élément qui le précède.
       Le ruban retiré, il n'y a plus rien à rapprocher : le tableau remontait de 10 px et sa
       première fiche passait SOUS la barre supérieure fixe. Défaut discret — on croit avoir mal
       défilé — et déjà rencontré ailleurs dans le projet sous la forme « piège DataTables
       -.625rem ».

       ⚠ Le sélecteur du thème est `div.dt-container` (0-1-1) : une règle en `.dt-container`
       (0-1-0) ne le bat pas, quel que soit l'ordre de chargement. Premier essai sans effet. */
    div.dt-container { margin-top: 0; }

    /* ── Bandeau DataTables : alléger le CONTENU, jamais la chaîne `dom` ──────────────────────
       Le bandeau mesurait 193 px : boutons d'export, « Afficher 100 entrées », ligne
       d'information et DEUX paginations, au-dessus de fiches qu'on ne voyait plus.

       ⚠ La tentation est de raccourcir la chaîne `dom` de l'initialisation. À NE PAS FAIRE : ces
       emplacements sont un contrat avec le JS de la page, qui y injecte la barre de RECHERCHE et
       les commandes. Les retirer les fait disparaître — sans erreur, `$(vide).append()` ne dit
       rien. On masque donc le contenu superflu, en gardant la structure intacte. */
    .dt-length { display: none; }                          /* « Afficher 100 entrées » */
    .dt-container > .row:last-child { display: none; }     /* info + SECONDE pagination */

    /* ── Respiration ─────────────────────────────────────────────────────────────────────────
       Le thème réserve 12 px de chaque côté sur .container-fluid ; sur 320 px c'est 7,5 % de la
       largeur utile perdue. On resserre sans supprimer (une carte collée au bord se lit mal). */
    .content > .container-fluid {
        padding-left: .75rem;
        padding-right: .75rem;
    }

    /* ══════════════════════════════════════════════════════════════════════════════════════
       PATRON « CARTES » — un tableau devient une pile de fiches (lot 2)
       ══════════════════════════════════════════════════════════════════════════════════════
       Un tableau de 8 colonnes sur 390 px n'a que deux issues : déborder, ou se replier en
       masquant des colonnes. Les deux perdent l'information. La troisième voie est de changer de
       forme : chaque LIGNE devient une fiche, chaque CELLULE une paire « libellé : valeur ».

       Le libellé vient du `<th>` de même rang, recopié dans `data-libelle` par
       assets/js/of-tableaux.js — pas à la main dans le balisage : la plupart de ces tableaux sont
       remplis en JS, et un attribut oublié donnerait une valeur sans nom.

       S'applique aux tableaux portant `.of-cartes`, et à eux seuls : un tableau reste parfois la
       bonne forme (deux colonnes courtes, un total).

       ⚠ Le fond passe par `--ct-table-bg` et JAMAIS par `background-color` : le thème surcharge
       cette dernière sur les cellules, et le diagnostic devient « ma feuille n'est pas chargée ».
       ⚠ Variables `--ct-*`, jamais `--bs-*` (celles-ci se résolvent en transparent, sans signal). */
    .of-cartes thead,
    .of-cartes tfoot .of-cartes-masquer { display: none; }

    .of-cartes,
    .of-cartes tbody,
    .of-cartes tfoot,
    .of-cartes tr,
    .of-cartes td,
    .of-cartes th {
        display: block;
        width: 100%;
    }

    .of-cartes tr {
        --ct-table-bg: var(--ct-secondary-bg);
        border: 1px solid var(--ct-border-color);
        border-radius: .5rem;
        margin-bottom: .5rem;
        padding: .35rem .25rem;
    }

    .of-cartes td,
    .of-cartes tfoot th {
        border: 0;
        /* ⚠ Rembourrage RÉDUIT, et `min-height` remis à zéro. Le thème pose ~.95rem de padding sur
           les cellules, ce qui donnait **46 à 52 px par ligne** de fiche — soit 482 px pour un seul
           contact sur un écran de 800. Une ligne de fiche est du texte, pas une cible tactile :
           c'est la LIGNE ENTIÈRE qui se touche, pas la cellule. */
        padding: .1rem .5rem !important;
        min-height: 0;
        line-height: 1.35;
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: .75rem;
        /* `table-nowrap` du thème (module Temps) empêcherait toute valeur longue de passer à la
           ligne : en fiche, c'est justement ce qu'on veut qu'elle fasse. */
        white-space: normal;
        text-align: left;
    }

    .of-cartes td::before {
        content: attr(data-libelle);
        flex: 0 0 40%;
        font-weight: 600;
        color: var(--ct-body-color);
        opacity: .75;
    }

    /* Colonnes sans en-tête — case à cocher, menu d'actions, icône seule : pas de libellé à
       afficher, et la valeur reprend toute la largeur plutôt que 60 % avec un vide devant. */
    .of-cartes td[data-libelle=""]::before { content: none; }
    .of-cartes td[data-libelle=""] { justify-content: flex-end; }

    /* Une ligne « aucun résultat » (colspan sur toute la largeur) n'est pas une fiche. */
    .of-cartes td[colspan]::before { content: none; }
    .of-cartes td[colspan] { justify-content: center; }

    /* ── Rangs de fiche ──────────────────────────────────────────────────────────────────────
       Traduire un tableau colonne par colonne ne suffit pas : 10 colonnes donnent une fiche de
       10 lignes, et un seul contact remplissait l'écran. Chaque tableau déclare donc ce qui fait
       le TITRE de sa fiche et ce qui n'a rien à y faire (cf. assets/js/of-tableaux.js).
       Le rang par défaut — ne rien déclarer — reste « résumé » : un tableau non traité garde le
       comportement du lot 2, il ne devient pas invisible. */
    .of-cartes td.of-carte-titre {
        font-size: 1rem;
        font-weight: 600;
        padding-bottom: .25rem !important;
        /* Le `space-between` hérité de `.of-cartes td` sert à écarter un libellé de sa valeur.
           Le titre n'a pas de libellé : sans ce retour à `flex-start`, il flottait au milieu de
           la fiche, aligné sur rien. */
        justify-content: flex-start;
    }
    .of-cartes td.of-carte-titre::before { content: none; }

    /* Masqué SUR LA FICHE seulement : la donnée reste dans le DOM, exportable, et la fiche
       détaillée du contact continue de tout montrer. On allège la liste, on n'ampute rien. */
    .of-cartes td.of-carte-detail { display: none; }

    /* Une cellule sans valeur n'afficherait que son libellé : « Rôle », « Email », « Ville »
       empilés, sans une seule information. Invisible dans un tableau, criant sur une fiche.
       (Classe posée par of-tableaux.js — le CSS ne sait pas lire le contenu d'une cellule.) */
    .of-cartes td.of-carte-vide { display: none; }

    /* La case à cocher occupait une LIGNE ENTIÈRE pour un carré de 20 px. On la remonte dans le
       coin de la fiche, à hauteur du titre : une ligne récupérée sur six, et le geste de
       sélection reste au même endroit d'une fiche à l'autre.
       ⚠ Repérée par son contenu (`:has(input[type="checkbox"])`) et non par un rang de fiche :
       la colonne de sélection n'a pas d'en-tête sur lequel poser une classe. */
    .of-cartes tr { position: relative; }
    .of-cartes td:has(> .form-check-input),
    .of-cartes td:has(> .position-relative > .form-check-input) {
        position: absolute;
        top: .3rem;
        right: .5rem;
        width: auto;
        padding: 0 !important;
    }

    /* ══════════════════════════════════════════════════════════════════════════════════════
       PATRON « MODALE PLEIN ÉCRAN »
       ══════════════════════════════════════════════════════════════════════════════════════
       Une modale centrée de 500 px sur un écran de 390 laisse des marges inutiles et un corps si
       court qu'il défile au bout de trois champs. Sous 768 px, elle prend l'écran.

       Règle GÉNÉRIQUE plutôt que la classe `modal-fullscreen-*` de Bootstrap : celle-ci
       demanderait de retoucher les 566 lignes de include/shell/modals.inc.php plus chaque modale
       de page. Une règle unique couvre tout, y compris les modales à venir.
       ⚠ `.modal-dialog-centered` doit être neutralisé : son centrage vertical laisse la modale
       flotter au milieu au lieu de remplir la hauteur. */
    .modal-dialog {
        margin: 0;
        max-width: 100%;
        width: 100%;
        height: 100%;
    }
    .modal-dialog.modal-dialog-centered { min-height: 100%; }
    .modal-content {
        height: 100%;
        border-radius: 0;
        border: 0;
    }
    /* Le corps défile, l'en-tête et le pied restent en place : sur un écran court, perdre le
       bouton « Enregistrer » en haut du défilement est le défaut le plus pénible. */
    .modal-body {
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }
    .modal-footer { padding-bottom: calc(.75rem + env(safe-area-inset-bottom)); }

    /* ── L'EXCEPTION : la modale d'ALERTE ────────────────────────────────────────────────────
       Le plein écran ci-dessus est juste pour un FORMULAIRE — rédiger un email, remplir une fiche :
       on veut toute la place, et un pied qui ne fuit pas au défilement. Il ne l'est pas pour une
       QUESTION de trois lignes : `#modal-window` est la modale d'alerte de la coquille (cf.
       `m_nav.inc.php`), elle ne porte jamais qu'un message et deux boutons. Étirée à 844 px pour
       trois lignes de texte, elle sépare la question de ses réponses par six cents pixels de vide,
       et l'on cherche les boutons.
       ⚠️ `max-height` malgré tout : un message d'erreur peut porter un bloc « Détails » repliable.
       Sans plafond, il déborderait de l'écran et le bouton passerait sous le pli. */
    #modal-window .modal-dialog { height: auto; min-height: 100%; padding: 1rem; }
    #modal-window .modal-content {
        height: auto; max-height: calc(100% - 2rem);
        border-radius: 12px; border: 1px solid var(--ct-border-color);
    }

    /* ══════════════════════════════════════════════════════════════════════════════════════
       PATRON « BARRE DE FILTRES »
       ══════════════════════════════════════════════════════════════════════════════════════
       Les filtres des listes sont posés côte à côte avec des largeurs FIGÉES en ligne
       (44 déclarations `style="min-width:150px"` dans le dépôt). Sur 390 px ils débordent.
       Un conteneur `.of-barre-filtres` suffit à les remettre en colonne pleine largeur — et
       c'est lui qui autorise la neutralisation ciblée des styles en ligne (plus bas), sans
       toucher aux 44 fichiers. */
    .of-barre-filtres {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: .5rem;
        /* ⚠ Le patron se pose parfois sur une `.row` Bootstrap (module Temps passé). Une `.row`
           porte des marges NÉGATIVES de gouttière ; en colonne, ses enfants à 100 % dépassent
           alors du conteneur de la gouttière entière — un débordement horizontal créé par la
           règle censée l'empêcher. On neutralise gouttière et marges : l'espacement est repris
           par `gap`. */
        margin-left: 0;
        margin-right: 0;
        --bs-gutter-x: 0;
    }
    .of-barre-filtres > * {
        width: 100%;
        max-width: none;
        padding-left: 0;
        padding-right: 0;
    }

    /* Repliée par défaut — mesuré sur le journal du Temps passé : dépliée, elle occupait tout le
       premier écran et les entrées de temps commençaient hors de vue. Le bouton et le décompte
       des filtres actifs sont posés par of-tableaux.js.
       ⚠ `display: none` et non `height: 0` : un champ de formulaire hors flux mais présent reste
       atteignable au clavier et par la validation HTML — un champ invisible qui bloque l'envoi
       d'un formulaire est une panne qu'on ne sait pas diagnostiquer. */
    .of-barre-filtres:not(.of-filtres-ouverte) { display: none; }

    .of-filtres-bascule {
        display: inline-flex;
        align-items: center;
        margin-bottom: .5rem;
    }

    /* ── `of-m-0` : commandes SANS OBJET sur un téléphone ────────────────────────────────────
       ⚠ PLUS AUCUN USAGE, et l'histoire mérite d'être gardée. Cette classe avait été créée au lot
       2 bis pour un seul cas : l'ouverture d'un document dans Word depuis le tchat, réputée
       impossible sur téléphone. La prémisse était FAUSSE — le schéma `ms-word:ofe|u|…` est résolu
       par les applications Office mobiles, ce qui a été vérifié en usage réel. La règle est
       conservée, vide d'emploi, parce qu'elle reste le bon outil pour une commande qui, elle, ne
       PEUT pas aboutir au doigt. Mais avant de la poser sur quoi que ce soit : vérifier que la
       commande échoue vraiment sur un appareil, plutôt que de le déduire. */
    .of-m-0 { display: none !important; }

    /* ── Fermeture du menu superposé ─────────────────────────────────────────────────────────
       Menu ouvert, le panneau (260 px, z-index 1055) RECOUVRE le hamburger : le recliquer pour
       refermer est impossible, pour un utilisateur comme pour un test. Le ✕ du panneau est donc
       la seule commande de fermeture visible — or il mesurait **19 px de large**, moins de la
       moitié du minimum tactile. Les autres sorties (voile, Échap, geste retour) existent mais
       ne se voient pas : celle-ci doit être atteignable du premier coup. */
    .button-close-fullsidebar {
        min-width: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* ── Barre d'onglets basse ───────────────────────────────────────────────────────────────
       Le balisage porte `d-none` : si CETTE feuille ne se charge pas, la barre reste invisible
       plutôt que d'atterrir en bloc au bas de toutes les pages de bureau. La contrepartie est
       qu'il faut un !important pour la révéler, `.d-none` étant elle-même en !important. C'est
       l'un des rares endroits où il est justifié — on bat une règle utilitaire, pas une règle
       de mise en page, et le sens de la lecture reste évident. */
    /* ⚠ À VÉRIFIER SUR UN TÉLÉPHONE RÉEL — ce que l'émulateur ne sait pas dire.
       Sous émulation mobile Chrome, `window.innerHeight` vaut 876 px pour 780 px de zone visible,
       et un `position: fixed; bottom: 0` se pose 96 px SOUS le bord de l'écran. On a d'abord cru
       à un vrai défaut iOS (la barre d'adresse) et compensé par `calc(100lvh - 100dvh)`.
       C'était faux : mesuré, `lvh` = `dvh` = `svh` = 780 dans ce même navigateur. L'émulateur se
       contredit donc lui-même — ses unités de viewport disent 780, son positionnement fixe utilise
       876 — ce qu'aucun appareil réel ne fait : iOS Safari et Chrome Android recalent les éléments
       fixes sur la zone VISIBLE.
       On s'en tient donc au comportement standard (`bottom: 0` + zone sûre), et c'est le TEST qui
       coupe l'émulation mobile pour ses mesures de géométrie (cf. verify-mobile-nav.js).
       Compenser un défaut d'émulateur dans le CSS de production l'aurait décalé sur les vrais
       appareils, exactement là où personne ne l'aurait vu venir. */
    /* Hauteur TOTALE de la barre : 52 px d'onglets + 1 px de bordure haute. Elle est déclarée
       une seule fois et relue partout (réserve de contenu, pied de page, fenêtres flottantes).
       Le premier essai écrivait « 52 » à quatre endroits : il manquait la bordure, et le pied de
       page passait sous la barre d'exactement 1 px — assez pour être faux, trop peu pour se voir. */
    :root { --of-tabbar-h: 53px; }

    .of-tabbar {
        display: flex !important;
        position: fixed;
        left: 0;
        right: 0;
        bottom: env(safe-area-inset-bottom);
        z-index: 1030;                       /* au-dessus du contenu, sous les modales (1055) */
        background: var(--ct-topbar-bg);
        border-top: 1px solid var(--ct-border-color);
    }

    .of-tabbar-item {
        flex: 1 1 0;
        min-width: 0;                        /* sinon un libellé long élargit l'onglet et déborde */
        min-height: 52px;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        padding: 6px 2px;
        border: 0;
        background: none;
        text-decoration: none;
        color: var(--ct-topbar-item-color);
        font-size: .68rem;
        line-height: 1;
    }

    .of-tabbar-item > i { font-size: 1.35rem; line-height: 1; }

    /* Le libellé ne doit jamais passer à la ligne : cinq onglets sur 320 px, c'est 64 px chacun,
       et « Agenda » y tient tout juste. Au-delà on coupe plutôt que de doubler la hauteur. */
    .of-tabbar-item > span {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .of-tabbar-item.active,
    .of-tabbar-item:hover { color: var(--ct-topbar-item-hover-color); }

    /* Réserver la place de la barre, sinon elle recouvre la fin du contenu — et c'est justement
       là que se trouvent les boutons d'un formulaire. */
    .content-page { padding-bottom: calc(var(--of-tabbar-h) + env(safe-area-inset-bottom)) !important; }

    /* ── Pied de page : retiré ───────────────────────────────────────────────────────────────
       Il ne porte qu'une mention de copyright, et étant `position: absolute; bottom: 0` de
       .content-page, il se posait PAR-DESSUS le contenu des pages dont la zone utile est calée
       sur la hauteur de fenêtre — la liste des emails passait dessous, illisible sur ses deux
       dernières lignes. Le remonter au-dessus de la barre d'onglets (premier essai) ne faisait
       que déplacer le recouvrement.
       Sur un téléphone, 60 px pour un copyright ne se défendent pas : la mention reste sur le
       bureau, où elle ne coûte rien. */
    .footer { display: none; }

    /* ── Fenêtres flottantes au-dessus de la barre d'onglets ─────────────────────────────────
       ⚠ Sélecteur calqué sur `.tchat-window:not(.tchat-min)` (0-2-0) de tchat-window.css : sa
       propre @media à 560 px y pose `bottom: 0`, et une règle à 0-1-0 ne l'aurait pas battue —
       la fenêtre de tchat serait passée SOUS la barre d'onglets, en dessous de 560 px seulement.
       Un défaut qui n'apparaît que sur les petits téléphones est de ceux qu'on ne voit jamais. */
    .tchat-window:not(.tchat-min),
    .tchat-window.tchat-min {
        bottom: calc(var(--of-tabbar-h) + env(safe-area-inset-bottom));
    }
    /* La hauteur de 85vh du tchat ne tient plus compte de la barre : on la reprend en dvh, qui
       ne bouge pas quand l'adresse de Safari se rétracte. */
    .tchat-window:not(.tchat-min) {
        height: calc(85dvh - 52px);
    }

    /* Le chrono cesse d'être une fenêtre déplaçable pour devenir une BANDE ancrée au-dessus de la
       barre d'onglets. Déplacer une fenêtre au doigt n'a pas de sens, et une fenêtre de 300 px
       posée n'importe où recouvre le contenu sur un écran qui en fait 320.
       ⚠ `!important` obligatoire : le glissement écrit `left`/`top` EN LIGNE (temps-client.js
       applyPos), et un style en ligne ne se bat pas autrement. Le glissement lui-même est
       désarmé côté JS, mais une position mémorisée d'une session de bureau doit aussi être
       ignorée — sinon la bande s'affiche hors écran. */
    .temps-window,
    .temps-window.temps-positioned {
        left: 0 !important;
        right: 0 !important;
        top: auto !important;
        bottom: calc(var(--of-tabbar-h) + env(safe-area-inset-bottom)) !important;
        width: 100% !important;
        max-width: 100% !important;
        border-radius: 0;
    }
    .temps-header { cursor: default; }

    /* ── Panneau des Notes ───────────────────────────────────────────────────────────────────
       Latéral, il prend 92 vw et laisse une bande morte à gauche ; ancré en bas, il tombe sous
       le pouce et se referme d'un glissement — c'est la forme naturelle d'un panneau au doigt.
       `offcanvas-bottom` est une classe du thème, vérifiée présente dans of-app.css. */
    .of-notes-panel.offcanvas-end {
        top: auto;
        bottom: 0;
        left: 0;
        right: 0;
        width: 100% !important;      /* bat le `width: 420px` de of-notes.css, chargé avant */
        max-width: 100% !important;
        height: 80dvh;
        border-left: 0;
        border-top: 1px solid var(--ct-border-color);
        transform: translateY(100%);
    }
    .of-notes-panel.offcanvas-end.showing,
    .of-notes-panel.offcanvas-end.show { transform: none; }

    /* ── Largeurs figées des barres de filtres ───────────────────────────────────────────────
       44 déclarations `style="min-width:150px"` (et voisines) sont posées EN LIGNE sur les
       filtres des listes — ex. app/finance/devis.php:34. Un attribut style ne se bat qu'à coups
       de !important. Plutôt que modifier 44 fichiers, on neutralise en un point, scopé au
       conteneur du patron « barre de filtres » introduit au lot 2 : la règle reste donc inerte
       tant qu'une page n'a pas explicitement adopté le patron. */
    .of-barre-filtres [style*="min-width"] {
        min-width: 0 !important;
        width: 100% !important;
    }
}
