/* ==========================================================================
   mobile.css — interface telephone d'aiudit
   --------------------------------------------------------------------------
   REGLE ABSOLUE : ce fichier ne doit JAMAIS modifier le rendu sur ordinateur.
   Toutes les regles vivent donc a l'interieur de @media (max-width: 820px).
   Le seul bloc hors de cette media query masque les elements injectes par
   mobile.js (prefixe .m-) au cas ou la fenetre serait elargie apres coup.

   style.css n'est pas touche : mobile.css est charge APRES lui dans chaque
   template, il gagne donc a specificite egale et n'a pas besoin de !important
   dans la majorite des cas.

   Sommaire
     0.  Jetons mobiles
     1.  Fondations (body, safe areas, cibles tactiles)
     2.  Barre de titre (app-header compacte)
     3.  Barre d'onglets basse (.m-tabbar, injectee)
     4.  Feuilles modales (.m-sheet, injectees)
     5.  Contenu : main, panels, toolbars
     6.  Listes de mandats
     7.  Detail mandat : onglets, sources, depot, reglages
     8.  Analyse : cycles, tables d'arbre, selecteur d'annees
     9.  Tables -> cartes (compte, document, ecriture, mapping)
     10. Modales plein ecran
     11. Tickets & messages
     12. Mapping
     13. Mon compte & cabinets
     14. Connexion / MFA / mot de passe
     15. Widget ticket flottant
     16. Bandeau d'alerte
     17. Dictee vocale
     18. Accessibilite & preferences systeme
   ========================================================================== */

@media (max-width: 820px) {

  /* ---------- 0. Jetons mobiles ------------------------------------------ */

  :root {
    --m-gutter: 14px;
    --m-tabbar-h: 54px;
    --m-header-h: 52px;          /* recalcule par mobile.js sur la vraie hauteur */
    --m-safe-top: env(safe-area-inset-top, 0px);
    --m-safe-bottom: env(safe-area-inset-bottom, 0px);
    --m-tap: 44px;               /* cible tactile minimale (HIG / Material) */
    --m-surface: rgba(255, 255, 255, .86);
    /* La feuille du telephone est l'equivalent tactile de la fenetre modale
       de l'ordinateur : meme arrondi, une seule grande surface flottante
       pour tout le site. */
    --m-radius-sheet: var(--radius-lg);
  }

  /* ---------- 1. Fondations ---------------------------------------------- */

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

  body {
    -webkit-tap-highlight-color: transparent;
    overscroll-behavior-y: contain;
    font-size: 15px;
    /* Place pour la barre d'onglets basse + l'encoche du bas. */
    padding-bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom));
  }

  /* Pages sans barre d'onglets (connexion, MFA...). */
  body.m-no-tabbar { padding-bottom: var(--m-safe-bottom); }

  /* Verrouillage du defilement quand une feuille modale est ouverte. */
  body.m-scroll-locked {
    position: fixed;
    inset: 0;
    overflow: hidden;
    width: 100%;
  }

  /* Les zones defilables gardent l'inertie iOS. */
  .tree-table-wrap,
  .modal-table-wrap,
  .toolbar,
  .cycles-nav,
  .m-sheet-body,
  .m-chiprow {
    -webkit-overflow-scrolling: touch;
  }

  /* Barres de defilement masquees sur les rangees horizontales. */
  .toolbar::-webkit-scrollbar,
  .cycles-nav::-webkit-scrollbar,
  .m-chiprow::-webkit-scrollbar,
  .dossier-tabs::-webkit-scrollbar { display: none; }
  .toolbar, .cycles-nav, .m-chiprow, .dossier-tabs { scrollbar-width: none; }

  /* 16px minimum sur les champs : en dessous, iOS zoome au focus. */
  input, select, textarea,
  .ticket-card-texte-edit,
  .ticket-card-suivi-input {
    font-size: 16px !important;
  }

  /* Cibles tactiles : tout ce qui se clique fait au moins 44px de haut. */
  .icon-btn,
  .btn-link,
  .btn-primary,
  .dossier-sort-btn,
  .cycles-nav-btn,
  .unit-btn,
  .modal-close,
  .toolbar-search-clear {
    min-height: var(--m-tap);
  }

  .btn-sm { min-height: 38px; }

  /* ... sauf un bouton primaire : il n'y en a jamais deux dans une meme rangee
     dense, c'est l'action principale de son formulaire ("Publier l'alerte",
     "Envoyer a l'agent"). Il reprend la cible standard. La regle vient apres
     .btn-sm, a specificite superieure : elle gagne dans les deux sens. */
  .btn-primary.btn-sm { min-height: var(--m-tap); }

  /* Les boutons purement iconiques restent carres. */
  .btn-icon-only { min-width: var(--m-tap); }

  /* ---------- 2. Barre de titre ------------------------------------------ */

  .app-header {
    background: var(--m-surface);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    z-index: 40;
  }

  .app-header-inner {
    max-width: none;
    padding: .45rem var(--m-gutter);
    padding-top: calc(.45rem + var(--m-safe-top));
    gap: .5rem;
    min-height: 52px;
  }

  .app-logo { height: 24px; }

  /* La version reste utile (le badge suit le deploiement) mais discrete. */
  .app-version {
    font-size: var(--fs-3xs);
    padding: .1rem .4rem;
  }

  /* La navigation du header part dans la barre basse : on la masque ici.
     On garde en revanche les controles metier (pastille d'avancement,
     tuile cabinet, icone de type) qui appartiennent au mandat ouvert. */
  .app-header .app-nav { display: none; }
  .app-header .app-nav-fin > .icon-btn,
  .app-header .app-nav-end > .icon-btn,
  .app-header > .app-header-inner > .icon-btn { display: none; }

  /* La fenetre de reglages part avec le reste de la navigation : elle ne fait
     que regrouper Mapping et Mon compte, que la barre basse porte deja chacun
     dans son onglet. Elle s'ouvre d'ailleurs au survol, et un telephone ne
     survole rien. Ses liens restent dans le document, seulement invisibles :
     c'est la que `mobile.js` va chercher les compteurs. */
  .app-header .app-reglages { display: none; }

  .app-nav-fin,
  .app-nav-end { margin-left: auto; }

  .dossier-header-right {
    gap: .4rem;
    margin-left: auto;
  }

  .app-titre,
  .dossier-detail-titre {
    font-size: var(--fs-md);
    min-width: 0;
    flex: 1 1 auto;
  }

  .dossier-detail-badges { gap: .25rem; }

  /* Bouton retour injecte par mobile.js quand la page en a un dans sa nav. */
  .m-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Cible tactile pleine : c'est le bouton le plus utilise de l'interface,
       il ne peut pas etre le seul a rester sous les 44px. Le retrait negatif
       compense le rembourrage ajoute pour que l'icone reste alignee sur la
       gouttiere de la page. */
    width: var(--m-tap);
    height: var(--m-tap);
    flex: none;
    margin-left: -.6rem;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--gray-6);
    cursor: pointer;
    transition: background .15s var(--ease);
  }
  .m-back:active { background: var(--gray-2); }
  .m-back .icon { width: var(--icon-xl); height: var(--icon-xl); stroke-width: 2; }

  /* ---------- 3. Barre d'onglets basse ----------------------------------- */

  .m-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 70;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: rgba(255, 255, 255, .9);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    border-top: 1px solid var(--gray-3);
    padding-bottom: var(--m-safe-bottom);
  }

  .m-tab {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    height: var(--m-tabbar-h);
    text-decoration: none;
    color: var(--gray-5);
    font-size: var(--fs-3xs);
    font-weight: 600;
    letter-spacing: .01em;
    -webkit-tap-highlight-color: transparent;
    transition: color .15s var(--ease);
  }

  .m-tab .icon {
    width: var(--icon-xl);
    height: var(--icon-xl);
    stroke-width: 1.7;
    transition: transform .2s var(--ease-spring);
  }

  .m-tab:active .icon { transform: scale(.88); }
  .m-tab.active { color: var(--cyan); }
  .m-tab.active .icon { stroke-width: 2.1; }

  /* Petit trait au-dessus de l'onglet actif, comme une app native. */
  .m-tab.active::before {
    content: "";
    position: absolute;
    top: 0;
    width: 26px;
    height: 2.5px;
    border-radius: 0 0 999px 999px;
    background: var(--cyan);
  }

  .m-tab-badge {
    position: absolute;
    top: 4px;
    left: 50%;
    margin-left: 4px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--red);
    color: #fff;
    font-size: var(--fs-3xs);
    font-weight: 700;
    line-height: 16px;
    text-align: center;
  }

  /* La seconde pastille -- ce qui attend un geste -- se pose a droite de la
     premiere plutot que par-dessus, et garde l'orange de « À valider ». */
  .m-tab-badge-action {
    margin-left: 22px;
    background: var(--orange);
  }

  /* ---------- 4. Feuilles modales (bottom sheets) ------------------------ */

  .m-sheet-backdrop {
    position: fixed;
    inset: 0;
    z-index: 90;
    background: rgba(14, 18, 22, .42);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    opacity: 0;
    transition: opacity .22s var(--ease);
  }
  .m-sheet-backdrop.open { opacity: 1; }

  .m-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 91;
    display: flex;
    flex-direction: column;
    max-height: 88vh;
    background: #fff;
    border-radius: var(--m-radius-sheet) var(--m-radius-sheet) 0 0;
    box-shadow: var(--shadow-lg);
    transform: translateY(100%);
    transition: transform .28s var(--ease);
    padding-bottom: var(--m-safe-bottom);
    touch-action: pan-y;
  }
  .m-sheet.open { transform: translateY(0); }

  .m-sheet-grip {
    flex: none;
    display: flex;
    justify-content: center;
    padding: .55rem 0 .2rem;
    cursor: grab;
  }
  .m-sheet-grip::before {
    content: "";
    width: 38px;
    height: 4px;
    border-radius: 999px;
    background: var(--gray-3);
  }

  .m-sheet-header {
    flex: none;
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .2rem var(--m-gutter) .6rem;
    border-bottom: 1px solid var(--gray-2);
  }

  .m-sheet-title {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: 700;
    flex: 1;
  }

  .m-sheet-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--m-tap);
    height: var(--m-tap);
    flex: none;
    border: 0;
    border-radius: 50%;
    background: var(--gray-1);
    color: var(--gray-5);
    cursor: pointer;
  }
  .m-sheet-close .icon { width: var(--icon-md); height: var(--icon-md); }

  .m-sheet-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: .8rem var(--m-gutter) 1.1rem;
    overscroll-behavior: contain;
  }

  /* Le contenu deplace dans une feuille reprend une mise en page verticale. */
  .m-sheet-body .toolbar {
    display: block;
    margin: 0 0 .8rem;
    overflow: visible;
  }
  .m-sheet-body .toolbar-group {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: .9rem;
  }
  .m-sheet-body .toolbar-label {
    width: 100%;
    margin-bottom: .1rem;
  }

  /* Tout formulaire deplace dans une feuille se met en colonne pleine largeur,
     avec des cibles a --m-tap : c'est la seule facon de saisir au pouce. */
  .m-sheet-body input[type="text"],
  .m-sheet-body input[type="search"],
  .m-sheet-body input[type="number"],
  .m-sheet-body input[type="date"],
  .m-sheet-body select,
  .m-sheet-body textarea {
    width: 100%;
    min-height: var(--m-tap);
  }

  .m-sheet-body .btn-primary,
  .m-sheet-body .btn-secondary {
    width: 100%;
    min-height: var(--m-tap);
    justify-content: center;
  }

  /* "Annuler" est un lien texte sur ordinateur : au doigt il lui faut la
     meme surface que les autres actions, sinon il est increvable a viser. */
  .m-sheet-body .btn-link {
    display: flex;
    width: 100%;
    min-height: var(--m-tap);
    align-items: center;
    justify-content: center;
    margin-top: .2rem;
  }

  /* ---------- 5. Contenu : main, panels, toolbars ------------------------ */

  .app-main {
    max-width: none;
    padding: .85rem var(--m-gutter) 1.2rem;
  }

  .panel { margin-bottom: 1.1rem; }

  .toolbar {
    /* Le champ de recherche prend sa propre rangee (cf. plus bas) ; les
       groupes de boutons se replient sur la ou les rangees suivantes. */
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: center;
    gap: .5rem;
    margin-bottom: .8rem;
    padding-bottom: .15rem;
  }

  .toolbar-group { flex: none; gap: .5rem; }

  /* mobile.js remonte la recherche en tete (order: -1). Sur une rangee
     partagee elle tombait a 139px, illisible : on lui donne la rangee
     entiere. `flex-basis: 100%` force le retour a la ligne des suivants. */
  .toolbar-search-group {
    flex: 1 0 100%;
    min-width: 0;
  }

  /* La boite de recherche est bordee et l'entoure de 2px : c'est le champ qui
     recoit le focus, pas la boite, donc c'est lui qui doit porter la cible. */
  .toolbar-search input { min-height: var(--m-tap); }

  .toolbar-label { font-size: var(--fs-sm); }

  /* Rangee de puces horizontale (filtres, annees, cycles...). */
  .m-chiprow {
    display: flex;
    gap: .4rem;
    overflow-x: auto;
    padding: .1rem 0 .35rem;
    margin: 0 calc(var(--m-gutter) * -1) .5rem;
    padding-left: var(--m-gutter);
    padding-right: var(--m-gutter);
  }

  .m-chip {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    height: var(--m-tap);
    padding: 0 .85rem;
    border: 1px solid var(--gray-3);
    border-radius: 999px;
    background: #fff;
    color: var(--gray-6);
    font-family: var(--sans);
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease);
  }
  .m-chip .icon { width: var(--icon); height: var(--icon); }
  .m-chip:active { transform: scale(.97); }
  .m-chip.active {
    background: var(--cyan-pale);
    border-color: var(--cyan);
    color: var(--cyan-dark);
  }
  .m-chip-count {
    display: inline-block;
    min-width: 18px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--gray-2);
    color: var(--gray-5);
    font-size: var(--fs-2xs);
    line-height: 18px;
    text-align: center;
  }
  .m-chip.active .m-chip-count { background: var(--cyan); color: #fff; }

  /* Bouton d'action flottant (nouveau mandat, nouveau ticket...). */
  .m-fab {
    position: fixed;
    right: var(--m-gutter);
    bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom) + 14px);
    z-index: 65;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    height: 50px;
    min-width: 50px;
    padding: 0 1.05rem;
    border: 0;
    border-radius: 999px;
    background: var(--cyan);
    color: #fff;
    font-family: var(--sans);
    font-size: var(--fs);
    font-weight: 700;
    box-shadow: 0 8px 22px rgba(0, 174, 239, .34), var(--shadow-md);
    cursor: pointer;
    transition: transform .18s var(--ease-spring), box-shadow .18s var(--ease);
  }
  .m-fab .icon { width: var(--icon-lg); height: var(--icon-lg); stroke-width: 2.2; }
  .m-fab:active { transform: scale(.94); }

  /* ---------- 6. Listes de mandats --------------------------------------- */

  /* Filtres et formulaire de creation partent dans des feuilles modales :
     mobile.js les deplace, on masque juste la version en ligne restante. */
  #filtre-tuiles,
  #filtre-tuiles-cabinets,
  #filtre-etats { flex-wrap: wrap; }

  .filtre-tuile,
  .filtre-etat-tuile { min-width: 38px; min-height: 38px; }

  .dossier-list-header {
    display: flex;
    align-items: center;
    gap: .25rem;
    padding: 0 .1rem .4rem;
    border-top: none;
  }

  /* Sur ordinateur, les deux boutons de tri sont cales par des marges fixes
     pour tomber pile au-dessus des colonnes "nom" et "etat" de la ligne. La
     ligne mobile est sur deux rangees : ces reperes ne correspondent plus a
     rien et laissaient les libelles flotter au milieu du vide. On les remet
     colles a gauche, comme une simple paire "trier par". */
  .dossier-list-header .dossier-sort-btn[data-sort="nom"] { margin-left: 0; }
  .dossier-list-header .dossier-sort-btn[data-sort="etat"],
  .dossier-list-header .dossier-sort-btn[data-sort="cloture"] {
    margin-left: 0;
    margin-right: 0;
  }
  /* « Exercice » vaut 68px sur ordinateur pour tomber sur sa colonne ; ici il
     n'y a pas de colonne, et cette largeur coupait son libelle. */
  .dossier-list-header .dossier-sort-btn[data-sort="cloture"] { width: auto; }

  .dossier-sort-btn { font-size: var(--fs-xs); padding: .3rem .5rem; }

  .dossier-list {
    border: 1px solid var(--gray-3);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
  }

  .dossier-item + .dossier-item { border-top: 1px solid var(--gray-2); }

  /* Sur ordinateur la ligne de mandat tient sur une seule rangee : tuile
     cabinet, icone de type, nom, compteur FEC, etat, assigne. A 375px, les
     cinq elements de droite sont incompressibles (~230px) et etranglaient le
     nom a 52px — soit deux mots par ligne pour une raison sociale.
     On passe donc sur deux rangees : le nom occupe seul la premiere, en pleine
     largeur ; tout le reste devient une ligne de metadonnees en dessous.
     Aucune information n'est retiree, elle est seulement re-repartie. */
  .dossier-link {
    display: grid;
    grid-template-columns: auto auto auto 1fr;
    align-items: center;
    column-gap: .5rem;
    row-gap: .25rem;
    padding: .7rem .75rem;
    min-height: 58px;
  }

  /* La tuile cabinet sert de reperage visuel de la ligne entiere : elle
     s'etend sur les deux rangees, comme une vignette. */
  .dossier-cabinet-slot {
    grid-column: 1;
    grid-row: 1 / span 2;
    width: 38px;
  }

  .dossier-nom {
    grid-column: 2 / -1;
    grid-row: 1;
    font-size: var(--fs-md);
    font-weight: 600;
    line-height: 1.25;
    /* Deux lignes maximum : les raisons sociales sont souvent longues. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
  }

  .dossier-icon { grid-column: 2; grid-row: 2; }

  .dossier-fec-count {
    grid-column: 3;
    grid-row: 2;
    font-size: var(--fs-2xs);
    /* Annule le `margin-left: auto` du bureau, qui poussait le compteur au
       bout de la rangee flex ; ici la grille place deja les colonnes. */
    margin-left: 0;
  }

  /* Les trois carres d'avancement (urgent / en cours / termine) font 28px et
     sont separes de 6px : au doigt, une ligne sur trois change d'etat par
     erreur — et c'est une action qui s'ecrit en base sans confirmation. On les
     amene a la cible standard, ce qui porte le rang de 96 a 144px. */
  .dossier-statut-group {
    grid-column: 4;
    grid-row: 2;
    justify-self: start;
    gap: .4rem;
  }

  /* Le slot est fige a 96px sur ordinateur pour que le badge cadenas, plus
     etroit que le rang, ne decale pas le compteur FEC de la ligne voisine.
     A 144px le rang en debordait et passait sous l'avatar du collaborateur ;
     ici la grille aligne deja les colonnes, la largeur fixe ne sert a rien. */
  .dossier-statut-slot { width: auto; }
  .avancement-seg { width: var(--m-tap); height: var(--m-tap); }

  /* Les collaborateurs secondaires reprennent depuis V3.35 le meme groupe de
     bulles chevauchees horizontalement qu'sur ordinateur (22px, deja lisible
     au doigt sans infobulle) : plus de reglage mobile specifique necessaire. */

  /* Le chevron mange de la place pour rien : la ligne entiere est cliquable. */
  .dossier-chevron { display: none; }

  .dossier-empty { padding: 1.6rem .8rem; text-align: center; }

  /* Formulaire de creation, quand il reste en ligne. */
  .dossier-form {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    align-items: stretch;
  }
  /* dossiers.js ouvre/ferme le formulaire via l'attribut hidden. */
  .dossier-form[hidden] { display: none; }
  .dossier-form input,
  .dossier-form select { width: 100%; }

  /* ---------- 7. Detail mandat ------------------------------------------- */

  /* Onglets Analyse / Source / Mandat en controle segmente. */
  .dossier-tabs {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 2px;
    margin-bottom: 1rem;
    padding: 3px;
    border: 0;
    border-radius: 999px;
    background: var(--gray-1);
    box-shadow: inset 0 0 0 1px var(--gray-2);
  }

  .dossier-tab-btn {
    position: relative;
    /* Les memes boutons servent d'onglets au mapping, hors controle segmente :
       ils y restaient a 33px de haut. */
    min-height: var(--m-tap);
    padding: .55rem .4rem;
    border: 0;
    border-radius: 999px;
    font-size: var(--fs-sm);
    color: var(--gray-5);
    margin-bottom: 0;
    transition: background .18s var(--ease), color .18s var(--ease);
  }

  .dossier-tab-btn.active {
    background: #fff;
    color: var(--black);
    border-bottom-color: transparent;
    box-shadow: var(--shadow-sm);
  }

  .dossier-tab-statut-dot { top: 6px; right: 8px; }

  /* Onglet Source : la zone d'import remonte au-dessus de la liste des fichiers.
     Sur ordinateur la liste tient dans l'ecran et la zone reste visible en
     dessous ; sur telephone, quatre exercices font deja 1 000px de cartes et il
     fallait defiler jusqu'en bas pour importer un FEC — l'action principale de
     l'onglet. Le [hidden] doit etre retabli explicitement : le `display: flex`
     ci-dessous l'emporterait sur le [hidden] par defaut du navigateur et les
     deux autres onglets afficheraient leur contenu en meme temps. */
  #tab-panel-source { display: flex; flex-direction: column; }
  #tab-panel-source[hidden] { display: none; }
  #tab-panel-source > #drop-section { order: -1; }

  /* Zone de depot des FEC. */
  .drop-zone { padding: 1.5rem 1rem; }
  .drop-zone-icon { width: 30px; height: 30px; }
  .drop-zone-title { font-size: var(--fs-md); }
  .drop-zone-sub { font-size: var(--fs-xs); }

  /* Reglages du mandat : tout passe en colonne. */
  .reglages-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .5rem;
  }
  .reglages-row input,
  .reglages-row select,
  .reglages-row .btn-primary { width: 100%; }
  /* Nom, SIREN, jour et mois de cloture heritaient du champ dense d'ordinateur
     (41px). Ce sont les seuls champs de saisie du mandat : ils prennent la
     cible tactile, et 16px de texte pour que iOS ne zoome pas a la prise de
     focus. Le jour et le mois gardent leur width: 4.5em en style inline, que
     la regle ci-dessus ne peut de toute facon pas surcharger. */
  .reglages-row input,
  .reglages-row select { min-height: var(--m-tap); font-size: 16px; }
  .reglages-group { margin-bottom: 1.4rem; }
  .reglages-hint { font-size: var(--fs-xs); }

  /* Exception : la date de cloture est un couple jour/mois. Les deux champs
     portent un width: 4.5em en style inline, donc la colonne les empilait en
     deux boites de 72px l'une sous l'autre — on lisait "JJ" puis "MM" comme
     deux reglages distincts. On remet la rangee, le bouton passe dessous. */
  .reglages-row:has(> #reglages-cloture-jour-input) {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
  .reglages-row:has(> #reglages-cloture-jour-input) > .btn-primary { flex: 1 0 100%; }

  .dossier-assigne-select { min-height: var(--m-tap); }

  .cabinet-toggle {
    width: 100%;
    overflow-x: auto;
  }
  /* Les segments faisaient 37x26 : a la souris c'est confortable, au doigt on
     tape a cote. Ils s'etalent sur la largeur du groupe et montent a la cible
     standard. `flex: 1 0 auto` plutot que `1` pour qu'un cabinet de plus fasse
     defiler la rangee au lieu de comprimer les segments. */
  .cabinet-toggle .cabinet-seg {
    flex: 1 0 auto;
    min-height: var(--m-tap);
  }
  /* Les deux carres du type de mandat montent a la cible tactile, comme ceux
     de l'avancement (.avancement-seg). Ils ne s'etirent pas sur la largeur du
     groupe : un carre etire n'est plus un carre, et il ne se lirait plus comme
     les autres selecteurs d'etat de la page. */
  .type-pill {
    /* La rangee des reglages etire ce qu'elle contient sur toute la largeur :
       sans cela, le groupe s'etendrait a 360px derriere deux carres de 44. */
    align-self: flex-start;
  }
  .type-pill .type-seg {
    width: var(--m-tap);
    height: var(--m-tap);
  }
  .type-pill .type-seg-icon { width: var(--icon); height: var(--icon); }

  /* ---------- 8. Analyse : cycles et tables d'arbre ---------------------- */

  .cycles-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: .4rem;
    padding: .5rem 0;
    margin: 0 calc(var(--m-gutter) * -1) .5rem;
    padding-left: var(--m-gutter);
    padding-right: var(--m-gutter);
    scroll-snap-type: x proximity;
  }

  .cycles-nav-btn {
    flex: none;
    scroll-snap-align: center;
    padding: .4rem .7rem;
    font-size: var(--fs-xs);
  }

  .cycle-section-title { font-size: var(--fs-md); }

  #tree-tables-container > .tree-table-wrap { margin-bottom: 1.2rem; }

  .tree-table-wrap {
    overflow-x: auto;
    border-radius: var(--radius);
    scroll-snap-type: x proximity;
  }

  .tree-table { font-size: var(--fs-xs); }

  /* Table a largeur naturelle : la colonne Poste est figee a gauche et les
     colonnes numeriques defilent horizontalement sous le doigt. */
  #tree-tables-container .tree-table {
    width: max-content;
    min-width: 100%;
  }
  /* Arithmetique de la rangee visible, a 375px de large (moins les gouttieres
     du panel il reste 347px de fenetre) : la colonne Poste est figee, donc elle
     mange en permanence sa largeur. Avec 190 + 106 il ne restait que 51px pour
     la deuxieme annee — on ne pouvait jamais comparer N et N-1 d'un coup d'oeil,
     alors que c'est tout l'interet de la feuille maitresse.
     148 + 98 + 98 = 344 : les deux exercices tiennent ensemble, la Variation
     reste accessible en poussant du doigt vers la gauche. */
  #tree-tables-container .tree-table col.col-poste { width: 148px; }
  #tree-tables-container .tree-table col.col-annee { width: 98px; }
  #tree-tables-container .tree-table col.col-variation { width: 100px; }

  /* Colonne masquee par le selecteur d'annees (mobile.js). En table-layout
     fixed, une largeur nulle sur le <col> replie vraiment la colonne : pas
     besoin de display:none, qui decalerait les cellules suivantes. */
  #tree-tables-container .tree-table col.m-col-off { width: 0; }
  /* `.tree-table thead th.th-variation` (0-2-2), declare plus bas, l'emportait
     sur `.tree-table th.m-col-off` et gardait sa font-size : l'en-tete
     "VARIATION" en nowrap imposait alors 54px de min-content a une colonne
     censee etre repliee. Le prefixe #tree-tables-container passe la regle a
     (1-2-2) et tranche independamment de l'ordre de declaration. */
  #tree-tables-container .tree-table th.m-col-off,
  #tree-tables-container .tree-table td.m-col-off {
    padding-left: 0;
    padding-right: 0;
    font-size: 0;
    overflow: hidden;
    white-space: nowrap;
    /* Sans cela il reste un liseré d'1px par colonne repliee. */
    border-left-width: 0;
    border-right-width: 0;
  }

  .tree-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--gray-1);
    padding: .45rem .5rem;
    font-size: var(--fs-2xs);
    /* "2024 (en cours)" en nowrap impose une largeur mini de 113px a la colonne
       et faisait deborder la grille reglee juste au-dessus. On autorise le
       retour a la ligne : le millesime reste sur la premiere ligne, la mention
       d'exercice ouvert passe dessous. Rien n'est perdu. */
    white-space: normal;
    line-height: 1.2;
  }
  .tree-table thead th.th-variation { font-size: var(--fs-3xs); }

  .tree-table td { padding: .42rem .5rem; }

  /* Colonne Poste figee a gauche. Le fond doit etre opaque pour que les
     colonnes numeriques passent dessous sans transparence ; les teintes de
     controle sont donc reappliquees en background-image par-dessus le blanc. */
  .tree-table td.label-cell,
  .tree-table thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 3;
    background-color: #fff;
    border-right: 1px solid var(--gray-2);
  }
  .tree-table thead th:first-child { z-index: 4; background-color: var(--gray-1); }

  .tree-table tr[data-controle="valide"] > td.label-cell {
    background-image: linear-gradient(var(--controle-valide-bg), var(--controle-valide-bg));
  }
  .tree-table tr[data-controle="probleme"] > td.label-cell {
    background-image: linear-gradient(var(--controle-probleme-bg), var(--controle-probleme-bg));
  }

  /* Le hover n'existe pas au doigt : on neutralise pour eviter les etats
     colles apres un tap sur iOS. */
  .tree-table tbody tr:not(.row-verification):hover td { background: none; }
  .tree-table tbody tr:not(.row-verification):hover td.label-cell { background-color: #fff; }

  .tree-table tr.level-0 > td.label-cell { font-size: var(--fs-sm); }

  /* Le chevron reste etroit : le nom du poste juste a cote plie et deplie la
     meme ligne (cf. renderNodeRow dans app.js), et c'est lui la vraie cible —
     plusieurs centaines de pixels. L'elargir couterait de l'indentation a
     chaque niveau de l'arbre, sur un ecran qui n'en a pas. On lui donne
     seulement la hauteur de la ligne. */
  .row-toggle { min-height: 26px; }
  .row-account,
  .row-label-text { padding: .1rem 0; }

  /* Deplier / replier un cycle : deux boutons pousses a droite du titre de
     section, ou il reste une centaine de pixels libres. Aucune raison qu'ils
     restent a 24px. */
  .cycle-section-toggle-btn { width: 38px; height: 38px; }
  .cycle-section-toggle-btn .icon { width: var(--icon-md); height: var(--icon-md); }

  /* Le bouton de commentaire d'un controle est dessine pour la souris : 20x20,
     et surtout opacity .35 tant que la ligne n'est pas survolee. Au doigt il n'y
     a pas de survol, donc il resterait invisible en permanence. On le rend
     toujours lisible et on l'agrandit. 28px et pas 44 : ces lignes d'arbre font
     26px de haut et il y en a des centaines — c'est la meme exception assumee
     que .row-toggle juste au-dessus, la cible reste doublee en surface. */
  .controle-commentaire-btn {
    width: 28px;
    height: 28px;
    opacity: .75;
  }
  .controle-commentaire-btn.a-commentaire { opacity: 1; }
  .controle-commentaire-btn .icon { width: var(--icon); height: var(--icon); }

  /* Le popover de commentaires est ancre en absolu sous son bouton, a une
     position calculee en JS pour une souris. Sur telephone ce bouton vit dans
     une ligne d'arbre qui defile lateralement, souvent tres bas dans la page :
     le popover atterrissait de biais, sous la barre d'onglets, ou coupe par le
     bord. On le repose au bas de l'ecran, comme les autres feuilles de
     l'interface. app.js pose top/left en style inline, d'ou le !important —
     confine au media query, l'affichage ordinateur ne le voit jamais. */
  .commentaire-popover {
    position: fixed !important;
    top: auto !important;
    left: 0 !important;
    right: 0;
    bottom: var(--m-clavier, 0px);
    width: auto;
    max-width: none;
    /* Le clavier repousse la feuille vers le haut : on lui retire d'autant de
       hauteur, sinon son en-tete sortirait par le haut de l'ecran. */
    max-height: calc(82vh - var(--m-clavier, 0px));
    display: flex;
    flex-direction: column;
    border: none;
    border-radius: var(--m-radius-sheet) var(--m-radius-sheet) 0 0;
    box-shadow: 0 -8px 30px rgba(0, 0, 0, .18);
    animation: none;
  }

  .commentaire-popover-header {
    padding: .85rem 1rem;
    font-size: var(--fs-lg);
  }

  .commentaire-popover-close {
    width: var(--m-tap);
    height: var(--m-tap);
    margin-right: -.4rem;
  }

  .commentaire-popover-close .icon { width: var(--icon-md); height: var(--icon-md); }

  /* La liste prend la place restante entre l'en-tete et le formulaire, au lieu
     de sa hauteur fixe de 260px : sur un ecran haut on voit plus de messages,
     sur un ecran court le formulaire reste visible. */
  .commentaire-popover-list {
    flex: 1;
    min-height: 0;
    max-height: none;
    padding: .5rem 1rem;
  }

  .commentaire-avatar { width: 26px; height: 26px; font-size: var(--fs-2xs); }
  .commentaire-auteur-nom { font-size: var(--fs); }
  .commentaire-date { font-size: var(--fs-sm); }
  .commentaire-texte { font-size: var(--fs-md); }

  .commentaire-delete-btn { width: 32px; height: 32px; }
  .commentaire-delete-btn .icon { width: var(--icon); height: var(--icon); }

  /* Le formulaire est colle en bas de la feuille : il garde la marge de la
     barre gestuelle sous lui, sauf quand le clavier est ouvert (le decalage
     est alors porte par la feuille entiere). */
  .commentaire-popover-form {
    padding: .7rem 1rem calc(.8rem + env(safe-area-inset-bottom));
  }

  .commentaire-popover-texte {
    font-size: 16px;
    min-height: 62px;
    padding: .55rem 2.4rem .55rem .65rem;
  }

  .commentaire-popover-form .commentaire-add-btn {
    align-self: stretch;
    min-height: var(--m-tap);
    font-size: var(--fs-md);
  }

  .variation-cell { font-size: var(--fs-2xs); }

  /* Selecteur d'annees affichees, injecte au-dessus des tables. */
  .m-yearbar {
    display: flex;
    align-items: center;
    gap: .4rem;
    overflow-x: auto;
    margin-bottom: .6rem;
  }
  .m-yearbar-label {
    flex: none;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--gray-4);
    padding-right: .1rem;
  }

  .controle-banner { font-size: var(--fs-sm); padding: .6rem .7rem; }

  /* Quand l'equilibre est bon, app.js vide le bandeau sans le masquer : sur
     ordinateur ce reliquat de 19px passe inapercu, sur telephone il ouvre un
     trou entre la barre d'outils et les cycles. */
  .controle-banner:empty { display: none; }

  /* Menu de selection des annees. */
  .annees-menu { width: min(88vw, 320px); }

  /* ---------- 9. Tables -> cartes ---------------------------------------- */

  /* mobile.js pose .m-cards sur les tables trop larges pour un telephone et
     copie l'en-tete de colonne dans data-label sur chaque cellule. La table
     devient alors une liste de cartes libelle / valeur. */

  table.m-cards,
  table.m-cards > tbody,
  table.m-cards > tbody > tr,
  table.m-cards > tbody > tr > td {
    display: block;
    width: auto;
  }

  table.m-cards { border-collapse: separate; min-width: 0; }
  table.m-cards > thead { display: none; }

  /* Les regles d'affichage ci-dessus l'emportent sur le [hidden] par defaut du
     navigateur (specificite superieure) : il faut le retablir explicitement,
     sinon les lignes filtrees (filtre du mapping des racines, notamment)
     resteraient visibles en mode cartes. */
  table.m-cards > tbody > tr[hidden],
  table.m-cards > tbody > tr > td[hidden] { display: none; }

  table.m-cards > tbody > tr {
    position: relative;
    /* Colonne flex plutot que bloc : l'empilement est identique, mais la carte
       peut alors reordonner ses cellules. La colonne titre n'est pas toujours la
       premiere du tableau (dans le visualiseur de FEC, "Libelle compte" est la
       cinquieme) et le nom se retrouvait en gras au milieu de la carte, sous des
       lignes anonymes. Le titre remonte en tete, cf. `order` plus bas. */
    display: flex;
    flex-direction: column;
    margin-bottom: .55rem;
    padding: .6rem .7rem;
    border: 1px solid var(--gray-3);
    border-radius: var(--radius);
    background: #fff;
    box-shadow: var(--shadow-sm);
  }

  table.m-cards > tbody > tr > td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    padding: .16rem 0;
    border: 0;
    font-size: var(--fs-sm);
    text-align: right;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  table.m-cards > tbody > tr > td::before {
    content: attr(data-label);
    flex: none;
    max-width: 45%;
    text-align: left;
    color: var(--gray-4);
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
  }

  /* Cellule vide ou colonne sans en-tete : inutile de l'afficher en carte. */
  table.m-cards > tbody > tr > td.m-empty { display: none; }

  /* Le numero d'ecriture est le seul chemin vers la piece complete. Rendu tel
     quel c'est un texte de 8x20px : impossible a viser au doigt, et rien ne dit
     qu'il est cliquable une fois le survol indisponible. En carte on lui donne
     la cible tactile standard et un fond qui le designe comme un bouton. */
  table.m-cards .row-ecriture-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--m-tap);
    min-height: var(--m-tap);
    padding: 0 .7rem;
    border-radius: var(--radius);
    background: var(--cyan-pale);
    font-weight: 600;
  }

  /* Cellule de titre : pleine largeur, sans etiquette, toujours en tete de carte
     quelle que soit sa position dans la ligne d'origine. */
  table.m-cards > tbody > tr > td.m-card-title {
    order: -1;
    /* Flex plutot que block : plusieurs titres commencent par une icone collee
       au texte (nom de fichier FEC, libelle de cycle). L'ecart vient du gap,
       et l'icone garde sa taille pendant que le libelle se replie. */
    display: flex;
    align-items: flex-start;
    gap: .4rem;
    margin-bottom: .3rem;
    padding-bottom: .35rem;
    border-bottom: 1px solid var(--gray-2);
    text-align: left;
    font-size: var(--fs);
    font-weight: 600;
    color: var(--black);
    /* Un nom de FEC fait 28 caracteres sans espace : sans cela il deborde de
       la carte au lieu de passer a la ligne. */
    overflow-wrap: anywhere;
  }
  table.m-cards > tbody > tr > td.m-card-title > .icon,
  table.m-cards > tbody > tr > td.m-card-title > svg { flex: none; }
  table.m-cards > tbody > tr > td.m-card-title::before { content: none; }

  /* Cellule d'actions (boutons) : alignee a droite, pas d'etiquette. */
  table.m-cards > tbody > tr > td.m-card-actions {
    display: flex;
    justify-content: flex-end;
    gap: .4rem;
    margin-top: .4rem;
    padding-top: .45rem;
    border-top: 1px solid var(--gray-2);
  }
  table.m-cards > tbody > tr > td.m-card-actions::before { content: none; }

  /* Montants : chiffres alignes, teinte conservee. */
  table.m-cards > tbody > tr > td.num {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
  }

  /* Ligne unique "Aucune ecriture" / "Chargement..." : pas de carte. */
  table.m-cards > tbody > tr > td[colspan] {
    display: block;
    text-align: center;
    color: var(--gray-5);
    font-size: var(--fs-sm);
  }
  table.m-cards > tbody > tr > td[colspan]::before { content: none; }

  /* Le marqueur de controle (bande verte/orange) devient un liseré gauche. */
  table.m-cards > tbody > tr[data-controle="valide"] { border-left: 4px solid var(--green); }
  table.m-cards > tbody > tr[data-controle="probleme"] { border-left: 4px solid var(--orange); }
  table.m-cards > tbody > tr[data-controle] > td { background: none; }
  table.m-cards > tbody > tr > td.controle-cell { display: none; }

  /* Ligne de solde de cloture : sur ordinateur deux filets cyan traversent la
     rangee de part en part. En cartes chaque cellule devient un bloc, donc
     chaque cellule redessinait ses deux filets — on lisait une pile de barres
     bleues au lieu d'un separateur. On rend le filet a la carte entiere. */
  table.modal-table.m-cards > tbody > tr.row-cloture {
    background: var(--cyan-pale);
    border: 2px solid var(--cyan);
  }
  table.modal-table.m-cards > tbody > tr.row-cloture > td {
    background: none;
    border-top: none;
    border-bottom: none;
  }

  .modal-table-wrap { padding: 0 0 1rem; overflow: visible; }

  .compte-detail-panel .modal-table-wrap { padding: 0 0 1rem; }

  /* Navigation compte precedent / suivant.
     Sur ordinateur les trois blocs (precedent, titre, suivant) tiennent sur une
     rangee. A 375px la colonne du milieu tombait a 130px : le numero de compte
     se coupait en deux lignes et les deux boutons voisins se reduisaient a un
     moignon ("10" pour 106 0000). On passe a deux rangees — le titre en entier
     au-dessus, la navigation dessous — via des zones nommees.
     Les zones sont indispensables : style.css place chaque enfant a la main
     (#compte-nav-prev { grid-column: 1 } etc.) pour survivre au masquage du
     bouton precedent, et ces regles en id survivraient a un simple changement
     de grid-template-columns — la colonne 3 serait recreee implicitement. */
  .modal-header .compte-nav {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "titre titre"
      "prev  next";
    align-items: center;
    gap: .5rem;
  }
  #compte-nav-prev { grid-area: prev; justify-self: start; }
  #compte-nav-next { grid-area: next; justify-self: end; }
  .modal-header .modal-compte-titre {
    grid-area: titre;
    display: block;
    justify-self: stretch;
    min-width: 0;
  }

  .compte-nav-btn {
    min-height: var(--m-tap);
    max-width: 100%;
    padding: .4rem .6rem;
    /* Seuls sur leur rangee, les deux boutons ont besoin d'une surface visible
       pour se lire comme des boutons et non comme du texte d'entete. */
    background: var(--gray-1);
  }
  .compte-nav-btn-lib { display: none; }
  .compte-nav-btn-num { font-size: var(--fs-sm); }

  /* Curseur de plage d'annees. Les deux <input type="range"> superposes portent
     pointer-events: none et seul le rond reprend la main : la cible reelle, ce
     n'est pas la barre, c'est le rond de 15px. On l'amene a 26px et on redonne
     de la hauteur a la piste pour qu'il ne mange pas le libelle au-dessus.
     (margin-top = (hauteur piste - hauteur rond) / 2, la piste fait 4px.) */
  .year-slider-group { width: 100%; }
  .year-range-slider { width: 100%; height: 34px; }

  .year-range-slider input[type="range"]::-webkit-slider-thumb {
    width: 26px;
    height: 26px;
    margin-top: -11px;
  }
  .year-range-slider input[type="range"]::-moz-range-thumb {
    width: 26px;
    height: 26px;
  }

  /* Case "non lettrees" : la case native fait 13px et son libelle 19px de haut.
     C'est le libelle qu'on vise au doigt, on lui donne la cible standard. */
  .filter-checkbox {
    min-height: var(--m-tap);
    margin-left: 0;
  }
  .filter-checkbox input[type="checkbox"] {
    width: 20px;
    height: 20px;
    flex: none;
  }

  .modal-filters {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .6rem;
  }

  .modal-compte-titre { font-size: var(--fs-lg); }
  .modal-compte-lib { font-size: var(--fs-sm); }

  /* ---------- 10. Modales plein ecran ------------------------------------ */

  .modal-overlay {
    padding: 0;
    align-items: stretch;
    justify-content: stretch;
  }

  .modal {
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    border-radius: 0;
    display: flex;
    flex-direction: column;
  }

  .modal-header {
    position: sticky;
    top: 0;
    z-index: 3;
    flex: none;
    gap: .5rem;
    padding: .7rem var(--m-gutter);
    padding-top: calc(.7rem + var(--m-safe-top));
    background: var(--m-surface);
    backdrop-filter: saturate(180%) blur(18px);
    -webkit-backdrop-filter: saturate(180%) blur(18px);
    border-bottom: 1px solid var(--gray-2);
  }

  .modal-close {
    min-width: var(--m-tap);
    margin-left: auto;
    /* Le bouton fermer ne se comprime jamais : c'est la seule sortie de la vue. */
    flex: none;
  }

  /* Le bloc titre d'un en-tete de modale est un flex item, donc min-width: auto :
     il ne peut pas descendre sous la largeur de son mot le plus long. Un nom de
     FEC fait 28 caracteres sans espace, le bloc restait a 332px et poussait la
     croix a 398px — la page entiere debordait de 23px vers la droite. On
     autorise le retrecissement et on laisse le nom se replier. */
  .modal-header > div { min-width: 0; }
  .modal-header h2 { overflow-wrap: anywhere; }

  .modal > .modal-table-wrap,
  .modal > .modal-filters {
    padding-left: var(--m-gutter);
    padding-right: var(--m-gutter);
  }

  .modal > .modal-table-wrap {
    flex: 1 1 auto;
    overflow-y: auto;
    padding-bottom: calc(1rem + var(--m-safe-bottom));
  }

  /* ---------- 11. Tickets & messages ------------------------------------- */

  .tickets-list { gap: .75rem; }

  .ticket-card {
    padding: .8rem .8rem .9rem;
    border-radius: var(--radius-lg);
  }

  .ticket-card-header {
    flex-wrap: wrap;
    gap: .5rem;
  }

  /* "Marquer comme lu" est seul dans l'en-tete de la carte message : ce n'est
     pas une rangee dense, l'exception a 38px des .btn-sm ne s'y justifie pas. */
  .ticket-card-header > form > .btn-sm { min-height: var(--m-tap); }

  .ticket-card-meta { min-width: 0; }
  .ticket-card-date { font-size: var(--fs-2xs); }

  .ticket-card-texte,
  .ticket-card-texte-edit { font-size: var(--fs); }

  .ticket-card-etiquettes-form,
  .ticket-card-suivi-form,
  .ticket-card-texte-form {
    flex-wrap: wrap;
    gap: .5rem;
  }

  .ticket-card-etiquettes-input,
  .ticket-card-suivi-input {
    width: 100%;
    /* 33px de haut : sous la cible tactile, et le clavier iOS zoome sur tout
       champ dont le texte fait moins de 16px. */
    min-height: var(--m-tap);
    font-size: 16px;
  }

  .ticket-card-page { font-size: var(--fs-xs); flex-wrap: wrap; }
  /* Le lien vers la page d'origine du ticket est un texte de 18px de haut :
     on lui donne de la hauteur sans le sortir de sa ligne. */
  .ticket-card-page a {
    display: inline-flex;
    align-items: center;
    min-height: var(--m-tap);
  }

  .ticket-statut-pipeline { width: 100%; }
  .ticket-statut-pipeline-form { flex: 1; }
  /* Les trois segments du pipeline (nouveau / envoye / resolu) font 27px de
     haut et sont colles les uns aux autres : au doigt on change de statut par
     erreur. Ils passent a la cible standard, la rangee reste sur une ligne. */
  .ticket-statut-seg {
    min-height: var(--m-tap);
    padding: 0 .5rem;
  }

  .ticket-card-telechargements { flex-wrap: wrap; gap: .4rem; }

  .ticket-card-thread-msg { font-size: var(--fs-sm); }

  .alertes-bandeau-admin-form {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    align-items: stretch;
  }
  .alertes-bandeau-admin-input {
    width: 100%;
    min-height: var(--m-tap);
    font-size: 16px;
  }

  /* ---------- 12. Mapping ------------------------------------------------ */

  .mapping-section-titre { font-size: var(--fs-lg); }
  .mapping-filtre-input { width: 100%; min-height: var(--m-tap); font-size: 16px; }

  /* Le mapping du bilan est un arbre : sa structure en colonnes porte le sens,
     il reste une table a defilement lateral avec la premiere colonne figee.
     Les trois autres tables (cycles, sous-totaux, racines) sont des listes
     d'enregistrements editables : mobile.js les passe en cartes (cf. 9), ou
     chaque champ est sur sa propre ligne, editable sans defilement lateral. */
  /* Ce defilement lateral n'existait nulle part : la table du bilan fait 595px
     et aucun de ses ancetres ne portait overflow-x, donc c'est la page entiere
     qui defilait de biais (609px de contenu pour 375px d'ecran). Le panneau ne
     contient que cette table, on peut lui donner le defilement sans emporter un
     titre ou un champ de filtre avec lui. Il doit rester un ancetre des
     cellules : c'est lui le conteneur de defilement dont depend le
     `position: sticky` de la premiere colonne. */
  .panel:has(> #bilan-mapping-table) {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  #bilan-mapping-table { font-size: var(--fs-xs); width: max-content; min-width: 100%; }
  #bilan-mapping-table th,
  #bilan-mapping-table td { padding: .35rem .45rem; }
  #bilan-mapping-table input[type="text"] {
    min-width: 140px;
    min-height: var(--m-tap);
    font-size: 16px;
  }

  /* Ajouter / supprimer un poste : deux boutons-icones de 34px de large, colles
     l'un a l'autre, dont l'un est destructif. La table defile lateralement, on
     peut leur donner la cible standard sans contraindre la mise en page. */
  #bilan-mapping-table .mapping-actions-cell .btn-link {
    min-width: var(--m-tap);
    min-height: var(--m-tap);
  }

  #bilan-mapping-table td:first-child,
  #bilan-mapping-table th:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background-color: #fff;
    border-right: 1px solid var(--gray-2);
  }
  #bilan-mapping-table thead th:first-child { background-color: var(--gray-1); z-index: 3; }

  /* Une carte de mapping est un formulaire, pas une fiche de lecture. Le couple
     etiquette-a-gauche / valeur-a-droite convient a un montant, pas a un champ
     de saisie : chaque champ prenait la largeur que lui laissait son etiquette,
     et la colonne de champs se retrouvait alignee ni a gauche ni a droite. On
     empile — etiquette au-dessus, champ sur toute la largeur — et chaque champ
     recoit la cible tactile standard. 16px de texte pour que le clavier iOS ne
     zoome pas la page a chaque prise de focus. */
  table.mapping-table.m-cards > tbody > tr > td:has(> select),
  table.mapping-table.m-cards > tbody > tr > td:has(> input[type="text"]) {
    flex-direction: column;
    align-items: stretch;
    gap: .25rem;
    padding: .3rem 0;
    text-align: left;
  }
  table.mapping-table.m-cards > tbody > tr > td:has(> select)::before,
  table.mapping-table.m-cards > tbody > tr > td:has(> input[type="text"])::before {
    max-width: 100%;
  }
  table.mapping-table.m-cards > tbody > tr > td > select,
  table.mapping-table.m-cards > tbody > tr > td > input[type="text"] {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: var(--m-tap);
    font-size: 16px;
  }
  /* Colonne "Code" des cycles et des sous-totaux : l'application y affiche
     l'icone du cycle et laisse le code dans l'attribut title. Sur ordinateur le
     survol le revele ; au doigt il n'y a pas de survol, et le code — qui est
     l'identifiant du cycle, celui qu'on saisit dans la table des racines —
     devenait invisible. On le remet a cote de l'icone. */
  table.mapping-table.m-cards .mapping-cycle-badge[title]::after {
    content: attr(title);
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: .02em;
  }

  /* Le code cycle de la table des racines est enveloppe dans une pastille, il
     echappe donc au selecteur d'enfant direct ci-dessus. Il reste etroit — c'est
     un code de trois lettres — mais il doit avoir la meme hauteur de frappe. */
  table.mapping-table.m-cards .mapping-cycle-badge > input[type="text"] {
    min-height: var(--m-tap);
    font-size: 16px;
  }

  table.mapping-table.m-cards > tbody > tr > td.m-card-title > input[type="text"] {
    max-width: 100%;
    width: 100%;
  }
  /* Les cases a cocher "Visible / Applicable" restent a droite, sans etirement.
     C'est la cellule entiere qui les bascule (mobile.js), pas la case seule :
     on lui donne la hauteur de la cible tactile et le curseur qui l'annonce. */
  table.m-cards > tbody > tr > td:has(> input[type="checkbox"]) {
    min-height: var(--m-tap);
    cursor: pointer;
    -webkit-tap-highlight-color: rgba(0, 0, 0, .06);
  }
  table.m-cards > tbody > tr > td > input[type="checkbox"] {
    flex: none;
    width: 22px;
    height: 22px;
  }

  /* ---------- 13. Mon compte & cabinets ---------------------------------- */

  .compte-panel { max-width: none; }

  .compte-form {
    display: flex;
    flex-direction: column;
    gap: .4rem;
  }
  .compte-form input,
  .compte-form .btn-primary { width: 100%; }
  .compte-form .btn-primary { margin-top: .8rem; min-height: var(--m-tap); }

  /* Palette de couleurs : les pastilles sont la seule facon de choisir la
     couleur d'un cabinet et elles sont collees les unes aux autres — 22px dans
     la liste des cabinets, 34px dans le profil. Au doigt, une pastille voisine
     part pour l'autre. Elles passent toutes a la cible standard et la palette
     se replie sur deux rangees. */
  .compte-couleurs { flex-wrap: wrap; gap: .5rem; }
  .compte-couleur-swatch,
  .cabinet-gestion-couleurs .compte-couleur-swatch {
    width: var(--m-tap);
    height: var(--m-tap);
  }

  /* Une ligne de cabinet devient une petite carte verticale. */
  .cabinet-gestion-form {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "apercu nom suppr"
      "init   init  init"
      "coul   coul  coul"
      "valid  valid valid";
    gap: .5rem;
    align-items: center;
    padding: .75rem;
    border: 1px solid var(--gray-3);
    border-radius: var(--radius);
    background: #fff;
    box-shadow: var(--shadow-sm);
  }
  .cabinet-gestion-apercu { grid-area: apercu; }
  .cabinet-gestion-nom,
  .cabinet-gestion-initiales { width: 100%; min-height: var(--m-tap); font-size: 16px; }
  .cabinet-gestion-nom { grid-area: nom; }
  .cabinet-gestion-initiales { grid-area: init; }
  .cabinet-gestion-couleurs { grid-area: coul; flex-wrap: wrap; }
  .cabinet-gestion-form > .btn-primary { grid-area: valid; width: 100%; min-height: var(--m-tap); }
  .cabinet-gestion-supprimer { grid-area: suppr; }

  .cabinet-gestion-list { display: flex; flex-direction: column; gap: .6rem; }

  .compte-logout { margin-top: 1.2rem; }
  .compte-logout .icon-btn { width: 100%; justify-content: center; }

  /* ---------- 14. Connexion / MFA / mot de passe -------------------------- */

  .login-wrap {
    padding: 1.5rem var(--m-gutter) 2rem;
    min-height: auto;
  }

  .login-card {
    width: 100%;
    max-width: none;
    padding: 1.4rem 1.15rem 1.6rem;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
  }

  .login-card input { width: 100%; }
  .login-card .btn-primary { width: 100%; min-height: var(--m-tap); }
  .login-sub { font-size: var(--fs); }

  .mfa-enroll-wrap { padding: 1.5rem var(--m-gutter); }
  .mfa-qr { width: min(220px, 62vw); height: auto; }
  .mfa-secret { font-size: var(--fs-sm); overflow-wrap: anywhere; }

  .disclaimer { font-size: var(--fs-2xs); }

  /* ---------- 15. Widget ticket flottant --------------------------------- */

  /* Le bouton flottant remonte au-dessus de la barre d'onglets. Quand un FAB
     de page est present, mobile.js pose .m-has-fab et le widget se decale
     encore, pour venir se poser au-dessus du FAB plutot que par-dessus.
     Attention : c'est le conteneur .ticket-widget qui porte le position:fixed,
     le bouton lui-meme est en flux statique. Decaler le bouton n'a aucun
     effet — les offsets doivent aller sur le conteneur. */
  .ticket-widget {
    bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom) + 14px);
    right: var(--m-gutter);
  }
  body.m-has-fab .ticket-widget {
    bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom) + 76px);
  }

  .ticket-widget-toggle {
    width: 46px;
    height: 46px;
  }

  /* Le conteneur du widget est a z-index 1000, bien au-dessus des feuilles
     modales (91) : sa bulle flottait par-dessus le contenu des feuilles.
     On l'escamote tant que le scroll est verrouille, c'est-a-dire tant qu'une
     feuille est ouverte — y compris le panneau du widget lui-meme, qui
     recouvre de toute facon cette zone et porte son propre bouton de
     fermeture (#ticket-widget-close). On ne touche qu'au bouton : masquer le
     conteneur emporterait le panneau avec lui. */
  body.m-scroll-locked .ticket-widget-toggle { display: none; }

  /* Le popover de commentaires est une feuille lui aussi (cf. §11) mais il ne
     verrouille pas le scroll — il est pose par app.js, partage avec
     l'ordinateur, et la page derriere doit rester lisible. Sa bulle recouvrait
     la zone de saisie : on l'escamote pendant qu'il est ouvert. */
  body:has(.commentaire-popover) .ticket-widget-toggle { display: none; }

  /* Le panneau devient une feuille pleine largeur ancree en bas. */
  .ticket-widget-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: 100%;
    max-width: none;
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    border-radius: var(--m-radius-sheet) var(--m-radius-sheet) 0 0;
    box-shadow: var(--shadow-lg);
    padding-bottom: var(--m-safe-bottom);
    z-index: 95;
  }
  /* ticket-widget.js ouvre/ferme le panneau via l'attribut hidden : la regle
     ci-dessus l'emporterait sinon sur le defaut du navigateur (meme
     specificite, declaree plus tard) et le panneau resterait ouvert. */
  .ticket-widget-panel[hidden] { display: none; }

  .ticket-widget-header { flex: none; }
  .ticket-widget-body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; }
  .ticket-widget-footer { flex: none; flex-wrap: wrap; gap: .5rem; }
  .ticket-widget-footer .btn-primary { width: 100%; min-height: var(--m-tap); }
  .ticket-widget-texte { min-height: 110px; }
  .ticket-widget-dropzone { padding: .8rem; font-size: var(--fs-sm); }

  /* ---------- 16. Bandeau d'alerte --------------------------------------- */

  .alerte-bandeau {
    padding: .55rem var(--m-gutter);
    padding-top: calc(.55rem + var(--m-safe-top));
    font-size: var(--fs-sm);
    align-items: flex-start;
    gap: .5rem;
  }
  .alerte-bandeau-texte { line-height: 1.35; }
  .alerte-bandeau-fermer {
    min-width: var(--m-tap);
    min-height: var(--m-tap);
    flex: none;
  }
  /* Le bandeau occupe deja l'encoche : le header n'a plus a la reserver. */
  body.m-has-bandeau .app-header-inner { padding-top: .45rem; }

  /* ---------- 17. Dictee vocale ------------------------------------------ */

  .ticket-widget-mic,
  .ticket-card-texte-mic,
  .ticket-card-suivi-mic {
    min-width: var(--m-tap);
    min-height: var(--m-tap);
  }

  .ticket-widget-mic-statut,
  .ticket-card-texte-mic-statut,
  .ticket-card-suivi-mic-statut { font-size: var(--fs-xs); }

  /* ---------- 17 bis. Etats masques -------------------------------------- */

  /* mobile.css declare des display: sur des elements que le JS pilote via
     l'attribut hidden. A specificite egale, la derniere regle gagne : sans
     ces garde-fous, .m-sheet { display: flex } l'emporterait sur le
     [hidden] par defaut du navigateur et la feuille resterait visible. */
  .m-sheet[hidden],
  .m-sheet-backdrop[hidden],
  .m-yearbar[hidden],
  .m-chiprow[hidden],
  .m-tabbar[hidden],
  .m-fab[hidden],
  .m-chip[hidden],
  .m-back[hidden],
  .m-tab[hidden],
  .m-sheet-header[hidden],
  .m-sheet-grip[hidden] { display: none; }

  /* Element retire de la mise en page mobile (deplace ailleurs par mobile.js). */
  .m-hidden { display: none !important; }

  /* Avec un bouton retour, le logo devient redondant et vole la largeur du
     titre : on le retire de la barre. */
  body.m-has-back .app-logo-link,
  body.m-has-back .app-version { display: none; }

  /* Titres de section a l'interieur d'une feuille modale. */
  .m-sheet-section-title {
    margin: 0 0 .5rem;
    font-size: var(--fs-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--gray-4);
  }
  .m-sheet-section-title:not(:first-child) { margin-top: 1.1rem; }

  /* Les tuiles de filtre respirent dans la feuille : elles passent a la ligne
     et gagnent une taille confortable au doigt. */
  .m-filters-sheet .filtre-tuiles,
  .m-filters-sheet .filtre-etats {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
  }
  .m-filters-sheet .filtre-tuile,
  .m-filters-sheet .filtre-etat-tuile {
    width: var(--m-tap);
    height: var(--m-tap);
    min-width: var(--m-tap);
    min-height: var(--m-tap);
  }

  /* Feuille "Affichage" de l'onglet Analyse : format des montants, arborescence
     et exports. Chaque groupe recupere la pleine largeur — le segmente des
     unites s'etale, les actions s'empilent en cibles de --m-tap. */
  .m-analyse-sheet .toolbar-group {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .4rem;
    margin: 0;
  }
  .m-analyse-sheet .unit-toggle {
    display: flex;
    width: 100%;
  }
  .m-analyse-sheet .unit-btn {
    flex: 1;
    min-height: var(--m-tap);
  }
  .m-analyse-sheet .btn-link {
    display: flex;
    width: 100%;
    min-height: var(--m-tap);
    align-items: center;
    justify-content: flex-start;
    gap: .5rem;
  }

  /* Formulaire de creation de mandat dans sa feuille. */
  .m-form-sheet .dossier-form { display: flex; }
  .m-form-sheet .dossier-form .btn-primary { min-height: var(--m-tap); }
  .m-form-sheet .dossier-form[hidden] { display: none; }

  /* ---------- 18. Accessibilite & preferences ---------------------------- */

  /* Pas d'etat :hover colle apres un tap. */
  @media (hover: none) {
    .dossier-link:hover,
    .icon-btn:hover,
    .cycles-nav-btn:hover,
    .dossier-tab-btn:hover { background: inherit; }
  }

  @media (prefers-reduced-motion: reduce) {
    .m-sheet,
    .m-sheet-backdrop,
    .m-tab .icon,
    .m-fab { transition: none; }
  }
}

/* ==========================================================================
   Hors mobile : filet de securite. Si la fenetre est elargie apres que
   mobile.js a injecte sa navigation (rotation, redimensionnement), les
   elements mobiles disparaissent et l'interface bureau reprend la main
   telle quelle.
   ========================================================================== */

@media (min-width: 821px) {
  .m-tabbar,
  .m-fab,
  .m-back,
  .m-sheet,
  .m-sheet-backdrop,
  .m-chiprow,
  .m-yearbar { display: none !important; }
}
