/* ═══════════════════════════════════════════
   Pharma Admin — Extended Components
   Extends tokens.css + base.html design system
   ═══════════════════════════════════════════ */

/* ─── KPI Cards ─── */
.pa-kpi {
  display: flex;
  align-items: flex-start;
  gap: var(--pa-s-4);
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-md);
  padding: 18px var(--pa-s-5);
  box-shadow: var(--pa-shadow-xs);
  transition: box-shadow var(--pa-t-base), transform var(--pa-t-base);
  position: relative;
  overflow: hidden;
}

.pa-kpi::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: var(--pa-r-md) var(--pa-r-md) 0 0;
  opacity: 0;
  transition: opacity var(--pa-t-base);
}

.pa-kpi:hover {
  box-shadow: var(--pa-shadow-sm);
  transform: translateY(-1px);
}

.pa-kpi:hover::after {
  opacity: 1;
}

.pa-kpi-icon {
  width: 40px;
  height: 40px;
  border-radius: var(--pa-r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  flex-shrink: 0;
  transition: transform var(--pa-t-base);
}

.pa-kpi:hover .pa-kpi-icon {
  transform: scale(1.05);
}

.pa-kpi-body {
  flex: 1;
  min-width: 0;
}

.pa-kpi-label {
  font-size: var(--pa-fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--pa-text-muted);
  font-weight: 600;
  margin-bottom: var(--pa-s-1);
  line-height: 1.3;
}

.pa-kpi-value {
  font-family: var(--pa-font-mono);
  font-size: var(--pa-fs-2xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--pa-text);
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pa-kpi-value .pa-kpi-unit {
  font-size: var(--pa-fs-md);
  font-weight: 500;
  color: var(--pa-text-muted);
  letter-spacing: 0;
}

.pa-kpi-sub {
  font-size: var(--pa-fs-xs);
  color: var(--pa-text-muted);
  margin-top: var(--pa-s-1);
  letter-spacing: 0.01em;
}

/* KPI color variants — icon bg + accent bar */
.pa-kpi--emerald .pa-kpi-icon {
  background: var(--pa-emerald-50);
  color: var(--pa-emerald-600);
}
.pa-kpi--emerald::after {
  background: var(--pa-emerald-400);
}

.pa-kpi--success .pa-kpi-icon {
  background: var(--pa-success-bg);
  color: #2c6230;
}
.pa-kpi--success::after {
  background: var(--pa-success);
}

.pa-kpi--warning .pa-kpi-icon {
  background: var(--pa-warning-bg);
  color: #8a4814;
}
.pa-kpi--warning::after {
  background: var(--pa-warning);
}

.pa-kpi--info .pa-kpi-icon {
  background: var(--pa-info-bg);
  color: #1f4e58;
}
.pa-kpi--info::after {
  background: var(--pa-info);
}

.pa-kpi--danger .pa-kpi-icon {
  background: var(--pa-danger-bg);
  color: #8d2e26;
}
.pa-kpi--danger::after {
  background: var(--pa-danger);
}

/* KPI XL variant — for primary dashboard KPIs that need visual impact */
.pa-kpi--xl {
  padding: var(--pa-s-6) var(--pa-s-5);
}

.pa-kpi--xl .pa-kpi-icon {
  width: 48px;
  height: 48px;
  font-size: 19px;
}

.pa-kpi--xl .pa-kpi-value {
  font-size: var(--pa-fs-3xl);
}

.pa-kpi--xl .pa-kpi-label {
  margin-bottom: var(--pa-s-2);
}

/* ─── Filter Card ─── */
.pa-filter-card {
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-md);
  box-shadow: var(--pa-shadow-xs);
  padding: var(--pa-s-5) var(--pa-s-6);
  margin-bottom: var(--pa-s-5);
}

.pa-filter-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--pa-s-5);
  padding-bottom: var(--pa-s-3);
  border-bottom: 1px solid var(--pa-divider);
  gap: var(--pa-s-3);
  flex-wrap: wrap;
}

.pa-filter-title {
  display: flex;
  align-items: center;
  gap: var(--pa-s-2);
  font-size: var(--pa-fs-sm);
  font-weight: 600;
  color: var(--pa-text-soft);
  letter-spacing: 0.01em;
}

.pa-filter-title i {
  color: var(--pa-emerald-500);
  font-size: 0.85rem;
}

.pa-filter-meta {
  display: flex;
  align-items: center;
  gap: var(--pa-s-2);
}

.pa-filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--pa-s-4);
  align-items: end;
}

.pa-filter-grid .form-label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.pa-filter-grid .form-label i {
  font-size: 0.7rem;
  opacity: 0.5;
}

/* Un libellé de champ qui porte une action secondaire (créer/éditer la
   ressource que le champ choisit). L'action reste sur la ligne du LIBELLÉ,
   pas sur celle du champ : elle est rare, et la largeur du champ ne l'est
   pas. Cible de 44px conservée par le padding négatif compensé.

   SPÉCIFICITÉ : les deux règles ci-dessus visent `.pa-filter-grid
   .form-label` (0-2-0) et son `i` (0-2-1). Un simple `.pa-label-act`
   (0-1-0) serait battu là où ce libellé vit justement — dans une grille de
   filtres : `justify-content` serait perdu et l'icône réduite à 0.7rem
   délavée. D'où le `.pa-filter-grid` en tête, et le `:where()` sur la
   seconde, qui monte la spécificité sans la faire dépendre du contexte. */
.pa-label-act,
.pa-filter-grid .pa-label-act {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pa-s-2);
}
.pa-label-act > a {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: -6px 0;
  border-radius: var(--pa-r-sm);
  color: var(--pa-text-muted);
}
.pa-label-act > a:hover {
  background: var(--pa-surface-tint);
  color: var(--pa-emerald-600);
}
/* Rend à l'icône de l'action sa taille pleine : la règle générique des
   libellés de filtre la destinait aux petites icônes décoratives. */
.pa-label-act > a > :where(i) {
  font-size: var(--pa-fs-sm);
  opacity: 1;
}
@media (max-width: 767px) {
  .pa-label-act > a { width: 44px; height: 44px; margin: -10px 0; }
}

/* Le filtre de modèle porte des noms SAISIS (« MUTUELLE AGENTS DE SANTE »,
   202px) : sa colonne de 160px les coupe, et le lot 54 n'y a rien changé —
   ce n'est pas le crayon qui bornait la largeur mais `minmax(160px, 1fr)`.
   Deux colonnes le portent à ~280-310px selon l'écran, sans toucher au
   minmax, qui vaut pour les quatre filtres et fixe aussi leur nombre. */
.pa-filter-grid .pa-filter-large { grid-column: span 2; }
/* Sous 767px la feuille de filtres met déjà chaque champ en `1 / -1`
   (l.3962 et voisines) : `span 2` y est sans objet, et le neutraliser évite
   qu'il entre en concurrence si la grille mobile change un jour. */
@media (max-width: 767px) {
  .pa-filter-grid .pa-filter-large { grid-column: 1 / -1; }
}

.pa-filter-actions {
  display: flex;
  align-items: flex-end;
  gap: var(--pa-s-2);
  padding-top: var(--pa-s-1);
}

/* ─── Table Wrap ─── */
.pa-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* Horizontal scroll shadow cue: shadows fade in/out at scrollable edges */
  background:
    linear-gradient(to right, var(--pa-surface) 30%, rgba(255, 255, 255, 0)) left center,
    linear-gradient(to left, var(--pa-surface) 30%, rgba(255, 255, 255, 0)) right center,
    radial-gradient(farthest-side at 0 50%, rgba(28, 34, 32, 0.14), rgba(28, 34, 32, 0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(28, 34, 32, 0.14), rgba(28, 34, 32, 0)) right center;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

.pa-table-wrap::-webkit-scrollbar {
  height: 5px;
}

.pa-table-wrap::-webkit-scrollbar-track {
  background: var(--pa-sand-100);
}

.pa-table-wrap::-webkit-scrollbar-thumb {
  background: var(--pa-sand-300);
  border-radius: 3px;
}

.pa-table-wrap::-webkit-scrollbar-thumb:hover {
  background: var(--pa-sand-400);
}

/* ─── Table Actions (inline button group in cells) ─── */
.pa-table-actions {
  display: inline-flex;
  gap: var(--pa-s-1);
  white-space: nowrap;
}
/* Mobile : les boutons peuvent passer à la ligne au lieu de déborder/tronquer
   (le dernier bouton, Partager, restait invisible en portrait). */
@media (max-width: 767px) {
  .pa-table-actions {
    flex-wrap: wrap;
    white-space: normal;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Tableaux en CARTES sur mobile — .pa-table--cards.pa-table--cards
   ═══════════════════════════════════════════════════════════════════════
   Sous 767px, chaque <tr> devient une carte. Opt-in, tableau par tableau :
   la classe s'ajoute à côté de .pa-table OU de .table (les deux familles
   de tableaux du projet ont le même rendu, seul le sélecteur diffère).

   TROIS RANGS DE LECTURE, tirés du balisage existant :
     · titre       = 1re cellule, sans intitulé
     · détail      = cellule courante, intitulé à gauche / valeur à droite
     · chiffre-clé = cellules .num / .num-col → bandeau en pied de carte

   Les intitulés viennent de data-label (via ::before). Une cellule SANS
   data-label reste lisible, simplement sans intitulé : la migration se
   fait donc gabarit par gabarit, sans rien casser entre-temps.

   BOOTSTRAP-TABLE : les 30 tableaux data-toggle="table" du projet
   utilisent l'extension sticky-header, qui CLONE le <thead> dans un
   .sticky-header-container inséré AVANT la table. Masquer thead ne
   suffit donc pas — le clone survivrait en barre flottante. Les deux
   sont neutralisés ci-dessous.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {

  .pa-table--cards.pa-table--cards {
    border-collapse: separate;
    border-spacing: 0;
    min-width: 0;   /* annule le min-width:500px de .table (base.html) */
  }

  /* L'en-tête ne sert plus : ses intitulés sont repris dans chaque carte.
     Masquage ACCESSIBLE — le thead reste lu par les lecteurs d'écran. */
  .pa-table--cards.pa-table--cards thead {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  /* Le clone flottant de l'extension sticky-header, lui, disparaît. */
  .pa-table-wrap:has(.pa-table--cards.pa-table--cards) .sticky-header-container,
  .pa-table--cards.pa-table--cards + .sticky-header-container,
  .sticky-header-container:has(+ .pa-table--cards.pa-table--cards) { display: none; }

  /* On sort des rôles de tableau : plus de colonnes communes entre lignes.
     (Les <tr> passent en flex juste en dessous, les <td> en flex aussi.) */
  .pa-table--cards.pa-table--cards,
  .pa-table--cards.pa-table--cards tbody,
  .pa-table--cards.pa-table--cards tfoot { display: block; width: auto; }

  /* ── LA CARTE ──
     La ligne est un conteneur FLEX qui passe à la ligne. C'est ce qui
     permet au bandeau chiffré de se mettre sur deux colonnes sans
     wrapper supplémentaire dans le HTML : les cellules courantes font
     100 % (donc une par ligne), les chiffrées 45 % + croissance (donc
     deux par ligne, et pleine largeur quand la dernière est seule).
     Bénéfice annexe : plus d'espaces inter-cellules à neutraliser,
     contrairement à une mise en page inline-block. */
  .pa-table--cards.pa-table--cards tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-md);
    box-shadow: var(--pa-shadow-xs);
    margin-bottom: var(--pa-s-3);
    overflow: hidden;
  }

  /* Zébrure et survol n'ont plus de sens hors du tableau */
  .pa-table--cards.pa-table--cards tbody tr:nth-child(even) td,
  .pa-table--cards.pa-table--cards tbody tr:hover td { background: transparent; }
  .pa-table--cards.pa-table--cards tbody tr:last-child td { border-bottom-width: 1px; }

  /* ── RANG 2 : cellule courante — une par ligne ── */
  .pa-table--cards.pa-table--cards tbody td {
    flex: 0 0 100%;
    width: 100%;
    box-sizing: border-box;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--pa-s-4);
    padding: 7px var(--pa-s-4);
    border-bottom: 1px solid var(--pa-border-soft);
    text-align: right;
    white-space: normal;
    min-height: 34px;
  }
  .pa-table--cards.pa-table--cards tbody td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    text-align: left;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--pa-text-faint);
    white-space: nowrap;
  }
  /* Cellule sans data-label : la valeur prend toute la ligne */
  .pa-table--cards.pa-table--cards tbody td:not([data-label]) {
    justify-content: flex-start;
    text-align: left;
  }
  .pa-table--cards.pa-table--cards tbody td:not([data-label])::before { display: none; }

  /* ── RANG 1 : le titre = 1re cellule ── */
  .pa-table--cards.pa-table--cards tbody td:first-child {
    background: var(--pa-surface-soft);
    border-bottom: 1px solid var(--pa-border);
    padding: var(--pa-s-3) var(--pa-s-4);
    font-family: var(--pa-font-display);
    font-size: var(--pa-fs-md);
    font-weight: 600;
    color: var(--pa-text);
    text-align: left;
    justify-content: flex-start;
    line-height: 1.3;
  }
  .pa-table--cards.pa-table--cards tbody td:first-child::before { display: none; }

  /* ── RANG 3 : les chiffres, en bandeau de pied sur deux colonnes ──
     flex:1 1 45% → deux par ligne ; si la dernière est seule sur sa
     ligne, elle s'étire à 100 % (cas d'un nombre IMPAIR de chiffres).
     Aucun marquage supplémentaire n'est requis dans les gabarits. */
  .pa-table--cards.pa-table--cards tbody td.num,
  .pa-table--cards.pa-table--cards tbody td.num-col {
    flex: 1 1 45%;
    width: auto;
    order: 1;   /* regroupe les chiffres en bandeau, même quand les
                   colonnes sont entrelacées dans le <tr> (Stock : Rayon,
                   Magasin… sont suivis de Fournisseur, Péremption) */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 1px;
    box-sizing: border-box;
    background: var(--pa-sand-50);
    /* Pas de filet vertical entre les deux colonnes : avec `order`, la
       cellule visuellement la plus à droite n'est pas forcément la
       dernière du DOM, et un :last-child poserait le filet au mauvais
       endroit. La séparation se fait par un liseré tiré du fond de la
       carte (outline sur le conteneur), qui ne dépend d'aucun comptage. */
    border: 0;
    border-top: 1px solid var(--pa-border-soft);
    box-shadow: 1px 0 0 var(--pa-border-soft);
    padding: var(--pa-s-2) var(--pa-s-4);
    text-align: left;
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-lg);
    font-weight: 600;
    vertical-align: top;
  }
  .pa-table--cards.pa-table--cards tbody td.num::before,
  .pa-table--cards.pa-table--cards tbody td.num-col::before { color: var(--pa-text-muted); }


  /* ── La ligne de total n'est pas une carte : c'est un récapitulatif ── */
  .pa-table--cards.pa-table--cards tfoot tr,
  .pa-table--cards.pa-table--cards tbody tr.total-row {
    display: flex;
    flex-wrap: wrap;
    background: var(--pa-emerald-50);
    border: 1px solid var(--pa-emerald-100);
    border-left: 3px solid var(--pa-emerald-500);
    border-radius: var(--pa-r-md);
    box-shadow: none;
    margin-top: var(--pa-s-4);
    margin-bottom: 0;
    overflow: hidden;
  }
  /* Le récapitulatif se lit en lignes pleine largeur, JAMAIS en colonnes :
     un total scindé en deux colonnes se confondrait avec le bandeau
     chiffré des cartes. D'où le flex:0 0 100% qui écrase le 45 % que ces
     cellules héritent de .num. */
  .pa-table--cards.pa-table--cards tfoot td,
  .pa-table--cards.pa-table--cards tbody tr.total-row td {
    flex: 0 0 100%;
    width: 100%;
    background: transparent !important;
    border: 0;
    border-top: 1px solid var(--pa-emerald-100);
    color: var(--pa-emerald-800);
    display: flex;
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    text-align: right;
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-md);
    font-weight: 700;
  }
  .pa-table--cards.pa-table--cards tfoot td:first-child,
  .pa-table--cards.pa-table--cards tbody tr.total-row td:first-child {
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: transparent;
    border-top: 0;
    color: var(--pa-emerald-600);
  }
  .pa-table--cards.pa-table--cards tfoot td::before,
  .pa-table--cards.pa-table--cards tbody tr.total-row td::before { color: var(--pa-emerald-600); }
  /* Les intitulés et les gouttières vivent sous `tbody td` : le <tfoot> n'en
     héritait pas. Sans `content`, le ::before est vide et le chiffre se
     retrouve seul, sans intitulé, collé au bord. On les rend au pied ici —
     `tr.total-row` dans le tbody, lui, les a déjà. */
  .pa-table--cards.pa-table--cards tfoot td {
    box-sizing: border-box;
    gap: var(--pa-s-4);
    padding: 7px var(--pa-s-4);
    min-height: 32px;
  }
  .pa-table--cards.pa-table--cards tfoot td::before {
    content: attr(data-label);
    flex: 0 0 auto;
    text-align: left;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
  }
  /* Cellule sans data-label (le libellé « Total », les cellules de calage) :
     la valeur prend la ligne, comme dans le tbody. */
  .pa-table--cards.pa-table--cards tfoot td:not([data-label]) {
    justify-content: flex-start;
    text-align: left;
  }
  .pa-table--cards.pa-table--cards tfoot td:not([data-label])::before { display: none; }
  /* Le libellé de pied devient un bandeau, pas une ligne parmi les autres. */
  .pa-table--cards.pa-table--cards tfoot td:first-child {
    background: var(--pa-emerald-100) !important;   /* bat le `transparent !important` de la règle générique du pied */
    padding: var(--pa-s-2) var(--pa-s-4);
  }
  /* Cellules de calage (colspan vides) : sans objet en carte.
     `:empty` ne suffit PAS — Jinja laisse des espaces/retours à la ligne
     dans un <td></td>, qui cessent alors d'être vides au sens CSS. D'où
     la classe explicite .pa-cell-vide à poser sur ces cellules. */
  .pa-table--cards.pa-table--cards tfoot td:empty,
  .pa-table--cards.pa-table--cards tbody tr.total-row td:empty,
  .pa-table--cards.pa-table--cards .pa-cell-vide { display: none; }

  /* ── Carte de totaux (opt-in : .pa-table--totcard) ────────────────────
     Le pied cesse d'être une pile de lignes égales.
       · 1re cellule            → bandeau de titre pleine largeur
       · .pa-cell-cle           → le chiffre qui décide, en grand
       · cellules chiffrées     → grille de deux
       · .pa-cell-ligne         → ligne pleine largeur (intitulé / valeur)
       · .pa-cell-ctrl          → fond plus clair : contrôle, pas recette
     L'ordre du DOM n'a pas à bouger : `order` replace le bandeau et le
     chiffre-clé en tête, où que Jinja les écrive.
     Le filet entre colonnes est un `gap` de 1px sur fond emerald-100 : aucun
     comptage de cellules, donc rien à casser quand une colonne est
     conditionnelle (Divers). Les `!important` sont imposés par le
     `background: transparent !important` de la règle générique du pied. */
  .pa-table--cards.pa-table--totcard tfoot tr {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: var(--pa-emerald-100);
    border-color: var(--pa-emerald-200);
  }
  .pa-table--cards.pa-table--totcard tfoot td {
    flex: none;
    width: auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    border-top: 0;
    text-align: left;
    background: var(--pa-emerald-50) !important;
  }
  .pa-table--cards.pa-table--totcard tfoot td::before { font-weight: 600; }

  .pa-table--cards.pa-table--totcard tfoot td:first-child {
    grid-column: 1 / -1;
    order: -2;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    background: var(--pa-emerald-500) !important;
    color: #fff;
    letter-spacing: 0.06em;
  }
  .pa-table--cards.pa-table--totcard tfoot td:first-child small {
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-xs);
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: none;
    opacity: 0.85;
  }

  .pa-table--cards.pa-table--totcard tfoot td.pa-cell-cle {
    grid-column: 1 / -1;
    order: -1;
    display: block;
    padding: var(--pa-s-3) var(--pa-s-4) var(--pa-s-2);
    font-size: var(--pa-fs-2xl);
    line-height: 1.05;
    background: var(--pa-surface) !important;
  }
  .pa-table--cards.pa-table--totcard tfoot td.pa-cell-cle::before {
    display: block;
    margin-bottom: 2px;
    color: var(--pa-emerald-600);
  }

  .pa-table--cards.pa-table--totcard tfoot td.pa-cell-ligne {
    grid-column: 1 / -1;
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--pa-s-4);
  }
  .pa-table--cards.pa-table--totcard tfoot td.pa-cell-ctrl {
    background: var(--pa-surface-soft) !important;
  }

  /* ── Colonnes libres (opt-in : .pa-table--libre) ──────────────────────
     Sur `etats` et `generateur`, les intitulés viennent de l'état joué :
     ils peuvent être longs (« N° Ordonnancier », « Quantité comptée »). Le
     `white-space: nowrap` des intitulés de carte, calibré sur des en-têtes
     écrits à la main, provoquerait un débordement horizontal. Ici, un
     intitulé a le droit de passer à la ligne.
     Aucun `!important` en jeu : à spécificité égale, c'est la position dans
     le fichier qui tranche — ce bloc doit rester APRÈS ceux des lots 77. */
  .pa-table--cards.pa-table--libre tbody td::before,
  .pa-table--cards.pa-table--libre tfoot td::before {
    white-space: normal;
    max-width: 55%;
  }

  /* ── Bandeau à libellés conservés (opt-in : .pa-bandeau-souple) ────────
     Lot 82. Un `.card-body` en `d-flex justify-content-between` dont les
     deux enfants portent du texte qui doit rester lisible : le libellé
     d'une période d'un côté, des boutons libellés de l'autre.

     `.d-flex` est nowrap, et `.card` porte overflow:hidden — sans cette
     règle, le contenu est coupé net au bord droit de la carte sous 375px,
     sans troncature ni défilement.

     Opt-in délibéré : `.card-body` est partout dans le dépôt, et la
     plupart de ces cartes tiennent parce qu'elles NE passent PAS à la
     ligne. Ne jamais promouvoir ce bloc en règle générique. */
  .pa-bandeau-souple {
    flex-wrap: wrap;
    gap: var(--pa-s-2);
  }

  /* Les boutons passés à la ligne restent alignés à droite, sous le
     libellé — et non centrés ni étirés. */
  .pa-bandeau-souple > form,
  .pa-bandeau-souple > .pa-table-actions {
    margin-left: auto;
  }

  /* ── Actions : pied de carte, cibles tactiles ≥44px ── */
  .pa-table--cards.pa-table--cards tbody td.pa-cell-actions {
    flex: 0 0 100%;
    width: 100%;
    order: 2;   /* sous le bandeau chiffré (order:1), donc toujours en pied
                   de carte quelle que soit sa position dans le <tr> */
    background: var(--pa-surface-soft);
    border-top: 1px solid var(--pa-border);
    border-bottom: 0;
    border-right: 0;
    align-items: center;
    justify-content: flex-end;
    gap: var(--pa-s-2);
    padding: var(--pa-s-2) var(--pa-s-3);
    min-height: 48px;
  }
  .pa-table--cards.pa-table--cards tbody td.pa-cell-actions::before { margin-right: auto; }
  /* `.btn-icon` (sans préfixe, base.html) fait 30px par défaut — sous le
     plancher tactile du paquet mobile. Il n'était pas visé ici : seuls
     `.pa-btn-icon` et `.btn` l'étaient, si bien que les écrans qui emploient
     la classe non préfixée gardaient des cibles de 30px en carte. */
  .pa-table--cards.pa-table--cards tbody td.pa-cell-actions .pa-btn-icon,
  .pa-table--cards.pa-table--cards tbody td.pa-cell-actions .btn-icon,
  .pa-table--cards.pa-table--cards tbody td.pa-cell-actions .btn {
    width: 44px;
    height: 44px;
    font-size: 15px;
  }

  /* ── Plus de débordement : aucun conteneur ne doit défiler ──
     ATTENTION, c'est le point qui a cassé au premier essai sur téléphone.
     bootstrap-table ne laisse PAS la table là où le gabarit l'a écrite :
     à l'initialisation il construit
        .bootstrap-table > .fixed-table-container > .fixed-table-body
     et y DÉPLACE la table. `.fixed-table-body` porte `overflow:auto`
     (bootstrap-table.min.css) : c'est LUI le défileur horizontal, pas
     `.pa-table-wrap`. Ne viser que `.pa-table-wrap` laisse donc les
     cartes dans un conteneur large qu'il faut faire défiler — symptôme
     observé sur Pixel 7 / iPhone 15.
     On neutralise donc les DEUX niveaux, et on force la largeur de la
     table à celle de l'écran (bootstrap-table pose des largeurs
     calculées en ligne sur <table> et <col>). */
  /* `:has()` remonte à n'importe quel ancêtre : la table peut avoir été
     DÉPLACÉE par bootstrap-table (elle n'est plus enfant direct de
     .pa-table-wrap), donc on cible tout conteneur qui la contient,
     à quelque profondeur que ce soit. */
  .pa-table-wrap:has(.pa-table--cards.pa-table--cards),
  .table-container:has(.pa-table--cards.pa-table--cards),
  .bootstrap-table:has(.pa-table--cards.pa-table--cards),
  .fixed-table-container:has(.pa-table--cards.pa-table--cards),
  .fixed-table-body:has(.pa-table--cards.pa-table--cards) {
    overflow: visible;
    height: auto;
    background: none;
    margin: 0;
    min-width: 0;
  }
  /* L'en-tête cloné par l'extension sticky-header ET celui que
     bootstrap-table place dans .fixed-table-header : inutiles en carte. */
  .bootstrap-table:has(.pa-table--cards.pa-table--cards) .fixed-table-header,
  .pa-table-wrap:has(.pa-table--cards.pa-table--cards) .sticky-header-container,
  .bootstrap-table:has(.pa-table--cards.pa-table--cards) .sticky-header-container { display: none; }

  /* bootstrap-table écrit des largeurs EN LIGNE sur la table et ses
     colonnes : elles priment sur toute règle de feuille, d'où !important
     (seul endroit du bloc où il est nécessaire). */
  .pa-table--cards.pa-table--cards { width: 100% !important; max-width: 100% !important; }
  .pa-table--cards.pa-table--cards colgroup,
  .pa-table--cards.pa-table--cards col { width: auto !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CARTE D'ENREGISTREMENT — .pa-rcard
   ═══════════════════════════════════════════════════════════════════════
   Rendu mobile d'une ligne de tableau riche (Stock…), quand la
   transposition générique `.pa-table--cards` produit un mur de lignes
   illisible. Ici la carte a une HIÉRARCHIE : un titre, quatre mesures en
   grille 2×2, et le reste replié derrière un bouton.

   Structure attendue :
     <article class="pa-rcard pa-rcard--warn">
       <div class="pa-rcard-body">          ← le filet de couleur borde CE bloc
         <div class="pa-rcard-head"> titre + sous-titre </div>
         <div class="pa-rcard-grid"> 4 × pa-rcard-cell </div>
       </div>
       <button class="pa-rcard-more"> … </button>   ← commande : fond teinté
       <div class="pa-rcard-det" hidden> … </div>
       <div class="pa-rcard-act"> … </div>          ← commande : fond teinté
     </article>

   RÈGLE DE COULEUR : ce qui se LIT est sur --pa-surface et bordé du filet
   d'état ; ce qui se TOUCHE est sur --pa-surface-soft et sans filet. Le
   filet est porté par `.pa-rcard-body` (un simple border-left) et non par
   un pseudo-élément en position absolue : sa hauteur suit le contenu
   réel — titre sur deux lignes, carte dépliée — sans valeur en dur, et
   sans se faire recouvrir par les blocs opaques qui suivent.

   La carte est masquée au-dessus de 767px : c'est le tableau qui sert.
   ═══════════════════════════════════════════════════════════════════════ */
.pa-rcard-list { display: none; }

@media (max-width: 767px) {

  .pa-rcard-list { display: block; }

  .pa-rcard {
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-md);
    box-shadow: var(--pa-shadow-xs);
    margin-bottom: var(--pa-s-3);
    overflow: hidden;
  }
  .pa-rcard:last-child { margin-bottom: 0; }

  /* ── Enveloppe du contenu : porte le filet d'état ── */
  .pa-rcard-body { border-left: 3px solid transparent; }
  .pa-rcard--ok   > .pa-rcard-body { border-left-color: var(--pa-emerald-400); }
  .pa-rcard--warn > .pa-rcard-body { border-left-color: var(--pa-warning); }
  .pa-rcard--dgr  > .pa-rcard-body { border-left-color: var(--pa-danger); }
  .pa-rcard--info > .pa-rcard-body { border-left-color: var(--pa-info); }

  /* ── Titre ── */
  .pa-rcard-head { padding: var(--pa-s-3) var(--pa-s-4); }
  .pa-rcard-nom {
    font-family: var(--pa-font-display);
    font-size: var(--pa-fs-md);
    font-weight: 600;
    line-height: 1.25;
    margin-bottom: 3px;
    text-wrap: balance;
  }
  .pa-rcard-sub {
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    flex-wrap: wrap;
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-faint);
  }
  .pa-rcard-code { font-family: var(--pa-font-mono); color: var(--pa-text-muted); }
  .pa-rcard-dot {
    width: 3px; height: 3px; border-radius: 50%;
    background: var(--pa-sand-300); flex: 0 0 auto;
  }

  /* ── En-tête à deux colonnes — .pa-rcard-head--split ──
     Pour les cartes dont UNE mesure décide de l'action, et qui doit se
     lire avant le reste : sur `preparation_commande`, le stock. Elle
     était un span inline collé à la fin du nom du produit — invisible.

     Variante opt-in : `.pa-rcard-head` sans le modificateur ne bouge pas,
     et les sept autres écrans qui s'en servent non plus. */
  .pa-rcard-head--split {
    display: flex;
    align-items: flex-start;
    gap: var(--pa-s-3);
  }
  .pa-rcard-head--split > .pa-rcard-hd-bd { flex: 1 1 auto; min-width: 0; }

  .pa-rcard-mesure {
    flex: 0 0 auto;
    text-align: right;
    padding-left: var(--pa-s-3);
    border-left: 1px solid var(--pa-border-soft);
  }
  .pa-rcard-mesure-k {
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--pa-text-faint);
  }
  .pa-rcard-mesure-v {
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-xl);
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
  }
  .pa-rcard-mesure-v--ok   { color: var(--pa-emerald-600); }
  .pa-rcard-mesure-v--warn { color: var(--pa-warning); }
  .pa-rcard-mesure-v--dgr  { color: var(--pa-danger); }

  /* ── Grille 2×2 des mesures ──
     Filets fins plutôt que gouttières : sans fond contrasté, une
     gouttière de 1px dessinerait une croix trop présente. */
  .pa-rcard-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    border-top: 1px solid var(--pa-border-soft);
  }
  .pa-rcard-cell {
    padding: var(--pa-s-2) var(--pa-s-4);
    min-width: 0;
    border-bottom: 1px solid var(--pa-border-soft);
  }
  .pa-rcard-cell:nth-child(odd) { border-right: 1px solid var(--pa-border-soft); }
  .pa-rcard-cell:nth-last-child(-n+2) { border-bottom: 0; }
  /* Cellule occupant la rangée entière — pour une mesure qui n'a pas de
     pendant, ou un nombre impair de cellules. Le filet vertical de
     nth-child(odd) n'a alors plus d'objet : il pendrait dans le vide. */
  .pa-rcard-cell--large { grid-column: 1 / -1; border-right: 0; }

  .pa-rcard-k {
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-muted);
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .pa-rcard-v {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-md);
    font-weight: 600;
  }
  .pa-rcard-v--muted { color: var(--pa-text-muted); }
  .pa-rcard-v--ok    { color: var(--pa-emerald-600); }
  .pa-rcard-v--warn  { color: var(--pa-warning); }
  .pa-rcard-v--dgr   { color: var(--pa-danger); }

  /* ── Jusqu'à TROIS mesures : UNE rangée, pas une grille ───────────
     La grille 2×2 coûtait deux rangées de 46px là où les colonnes
     tiennent en une.

     LE SEUIL EST BORNÉ PAR LA VALEUR, PAS PAR LE NOMBRE DE COLONNES.
     Un montant à sept chiffres — « 1 284 500 F », le format courant de cet
     inventaire — mesure 82px en mono 14px. La largeur utile à 390px est de
     358px ; chaque cellule perd 24px de rembourrage. Il reste donc :

         3 colonnes → 119 − 24 = 95px  ✓ le montant tient
         4 colonnes →  89 − 24 = 65px  ✗ il déborde de 17px

     Le premier jet de ce lot posait quatre, en bornant sur le LIBELLÉ
     (« une cinquième colonne tombe sous 72px »). Le libellé peut
     s'élider — la valeur, non : un montant tronqué est un faux montant.
     Quatre colonnes ne tiendraient quà partir de 430px, et le chantier
     entier est mesuré à 390. On ne conçoit pas un gain qui commence
     au-dessus de l'appareil de référence.

     À quatre cellules et plus, la grille 2×2 reprend la main. Les trois
     écrans concernés (preparation_commande, tiers_payant,
     analyse_fournisseurs) ne gagnent donc rien ici : leur quatrième mesure
     doit descendre dans le sous-titre, et c'est un arbitrage de contenu
     par écran — le travail de la future macro mcard, pas d'une feuille de
     style.

     Sélecteur et non classe à poser : les écrans en profitent sans être
     touchés. `:has` est déjà employé par la feuille de filtres (l.3650),
     le socle est acquis ; sur un moteur qui l'ignorerait la règle est
     sautée et la grille 2×2 subsiste — la dégradation est le comportement
     actuel, pas une casse. */
  .pa-rcard-grid:not(:has(> :nth-child(4))) {
    display: flex;
    align-items: stretch;
  }
  .pa-rcard-grid:not(:has(> :nth-child(4))) > .pa-rcard-cell {
    flex: 1;
    /* 8px par côté et non 16 : à trois colonnes, ces 16px rendus sont ce
       qui fait tenir un montant à HUIT chiffres (99px) dans les 109px de
       cellule. Le rembourrage large de la grille 2×2 était calibré pour
       deux colonnes, où la place ne manquait pas. */
    padding: var(--pa-s-2);
    border-bottom: 0;
    border-right: 1px solid var(--pa-border-soft);
  }
  .pa-rcard-grid:not(:has(> :nth-child(4))) > .pa-rcard-cell:last-child {
    border-right: 0;
  }
  /* Une cellule pleine largeur n'a plus de sens dans une rangée unique :
     elle y est déjà une colonne parmi les autres. */
  .pa-rcard-grid:not(:has(> :nth-child(4))) > .pa-rcard-cell--large { flex: 1; }

  /* Les libellés perdent 1px : à quatre colonnes, « Val. stock » et
     « Prix achat » tiennent, « Manquants d'inventaire » non — l'ellipse du
     LIBELLÉ est acceptable, celle de la VALEUR ne l'est pas. */
  /* LA VALEUR GARDE SA TAILLE (--pa-fs-md, 15px). Une descente à 14px a
     été essayée puis écartée : mesurée, la cellule fait 109px et le montant
     à sept chiffres 90,7px — un montant à HUIT chiffres (99px) passe encore.
     14px ne figure pas dans l'échelle (12,5 puis 15) : créer un jeton pour un
     gain sans usage coûte plus que de ne rien faire. C'est le même arbitrage
     que les 10px du rythme, refusés pour la même raison.

     ── Limite connue, assumée, non corrigée ──
     À 320px (iPhone SE), une carte à trois mesures longues empiète de 3 à
     10px sur le filet voisin : analyse_clients est le cas réel. Rien ne sort de la carte, aucune valeur
     n'est tronquée. Repasser ces cartes en 2×2 sous 360px coûterait ~100px
     de hauteur pour 10px de chevauchement — le remède serait dix fois le mal.
     Si le cas devient gênant, la réponse est de descendre la troisième
     mesure dans le sous-titre, écran par écran, comme sur gestion_proforma —
     pas de dégrader la règle pour tous. */

  /* ── Rangée de scores — .pa-rcard-scores ──
     Un triplet dont les termes n'ont de sens que LUS ENSEMBLE (RFM :
     récence, fréquence, montant). Éclatés dans la grille 2×2, séparés par
     d'autres valeurs, ce ne sont plus un score mais trois chiffres.
     D'où une rangée à part, pleine largeur, hors de `.pa-rcard-grid` — dont
     les filets sont posés en nth-child(odd) / nth-last-child(-n+2) et
     supposent un nombre pair de cellules. */
  .pa-rcard-scores {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    border-top: 1px solid var(--pa-border-soft);
  }
  .pa-rcard-scores > div {
    padding: var(--pa-s-2) var(--pa-s-3);
    text-align: center;
  }
  .pa-rcard-scores > div + div { border-left: 1px solid var(--pa-border-soft); }
  .pa-rcard-score-k {
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--pa-text-faint);
    text-transform: uppercase;
  }
  .pa-rcard-score-v {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-lg);
    font-weight: 700;
    line-height: 1.2;
    color: var(--pa-text);
  }
  .pa-rcard-score-v--ok  { color: var(--pa-emerald-600); }
  .pa-rcard-score-v--bas { color: var(--pa-danger); }
  .pa-rcard-score-v--mid { color: var(--pa-warning); }

  /* ── Bouton de dépliage (commande → fond teinté, pas de filet) ── */
  .pa-rcard-more {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--pa-s-2);
    width: 100%;
    padding: 9px var(--pa-s-3);
    border: 0;
    border-top: 1px solid var(--pa-border-soft);
    background: var(--pa-surface-soft);
    color: var(--pa-text-muted);
    cursor: pointer;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    transition: background var(--pa-t-fast), color var(--pa-t-fast);
  }
  .pa-rcard-more:hover { background: var(--pa-sand-100); color: var(--pa-emerald-600); }
  .pa-rcard-more:focus-visible { outline: 2px solid var(--pa-emerald-500); outline-offset: -2px; }
  .pa-rcard-more i { transition: transform var(--pa-t-base); }
  .pa-rcard-more[aria-expanded="true"] i { transform: rotate(180deg); }

  /* ── Détails repliés ── */
  .pa-rcard-det { border-top: 1px solid var(--pa-border-soft); background: var(--pa-surface); }
  .pa-rcard-det[hidden] { display: none; }
  .pa-rcard-det dl { margin: 0; padding: var(--pa-s-2) var(--pa-s-4) var(--pa-s-3); }
  .pa-rcard-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--pa-s-3);
    padding: 5px 0;
    border-bottom: 1px dotted var(--pa-border);
  }
  .pa-rcard-row:last-child { border-bottom: 0; }
  .pa-rcard-row dt {
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--pa-text-faint);
    text-transform: uppercase;
    white-space: nowrap;
  }
  .pa-rcard-row dd {
    margin: 0;
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-soft);
    text-align: right;
  }
  .pa-rcard-row dd.num {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
  }

  /* ── Dépliement en mode SAISIE — .pa-rcard-form ──
     `.pa-rcard-det dl` pose des paires libellé/valeur en flex horizontal :
     bon pour lire, inutilisable pour saisir (le champ n'aurait que la
     moitié droite). Ce bloc est la variante « formulaire » du même
     dépliement — libellé au-dessus, champ pleine largeur, 44px de haut.
     Premier écran du chantier où l'on SAISIT dans une carte. */
  .pa-rcard-form { margin: 0; padding: var(--pa-s-2) var(--pa-s-4) var(--pa-s-3); }
  .pa-rcard-field { padding: var(--pa-s-2) 0; }
  .pa-rcard-field + .pa-rcard-field { border-top: 1px dotted var(--pa-border); }
  .pa-rcard-field label {
    display: block;
    margin-bottom: 4px;
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--pa-text-faint);
    text-transform: uppercase;
  }
  .pa-rcard-field input {
    display: block;
    width: 100%;
    min-height: 44px;              /* plancher tactile du paquet mobile */
    padding: var(--pa-s-2) var(--pa-s-3);
    font-size: 16px;               /* < 16px : iOS zoome au focus */
    font-family: inherit;
    color: var(--pa-text);
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-sm);
  }
  .pa-rcard-field input:focus {
    outline: 2px solid var(--pa-emerald-500);
    outline-offset: -1px;
    border-color: var(--pa-emerald-500);
  }
  /* Champ vide ET imprimé sur le modèle courant : c'est ce qui manquera au
     document. Avertissement, jamais --pa-danger — l'incomplet n'est pas
     une faute de saisie. */
  .pa-rcard-field--vide input {
    border-left: 3px solid var(--pa-warning);
    background: var(--pa-warning-bg);
  }
  /* Champs absents du modèle choisi : rangés en fin de dépliement sous un
     intertitre unique. Ils restent SAISISSABLES — ne pas être imprimé sur
     ce modèle ne veut pas dire ne pas compter : la donnée est enregistrée
     et servira sur un autre modèle. */
  .pa-rcard-horsmodele {
    margin-top: var(--pa-s-3);
    padding-top: var(--pa-s-2);
    border-top: 1px solid var(--pa-border-soft);
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--pa-text-faint);
    text-transform: uppercase;
    /* Devenu un <button> (lot 49) : il déplie les champs qui ne partent pas à
       l'impression avec le modèle choisi. Réinitialisation du bouton, et une
       cible de 44px comme partout ailleurs. */
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    width: 100%;
    min-height: 44px;
    background: none;
    border: 0;
    /* Repose le filet APRÈS la réinitialisation : `border: 0` est dans le même
       bloc et l'emporterait par ordre de source, faisant disparaître le trait
       de séparation voulu plus haut. */
    border-top: 1px solid var(--pa-border-soft);
    text-align: left;
    cursor: pointer;
  }
  .pa-rcard-horsmodele::after {
    content: '\f078';
    font: 900 0.7em 'Font Awesome 6 Free';
    margin-left: auto;
    transition: transform var(--pa-t-base);
  }
  .pa-rcard-horsmodele[aria-expanded="true"]::after { transform: rotate(180deg); }

  /* Pastille de tête : compte les champs vides QUI SERONT IMPRIMÉS.
     Absente quand il n'y en a pas — pas de pastille « 0 » qui ferait du
     bruit sur les lignes déjà bonnes. */
  .pa-rcard-manque {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px var(--pa-s-2);
    border-radius: var(--pa-r-pill);
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    background: var(--pa-warning-bg);
    color: #8a4814;                /* même encre que .pa-badge-warning */
    border: 1px solid #ead2a6;
  }

  /* Ligne exclue : le filet de complétude reste (c'est une propriété de la
     ligne), mais il ne doit pas crier sur une ligne hors jeu. */
  .pa-rcard.excluded { opacity: 0.55; }

  /* ── Barre d'actions (commande → fond teinté, cibles ≥44px) ── */
  .pa-rcard-act {
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    /* La barre passe à la ligne plutôt que d'écraser son libellé : avec
       quatre boutons à 44px (188px avec les écarts) il ne reste pas de quoi
       écrire une période complète, et c'est .pa-table-actions qui cédait —
       trois boutons sur une ligne, le quatrième seul dessous. */
    flex-wrap: wrap;
    padding: var(--pa-s-2) var(--pa-s-3);
    border-top: 1px solid var(--pa-border);
    background: var(--pa-surface-soft);
  }
  /* Case d'inclusion : grande cible à gauche de la barre d'actions. Le
     libellé fait partie de la cible — une case nue est ambiguë et trop
     petite pour le pouce. */
  .pa-rcard-check {
    display: inline-flex;
    align-items: center;
    gap: var(--pa-s-2);
    min-height: 44px;
    padding: 0 var(--pa-s-2) 0 0;
    margin: 0;
    cursor: pointer;
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-soft);
    user-select: none;
  }
  .pa-rcard-check input { width: 22px; height: 22px; flex: 0 0 auto; accent-color: var(--pa-emerald-500); }
  .pa-rcard-check:focus-within { outline: 2px solid var(--pa-emerald-500); outline-offset: 2px; border-radius: var(--pa-r-sm); }

  .pa-rcard-act .pa-rcard-fourn {
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* 9,5rem = 152px : une période complète (« 01/07/2026 → 31/07/2026 »,
       ~125px en fs-xs) plus sa marge de sécurité. C'est un MINIMUM, pas une
       largeur : à deux ou trois boutons (92 et 140px) le libellé tient à côté
       et n'a pas bougé ; à quatre (188px), 152 + 188 + l'écart dépassent les
       332px utiles et la barre passe à la ligne. Le seuil est donc porté par
       une mesure, pas par un compte de boutons que le CSS ne connaît pas. */
    flex: 1 1 auto;
    min-width: 9.5rem;
  }
  .pa-rcard-act .pa-table-actions { margin-left: auto; }
  /* 44px : plancher de cible tactile du paquet mobile (38px était en dessous).
     La barre d'actions ne contient que .pa-rcard-fourn + .pa-table-actions,
     donc l'élargissement ne pousse rien hors de la carte. */
  .pa-rcard-act .btn-icon,
  .pa-rcard-act .pa-btn-icon { width: 44px; height: 44px; font-size: 14px; }

  @media (prefers-reduced-motion: reduce) {
    .pa-rcard-more, .pa-rcard-more i { transition: none; }
  }

  /* Le tableau cède la place à la liste de cartes */
  /* Le nom promet un masquage inconditionnel : exiger EN PLUS `pa-table-wrap`
     en faisait une option de ce seul conteneur, et laissait passer le tableau
     de `preparation_releves`, qui vit dans un `.table-responsive` (lot 51). */
  .pa-rcard-list ~ .pa-table-wrap,
  .pa-hide-mobile { display: none; }
}

/* ─── Page Section Header ─── */
.pa-section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--pa-s-4);
  flex-wrap: wrap;
  margin-bottom: var(--pa-s-6);
}

.pa-section-title {
  display: flex;
  align-items: center;
  gap: var(--pa-s-3);
}

.pa-section-title h1 {
  font-family: var(--pa-font-display);
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--pa-text);
  margin: 0;
  line-height: 1.15;
}

.pa-section-title i {
  color: var(--pa-emerald-500);
  font-size: 1.2rem;
  opacity: 0.8;
}

.pa-section-sub {
  color: var(--pa-text-faint);
  font-size: var(--pa-fs-sm);
  margin-top: var(--pa-s-1);
  letter-spacing: 0.01em;
}

/* ─── Context Toolbar ─── */
.pa-contextbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pa-s-4);
  border-bottom: 1px solid var(--pa-border-soft);
  margin-bottom: var(--pa-s-6);
  padding: 0;
}

.pa-contextbar-nav {
  display: flex;
  align-items: center;
  gap: var(--pa-s-1);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.pa-contextbar-nav::-webkit-scrollbar {
  display: none;
}

.pa-contextbar-link {
  display: inline-flex;
  align-items: center;
  gap: var(--pa-s-2);
  padding: var(--pa-s-3) var(--pa-s-4);
  font-family: var(--pa-font-sans);
  font-size: var(--pa-fs-sm);
  font-weight: 500;
  color: var(--pa-text-muted);
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  border-radius: var(--pa-r-sm) var(--pa-r-sm) 0 0;
  transition: color var(--pa-t-fast), border-color var(--pa-t-fast), background var(--pa-t-fast);
}

.pa-contextbar-link i {
  font-size: 13px;
  opacity: 0.7;
}

.pa-contextbar-link:hover {
  color: var(--pa-text-soft);
  background: var(--pa-sand-100);
  text-decoration: none;
}

.pa-contextbar-link-active,
.pa-contextbar-link-active:hover {
  color: var(--pa-emerald-600);
  border-bottom-color: var(--pa-emerald-500);
  background: transparent;
}

.pa-contextbar-link-active i {
  opacity: 1;
}

.pa-contextbar-actions {
  display: flex;
  align-items: center;
  gap: var(--pa-s-2);
  flex-shrink: 0;
  padding-bottom: var(--pa-s-2);
}

/* ─── Fiche article (modale globale, composant fiche_article.html) ─── */
.fiche-overlay {
    position: fixed; inset: 0; z-index: 2000;
    background: rgba(0,0,0,0.5);
    display: none; align-items: center; justify-content: center;
    padding: 1rem;
}
.fiche-overlay.open { display: flex; }
.fiche-panel {
    width: min(620px, 100%); max-height: 90vh; overflow-y: auto;
    background: var(--pa-surface, #fff);
    border-radius: var(--pa-r-lg, 10px);
    box-shadow: 0 8px 40px rgba(0,0,0,0.28);
    transform: scale(0.95); opacity: 0;
    transition: transform 0.22s cubic-bezier(.4,0,.2,1), opacity 0.22s;
    display: flex; flex-direction: column;
}
.fiche-overlay.open .fiche-panel { transform: scale(1); opacity: 1; }
.fiche-header {
    display: flex; align-items: flex-start; justify-content: space-between;
    padding: 1.1rem 1.25rem 0.9rem;
    border-bottom: 1px solid var(--pa-border, #e5e7eb);
    position: sticky; top: 0;
    background: var(--pa-surface, #fff);
    border-radius: var(--pa-r-lg, 10px) var(--pa-r-lg, 10px) 0 0;
    z-index: 1;
}
.fiche-header-title { font-family: var(--pa-font-display, serif); font-size: 1.2rem; font-weight: 600; color: var(--pa-text, #1c2220); line-height: 1.25; }
.fiche-header-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; margin-top: 4px; }
.fiche-header-sub  { font-size: 0.78rem; color: var(--pa-text-muted, #6b7280); }
.fiche-close {
    background: none; border: none; cursor: pointer; font-size: 1.1rem;
    color: var(--pa-text-muted); padding: 2px 6px; border-radius: 4px; flex-shrink: 0;
}
.fiche-close:hover { background: var(--pa-sand-100); color: var(--pa-text); }
.fiche-body { padding: 0; flex: 1; overflow-y: auto; }
.fiche-loader {
    display: flex; align-items: center; justify-content: center;
    height: 200px; font-size: 1.5rem; color: var(--pa-text-muted);
}
.fiche-section { margin-bottom: 0; padding: 0; }
.fiche-section-title {
    display: flex; align-items: center; gap: 0.5rem;
    font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.07em; color: var(--pa-emerald-500, #3a7b3e);
    background: var(--pa-sand-50, #f8fafc);
    border-top: 1px solid var(--pa-border, #e5e7eb);
    border-bottom: 1px solid var(--pa-border, #e5e7eb);
    padding: 0.55rem 1.25rem; margin-bottom: 0;
}
.fiche-section:first-child .fiche-section-title { border-top: none; }
.fiche-section-body { padding: 0.9rem 1.25rem; }
.fiche-kpis { padding: 1rem 1.25rem; background: var(--pa-surface-soft, #f8fafc); border-bottom: 1px solid var(--pa-border, #e5e7eb); }
.fiche-kpis .grid { gap: 0.75rem; }
.fiche-kpis .pa-kpi { padding: 14px 16px; }
.fiche-kpis .pa-kpi-value { font-size: var(--pa-fs-xl, 22px); }
.fiche-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem 1rem; }
.fiche-field { display: flex; flex-direction: column; gap: 2px; }
.fiche-field-label { font-size: 0.68rem; color: var(--pa-text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.fiche-field-value { font-size: 0.88rem; font-weight: 600; color: var(--pa-text); }
.fiche-field-value.num { font-family: var(--pa-font-mono, monospace); }
.fiche-statut-rupture  { color: #922b21; }
.fiche-statut-critique { color: #935116; }
.fiche-statut-faible   { color: #7d6608; }
.fiche-statut-normal   { color: #1e8449; }

/* Reconditionnement : lien cliquable vers l'article lié (boîte <-> unité) */
.fiche-recond-link {
    display: flex; align-items: center; gap: 0.75rem;
    width: 100%; text-align: left; cursor: pointer;
    padding: 0.6rem 0.75rem; margin-bottom: 0.5rem;
    background: var(--pa-surface, #fff);
    border: 1px solid var(--pa-border, #e5e7eb);
    border-radius: var(--pa-r-sm, 8px);
    transition: border-color var(--pa-t-fast, .15s), background var(--pa-t-fast, .15s);
    font: inherit; color: inherit;
}
.fiche-recond-link:last-child { margin-bottom: 0; }
.fiche-recond-link:hover {
    border-color: var(--pa-info, #3498db);
    background: var(--pa-info-bg, rgba(52,152,219,0.07));
}
.fiche-recond-icon {
    flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--pa-info-bg, rgba(52,152,219,0.12));
    color: var(--pa-info, #2980b9);
}
.fiche-recond-main { flex: 1 1 auto; min-width: 0; }
.fiche-recond-role {
    font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
    font-weight: 700; color: var(--pa-info, #2980b9);
}
.fiche-recond-desc {
    font-size: 0.85rem; font-weight: 600; color: var(--pa-text, #1c2220);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fiche-recond-meta { font-size: 0.72rem; color: var(--pa-text-muted, #6b7280); }
.fiche-recond-arrow { flex: 0 0 auto; color: var(--pa-text-muted, #9ca3af); }
.fiche-recond-link:hover .fiche-recond-arrow { color: var(--pa-info, #2980b9); }
.fiche-table { width: 100%; border-collapse: collapse; font-size: 0.8rem; }
.fiche-table th {
    padding: 0.4rem 1.25rem; text-align: left; font-size: 0.68rem;
    font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--pa-text-muted); border-bottom: 1px solid var(--pa-border);
    background: var(--pa-sand-50, #f8fafc);
}
.fiche-table td {
    padding: 0.45rem 1.25rem; border-bottom: 1px solid var(--pa-divider, var(--pa-border));
    color: var(--pa-text);
}
.fiche-table tbody tr:last-child td { border-bottom: none; }
.fiche-table tbody tr:hover td { background: var(--pa-sand-50, #f8fafc); }

/* ─── Panier d'évaluation d'ordonnance (composant panier_evaluation.html) ─── */
.panier-overlay {
    position: fixed; inset: 0;
    background: rgba(28, 34, 32, 0.5);
    z-index: 1500; display: none;
    backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.panier-overlay.open { display: block; }

.panier-drawer {
    position: fixed; top: 0; right: 0; height: 100%;
    width: min(400px, 100%);
    background: var(--pa-surface);
    border-left: 1px solid var(--pa-border-soft);
    box-shadow: -8px 0 40px rgba(28, 34, 32, 0.18);
    transform: translateX(100%);
    transition: transform var(--pa-t-base);
    z-index: 1600;
    display: flex; flex-direction: column;
}
.panier-drawer.open { transform: translateX(0); }

.panier-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    padding: var(--pa-s-4) var(--pa-s-5);
    border-bottom: 1px solid var(--pa-border-soft);
    flex-shrink: 0;
}
.panier-title {
    display: flex; align-items: center; gap: var(--pa-s-2);
    font-family: var(--pa-font-display);
    font-size: var(--pa-fs-md); font-weight: 600; color: var(--pa-text);
}
.panier-title i { color: var(--pa-emerald-500); }
.panier-sub { font-size: var(--pa-fs-xs); color: var(--pa-text-muted); margin-top: 2px; }
.panier-close {
    background: none; border: none; cursor: pointer;
    font-size: 1.1rem; color: var(--pa-text-muted);
    padding: 2px 6px; border-radius: var(--pa-r-sm); flex-shrink: 0;
}
.panier-close:hover { background: var(--pa-sand-100); color: var(--pa-text); }

/* Zone scrollable unique : articles + résumé (le vide éventuel reste en bas) */
.panier-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--pa-s-4);
    display: flex;
    flex-direction: column;
    gap: var(--pa-s-4);
}

.panier-body { display: flex; flex-direction: column; gap: var(--pa-s-2); }

.panier-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    min-height: 240px; gap: var(--pa-s-3); padding: var(--pa-s-5);
    color: var(--pa-text-faint); text-align: center;
}
.panier-empty i { font-size: 2.2rem; opacity: 0.4; }
.panier-empty p { font-size: var(--pa-fs-sm); margin: 0; line-height: 1.5; }

/* Carte article */
.panier-item {
    position: relative;
    padding: var(--pa-s-3);
    background: var(--pa-surface);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-md);
    box-shadow: var(--pa-shadow-xs);
}
.panier-item-name {
    font-size: var(--pa-fs-sm); font-weight: 700; color: var(--pa-text);
    line-height: 1.3; padding-right: 24px;
}
.panier-item-sub {
    font-family: var(--pa-font-mono); font-size: var(--pa-fs-xs);
    color: var(--pa-text-faint); margin-top: 2px;
}
.panier-item-row {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: var(--pa-s-3);
}
.panier-item-qty { display: flex; align-items: center; gap: 4px; }
.panier-qty-btn {
    width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
    background: var(--pa-surface-soft); border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-sm); color: var(--pa-text-soft);
    font-size: var(--pa-fs-md); font-weight: 600; line-height: 1; cursor: pointer;
    transition: all var(--pa-t-fast);
}
.panier-qty-btn:hover {
    background: var(--pa-emerald-50); border-color: var(--pa-emerald-200);
    color: var(--pa-emerald-700);
}
.panier-qty-btn:disabled {
    opacity: 0.4; cursor: not-allowed;
    background: var(--pa-surface-soft); color: var(--pa-text-faint);
    border-color: var(--pa-border-soft);
}
.panier-qty-input {
    width: 42px; height: 28px; text-align: center;
    border: 1px solid var(--pa-border-soft); border-radius: var(--pa-r-sm);
    font-family: var(--pa-font-mono); font-size: var(--pa-fs-sm); font-weight: 600; color: var(--pa-text);
    background: var(--pa-surface);
}
.panier-qty-input:focus { outline: none; border-color: var(--pa-emerald-400); }
.panier-item-total {
    font-family: var(--pa-font-mono); font-size: var(--pa-fs-md); font-weight: 700;
    color: var(--pa-text);
}
.panier-item-del {
    position: absolute; top: 6px; right: 6px;
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    background: none; border: none; cursor: pointer;
    color: var(--pa-text-faint); border-radius: var(--pa-r-sm);
    transition: all var(--pa-t-fast);
}
.panier-item-del:hover { background: var(--pa-danger-bg); color: var(--pa-danger); }

/* Carte résumé */
.panier-summary {
    background: var(--pa-surface-soft);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-md);
    padding: var(--pa-s-4);
    display: flex; flex-direction: column; gap: var(--pa-s-2);
}
.panier-summary[hidden] { display: none; }

.psum-line {
    display: flex; align-items: center; justify-content: space-between;
    font-size: var(--pa-fs-sm); color: var(--pa-text-soft);
}
.psum-num { font-family: var(--pa-font-mono); color: var(--pa-text); font-weight: 600; }
.psum-neg { color: var(--pa-danger); }
.psum-hint { color: var(--pa-text-faint); font-size: var(--pa-fs-xs); }
.psum-line-strong { color: var(--pa-text); }
.psum-line-strong .psum-num { font-weight: 700; }

.psum-field {
    display: flex; align-items: center; justify-content: space-between; gap: var(--pa-s-3);
    padding: var(--pa-s-1) 0;
    border-top: 1px solid var(--pa-divider);
    border-bottom: 1px solid var(--pa-divider);
}
.psum-field label { font-size: var(--pa-fs-sm); font-weight: 500; color: var(--pa-text); }
.psum-input { display: flex; align-items: center; gap: var(--pa-s-1); flex-shrink: 0; }
.psum-input .pa-input { width: 64px; text-align: right; }
.psum-input > span { font-size: var(--pa-fs-sm); color: var(--pa-text-muted); }

.psum-total {
    display: flex; align-items: center; justify-content: space-between; gap: var(--pa-s-2);
    margin-top: var(--pa-s-2); padding: var(--pa-s-3) var(--pa-s-4);
    background: var(--pa-emerald-50);
    border: 1px solid var(--pa-emerald-100);
    border-radius: var(--pa-r-md);
}
.psum-total-label { font-size: var(--pa-fs-sm); font-weight: 600; color: var(--pa-emerald-800); }
.psum-total-val {
    font-family: var(--pa-font-mono); font-size: var(--pa-fs-xl); font-weight: 800;
    color: var(--pa-emerald-700); white-space: nowrap;
}

.panier-payer {
    display: flex; align-items: center; gap: var(--pa-s-2);
    margin-top: var(--pa-s-2); padding: var(--pa-s-2) var(--pa-s-3);
    background: var(--pa-surface); border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-sm); cursor: pointer; user-select: none;
}
.panier-payer input { position: absolute; opacity: 0; width: 0; height: 0; }
.panier-payer-switch {
    position: relative; flex-shrink: 0;
    width: 38px; height: 22px; border-radius: var(--pa-r-pill);
    background: var(--pa-sand-200, var(--pa-border)); transition: background var(--pa-t-fast);
}
.panier-payer-switch::after {
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 18px; height: 18px; border-radius: 50%;
    background: #fff; box-shadow: 0 1px 2px rgba(28,34,32,.25);
    transition: transform var(--pa-t-fast);
}
.panier-payer input:checked + .panier-payer-switch { background: var(--pa-emerald-500); }
.panier-payer input:checked + .panier-payer-switch::after { transform: translateX(16px); }
.panier-payer input:focus-visible + .panier-payer-switch { box-shadow: 0 0 0 3px var(--pa-emerald-100); }
.panier-payer-label { font-size: var(--pa-fs-sm); color: var(--pa-text-soft); }
.panier-payer-label b { color: var(--pa-emerald-700); font-weight: 700; }

.panier-actions {
    display: flex; gap: var(--pa-s-2); margin-top: var(--pa-s-2);
}
.panier-actions .panier-send { flex: 1 1 auto; justify-content: center; }
.panier-actions .panier-send:disabled { opacity: 0.55; cursor: not-allowed; }
.panier-actions .panier-clear { flex: 0 0 auto; margin-top: 0; }

.panier-send-msg {
    margin-top: var(--pa-s-2); padding: var(--pa-s-2) var(--pa-s-3);
    border-radius: var(--pa-r-sm); font-size: var(--pa-fs-xs); line-height: 1.4;
}
.panier-send-msg.is-ok {
    background: var(--pa-success-bg, var(--pa-emerald-50)); color: var(--pa-emerald-800);
    border: 1px solid var(--pa-emerald-100);
}
.panier-send-msg.is-err {
    background: var(--pa-danger-bg); color: var(--pa-danger);
    border: 1px solid #e7c1b9;
}

/* Bouton « Voir le ticket » : apparaît après un envoi SCO réussi. */
.panier-share-ticket {
    width: 100%; justify-content: center; margin-top: var(--pa-s-2);
}
.panier-share-ticket:disabled { opacity: 0.55; cursor: not-allowed; }

/* ─── Modal d'aperçu du ticket d'ordonnance ──────────────────────────────
   Preview avant partage (+ capture d'écran possible). Reçu = réplique du
   ticket PDF 80 mm, tokens --pa-* uniquement. z-index au-dessus du drawer. */
.ticket-modal-overlay {
    position: fixed; inset: 0; z-index: 3000;
    background: rgba(28, 34, 32, 0.55);
    display: flex; align-items: center; justify-content: center;
    padding: var(--pa-s-4); opacity: 0; pointer-events: none;
    transition: opacity var(--pa-t-base);
}
.ticket-modal-overlay.open { opacity: 1; pointer-events: auto; }
.ticket-modal {
    background: var(--pa-surface); border-radius: var(--pa-r-lg);
    box-shadow: var(--pa-shadow-lg); width: 100%; max-width: 380px;
    max-height: 88vh; display: flex; flex-direction: column; overflow: hidden;
    transform: translateY(12px); transition: transform var(--pa-t-base);
}
.ticket-modal-overlay.open .ticket-modal { transform: translateY(0); }
.ticket-modal-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--pa-s-3) var(--pa-s-4); border-bottom: 1px solid var(--pa-divider);
}
.ticket-modal-head .tm-title {
    font-weight: 600; font-size: var(--pa-fs-md);
    display: flex; align-items: center; gap: var(--pa-s-2);
}
.ticket-modal-head .tm-title i { color: var(--pa-text-muted); }
.ticket-modal-close {
    width: 30px; height: 30px; border: 0; background: transparent;
    color: var(--pa-text-muted); border-radius: var(--pa-r-sm); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: all var(--pa-t-fast);
}
.ticket-modal-close:hover { background: var(--pa-sand-100); color: var(--pa-text); }
.ticket-modal-body {
    padding: var(--pa-s-4); overflow-y: auto; background: var(--pa-sand-100);
    display: flex; justify-content: center;
}
.ticket-modal-foot {
    display: flex; gap: var(--pa-s-2); padding: var(--pa-s-3) var(--pa-s-4);
    border-top: 1px solid var(--pa-divider);
}
.ticket-modal-foot .pa-btn { flex: 1 1 auto; justify-content: center; }

/* Reçu (papier) — réplique du ticket PDF */
.trecu {
    position: relative; background: #fff; width: 300px; max-width: 100%;
    padding: var(--pa-s-5) var(--pa-s-4); border-radius: var(--pa-r-sm);
    box-shadow: var(--pa-shadow-sm); color: var(--pa-ink-900);
    font-size: 12px; line-height: 1.35;
}
.trecu-soc-nom { font-weight: 700; text-align: center; font-size: 14px; }
.trecu-soc-info { text-align: center; color: var(--pa-text-muted); font-size: 10px; }
.trecu-titre {
    text-align: center; color: var(--pa-emerald-600); font-weight: 700;
    font-size: 12px; letter-spacing: 0.04em; margin-top: var(--pa-s-2);
}
.trecu-op { text-align: center; font-weight: 700; font-size: 18px; margin-top: 2px; }
.trecu-date { text-align: center; color: var(--pa-text-muted); font-size: 10px; }
.trecu-rule { border: 0; border-top: 1px solid var(--pa-ink-700); margin: var(--pa-s-2) 0; }
.trecu-rule-light { border: 0; border-top: 1px dashed var(--pa-border-strong); margin: 4px 0; }
.trecu-art-name { font-weight: 700; }
.trecu-art-line { display: flex; justify-content: space-between; gap: var(--pa-s-2); color: var(--pa-text-soft); }
.trecu-art-line .num { font-family: var(--pa-font-mono); font-size: 11px; }
.trecu-art-total { font-weight: 700; color: var(--pa-ink-900); font-family: var(--pa-font-mono); white-space: nowrap; }
.trecu-sum-block { margin-top: var(--pa-s-2); }
.trecu-sum { display: flex; justify-content: space-between; gap: var(--pa-s-2); margin: 2px 0; }
.trecu-sum .num { font-family: var(--pa-font-mono); }
.trecu-sum-strong { font-weight: 700; color: var(--pa-ink-900); }
.trecu-pay {
    display: flex; justify-content: space-between; align-items: center;
    border-top: 2px solid var(--pa-emerald-500); margin-top: var(--pa-s-2);
    padding-top: var(--pa-s-2);
}
.trecu-pay-label { font-weight: 700; }
.trecu-pay-val { font-weight: 700; font-size: 16px; color: var(--pa-emerald-600); font-family: var(--pa-font-mono); }
.trecu-foot { text-align: center; color: var(--pa-text-muted); font-size: 10px; margin-top: var(--pa-s-2); }
.trecu-watermark {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    pointer-events: none;
}
.trecu-watermark span {
    transform: rotate(-30deg); font-size: 46px; font-weight: 800;
    color: rgba(168, 57, 47, 0.13); letter-spacing: 0.05em;
}

.panier-toast {
    position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px);
    background: var(--pa-ink-800, #1c2220); color: #fff;
    padding: var(--pa-s-2) var(--pa-s-4); border-radius: var(--pa-r-pill);
    font-size: var(--pa-fs-sm); box-shadow: var(--pa-shadow-lg);
    opacity: 0; pointer-events: none; z-index: 2500;
    transition: opacity var(--pa-t-fast), transform var(--pa-t-fast);
}
.panier-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ─── Toasts globaux (PharmaPWA.toast) ────────────────────────────────────
   Toast canonique de l'app : reprend le langage du .panier-toast (pill sombre,
   bas d'écran, fade-up) mais EMPILABLE et À NIVEAUX (succès/info/warning/danger).
   Non bloquant : le conteneur ignore les clics, seuls les toasts sont cliquables
   (pour fermeture anticipée). Auto-injecté par pwa.js. */
.pa-toast-stack {
    position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
    display: flex; flex-direction: column; gap: var(--pa-s-2);
    z-index: 2600; pointer-events: none;
    width: max-content; max-width: min(92vw, 420px);
}
.pa-toast {
    display: flex; align-items: center; gap: var(--pa-s-2);
    background: var(--pa-ink-800); color: #fff;
    padding: 9px var(--pa-s-4) 9px 9px; border-radius: var(--pa-r-pill);
    font-size: var(--pa-fs-sm); line-height: 1.35; box-shadow: var(--pa-shadow-lg);
    opacity: 0; transform: translateY(10px); pointer-events: auto; cursor: default;
    transition: opacity var(--pa-t-base), transform var(--pa-t-base);
}
.pa-toast.show { opacity: 1; transform: translateY(0); }
.pa-toast-ico {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; flex: 0 0 auto; border-radius: var(--pa-r-pill);
    font-size: 12px; background: rgba(255, 255, 255, .14);
}
.pa-toast-msg { min-width: 0; }
.pa-toast--success .pa-toast-ico { color: var(--pa-emerald-200); }
.pa-toast--info    .pa-toast-ico { color: #b9d4d9; }
.pa-toast--warning .pa-toast-ico { color: #ead2a6; }
.pa-toast--danger  .pa-toast-ico,
.pa-toast--error   .pa-toast-ico { color: #e7c1b9; }
@media (max-width: 767px) {
    .pa-toast-stack {
        left: var(--pa-s-3); right: var(--pa-s-3); transform: none;
        bottom: calc(16px + env(safe-area-inset-bottom, 0px));
        width: auto; max-width: none;
    }
    .pa-toast { width: 100%; }
}

/* ─── Partage natif (Web Share) : progressive enhancement ─────────────────
   Le bouton « Partager » (.js-pa-share) n'apparaît que si l'API Web Share
   existe (pwa.js ajoute .pwa-can-share sur <html>). Sélecteur volontairement
   plus spécifique que .btn car le <style> inline de base.html est chargé APRÈS
   cette feuille. Sur desktop sans partage, PDF/Imprimer couvrent déjà le besoin. */
html:not(.pwa-can-share) .js-pa-share { display: none; }

/* ─── Bouton « Copier » inline (Clipboard API via pwa.js) ─────────────────
   Affordance discrète posée à côté d'une référence copiable (n° facture,
   code article, code-barres, id client…). Handler délégué [data-copy]. */
.pa-copy-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0; margin-left: 5px;
    border: 0; background: transparent; color: var(--pa-text-faint);
    border-radius: var(--pa-r-xs); cursor: pointer; font-size: 11px;
    vertical-align: middle; line-height: 1;
    transition: color var(--pa-t-fast), background var(--pa-t-fast);
}
.pa-copy-btn:hover { color: var(--pa-emerald-600); background: var(--pa-sand-100); }
.pa-copy-btn:active { color: var(--pa-emerald-700); }

/* ─── Bannière d'aide à l'installation PWA ────────────────────────────────
   Invite discrète, bas d'écran, fermable, injectée par pwa.js. N'apparaît que
   hors mode standalone et si non déjà rejetée (localStorage). Variante Android/
   desktop (bouton Installer) ou iOS (instructions Partager → écran d'accueil). */
.pa-install-banner {
    position: fixed; left: 50%; transform: translateX(-50%) translateY(150%);
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 2400; width: max-content; max-width: min(94vw, 460px);
    display: flex; align-items: center; gap: var(--pa-s-3);
    padding: var(--pa-s-3) var(--pa-s-4);
    background: var(--pa-surface); color: var(--pa-text);
    border: 1px solid var(--pa-border); border-radius: var(--pa-r-lg);
    box-shadow: var(--pa-shadow-lg); opacity: 0;
    transition: transform var(--pa-t-slow), opacity var(--pa-t-slow);
}
.pa-install-banner.show { transform: translateX(-50%) translateY(0); opacity: 1; }
.pa-install-icon { flex: 0 0 auto; }
.pa-install-icon img { width: 44px; height: 44px; border-radius: var(--pa-r-md); display: block; }
.pa-install-body { min-width: 0; }
.pa-install-title { font-weight: 600; font-size: var(--pa-fs-md); color: var(--pa-text); }
.pa-install-text { color: var(--pa-text-muted); font-size: var(--pa-fs-sm); line-height: 1.35; }
.pa-install-text .pa-ios-share { color: var(--pa-info); padding: 0 2px; }
.pa-install-actions { display: flex; align-items: center; gap: var(--pa-s-2); flex: 0 0 auto; margin-left: var(--pa-s-2); }
@media (max-width: 767px) {
    .pa-install-banner { left: var(--pa-s-3); right: var(--pa-s-3); width: auto; max-width: none; transform: translateY(150%); }
    .pa-install-banner.show { transform: translateY(0); }
}

/* ─── Recherche produit + panier (chrome header, accès libre) ─── */
.header-right {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    margin-left: auto;
}

.header-search {
    position: relative;
    display: flex;
    align-items: center;
    flex: 0 0 auto;
}

/* État replié : bouton loupe rond */
.header-search-toggle {
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--pa-surface-soft);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-sm);
    color: var(--pa-text-soft);
    font-size: var(--pa-fs-sm);
    cursor: pointer;
    transition: all var(--pa-t-fast);
}

.header-search-toggle:hover {
    background: var(--pa-sand-100);
    color: var(--pa-text);
}

/* État déplié : la loupe devient l'icône interne du champ */
.header-search.expanded .header-search-toggle {
    position: absolute;
    left: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
    width: 28px;
    height: 28px;
    background: transparent;
    border: none;
    color: var(--pa-text-faint);
    z-index: 1;
}

.header-search-input {
    width: 0;
    height: 38px;
    padding: 0;
    border: 1px solid transparent;
    opacity: 0;
    pointer-events: none;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-sm);
    color: var(--pa-text);
    background: var(--pa-surface-soft);
    border-radius: var(--pa-r-sm);
    transition: width var(--pa-t-base), opacity var(--pa-t-fast), padding var(--pa-t-base);
}

.header-search.expanded .header-search-input {
    width: 280px;
    padding: 0 0.85rem 0 2.1rem;
    border-color: var(--pa-border-soft);
    opacity: 1;
    pointer-events: auto;
}

.header-search-input::placeholder {
    color: var(--pa-text-faint);
}

.header-search-input:focus {
    outline: none;
    background: var(--pa-surface);
    border-color: var(--pa-emerald-400);
    box-shadow: 0 0 0 3px var(--pa-emerald-100);
}

.header-search-results {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    left: auto;
    width: 360px;
    max-width: calc(100vw - 1.5rem);
    max-height: 380px;
    overflow-y: auto;
    background: var(--pa-surface);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-md);
    box-shadow: var(--pa-shadow-lg);
    z-index: 300;
}

.header-search.open .header-search-results {
    display: block;
}

.header-search-item {
    display: flex;
    align-items: center;
    width: 100%;
    border-bottom: 1px solid var(--pa-divider);
    transition: background var(--pa-t-fast);
}

.header-search-item:hover {
    background: var(--pa-surface-soft);
}

.header-search-item:last-child {
    border-bottom: none;
}

.header-search-item-open {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.6rem 0.5rem 0.6rem 0.85rem;
    background: none;
    border: none;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.header-search-item-add {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    margin-right: 0.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--pa-emerald-50);
    color: var(--pa-emerald-700);
    border: 1px solid var(--pa-emerald-100);
    border-radius: var(--pa-r-sm);
    cursor: pointer;
    transition: all var(--pa-t-fast);
}

.header-search-item-add:hover {
    background: var(--pa-emerald-600);
    color: #fff;
    border-color: var(--pa-emerald-600);
}
.header-search-item-add:disabled {
    background: var(--pa-surface-soft);
    color: var(--pa-text-faint);
    border-color: var(--pa-border-soft);
    opacity: 0.55;
    cursor: not-allowed;
}

.header-search-item-main {
    flex: 1 1 auto;
    min-width: 0;
}

.header-search-item-name {
    font-size: var(--pa-fs-sm);
    font-weight: 700;
    color: var(--pa-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.header-search-item-code {
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-faint);
}

.header-search-item-meta {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    flex-shrink: 0;
}

.header-search-stock {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1px;
    min-width: 56px;
}

.header-search-stock .hs-stk-label {
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    line-height: 1;
}

.header-search-stock .hs-stk-val {
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-xl);
    font-weight: 800;
    color: var(--pa-text);
    line-height: 1.15;
}
.header-search-stock .hs-stk-val--rupture { color: var(--pa-danger); }

.header-search-price {
    display: flex;
    align-items: baseline;
    gap: 2px;
    min-width: 58px;
    justify-content: flex-end;
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-lg);
    font-weight: 700;
    color: var(--pa-emerald-700);
    white-space: nowrap;
}

.header-search-price .hs-cur {
    font-size: var(--pa-fs-xs);
    font-weight: 600;
    color: var(--pa-text-muted);
}

.header-search-empty {
    padding: 0.85rem;
    text-align: center;
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-faint);
}

.header-cart-btn {
    position: relative;
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--pa-surface-soft);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-sm);
    color: var(--pa-text-soft);
    font-size: var(--pa-fs-sm);
    cursor: pointer;
    transition: all var(--pa-t-fast);
}

.header-cart-btn:hover {
    background: var(--pa-sand-100);
    color: var(--pa-text);
}

.header-cart-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--pa-emerald-600);
    color: #fff;
    font-family: var(--pa-font-sans);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    border-radius: var(--pa-r-pill);
    box-shadow: 0 0 0 2px var(--pa-surface);
}

.header-scan-btn {
    width: 38px;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--pa-surface-soft);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-sm);
    color: var(--pa-text-soft);
    font-size: var(--pa-fs-sm);
    cursor: pointer;
    transition: all var(--pa-t-fast);
}

.header-scan-btn:hover {
    background: var(--pa-sand-100);
    color: var(--pa-text);
}

/* ─── Scanner de codes-barres (modale) ─── */
.bc-modal-content {
    border-radius: var(--pa-r-lg);
    border: 1px solid var(--pa-border-soft);
}

.bc-modal-header {
    border-bottom: 1px solid var(--pa-divider);
}

.bc-modal-header .modal-title {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--pa-font-display);
    font-size: var(--pa-fs-lg);
    font-weight: 600;
    color: var(--pa-text);
}

.bc-modal-header .modal-title i {
    color: var(--pa-emerald-600);
}

.bc-modal-body {
    padding: var(--pa-s-5);
}

.bc-modal-footer {
    border-top: 1px solid var(--pa-divider);
}

.bc-viewport {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
    max-height: 60vh;
    overflow: hidden;
    background: var(--pa-ink-900, #14251c);
    border-radius: var(--pa-r-md);
}

.bc-reader {
    width: 100%;
    height: 100%;
}

.bc-reader video {
    width: 100% !important;
    height: 100% !important;
    object-fit: cover;
}

.bc-frame {
    position: absolute;
    inset: 12%;
    pointer-events: none;
}

.bc-frame-corner {
    position: absolute;
    width: 26px;
    height: 26px;
    border: 3px solid var(--pa-emerald-400);
}

.bc-frame-corner--tl { top: 0; left: 0; border-right: none; border-bottom: none; border-top-left-radius: var(--pa-r-sm); }
.bc-frame-corner--tr { top: 0; right: 0; border-left: none; border-bottom: none; border-top-right-radius: var(--pa-r-sm); }
.bc-frame-corner--bl { bottom: 0; left: 0; border-right: none; border-top: none; border-bottom-left-radius: var(--pa-r-sm); }
.bc-frame-corner--br { bottom: 0; right: 0; border-left: none; border-top: none; border-bottom-right-radius: var(--pa-r-sm); }

.bc-frame-scanline {
    position: absolute;
    left: 4%;
    right: 4%;
    top: 0;
    height: 2px;
    background: var(--pa-emerald-400);
    box-shadow: 0 0 8px 1px var(--pa-emerald-400);
    animation: bc-scan 2.1s ease-in-out infinite;
}

@keyframes bc-scan {
    0%   { top: 4%; opacity: 0.9; }
    50%  { top: 92%; opacity: 0.5; }
    100% { top: 4%; opacity: 0.9; }
}

/* La ligne signale que la caméra cherche encore — d'autant plus utile depuis
   que les lectures aberrantes sont écartées en silence. Sans mouvement, elle
   reste affichée au centre du cadre : le repère demeure, l'animation part. */
@media (prefers-reduced-motion: reduce) {
    .bc-frame-scanline {
        animation: none;
        top: 50%;
    }
}

.bc-hint {
    margin: var(--pa-s-3) 0 0;
    text-align: center;
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-muted);
}

.bc-status {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--pa-s-3);
    aspect-ratio: 1 / 1;
    max-height: 60vh;
    padding: var(--pa-s-5);
    text-align: center;
    background: var(--pa-surface-soft);
    border: 1px solid var(--pa-border-soft);
    border-radius: var(--pa-r-md);
}

.bc-status-icon {
    font-size: 2rem;
}

.bc-status--loading .bc-status-icon { color: var(--pa-emerald-600); }
.bc-status--info .bc-status-icon    { color: var(--pa-info); }
.bc-status--warning .bc-status-icon { color: var(--pa-warning); }
.bc-status--danger .bc-status-icon  { color: var(--pa-danger); }

.bc-status-text {
    margin: 0;
    font-size: var(--pa-fs-sm);
    color: var(--pa-text);
    max-width: 32ch;
}

/* ─── Responsive adjustments ─── */
@media (max-width: 767px) {
  .pa-filter-card {
    padding: var(--pa-s-4);
  }

  .pa-filter-grid {
    grid-template-columns: 1fr;
  }

  .pa-section-head {
    flex-direction: column;
    align-items: flex-start;
    margin-bottom: var(--pa-s-5);
  }

  /* ── Dégagement du haut d'écran sur les pages en motif D ──
     Mesuré sur articles_vendus : 231 px avant la barre « Filtre actif »,
     soit 30 % d'un écran de 780 px consommés avant la première information
     utile. Trois réductions, uniquement sur les écrans qui ont une pile
     mobile (`body.pa-liste-mobile`) — les autres gardent leur en-tête.

     1. Le titre de page et son sous-titre disparaissent : la contextbar
        juste dessous porte déjà l'onglet actif (« Articles vendus »), et
        le titre le répétait mot pour mot. */
  body.pa-liste-mobile .pa-section-head { display: none; }

  /* 2. La contextbar se resserre : elle n'a plus de titre au-dessus dont il
        faut la séparer. */
  body.pa-liste-mobile .pa-contextbar {
    margin-bottom: var(--pa-s-4);
  }

  /* 3. Le brand de l'en-tête ne se replie jamais sur deux lignes.
        À 390px il tient sur une (mesuré : 25px pour une interligne de
        25,2px), mais `white-space: normal` le laisse libre de passer à deux
        dès qu'un écran est plus étroit ou le nom plus long — la hauteur de
        l'en-tête sticky suivrait, et TOUTES les pages descendraient d'autant.
        Garde préventive, pas correction d'un défaut observé. */
  .header-brand { flex-wrap: nowrap; min-width: 0; }
  .header-brand-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Titre puis sous-titre, PUIS les actions sur toute la largeur.
     La bascule en colonne ci-dessus était déjà posée ; ce qui manquait est
     l'étirement : avec align-items:flex-start, un bloc d'actions se réduit à
     la largeur de son contenu et laisse un vide à droite. 9 gabarits portent
     un 2e bloc dans .pa-section-head (compte_client, role_editer,
     preparation_commande, impression_fichier, comparaison_periodes,
     generateur, generateur_liste, admin_notifications, reedit_facture).
     Le 1er enfant (titre + sous-titre) est exclu : l'étirer n'apporte rien et
     donnerait une cible de survol pleine largeur. */
  .pa-section-head > * + * {
    width: 100%;
  }

  .pa-section-title h1 {
    font-size: 24px;
  }

  /* Recherche dépliée : largeur réduite + masquer le texte de marque pour la place */
  .header:has(.header-search.expanded) .header-brand-text {
    display: none;
  }

  .header-search.expanded .header-search-input {
    width: 190px;
  }

  /* Résultats : panneau fixe pleine largeur sous le header (évite le débordement
     hors écran quand le champ n'est pas au bord droit) */
  .header-search-results {
    position: fixed;
    top: calc(var(--pa-header-h) + 4px);
    left: 8px;
    right: 8px;
    width: auto;
    max-width: none;
    max-height: calc(100vh - var(--pa-header-h) - 16px);
  }

  /* Lignes plus aérées : code sous le nom, stock + prix alignés à droite */
  .header-search-item-open {
    padding: 0.65rem 0.5rem 0.65rem 0.85rem;
  }

  .header-search-item-name {
    white-space: normal;
  }
}

@media (min-width: 768px) and (max-width: 991px) {
  .pa-kpi-value {
    font-size: var(--pa-fs-2xl);
  }
}

/* ─── Sélecteur de design de relevé (modal modèles) ───
   Cartes-radio + réglages de personnalisation. Les couleurs des
   bandeaux reflètent la palette réelle de chaque design imprimé. */
.pa-design-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--pa-s-2);
}
.pa-design-card {
  position: relative;
  display: block;
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-md);
  padding: var(--pa-s-2);
  background: var(--pa-surface);
  cursor: pointer;
  transition: border-color var(--pa-t-base), box-shadow var(--pa-t-base);
}
.pa-design-card:hover { border-color: var(--pa-border-strong); }
.pa-design-card.is-active {
  border-color: var(--pa-emerald-500);
  box-shadow: 0 0 0 2px var(--pa-emerald-100);
}
.pa-design-card input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.pa-design-swatch {
  height: 26px;
  border-radius: var(--pa-r-sm);
  margin-bottom: var(--pa-s-2);
  border: 1px solid rgba(0, 0, 0, .08);
}
.pa-design-card[data-design="existant"] .pa-design-swatch {
  background: linear-gradient(90deg, #053D65 0 68%, #c8c8c8 68%);
}
.pa-design-card[data-design="institutionnel"] .pa-design-swatch {
  background: linear-gradient(90deg, #16305B 0 62%, #B08A3E 62%);
}
.pa-design-card[data-design="epure"] .pa-design-swatch {
  background: linear-gradient(90deg, #0F7A5A 0 62%, #E2E8E4 62%);
}
.pa-design-name {
  font-weight: 600;
  font-size: var(--pa-fs-sm);
  color: var(--pa-text);
}
.pa-design-desc {
  font-size: var(--pa-fs-xs);
  color: var(--pa-text-muted);
  line-height: 1.3;
  margin-top: 2px;
}
.pa-color-field { display: flex; align-items: center; gap: var(--pa-s-2); }
.pa-color-field input[type="color"] {
  width: 40px;
  height: 32px;
  padding: 2px;
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-sm);
  background: var(--pa-surface);
  cursor: pointer;
}
.pa-color-field .pa-color-label { font-size: var(--pa-fs-xs); color: var(--pa-text-soft); }

/* ─── Modal modèles : grille réglages | aperçu ─── */
.pa-modele-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
  gap: var(--pa-s-4);
  align-items: start;
}
.pa-modele-controls { min-width: 0; }
.pa-modele-aside {
  position: sticky;
  top: 0;
  align-self: start;
}
.pa-modele-aside-label {
  font-size: var(--pa-fs-xs);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--pa-text-muted);
  font-weight: 600;
  margin-bottom: var(--pa-s-2);
}
/* Variante page pleine : l'aperçu se cale sous le header collant et défile seul. */
.pa-modele-aside--page {
  top: calc(var(--pa-header-h) + var(--pa-s-3));
  max-height: calc(100vh - var(--pa-header-h) - var(--pa-s-5));
  overflow: auto;
}
@media (max-width: 860px) {
  .pa-modele-grid { grid-template-columns: 1fr; }
  .pa-modele-aside { position: static; margin-top: var(--pa-s-3); }
  .pa-modele-aside--page { max-height: none; overflow: visible; }
}

/* ─── Accordéon de personnalisation par section ─── */
.pa-sec-accordion {
  display: flex;
  flex-direction: column;
  gap: var(--pa-s-1);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-md);
  overflow: hidden;
}
.pa-sec { border-bottom: 1px solid var(--pa-border-soft); background: var(--pa-surface); }
.pa-sec:last-child { border-bottom: none; }
.pa-sec-head {
  display: flex;
  align-items: center;
  gap: var(--pa-s-2);
  width: 100%;
  padding: var(--pa-s-2) var(--pa-s-3);
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--pa-text);
}
.pa-sec-head:hover { background: var(--pa-surface-soft); }
.pa-sec-caret {
  color: var(--pa-text-muted);
  transition: transform var(--pa-t-base);
  font-size: var(--pa-fs-xs);
}
.pa-sec.is-open .pa-sec-caret { transform: rotate(90deg); }
.pa-sec-name { font-weight: 600; font-size: var(--pa-fs-sm); flex: 1; }
.pa-sec-swatches { display: flex; gap: 3px; }
.pa-sec-chip {
  width: 16px; height: 16px;
  border-radius: var(--pa-r-xs);
  border: 1px solid var(--pa-border);
}
.pa-sec-chip.is-inherit {
  background: repeating-linear-gradient(45deg, var(--pa-surface-soft) 0 3px, var(--pa-border) 3px 4px);
}
.pa-sec-body {
  display: none;
  padding: var(--pa-s-2) var(--pa-s-3) var(--pa-s-3);
  flex-direction: column;
  gap: var(--pa-s-2);
  border-top: 1px dashed var(--pa-border-soft);
}
.pa-sec.is-open .pa-sec-body { display: flex; }

/* Ligne d'override : libellé + color + case hériter */
.pa-ov-row { display: flex; align-items: center; gap: var(--pa-s-2); flex-wrap: wrap; }
.pa-ov-label { font-size: var(--pa-fs-xs); color: var(--pa-text-soft); width: 46px; flex: none; }
.pa-ov-row input[type="color"] {
  width: 38px; height: 30px; padding: 2px;
  border: 1px solid var(--pa-border); border-radius: var(--pa-r-sm);
  background: var(--pa-surface); cursor: pointer;
}
.pa-ov-row input[type="color"]:disabled { opacity: .4; cursor: not-allowed; }
.pa-ov-inherit { display: flex; align-items: center; gap: 4px; font-size: var(--pa-fs-xs); color: var(--pa-text-muted); }
.pa-sec-extra { display: flex; align-items: center; gap: var(--pa-s-2); flex-wrap: wrap; padding-top: 2px; }
.pa-sec-extra .form-select-sm { width: auto; }

/* ─── Aperçu en direct du relevé (maquette) ─── */
.pa-rp {
  background: #fff;
  color: #1b1f27;
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-sm);
  box-shadow: var(--pa-shadow-sm);
  padding: 10px 12px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 6.5px;
  line-height: 1.35;
  overflow: hidden;
}
/* Portrait : feuille plus étroite pour rappeler le format de sortie. */
.pa-rp--portrait { max-width: 74%; margin: 0 auto; }
.pa-rp-head { display: flex; align-items: flex-start; gap: 6px; padding: 5px 6px; }
.pa-rp-logo {
  width: 22px; height: 22px; flex: none;
  border: 1px solid currentColor; border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
}
.pa-rp-co { flex: 1; min-width: 0; }
.pa-rp-co-name { font-size: 10px; font-weight: 700; margin-bottom: 1px; }
.pa-rp-meta { text-align: right; font-size: 6px; }
.pa-rp-title { padding: 5px 6px; margin-top: 2px; }
.pa-rp-title-h { font-size: 13px; font-weight: 700; }
.pa-rp-title-sub { font-size: 6.5px; }
.pa-rp-blocks { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 4px; }
.pa-rp-block-t { font-size: 6.5px; font-weight: 700; text-align: center; padding: 2px 4px; letter-spacing: .04em; }
.pa-rp-block-b { padding: 2px 4px; }
.pa-rp-kv { display: flex; justify-content: space-between; gap: 6px; }
.pa-rp-kv b { font-weight: 700; }
.pa-rp-table { width: 100%; border-collapse: collapse; margin-top: 5px; font-size: 6px; }
.pa-rp-table th, .pa-rp-table td { padding: 2px 3px; text-align: left; }
.pa-rp-table td.n, .pa-rp-table th.n { text-align: right; font-variant-numeric: tabular-nums; }
/* modes de traits (var --rp-tr = couleur des filets) */
.pa-rp-table.tr-tous th, .pa-rp-table.tr-tous td { border: 0.5px solid var(--rp-tr, #c8c8c8); }
.pa-rp-table.tr-verticaux th, .pa-rp-table.tr-verticaux td { border-left: 0.5px solid var(--rp-tr, #c8c8c8); border-right: 0.5px solid var(--rp-tr, #c8c8c8); }
.pa-rp-table.tr-horizontaux th, .pa-rp-table.tr-horizontaux td { border-top: 0.5px solid var(--rp-tr, #c8c8c8); border-bottom: 0.5px solid var(--rp-tr, #c8c8c8); }
.pa-rp-recap { margin-top: 6px; width: 55%; margin-left: auto; padding: 3px 5px; }
.pa-rp-recap-t { font-weight: 700; text-align: center; font-size: 6.5px; margin-bottom: 2px; }
.pa-rp-net { display: flex; justify-content: space-between; font-weight: 700; border-top: 1px solid currentColor; margin-top: 2px; padding-top: 2px; }

/* ─── Aperçu en direct du modèle d'états (maquette) ─── */
.pa-ep {
  background: #fff;
  color: #1c2220;
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-sm);
  box-shadow: var(--pa-shadow-sm);
  padding: 12px 14px;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 8px;
  line-height: 1.4;
  overflow: hidden;
}
.pa-ep-soc { font-size: 11px; font-weight: 700; padding: 2px 3px; }
.pa-ep-title { font-size: 16px; font-weight: 700; text-align: center; letter-spacing: .02em; padding: 3px; margin: 2px 0; }
.pa-ep-info { display: flex; justify-content: space-between; gap: 8px; padding: 3px; font-size: 8px; }
.pa-ep-lbl { color: #888; }
.pa-ep-rule { height: 1px; margin: 3px 0 5px; }
.pa-ep-table { width: 100%; border-collapse: collapse; font-size: 7.5px; }
.pa-ep-table th { font-weight: 700; text-align: left; padding: 3px 4px; text-transform: uppercase; }
.pa-ep-table td { padding: 2.5px 4px; }
.pa-ep-table th.n, .pa-ep-table td.n { text-align: right; font-variant-numeric: tabular-nums; }
.pa-ep-table tfoot td { font-weight: 700; padding-top: 3px; }
.pa-ep-footer { margin-top: 8px; font-style: italic; font-size: 6.5px; display: flex; justify-content: space-between; }

/* ═══════════════════════════════════════════════════════════════
   PARCOURS GUIDÉ (STEPPER) — composant partagé
   Utilisé par le Générateur d'états (generateur.html) et la Création
   d'étiquettes (etiquettes_creation.html). Prefixe `gen-` historique
   conservé pour réutiliser tel quel le markup/JS existant du générateur.
   ═══════════════════════════════════════════════════════════════ */

/* Hint générique (utilisé dans les étapes) */
.gen-step-hint { font-size: var(--pa-fs-xs); color: var(--pa-text-muted); margin-top: 3px; }

/* Rail d'étapes cliquable (navigation libre) */
.gen-rail { display: flex; gap: var(--pa-s-2); flex-wrap: wrap; margin-bottom: var(--pa-s-3); }
.gen-rail-step {
    flex: 1 1 auto; min-width: 130px;
    display: flex; align-items: center; gap: var(--pa-s-2);
    padding: var(--pa-s-2) var(--pa-s-3);
    border: 1px solid var(--pa-border); border-radius: var(--pa-r-md);
    background: var(--pa-surface); color: var(--pa-text-muted);
    cursor: pointer; text-align: left;
    transition: border-color var(--pa-t-fast), background var(--pa-t-fast), color var(--pa-t-fast);
}
.gen-rail-step:hover { border-color: var(--pa-border-strong); color: var(--pa-text); }
.gen-rail-num {
    flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: var(--pa-fs-xs);
    background: var(--pa-surface-soft); color: var(--pa-text-muted);
    border: 1px solid var(--pa-border);
}
.gen-rail-txt { font-size: var(--pa-fs-sm); font-weight: 600; white-space: nowrap; }
.gen-rail-step.is-done { color: var(--pa-emerald-600); border-color: var(--pa-emerald-200); background: var(--pa-emerald-50); }
.gen-rail-step.is-done .gen-rail-num { background: var(--pa-emerald-500); color: #fff; border-color: var(--pa-emerald-500); }
.gen-rail-step.is-active { border-color: var(--pa-emerald-500); background: var(--pa-surface); color: var(--pa-text); box-shadow: 0 0 0 1px var(--pa-emerald-500) inset; }
.gen-rail-step.is-active .gen-rail-num { background: var(--pa-emerald-500); color: #fff; border-color: var(--pa-emerald-500); }
@media (max-width: 767px) {
    .gen-rail-txt { display: none; }
    .gen-rail-step { min-width: 0; flex: 1; justify-content: center; }
}

/* Variante ONGLETS du même composant (lecteur d'états) : pas de numéro, donc
   les étapes ne s'étirent pas et le libellé DOIT rester visible en mobile —
   sans numéro ni texte, le bouton serait vide. Débordement en défilement
   horizontal, jamais en retour à la ligne : c'est ce retour à la ligne qui
   cassait l'alignement de la barre. */
.gen-rail--onglets {
    flex-wrap: nowrap;
    overflow-x: auto;
    margin-bottom: 0;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
}
/* Largeur UNIFORME : base 0 + croissance égale → toutes les catégories font la
   même largeur quel que soit leur libellé (« Fiscal » 72 px, « Achats » 85 px
   à l'état naturel). Le plancher est calé sur le libellé le plus large, de
   sorte qu'aucun onglet ne se comprime : quand les 7 n'entrent plus, la piste
   défile au lieu de les écraser. */
.gen-rail--onglets .gen-rail-step {
    flex: 1 1 0;
    min-width: 92px;
    justify-content: center;
}
.gen-rail--onglets .gen-rail-txt { display: inline; }
.gen-rail--onglets::-webkit-scrollbar { height: 5px; }
.gen-rail--onglets::-webkit-scrollbar-thumb {
    background: var(--pa-border-strong); border-radius: var(--pa-r-pill);
}
/* Catégorie sans aucun état : l'onglet reste en place — la barre garde la même
   disposition d'une visite à l'autre et on voit où ira un futur état — mais il
   est inerte. Filet en pointillé = emplacement encore vide. */
.gen-rail--onglets .gen-rail-step:disabled {
    opacity: .45;
    cursor: default;
    border-style: dashed;
}
.gen-rail--onglets .gen-rail-step:disabled:hover {
    border-color: var(--pa-border);
    color: var(--pa-text-soft);
}
/* Recherche en cours : les onglets ne commandent plus l'affichage. */
.gen-rail--onglets.is-neutre .gen-rail-step { opacity: .45; }

/* Panneau d'étape */
.gen-panel { animation: gen-fade var(--pa-t-base) ease; }
@keyframes gen-fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.gen-panel-head { display: flex; align-items: flex-start; gap: var(--pa-s-3); margin-bottom: var(--pa-s-3); }
.gen-panel-ic {
    flex-shrink: 0; width: 34px; height: 34px; border-radius: var(--pa-r-md);
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--pa-emerald-50); color: var(--pa-emerald-600);
    border: 1px solid var(--pa-emerald-200);
}
.gen-panel-title { font-weight: 700; font-family: var(--pa-font-display); font-size: var(--pa-fs-lg); color: var(--pa-text); line-height: 1.15; }
.gen-panel-sub { font-size: var(--pa-fs-xs); color: var(--pa-text-muted); margin-top: 2px; }

/* Options avancées (repli natif <details>) */
.gen-adv { margin-top: var(--pa-s-3); border: 1px dashed var(--pa-border); border-radius: var(--pa-r-md); background: var(--pa-surface-soft); }
.gen-adv[open] { background: var(--pa-surface); }
.gen-adv-sum {
    cursor: pointer; list-style: none; user-select: none;
    padding: var(--pa-s-2) var(--pa-s-3);
    font-size: var(--pa-fs-sm); font-weight: 600; color: var(--pa-text-soft);
    display: flex; align-items: center; gap: var(--pa-s-2);
}
.gen-adv-sum::-webkit-details-marker { display: none; }
.gen-adv-sum:hover { color: var(--pa-text); }
.gen-adv-caret { font-size: 10px; color: var(--pa-text-muted); transition: transform var(--pa-t-fast); }
.gen-adv[open] .gen-adv-caret { transform: rotate(90deg); }
.gen-adv-body { padding: 0 var(--pa-s-3) var(--pa-s-3); }

/* Pied de parcours (barre d'action collante) */
.gen-wizard-foot {
    display: flex; align-items: center; gap: var(--pa-s-2); flex-wrap: wrap;
    margin-top: var(--pa-s-3); padding: var(--pa-s-3);
    border: 1px solid var(--pa-border); border-radius: var(--pa-r-md);
    background: var(--pa-surface);
    position: sticky; bottom: var(--pa-s-2); z-index: 5;
    box-shadow: var(--pa-shadow-sm);
}
.gen-foot-spacer { flex: 1 1 auto; }
.gen-foot-step { font-size: var(--pa-fs-xs); color: var(--pa-text-muted); font-weight: 600; }


/* ═══════════════════════════════════════════════════════════════════════
   Barre d'onglets MOBILE — .pa-tabbar
   À APPENDRE à la fin de static/css/pa-layout.css
   ═══════════════════════════════════════════════════════════════════════
   Cinq destinations fixes sous 767px, calées sur le quotidien du
   pharmacien : Accueil · Caisse · Tickets · Fiscal · Menu.

   POURQUOI EN PLUS DU TIROIR, ET NON À SA PLACE : le tiroir porte 6
   sections et ~30 destinations — il reste la carte complète. La tab bar
   ne sert que les 4 écrans ouverts plusieurs fois par jour, à portée de
   pouce, sans ouvrir le tiroir. Le 5e onglet OUVRE le tiroir : rien
   n'est retiré, la profondeur reste accessible d'un geste.

   Desktop INCHANGÉ : tout est enfermé dans la media query. Aucune règle
   ne fuit au-delà de 767px.
   ═══════════════════════════════════════════════════════════════════════ */

.pa-tabbar { display: none; }

@media (max-width: 767px) {

  .pa-tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* 150 : au-dessus du contenu et du .header sticky (100), SOUS l'overlay
       du tiroir (200) et le tiroir lui-même (300) — le tiroir ouvert doit
       couvrir la barre, sinon on garde deux navigations actives en même
       temps. (Le paquet de conception annonçait 900 sous un overlay à 1500 :
       le 1500 est celui du panier d'évaluation, pas du tiroir, dont
       l'overlay est à 200 — voir base.html l.449/466.) */
    z-index: 150;
    background: var(--pa-surface);
    border-top: 1px solid var(--pa-border);
    box-shadow: 0 -1px 3px rgba(28, 34, 32, .05);
    /* Encoche / barre gestuelle : la hauteur utile reste 56px, le
       rembourrage bas s'ajoute au-dessous. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .pa-tabbar-item {
    flex: 1 1 0;
    min-width: 0;
    height: 56px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border: 0;
    background: none;
    padding: 0;
    cursor: pointer;
    font-family: var(--pa-font-sans);
    color: var(--pa-text-faint);
    text-decoration: none;
    transition: color var(--pa-t-fast);
  }

  .pa-tabbar-item i {
    font-size: 1.05rem;
    line-height: 1;
  }

  .pa-tabbar-label {
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    /* Un libellé qui déborde casserait la rangée : on tronque plutôt. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .pa-tabbar-item:active {
    background: var(--pa-sand-100);
  }

  .pa-tabbar-item.active {
    color: var(--pa-emerald-500);
  }

  /* Filet supérieur sur l'onglet actif — même rôle que le fond plein du
     .nav-item actif du tiroir, mais sans épaissir une barre de 56px. */
  .pa-tabbar-item.active::before {
    content: "";
    position: absolute;
    top: 0;
    width: 28px;
    height: 2px;
    border-radius: 0 0 var(--pa-r-xs) var(--pa-r-xs);
    background: var(--pa-emerald-500);
  }

  .pa-tabbar-item { position: relative; }

  /* ── Dégagement du contenu ──
     Sans cela, le dernier bloc de chaque page passe SOUS la barre. */
  body.has-tabbar .main,
  body.has-tabbar .pa-page {
    padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px) + var(--pa-s-5));
  }

  /* ── Éléments flottants déjà posés en bas ──
     Toast et bandeau d'installation se superposaient à la barre : on les
     remonte d'une hauteur de barre. Le sélecteur reste borné à
     body.has-tabbar pour que les pages sans barre gardent leur position
     d'origine.

     C'est .pa-toast-stack qui porte le position:fixed et le bottom (l.1296) —
     .pa-toast est un enfant NON positionné, lui poser un `bottom` n'aurait
     aucun effet. Le bandeau, lui, est bien positionné sur lui-même (l.1356).

     .panier-toast (l.1281, z 2500) est un SECOND système de toast, distinct
     de .pa-toast-stack : il est rendu sur toutes les pages via
     components/panier_evaluation.html, plus une instance #etq-toast dans
     etiquettes.html. Il porte son propre position:fixed / bottom: 24px, donc
     il lui faut sa propre remontée. On ne touche QUE `bottom` : son centrage
     par transform: translateX(-50%) doit rester intact. */
  body.has-tabbar .pa-toast-stack,
  body.has-tabbar .panier-toast,
  body.has-tabbar .pa-install-banner {
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Page « Plus » — .pa-mnav
   À APPENDRE à la fin de static/css/pa-layout.css
   ═══════════════════════════════════════════════════════════════════════
   Écran de navigation mobile. Contrairement aux autres composants de ce
   chantier, ces règles ne sont PAS enfermées dans une media query : la
   page /plus est atteignable en desktop (lien direct, favori), et elle
   doit y rester lisible plutôt que de s'étirer sur 1400 px. Elle est
   donc simplement bornée en largeur.

   Cibles tactiles : 56 px par ligne, au-dessus du plancher de 44 px.
   ═══════════════════════════════════════════════════════════════════════ */

.pa-mnav {
  max-width: 560px;
  margin: 0 auto;
  padding-bottom: var(--pa-s-6);
}

/* ── Identité ── */
.pa-mnav-me {
  display: flex;
  align-items: center;
  gap: var(--pa-s-4);
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-lg);
  padding: var(--pa-s-4);
  margin-bottom: var(--pa-s-6);
  text-decoration: none;
  color: var(--pa-text);
  box-shadow: var(--pa-shadow-xs);
}
.pa-mnav-me:hover { background: var(--pa-sand-50); text-decoration: none; color: var(--pa-text); }

.pa-mnav-me-av {
  width: 48px; height: 48px;
  border-radius: var(--pa-r-md);
  background: linear-gradient(135deg, var(--pa-emerald-400), var(--pa-emerald-700));
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 19px;
  flex-shrink: 0;
}
.pa-mnav-me-bd { flex: 1; min-width: 0; }
.pa-mnav-me-nm {
  display: block;
  font-size: var(--pa-fs-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pa-mnav-me-rl {
  display: block;
  font-size: var(--pa-fs-sm);
  color: var(--pa-text-muted);
  margin-top: 2px;
}

/* ── Groupes ── */
.pa-mnav-group { margin-bottom: var(--pa-s-6); }

.pa-mnav-title {
  font-size: var(--pa-fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--pa-text-muted);
  padding: 0 var(--pa-s-2) var(--pa-s-2);
}

.pa-mnav-list {
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-md);
  overflow: hidden;
  box-shadow: var(--pa-shadow-xs);
}

/* ── Ligne ── */
.pa-mnav-item {
  display: flex;
  align-items: center;
  gap: var(--pa-s-3);
  /* 56px : au-dessus du plancher de 44px, et confortable au pouce. */
  min-height: 56px;
  padding: var(--pa-s-2) var(--pa-s-4);
  border-bottom: 1px solid var(--pa-divider);
  text-decoration: none;
  color: var(--pa-text);
  transition: background var(--pa-t-fast);
}
.pa-mnav-item:last-child { border-bottom: 0; }
.pa-mnav-item:hover { background: var(--pa-sand-50); text-decoration: none; color: var(--pa-text); }
.pa-mnav-item:active { background: var(--pa-sand-100); }

.pa-mnav-ic {
  width: 36px; height: 36px;
  border-radius: var(--pa-r-sm);
  background: var(--pa-emerald-50);
  color: var(--pa-emerald-600);
  display: flex; align-items: center; justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}

.pa-mnav-lb {
  flex: 1;
  min-width: 0;
  font-size: var(--pa-fs-md);
  font-weight: 500;
  letter-spacing: -0.01em;
}

.pa-mnav-chev {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--pa-text-faint);
}

/* ── Sortie ── */
.pa-mnav-out {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--pa-s-2);
  min-height: 52px;
  border-radius: var(--pa-r-md);
  border: 1px solid var(--pa-border);
  background: var(--pa-surface);
  color: var(--pa-danger);
  font-size: var(--pa-fs-md);
  font-weight: 600;
  text-decoration: none;
  margin-top: var(--pa-s-2);
}
.pa-mnav-out:hover { background: var(--pa-danger-bg); color: var(--pa-danger); text-decoration: none; }

/* ═══════════════════════════════════════════════════════════════════════
   MOTIF D — Liste compacte mobile · .pa-mlist
   À APPENDRE à la fin de static/css/pa-layout.css
   ═══════════════════════════════════════════════════════════════════════
   POURQUOI UN QUATRIÈME MOTIF. Les motifs A (pa-table--cards) et B
   (pa-rcard) transposent un tableau : ils montrent TOUTES les colonnes.
   C'est juste pour un tableau qu'on lit en entier — une déclaration TVA
   tient en une douzaine de lignes.

   Sur articles_tva (1166 lignes), le motif A produit 228 px par article :
   sept cellules, six intitulés répétés, deux cellules qui redisent le
   même nombre quand la TVA est nulle. Soit 266 000 px de défilement.

   La liste ne transpose pas : elle CHOISIT. Un titre, une ligne de
   contexte, le chiffre qui compte, et le détail au toucher. 56 px par
   ligne — 4,1× plus compact — et 12,8 lignes par écran au lieu de 3,1.

   RÈGLE DE CHOIX : au-delà d'environ 30 lignes, ni A ni B, mais D.
   A et B conviennent aux tableaux qu'on LIT ; D aux tableaux qu'on
   PARCOURT. Le critère est le volume, pas le nombre de colonnes.

   COÛT : comme le motif B, la liste est un second balisage. Le macro de
   components/mobile_list.html réduit chaque ligne à un appel, et les
   deux boucles vivent dans le MÊME gabarit, côte à côte — le risque de
   divergence est donc faible, contrairement à plus.html / base.html.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {

  /* ── Le conteneur ── */
  .pa-mlist {
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-lg);
    overflow: hidden;
    box-shadow: var(--pa-shadow-xs);
  }

  /* ── La ligne ──
     56px : au-dessus du plancher de 44px. Le min-height mord — le
     contenu (titre 15px + sous-titre 12.5px + 16px de rembourrage)
     culmine à 50px. Ne pas réduire le rembourrage en croyant gagner de
     la place : c'est le min-height qui fixe la hauteur.

     ⚠ width:100% ET la réinitialisation du bouton sont OBLIGATOIRES.
     Le macro rend un <button> dès que la ligne est actionnable (pour
     réutiliser un gestionnaire JS existant) — c'est le chemin nominal,
     pas un cas limite. Or un <button> s'ajuste à son contenu, là où un
     <a> ou un <div> remplit son conteneur : sans width, les lignes
     forment un escalier de largeurs différentes (296, 337, 279, 287px
     mesurés à 390px). Un contrôle de balisage ne voit pas ce défaut ;
     il faut ouvrir la page. */
  .pa-mrow {
    display: flex;
    align-items: center;
    gap: var(--pa-s-3);
    width: 100%;
    box-sizing: border-box;
    min-height: 56px;
    padding: var(--pa-s-2) var(--pa-s-3);
    /* Réinitialisation des styles natifs du <button> */
    margin: 0;
    border: 0;
    border-bottom: 1px solid var(--pa-divider);
    background: var(--pa-surface);
    font-family: inherit;
    font-size: inherit;
    text-align: left;
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    text-decoration: none;
    color: inherit;
    transition: background var(--pa-t-fast);
  }
  .pa-mrow:last-child { border-bottom: 0; }
  .pa-mrow:active { background: var(--pa-sand-100); }
  .pa-mrow:hover { text-decoration: none; color: inherit; }

  /* Part relative, en filet de 2px sous la ligne. Rapportée au MEILLEUR
     élément et non au total : sur sept jours, des parts au total donneraient
     sept barres à ~14 %, toutes indiscernables.
     Un filet plutôt qu'un pourcentage écrit — on cherche « lequel domine »,
     pas une valeur au dixième, et une colonne de plus ne tient pas à 390px.
     Vérifié avant de poser : `.pa-mrow` n'avait aucun pseudo-élément, ni
     ::after ni ::before — la règle n'en écrase donc aucune. */
  .pa-mrow--part { position: relative; }
  .pa-mrow--part::after {
    content: "";
    position: absolute;
    left: 0; bottom: 0; height: 2px;
    width: var(--pa-part, 0);
    background: var(--pa-emerald-300);
  }

  /* ── Ligne + UNE action secondaire ──
     Le tableau desktop porte deux ou trois boutons par ligne ; la ligne
     dense n'en garde qu'un, parce que la ligne elle-même EST déjà une
     action. Le bouton est un FRÈRE de la ligne : un <button> dans un <a>
     est invalide, et la délégation JS écoute sur `.pa-mrow` — imbriqué,
     un appui sur le bouton ouvrirait aussi la fiche.

     C'est le conteneur qui porte alors le filet du bas : sinon le filet
     s'arrête avant le bouton et la ligne paraît coupée. */
  .pa-mrow-w {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--pa-divider);
    background: var(--pa-surface);
  }
  .pa-mrow-w:last-child { border-bottom: 0; }
  .pa-mrow-w > .pa-mrow { flex: 1; min-width: 0; border-bottom: 0; }
  /* Le bouton prend 44px hors du corps : à 390px il ne reste que 127px
     pour le titre. Resserrer les écarts et le rembourrage droit de la
     ligne en rend 16 — le titre se tronque encore, mais le code article
     du sous-titre, lui, ne bouge pas. */
  .pa-mrow-w > .pa-mrow { gap: var(--pa-s-2); padding-right: var(--pa-s-2); }

  .pa-mrow-act {
    flex: 0 0 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Deux cibles de 44px côte à côte sans limite se lisent comme une
       seule : le filet gauche est ce qui dit qu'il y en a deux. */
    border: 0;
    border-left: 1px solid var(--pa-divider);
    background: transparent;
    color: var(--pa-text-soft);
    font-size: 15px;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    text-decoration: none;
    transition: background var(--pa-t-fast);
  }
  .pa-mrow-act:active { background: var(--pa-sand-100); }
  .pa-mrow-act:hover { color: var(--pa-text); text-decoration: none; }

  /* ── Pastille de classement (groupe fiscal, état, catégorie) ──
     Remplace une colonne entière : lisible verticalement d'un coup
     d'œil, sans répéter l'intitulé 1166 fois. */
  .pa-mrow-tag {
    flex: 0 0 auto;
    width: 26px; height: 26px;
    border-radius: var(--pa-r-sm);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-xs);
    font-weight: 700;
    background: var(--pa-sand-100);
    color: var(--pa-text-soft);
  }
  .pa-mrow-tag--a    { background: var(--pa-info-bg);    color: #1f4e58; }
  .pa-mrow-tag--b    { background: var(--pa-warning-bg); color: #8a4814; }
  .pa-mrow-tag--ok   { background: var(--pa-success-bg); color: #2c6230; }
  .pa-mrow-tag--warn { background: var(--pa-warning-bg); color: #8a4814; }
  .pa-mrow-tag--dgr  { background: var(--pa-danger-bg);  color: #8d2e26; }
  /* Ton neutre de la pastille — mêmes valeurs que `--a`, sans la lettre.
     `--a` et `--b` sont les GROUPES fiscaux (articles_tva) : leur pastille
     écrit « A » ou « B », ce qui est juste là-bas et faux partout ailleurs.
     Un écran qui a besoin du bleu pour un ÉTAT (segment Récent, Régulier,
     Dormant de analyse_clients) doit pouvoir l'obtenir avec une icône. */
  .pa-mrow-tag--info { background: var(--pa-info-bg); color: #1f4e58; }

  /* ── Corps : titre + une seule ligne de contexte ── */
  .pa-mrow-bd { flex: 1; min-width: 0; }

  .pa-mrow-t {
    display: block;
    font-size: var(--pa-fs-base);
    font-weight: 500;
    letter-spacing: -0.01em;
    /* Explicite : la ligne est un <a>, et une règle `a { color }` du socle
       colore sinon les titres en couleur de lien — une liste de 40 noms
       d'articles en vert se parcourt mal. */
    color: var(--pa-text);
    /* Une ligne, tronquée. Un nom d'article sur deux lignes ferait
       varier la hauteur des lignes et détruirait le balayage vertical. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .pa-mrow-s {
    display: block;
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-muted);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* ── Chiffre-clé, aligné à droite ──
     Tabulaire : c'est ce qui permet de comparer une colonne de nombres
     verticalement, sans les intitulés. */
  .pa-mrow-v {
    flex: 0 0 auto;
    text-align: right;
  }
  .pa-mrow-v b {
    display: block;
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-base);
    font-weight: 600;
    letter-spacing: -0.01em;
  }
  .pa-mrow-v b.neg { color: var(--pa-danger); }
  .pa-mrow-v span {
    display: block;
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: 0.05em;
    color: var(--pa-text-faint);
    margin-top: 1px;
  }

  .pa-mrow-chev {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--pa-text-faint);
  }

  /* ── Bandeau de total, en TÊTE de liste ──
     Sur un écran de ventilation, c'est le chiffre qu'on vient chercher :
     il ne doit pas être en pied, après 1166 lignes. */
  .pa-mtotal {
    background: var(--pa-ink-900);
    color: var(--pa-sand-50);
    border-radius: var(--pa-r-lg);
    padding: var(--pa-s-4);
    display: flex;
    align-items: center;
    gap: var(--pa-s-4);
  }
  .pa-mtotal-bd { flex: 1; min-width: 0; }
  .pa-mtotal-hd { display: flex; align-items: center; gap: var(--pa-s-4); }
  .pa-mtotal-k {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: var(--pa-emerald-300);
  }
  /* Tons de bandeau - .pa-tone--ok / --warn / --dgr
     Portes par la valeur principale (tone=) ou par un split declare en
     TRIPLET (libelle, valeur, ton). Vocabulaire de `mrow`, pas un nouveau
     lexique.
     Variantes CLAIRES : le bandeau est sur --pa-ink-900, ou --pa-success et
     --pa-danger manqueraient de contraste. --pa-emerald-300 etait deja
     employe ici pour cette raison ; les deux autres suivent la meme regle. */
  .pa-mtotal-v.pa-tone--ok,
  .pa-mtotal-split .v.pa-tone--ok,
  .pa-mtotal-sp b.pa-tone--ok   { color: var(--pa-emerald-300); }
  .pa-mtotal-v.pa-tone--warn,
  .pa-mtotal-split .v.pa-tone--warn,
  .pa-mtotal-sp b.pa-tone--warn { color: var(--pa-warning-on-dark); }
  .pa-mtotal-v.pa-tone--dgr,
  .pa-mtotal-split .v.pa-tone--dgr,
  .pa-mtotal-sp b.pa-tone--dgr  { color: var(--pa-danger-on-dark); }
  .pa-mtotal-v {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-2xl);
    font-weight: 600;
    letter-spacing: -0.03em;
    line-height: 1;
    margin-top: 4px;
  }
  .pa-mtotal-v small { font-size: var(--pa-fs-sm); opacity: 0.55; margin-left: 3px; }
  .pa-mtotal-sp {
    flex: 0 0 auto;
    text-align: right;
    font-size: 10.5px;
    color: var(--pa-text-faint);
    line-height: 1.5;
  }
  .pa-mtotal-sp b {
    display: block;
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-sm);
    color: var(--pa-sand-50);
    font-weight: 600;
  }
  .pa-mtotal-sp span + b { margin-bottom: 4px; }

  /* ── Variante à rangée de pied : total + TROIS chiffres ──
     Absorbe un bloc de cartes KPI. Sur les écrans en motif D, ces cartes
     répètent en 415px ce que le bandeau dit en 135 — et l'une d'elles
     (CA TTC) redit exactement le total, à 400px d'écart.
     Trois maximum : à 390px, une quatrième colonne tombe sous 80px. */
  .pa-mtotal--stacked { flex-direction: column; align-items: stretch; gap: 0; }
  .pa-mtotal-split {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--pa-s-2);
    margin-top: var(--pa-s-4);
    padding-top: var(--pa-s-3);
    border-top: 1px solid rgba(255, 255, 255, 0.13);
  }
  .pa-mtotal-split .k {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.03em;
    color: var(--pa-text-faint);
  }
  .pa-mtotal-split .v {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-md);
    font-weight: 600;
    margin-top: 3px;
    letter-spacing: -0.01em;
  }
  /* État vide : le zéro devient une phrase, pas quatre cartes à 0. */
  .pa-mtotal-note {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--pa-s-2);
    padding: 4px 10px;
    border-radius: var(--pa-r-pill);
    background: rgba(255, 255, 255, 0.09);
    color: var(--pa-text-faint);
    font-size: var(--pa-fs-sm);
  }

  /* ── Filtre replié en une ligne ──
     Le bloc de filtres desktop fait ~520px empilé sous 767px. Replié en
     une ligne qui DIT ce qui est filtré, il en fait 50 — et s'ouvre au
     toucher (cible du <details> ou du panneau existant). */
  .pa-mfilter {
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-md);
    padding: 0 var(--pa-s-3);
    min-height: 50px;
    text-decoration: none;
    color: var(--pa-text);
  }
  .pa-mfilter > i:first-child { flex: 0 0 auto; color: var(--pa-emerald-500); font-size: 13px; }
  .pa-mfilter-tx {
    flex: 1; min-width: 0;
    font-size: var(--pa-fs-sm);
    line-height: 1.3;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .pa-mfilter-tx b { font-weight: 600; }
  .pa-mfilter > i:last-child { flex: 0 0 auto; color: var(--pa-text-faint); font-size: 12px; }

  /* ── En-tête de section ── */
  .pa-msec {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--pa-s-3);
    padding: var(--pa-s-5) var(--pa-s-2) var(--pa-s-2);
  }
  .pa-msec-t {
    font-size: var(--pa-fs-xs);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--pa-text-muted);
  }

  /* ── Liste vide ──
     Un filtre à 0 résultat ne doit JAMAIS donner un écran blanc : la barre de
     filtre reste au-dessus (seul moyen d'élargir la recherche depuis le
     mobile) et la liste affiche cette note à sa place. Sans elle, l'écran
     n'offre aucune sortie — le motif D remplace le panneau par une feuille. */
  .pa-mempty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--pa-s-2);
    padding: var(--pa-s-7) var(--pa-s-4);
    text-align: center;
    color: var(--pa-text-muted);
    font-size: var(--pa-fs-sm);
    line-height: 1.4;
  }
  .pa-mempty i { font-size: 22px; color: var(--pa-sand-300); }
  .pa-mempty b { color: var(--pa-text); font-weight: 600; font-size: var(--pa-fs-md); }

  .pa-msec-a {
    font-size: var(--pa-fs-sm);
    font-weight: 600;
    color: var(--pa-emerald-500);
    text-decoration: none;
  }

  /* ── Actions de page ──
     ⚠ NE PAS DUPLIQUER LE BLOC. Les boutons sont câblés par getElementById
     (#atPdfForm, #atPrintBtn) : deux éléments de même id et le second ne
     répond plus. Toujours DÉPLACER le bloc unique.

     `.pa-mactions-order` ET `.pa-mactions` (v1, qui descendaient les
     actions SOUS la liste) sont retirées : la v2 les remonte au-dessus du
     bandeau, via `.pa-mactions-top` (plus bas) et `.pa-mactions-bloc`.
     Voir INTEGRATION-lot5-v2.md, correction 2. */

  /* ── Bascule desktop / mobile ──
     Même principe que le motif C, mais avec le préfixe pa-. */
  .pa-mobile-only  { display: block; }
  .pa-desktop-only { display: none !important; }
}

@media (min-width: 768px) {
  .pa-mobile-only { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Filtres rapides mobiles — .pa-mchips
   À APPENDRE à la fin de static/css/pa-layout.css (après .pa-mlist)
   ═══════════════════════════════════════════════════════════════════════
   POURQUOI LES SORTIR DU PANNEAU. Les filtres rapides — Tous, En rupture,
   Stock faible, Reconditionnés, Groupe A, Groupe B — sont les plus
   utilisés et les moins coûteux à toucher. Enfermés dans le panneau
   replié du motif D, ils demandent deux gestes (ouvrir, choisir) pour
   une action d'un seul.

   Ils se placent SOUS la puce de filtre et AU-DESSUS du bandeau de
   total : après le résumé de ce qui est filtré, avant le chiffre qui en
   découle.

   Le panneau reste la place des filtres coûteux — dates, fournisseur,
   recherche texte. Les puces ne le remplacent pas.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {

  /* ── Le rail ──
     Défilement horizontal : à 390px, six puces ne tiennent pas, et les
     empiler sur trois rangées coûterait 130px. Le dégradé de bord dit
     qu'il y en a d'autres — sans lui, un rail qui déborde ressemble à un
     rail complet. */
  .pa-mchips {
    display: flex;
    gap: var(--pa-s-2);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Le rembourrage vertical laisse respirer l'ombre de la puce active ;
       l'horizontal évite que la première et la dernière soient collées
       aux bords de l'écran. */
    padding: var(--pa-s-1) var(--pa-s-4);
    /* Débord jusqu'aux bords de l'écran, en compensant le padding de la
       page : un rail qui s'arrête à 16px du bord semble tronqué. */
    margin: 0 calc(-1 * var(--pa-s-4));
    mask-image: linear-gradient(to right, transparent 0, #000 12px,
                                #000 calc(100% - 20px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 12px,
                                        #000 calc(100% - 20px), transparent 100%);
  }
  .pa-mchips::-webkit-scrollbar { display: none; }

  /* ── La puce ──
     40px : sous le plancher de 44px, mais la cible réelle est bien plus
     large que haute (80 à 150px), donc la surface touchable reste
     confortable. C'est un compromis assumé — six puces à 44px de haut
     coûteraient 8px de plus sans rien gagner en précision. */
  .pa-mchip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 0 var(--pa-s-3);
    border-radius: var(--pa-r-pill);
    border: 1px solid var(--pa-border-strong);
    background: var(--pa-surface);
    color: var(--pa-text-soft);
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-sm);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    transition: background var(--pa-t-fast), border-color var(--pa-t-fast);
  }
  .pa-mchip:hover { text-decoration: none; color: var(--pa-text-soft); }
  .pa-mchip:active { background: var(--pa-sand-100); }

  .pa-mchip i { font-size: 11px; opacity: 0.75; }

  /* Le compteur : même poids que le libellé serait du bruit, et l'omettre
     ferait perdre l'information la plus utile — combien de lignes derrière
     chaque filtre. */
  .pa-mchip-n {
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-xs);
    font-weight: 500;
    opacity: 0.7;
  }

  /* ── Tons ──
     Reprennent les couleurs déjà portées par ces filtres dans le panneau
     desktop : la reconnaissance se fait à la couleur avant le mot. */
  .pa-mchip--warn { border-color: #ead2a6; color: #8a4814; }
  .pa-mchip--dgr  { border-color: #e7c1b9; color: #8d2e26; }
  .pa-mchip--info { border-color: #b9d4d9; color: #1f4e58; }
  .pa-mchip--ok   { border-color: #b6d3c1; color: #1a5c3a; }

  /* ── État actif ──
     Rempli du ton de la puce. Un simple gras ne suffirait pas : sur un
     rail qui défile, l'utilisateur doit voir lequel est actif sans
     comparer les épaisseurs. */
  .pa-mchip.is-active {
    background: var(--pa-emerald-500);
    border-color: var(--pa-emerald-500);
    color: #fff;
  }
  .pa-mchip.is-active .pa-mchip-n { opacity: 0.85; }
  .pa-mchip--warn.is-active { background: #8a4814; border-color: #8a4814; color: #fff; }
  .pa-mchip--dgr.is-active  { background: #8d2e26; border-color: #8d2e26; color: #fff; }
  .pa-mchip--info.is-active { background: #1f4e58; border-color: #1f4e58; color: #fff; }
  .pa-mchip--ok.is-active   { background: #1a5c3a; border-color: #1a5c3a; color: #fff; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Rythme, pagination et actions — corrections v2 du motif D
   À APPENDRE à la fin de static/css/pa-layout.css (après pa-mchips)
   ═══════════════════════════════════════════════════════════════════════
   Quatre corrections après observation du rendu réel sur articles_vendus.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {

  /* ── LE RYTHME VERTICAL A UN SEUL PROPRIÉTAIRE : .main ──
     L'écart entre deux sections venait de TROIS sources qui se combinaient
     selon les voisins — l'utilitaire `mb-*` du bloc précédent (8 ou 24px, en
     !important), la compensation `margin-top: -1 * --bs-gutter-y` de `.row`,
     et la marge propre des composants mobiles. Cinq voisinages, cinq écarts.
     Les lots 34 et 35 corrigeaient un voisinage à la fois ; il y en a autant
     que de pages, d'où leur retrait au profit de cette seule déclaration.

     `<main class="main">` (base.html:2042) est le conteneur unique de toutes
     les pages, et ses enfants directs SONT les sections.

     `margin-bottom` plutôt que `> * + * { margin-top }` pour deux raisons :
     un enfant masqué (`pa-desktop-only`) ne laisse aucun écart derrière lui,
     alors que `+` l'atteint et décale son suivant — le piège du lot 34 en
     sens inverse ; et le premier enfant visible ne récolte pas un écart
     au-dessus de lui quand il est précédé d'un bloc desktop.

     `!important` est nécessaire, pas décoratif : les utilitaires d'espacement
     de Bootstrap sont eux-mêmes déclarés en !important.

     DOCTRINE DES COMBINATEURS DE FRÈRE, acquise aux lots 34-35 et valable
     au-delà d'eux : `+` ne saute rien, pas même un nœud masqué — un frère
     `pa-desktop-only` intercalé rend la règle inerte, sans erreur ni
     avertissement ; `~`, lui, ne s'arrête pas et atteint le troisième frère
     comme le premier. Les deux se trompent en silence. */
  .main > * {
    margin-top: 0 !important;
    margin-bottom: var(--pa-s-6) !important;
  }

  /* ── Les rangées Bootstrap, en mobile : une colonne, un gap ──
     La gouttière verticale de Bootstrap est en DEUX déclarations :
       .row     { margin-top: calc(-1 * var(--bs-gutter-y)); }  la compensation
       .row > * { margin-top: var(--bs-gutter-y); }             l'écart réel
     La règle ci-dessus annule la première et laisse la seconde : chaque rangée
     gardait 24px au-dessus de sa première colonne, plus les 24px de la section
     précédente. D'où 48 — le défaut du lot 34 revenu par une autre porte, une
     compensation neutralisée en croyant neutraliser l'écart qu'elle compense.

     `grid` plutôt qu'un rattrapage de marges, pour une raison précise : le
     `gap` d'une grille IGNORE les enfants en `display: none`. Les colonnes
     `pa-desktop-only` ne laissent donc aucun écart fantôme — ce qu'aucun jeu
     de `margin` ne sait faire, `+` atteignant les nœuds masqués et
     `:last-child` les désignant.

     PORTÉE : `> .row`, enfants DIRECTS de .main uniquement. Les rangées
     imbriquées gardent Bootstrap — `cloture_caisse` a des `col-6 col-md-3` qui
     doivent rester deux par ligne en mobile, et forcer `1fr` les empilerait.
     Vérifié : ses six rangées sont toutes à une profondeur ≥ 1, donc hors de
     portée. Les douze rangées ciblées sont toutes en `col-lg-*`, donc déjà
     pleine largeur sous 992px.

     Le retrait des `padding` / `margin` horizontaux n'est pas cosmétique : la
     gouttière horizontale fonctionne par marges négatives sur la rangée et
     rembourrage sur les colonnes. En grille, la marge négative subsisterait
     sans son rembourrage compensateur et la rangée dépasserait de 12px de
     chaque côté. */
  .main > .row {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--pa-s-6);
    margin-left: 0;
    margin-right: 0;
  }
  .main > .row > * {
    width: auto;
    max-width: none;
    /* Sans ceci, un élément de grille ne descend pas sous la largeur minimale
       de son contenu (`min-width: auto`) : la matrice de fréquentation à douze
       tranches élargit sa piste à ~700px et fait déborder LA PAGE au lieu de
       défiler dans son `.pa-table-wrap`, dont l'`overflow-x` n'a alors plus
       rien à contenir. Même cause que les champs de date du lot 34 — un cran
       plus haut dans l'arbre, et introduite par le passage en grille du 40. */
    min-width: 0;
    margin-top: 0;
    padding-left: 0;
    padding-right: 0;
  }

  /* ═══ 1. RYTHME VERTICAL ═══
     Les blocs se touchaient : filtre, bandeau, liste et actions
     formaient une colonne continue sans respiration. La maquette sépare
     chaque groupe — c'est ce qui distingue des sections d'une pile.

     Un seul point d'application : l'espacement appartient au CONTENEUR,
     pas à chaque bloc. Le sélecteur `> * + *` donne le même écart entre
     tous les voisins, et survit à l'ajout ou au retrait d'un bloc. */
  /* ── Rythme 12 / 24 / 8 ────────────────────────────────────────────
     Un écart unique de 16px ne groupait rien : la barre de filtre, ses
     puces, le bandeau et la liste se lisaient comme quatre blocs sans
     parenté. Trois valeurs suffisent, et elles sont toutes déjà dans
     l'échelle — 10px avait été proposé pour le rythme intérieur, mais
     l'échelle est en base 4 : inventer un jeton pour 2px de différence
     coûterait plus qu'il ne rend.

       12  À L'INTÉRIEUR d'un groupe. Actions, filtre, puces, bandeau :
           une cause et ses conséquences, elles se touchent presque.
       24  ENTRE deux groupes. Un seul écart coupe dans la pile —
           celui qui sépare les filtres de leur résultat.
        8  Entre un INTITULÉ et ce qu'il nomme. Un titre colle à son
           objet, sinon il flotte entre les deux.

     L'espacement appartient au CONTENEUR, pas aux blocs : `> * + *`
     donne le même écart à tous les voisins et survit à l'ajout comme au
     retrait d'un bloc. */
  .pa-mstack > * + * { margin-top: var(--pa-s-3); }

  /* L'intitulé de section OUVRE le groupe des données : la coupure est
     AVANT lui. (La v1 posait l'inverse — 8px avant, écart plein après —
     ce qui rattachait le titre aux filtres plutôt qu'à sa liste.) */
  .pa-mstack > * + .pa-msec { margin-top: var(--pa-s-6); }
  .pa-mstack > .pa-msec + * { margin-top: var(--pa-s-2); }


  /* ═══ 2. ACTIONS EN HAUT ═══
     Correction d'une erreur de la v1, qui les descendait sous la liste :
     avec 346 articles, il fallait parcourir toute la liste pour exporter.
     Elles remontent au-dessus du bandeau — atteignables sans défiler.

     Les icônes seules suffisent : les trois actions sont universelles, et
     leurs libellés forçaient un retour à la ligne à 390px. Le title
     reste, pour l'appui long et les lecteurs d'écran. */
  .pa-mactions-top {
    display: flex;
    gap: var(--pa-s-2);
    justify-content: flex-end;
  }
  .pa-mactions-top > * {
    /* Pas de flex: 1 — ces boutons ne sont pas la raison d'être de
       l'écran. Ils occupent leur taille, alignés à droite, et laissent la
       largeur au contenu. */
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--pa-r-md);
    border: 1px solid var(--pa-border);
    background: var(--pa-surface);
    color: var(--pa-text-soft);
    font-size: 15px;
    text-decoration: none;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
  }
  .pa-mactions-top > *:active { background: var(--pa-sand-100); }
  .pa-mactions-top > * span { display: none; }


  /* ═══ 3. PAGINATION → DÉFILEMENT ═══
     « 1 2 3 … 7 » avec des cibles de 32px est un motif de souris : sept
     pages veulent dire sept allers-retours serveur pour parcourir 346
     articles. Un bouton unique, pleine largeur, ajoute la tranche
     suivante à la liste en place. */
  .pa-mmore {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--pa-s-2);
    width: 100%;
    min-height: 48px;
    border-radius: var(--pa-r-md);
    border: 1px solid var(--pa-border-strong);
    background: var(--pa-surface);
    color: var(--pa-emerald-600);
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-base);
    font-weight: 600;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
  }
  .pa-mmore:active { background: var(--pa-sand-100); }
  .pa-mmore[disabled] { opacity: 0.5; pointer-events: none; }

  /* Le compte restant, sous le bouton : sans lui, on ne sait pas si un
     appui de plus suffit ou s'il en reste six. */
  .pa-mmore-n {
    display: block;
    text-align: center;
    font-family: var(--pa-font-mono);
    /* Lot 63 — 12.5px : en mono, en encre faible et centré, 11px ne se
       lisait pas. C'est le seul nœud qui dit où l'on en est dans la
       liste. */
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-faint);
    margin-top: var(--pa-s-2);
  }

  /* Mention de plafond (vue écrêtée, route sans pagination — cf. `plafonne`
     dans components/mobile_screen.html). Reprend le compteur du « Afficher
     plus » qu'elle REMPLACE, d'où la même classe ; elle a seulement besoin
     de respirer, n'ayant pas de bouton au-dessus d'elle. */
  .pa-mplafond {
    margin-top: var(--pa-s-4);
    padding: 0 var(--pa-s-3);
    line-height: 1.5;
  }

  /* La pagination desktop reste, masquée. */
  .pa-mstack .pagination,
  .pa-mstack .pa-pagination { display: none; }


  /* ═══ 4. TITRE DE PAGE — correction de la v2 ═══
     La v2 a fait masquer le titre ENTIER, sous-titre compris. Trop :
     la maquette garde le titre, elle ne garde pas le sous-titre.

     Ce qui était redondant, c'est le sous-titre descriptif —
     « Détail des articles écoulés sur la période · quantités, chiffre
     d'affaires & stock » sur deux lignes, ~44px pour paraéphraser le
     titre. Le titre lui-même ancre la page : sans lui, l'écran commence
     par une rangée d'onglets, et on ne sait plus ce qu'on regarde
     au-dessus d'eux.

     Le titre reprend la forme de la maquette : Fraunces, gros, avec la
     date alignée à droite sur la même ligne de base. */
  .pa-mtitle {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--pa-s-3);
    padding: var(--pa-s-2) 0 var(--pa-s-1);
  }
  .pa-mtitle h1,
  .pa-mtitle .pa-mtitle-t {
    margin: 0;
    font-family: var(--pa-font-display);
    font-size: 27px;
    font-weight: 500;
    letter-spacing: -0.025em;
    line-height: 1.05;
    color: var(--pa-text);
  }
  /* La date, ou tout contexte court, en mono — elle se lit comme une
     donnée, pas comme un sous-titre. */
  .pa-mtitle-meta {
    flex: 0 0 auto;
    font-family: var(--pa-font-mono);
    font-size: var(--pa-fs-sm);
    color: var(--pa-text-muted);
    white-space: nowrap;
    padding-bottom: 3px;
  }
  /* Le sous-titre descriptif, lui, reste masqué. */
  .pa-mtitle-sub { display: none; }


  /* ═══ 5. TYPOGRAPHIE DU BANDEAU — correction de la v1 ═══
     Le bandeau rendait plus petit que la maquette : j'avais écrit des
     tokens (--pa-fs-2xl à 26px, --pa-fs-sm à 12.5px) là où la maquette
     portait 34px et 15px. L'écart ne se voit pas dans le code — les
     tokens ont l'air raisonnables — mais il se voit à l'écran : le
     chiffre principal perd sa préséance, et les trois valeurs de pied
     tombent au niveau de leurs propres libellés.

     Valeurs fixes ici, et non des tokens : ce sont des tailles
     d'affichage propres à ce composant, pas des échelons de l'échelle
     typographique de l'interface. */
  .pa-mtotal-v {
    font-size: 34px;
    letter-spacing: -0.035em;
  }
  .pa-mtotal-v small { font-size: 15px; }

  /* Rangée de pied : la valeur doit dominer clairement son libellé.
     15px contre 11px — un rapport d'environ 1,4, celui de la maquette.
     À 12.5px contre 10px le rapport tombe à 1,25 et les deux lignes se
     lisent comme un bloc gris. */
  .pa-mtotal-split .k {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.03em;
  }
  .pa-mtotal-split .v {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    margin-top: 3px;
  }
  /* Même rapport dans la variante à deux splits en colonne. */
  .pa-mtotal-sp { font-size: 11px; }
  .pa-mtotal-sp b { font-size: 15px; }


  /* ═══ 6. LA LIGNE DE LISTE — correction du lot 5 ═══
     Comparée à la maquette Caisse, la ligne rendait plate : pastille
     trop petite, titre au même niveau que son sous-titre, et aucune
     respiration verticale. Trois défauts distincts.

     ── 6a. La pastille ──
     26px était une erreur d'échelle : à cette taille elle se lit comme
     une puce de texte, pas comme le repère visuel d'une colonne. La
     maquette portait 36 — assez pour ancrer la ligne et pour être
     balayée verticalement sans lire les lettres. */
  .pa-mrow-tag {
    width: 36px;
    height: 36px;
    border-radius: var(--pa-r-md);
    font-size: var(--pa-fs-sm);
    font-weight: 700;
  }

  /* ── 6b. La hiérarchie titre / contexte ──
     Le défaut de fond : titre à 14px/500 contre sous-titre à 11px/400
     donne un contraste de poids trop faible, et les deux lignes se
     lisent comme un bloc. Le nom de l'article est l'information que
     l'œil cherche — il doit dominer nettement.

     Trois leviers actionnés ensemble, parce qu'un seul ne suffit pas :
     la taille (15 contre 11), le poids (600 contre 500), et la couleur
     (encre pleine contre gris estompé). */
  .pa-mrow-t {
    font-size: 15px;
    font-weight: 600;
    color: var(--pa-text);
  }
  .pa-mrow-s {
    font-size: 11px;
    font-weight: 500;
    /* Plus estompé que --pa-text-muted : le contexte accompagne, il ne
       concurrence pas. */
    color: var(--pa-text-faint);
    margin-top: 3px;
  }

  /* Le chiffre-clé monte avec le titre — sinon c'est lui qui paraît
     secondaire, alors qu'il est la seconde raison de lire la ligne. */
  .pa-mrow-v b { font-size: 15px; }
  .pa-mrow-v span { font-size: 9.5px; }

  /* ── 6c. Respiration ──
     56px avec une pastille de 36 ne laisse que 10px de part et d'autre :
     la ligne paraît compressée. 64px avec un rembourrage vertical franc
     aère sans coûter beaucoup — 11 lignes par écran au lieu de 12,8,
     contre 3,1 pour le motif A. */
  .pa-mrow {
    padding: var(--pa-s-3) var(--pa-s-3);
    gap: var(--pa-s-3);
  }


  /* ═══ 8. LE TITRE NE SE TRONQUE PLUS ═══
     La règle d'origine (section « Corps », plus haut) disait : une ligne,
     tronquée, « un nom d'article sur deux lignes ferait varier la hauteur
     des lignes et détruirait le balayage vertical ».

     Le raisonnement est bon en général et FAUX SUR CES DONNÉES. Une
     désignation pharmaceutique porte son discriminant À LA FIN — forme,
     dosage, conditionnement :

       FLUDITEC SP ADULTES F/200ML
       FLUDITEC SP ENFANT-NOURRISSON
       DYNAPAR QPS 4 SPR F/30ML
       DYNAPAR QPS 4P100 SOL

     Tronquer par la fin supprime précisément ce qui distingue deux
     homonymes. Sur un écran de stock, c'est une erreur de dispensation en
     puissance — pas un défaut d'esthétique.

     ── La correction : trois lignes, à hauteur NON réservée ──
     Le clamp seul suffit. La réserve (min-height sur le titre et le
     sous-titre) a été essayée puis RETIRÉE : elle imposait 128px à TOUTES
     les lignes, y compris « MORPHINE INJ 10MG UNIT · 6 u. · 30 000 F »,
     qui tient en 78. Les trois quarts des lignes payaient le pire cas.

     La ligne est donc ÉLASTIQUE — plancher 78px, plafond 128px atteint
     seulement quand le contenu l'exige. La régularité du balayage était le
     bon souci, mais elle se paie moins cher que ça : le plancher commun et
     la position fixe de la pastille suffisent à tenir la colonne de
     gauche, et c'est elle que l'œil suit. */
  .pa-mrow-t {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* TROIS lignes. Mesuré : au clamp à 2, quatre des 50 lignes de
       `peremptions` restaient écrêtées SUR LE TITRE — « FEKEY
       33MG/1.50MG/7.5MCG/1.5MG SPA2 », « CETRAXAL PLUS 3MG/0.25MG GTTE
       AURI ». Ce sont exactement les désignations que 8bis protège : celles
       dont le discriminant (dosage, forme) est à la fin.

       Les désignations plafonnent à 35 caractères (champ à largeur fixe du
       programme externe), quand deux lignes à 15px en tiennent ~34 : le
       dépassement est structurel, pas anecdotique.

       PAS de min-height : le clamp borne le maximum, le contenu fixe le
       reste. Un titre d'une ligne n'occupe qu'une ligne. */
    -webkit-line-clamp: 3;
    white-space: normal;
    overflow: hidden;
    line-height: 1.25;
    /* Coupe les mots plutôt que de laisser un « F/200ML » déborder seul :
       les désignations n'ont pas d'espaces au bon endroit. */
    overflow-wrap: anywhere;
  }

  /* La pastille et le chiffre s'alignent en HAUT, pas au centre : avec un
     titre sur deux lignes, un alignement centré les fait flotter au milieu
     du bloc de texte au lieu de s'aligner sur sa première ligne. */
  .pa-mrow { align-items: flex-start; }
  .pa-mrow-tag,
  .pa-mrow-v { margin-top: 1px; }
  .pa-mrow-chev { align-self: center; }


  /* ═══ 8bis. LE SOUS-TITRE NON PLUS ═══
     Il était tronqué lui aussi — « 1 u. · 1 572 F · SIROPS/SOL… ». Une
     première correction proposait de sacrifier la catégorie. ELLE ÉTAIT
     MAUVAISE, et le commanditaire l'a refusée avec la bonne raison :

       « Ce sont des produits pharmaceutiques dont chaque information est
         importante et dans certains cas implique une décision sur un coup
         d'œil. »

     ── LE PRINCIPE, pour tout le reste du chantier ──
     SUR CES DONNÉES, ON NE SACRIFIE AUCUN CHAMP POUR GAGNER DE LA HAUTEUR.
     La ligne grandit. Une ellipse et un champ retiré coûtent la même chose
     — l'information n'est pas là — et « moins de lignes par écran » est un
     prix acceptable quand l'alternative est une décision prise sur une
     donnée absente.

     Ce que le motif D peut encore faire, et qui reste légitime :
       — supprimer la RÉPÉTITION (« exonéré » au lieu de « TVA 0 » répété
         mille fois) ;
       — supprimer la REDONDANCE (une colonne qui redit une autre) ;
       — déplacer vers la fiche ce qui n'aide pas à CHOISIR une ligne.
     Ce qu'il ne peut plus faire : couper un champ qui distingue deux
     produits, ou qui pourrait fonder une décision. */
  .pa-mrow-s {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* TROIS lignes, et non deux comme le disait la première rédaction de
       8bis. Mesuré après application : à 2 lignes, 7 des 50 lignes de
       `peremptions` restaient écrêtées — le sous-titre le plus long du
       dépôt fait 48 caractères (« 17 u. · 73 100 F · ALIMENTS LACT. 1er &
       2eme AGE »), quand deux lignes à 11px en tiennent ~44.

       Le principe ne souffre pas d'exception à 86 % : « rien n'est
       tronqué » vaut pour les 50 lignes, pas pour 43.

       PAS de min-height ici non plus — cf. `.pa-mrow-t`. */
    -webkit-line-clamp: 3;
    white-space: normal;
    overflow: hidden;
    line-height: 1.35;
    overflow-wrap: anywhere;
  }

  /* ── La ligne est ÉLASTIQUE : plancher 78, plafond 128 ──
     78px = pastille de 36 + rembourrage (24) et, en pratique, un titre
     d'une ligne (18.75) + son sous-titre d'une ligne (14.85) + l'écart (3)
     = 60.6 de contenu, que le plancher arrondit vers le haut pour laisser
     respirer. C'est la hauteur d'une ligne courte — « MORPHINE INJ 10MG
     UNIT · 6 u. · 30 000 F ».

     128px = le pire cas, atteint SEULEMENT quand le contenu l'exige :
     titre 3 lignes (56.25) + sous-titre 3 lignes (44.55) + écart (3) +
     rembourrage (24). Le clamp l'empêche d'aller plus haut.

     Pourquoi la réserve a été retirée. Elle imposait 128 à TOUTES les
     lignes pour que le balayage vertical garde son rythme. Le souci était
     juste, le remède trop cher : les trois quarts des lignes payaient le
     pire cas, et un écran ne montrait plus que 5 lignes au lieu de 8. Ce
     qui tient la colonne de gauche, c'est la pastille — même taille, même
     position, alignée en haut : l'œil la suit sans avoir besoin que les
     lignes soient toutes identiques.

     Historique des valeurs, parce qu'elles se déduisent du contenu réel et
     non d'un choix esthétique :
       ·  64px — 1 ligne + 1 ligne, tronquées (avant 8) ;
       ·  94px — 2 + 2 réservées : écrêtait 7 lignes sur 50 ;
       · 109px — 2 + 3 réservées : écrêtait encore 4 titres sur 50 ;
       · 128px — 3 + 3 réservées : plus aucun écrêtage, mais hauteur fixe ;
       ·  78 → 128px — 3 + 3 CLAMPÉES, sans réserve : aucun écrêtage non
         plus (critère de recette), et la ligne courte redevient courte. */
  .pa-mrow { min-height: 78px; }


  /* ── AJOUTS LOCAUX (hors paquet) ──
     Trois besoins propres à ce dépôt, à conserver lors des recopies. */

  /* 1. Ordre des trois frères (piège de l'étape 1) : .pa-mstack est FRÈRE
        de la carte desktop, et le bloc d'actions est sorti de la carte lui
        aussi — sinon il disparaîtrait avec elle. */
  .pa-flux-mobile { display: flex; flex-direction: column; }
  .pa-flux-mobile > .pa-mactions-bloc { order: 0; margin-bottom: var(--pa-s-4); }
  .pa-flux-mobile > .pa-mobile-only { order: 1; }

  /* 2. Les trois contrôles portent des classes Bootstrap (.btn-danger,
        .btn-primary) qui l'emportent sur la boîte neutre de
        `.pa-mactions-top > *` : un bouton restait vert plein, un autre sans
        bordure. On neutralise à spécificité égale. */
  .pa-mactions-top > .btn,
  .pa-mactions-top > form > .btn {
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    color: var(--pa-text-soft);
    box-shadow: none;
  }
  .pa-mactions-top > .btn:active,
  .pa-mactions-top > form > .btn:active { background: var(--pa-sand-100); }
  .pa-mactions-top i { margin: 0; font-size: 15px; }

  /* 3. Le bouton « Exporter PDF » vit dans #atPdfForm, qu'on ne peut pas
        défaire sans casser l'export : le <form> devient la boîte de 44px et
        le bouton s'y étire, pour que la cible tactile reste 44 × 44. */
  .pa-mactions-top > form { padding: 0; border: 0; background: none; }
  .pa-mactions-top > form > .btn {
    width: 100%; height: 100%; min-height: 0; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }


  /* ═══ 7. EN-TÊTE MOBILE — le hamburger disparaît ═══
     Décision actée : la page « Plus » porte la même carte que le tiroir,
     avec des cibles de 56px et un bouton retour du téléphone qui
     fonctionne. Le hamburger est donc redondant en mobile, et la largeur
     qu'il occupe revient au nom de l'application.

     Masqué, pas retiré du DOM : le tiroir reste ouvrable en desktop, et
     le JS de base.html (`menuBtn.addEventListener`, base.html:2191) ne
     lève pas — il trouve toujours l'élément.

     ── Écart avec le paquet, assumé ──
     Le paquet prescrivait aussi `.header .brand { white-space: nowrap }`
     en garde. Deux raisons de ne pas la recopier :
       — le balisage réel n'a pas de `.brand` (base.html:1952-1957 : c'est
         `.header-brand` / `.header-brand-text`) — la règle serait morte ;
       — la garde EXISTE DÉJÀ, posée plus haut sur les bons sélecteurs
         (`.header-brand` + `.header-brand-text`, l.1865-1870, avec
         l'ellipse en plus). La recopier ferait un doublon divergent.
     Seul `#menuBtn` est donc repris — le seul sélecteur que le paquet
     donnait pour certain, et il est exact. */
  #menuBtn { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Feuille de filtres — .pa-msheet
   À APPENDRE à la fin de static/css/pa-layout.css
   ═══════════════════════════════════════════════════════════════════════
   Le modèle de la maquette Tickets, généralisé à tous les écrans en
   motif D.

   POURQUOI PAS LA BASCULE PAR CLASSE. Elle marche, mais elle déplace le
   contenu : ouvrir les filtres pousse la liste de 520px vers le bas, et
   la refermer la ramène. Sur une liste qu'on parcourait, on perd sa
   place à chaque essai de filtre.

   La feuille recouvre au lieu de pousser. Trois conséquences qui la
   rendent supérieure :
     — la liste ne bouge pas ;
     — l'action de validation est un bouton unique, pleine largeur, en
       bas, sous le pouce ;
     — ce bouton peut ANNONCER LE RÉSULTAT (« Afficher 14 tickets »),
       ce qu'un panneau déplié ne fait pas.

   Deux éléments visibles en permanence : la ligne « FILTRE ACTIF » qui
   dit ce qui est filtré, et le bouton « Filtrer » qui ouvre la feuille.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {

  /* ── La barre visible ──
     Remplace .pa-mfilter de la v1 : même rôle, mais le bouton d'ouverture
     est explicite au lieu d'un chevron. Un chevron sur une ligne de
     résumé ne dit pas qu'on peut agir dessus. */
  /* Le panneau de filtres ne prend PAS de place dans le flux mobile : il est
     remplacé par .pa-mfilterbar, et déplacé dans la feuille à l'ouverture.
     Sans cette règle, son retrait du flux au moment du déplacement ferait
     remonter la liste de sa hauteur (556px mesurés) — exactement le
     décalage que la feuille est censée supprimer.
     PORTÉE : uniquement les écrans qui ONT une feuille (.pa-filtres-sheet).
     Les ~27 écrans pas encore convertis gardent leur panneau visible —
     masquer `.pa-filter-card` globalement les priverait de leurs filtres.

     La portée est exprimée par la PRÉSENCE DE LA FEUILLE (`:has`), et non
     plus par `body.pa-liste-mobile` : la feuille n'est pas propre au motif D.
     `tiers_payant` est en motif B et s'en sert — son panneau déplié poussait
     la 1re carte à y=1148 sur un écran de 844. Le critère juste est « cet
     écran a-t-il une autre voie vers ses filtres ? », pas « est-il en motif
     D ? ». Le repli sur `body.pa-liste-mobile` reste, pour les navigateurs
     sans `:has` (et parce qu'il ne coûte rien). */
  body.pa-liste-mobile .pa-filter-card,
  body:has(#pa-filtres-sheet) .pa-filter-card { display: none; }
  body.pa-liste-mobile .pa-msheet-champs .pa-filter-card,
  body:has(#pa-filtres-sheet) .pa-msheet-champs .pa-filter-card { display: block; }
  /* La feuille fournit son propre en-tête et son bouton de validation. */
  .pa-msheet-champs .pa-filter-header,
  .pa-msheet-champs .pa-filter-actions { display: none; }

  /* ── Les deux dates sur une rangée ──
     Dans la feuille seulement : sur desktop la grille de filtres garde sa
     mise en page. « Du ... au ... » lues côte à côte forment UNE période ;
     empilées, ce sont deux champs indépendants et on relit la première pour
     saisir la seconde.

     Le critère est le TYPE du champ, jamais sa position : la grille est
     plate (sept frères sans classe), et un :nth-child se décalerait au
     premier champ ajouté en tête, sans que rien ne le montre.

     Tout ce qui n'est pas une date occupe la largeur entière — un select de
     fournisseur sur une demi-largeur tronque ses options.

     Le `gap` est redéclaré : la règle de base vaut --pa-s-4 (16px), et le
     rythme intérieur d'un groupe est --pa-s-3 dans toute la pile mobile.
     La feuille ne fait pas exception.

     ⚠ Cette règle REMPLACE `grid-template-columns: 1fr` (v1). Deux règles
     contradictoires sur le même sélecteur se seraient départagées par
     l'ordre du fichier — un doublon divergent, à 300 lignes d'écart. */
  .pa-msheet-champs .pa-filter-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--pa-s-3);
  }
  .pa-msheet-champs .pa-filter-grid > * { grid-column: 1 / -1; }
  /* Un enfant de grille a min-width:auto : il ne peut pas être plus étroit
     que son contenu. Le minimum d'un input[type=date] (≈130px : 85 de
     ::-webkit-date-and-time-value, 24 de rembourrage, 2 de bordure, ~20
     d'indicateur natif) devient donc un plancher pour la COLONNE, la grille
     déborde et le second champ sort du cadre. Le `width: 100%` de
     `.form-control` n'y peut rien : il fixe la largeur VOULUE, pas la
     minimale.
     min-width:0 rend la colonne compressible ; max-width:100% empêche
     l'input de ressortir de la colonne ainsi rétrécie. Les deux, pas une.
     `select` est inclus : à options longues il a le même défaut, et il
     déborderait même sans grille à deux colonnes. */
  .pa-msheet-champs .pa-filter-grid > * { min-width: 0; }
  .pa-msheet-champs .pa-filter-grid input,
  .pa-msheet-champs .pa-filter-grid select { max-width: 100%; }
  .pa-msheet-champs .pa-filter-grid > *:has(input[type="date"]) {
    grid-column: auto;
  }

  /* Sous 360px, deux champs de date ne tiennent plus : 138px par champ, et
     le format natif « jj/mm/aaaa » en demande ~125 selon la plateforme —
     la marge est trop faible pour être garantie. On réempile. */
  @media (max-width: 359px) {
    .pa-msheet-champs .pa-filter-grid > *:has(input[type="date"]) {
      grid-column: 1 / -1;
    }
  }

  .pa-mfilterbar {
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    background: var(--pa-surface);
    border: 1px solid var(--pa-border);
    border-radius: var(--pa-r-md);
    padding: var(--pa-s-2) var(--pa-s-2) var(--pa-s-2) var(--pa-s-3);
    /* 66 et non 56 : la barre porte désormais TROIS lignes (libellé, portée,
       dates) au lieu de deux. */
    min-height: 66px;
  }
  .pa-mfilterbar-bd { flex: 1; min-width: 0; }
  .pa-mfilterbar-k {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--pa-text-muted);
  }
  .pa-mfilterbar-k i { color: var(--pa-emerald-500); font-size: 10px; }
  /* La PORTÉE du filtre — « tous groupes », « En rupture », un nom de
     fournisseur. C'est la réponse à « qu'est-ce que je regarde ? », donc
     elle domine.

     En sans et non en mono : la première version mettait la portée et
     les dates dans une même ligne monospace, ce qui les faisait lire
     comme une chaîne technique échappée d'un journal plutôt que comme
     une phrase. Le mono est pour les données ; « tous groupes » est du
     langage. */
  .pa-mfilterbar-v {
    display: block;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-base);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--pa-text);
    margin-top: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Les DATES, sur leur propre ligne. Deux bénéfices : elles cessent de
     concurrencer la portée, et la troncature disparaît — à 390px, une
     seule ligne ne pouvait pas contenir les deux, et c'est la date de
     fin qui était coupée (« 31/07/2… »). */
  .pa-mfilterbar-d {
    display: block;
    font-family: var(--pa-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--pa-fs-xs);
    color: var(--pa-text-faint);
    margin-top: 2px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .pa-mfilterbar-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 40px;
    padding: 0 var(--pa-s-3);
    border: 0;
    border-radius: var(--pa-r-sm);
    background: var(--pa-emerald-500);
    color: #fff;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-sm);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
  }
  .pa-mfilterbar-btn:active { background: var(--pa-emerald-600); }

  /* ── Le voile ──
     Au-dessus du toast, sinon un toast déclenché pendant que la feuille est
     ouverte s'afficherait par-dessus elle.

     ⚠ 2700 et non 2600 : le paquet visait « au-dessus du toast (2500) »,
     mais dans CE dépôt c'est `.pa-toast-stack` qui porte le positionnement
     et il est à **2600** (pa-layout.css l.1302) — pas 2500. À égalité de
     z-index, c'est l'ordre du DOM qui tranche, et la pile de toasts est
     injectée en fin de <body> : elle passait donc DEVANT la feuille
     (vérifié par elementFromPoint). */
  .pa-msheet-scrim {
    position: fixed;
    inset: 0;
    z-index: 2700;
    background: rgba(28, 34, 32, 0.42);
    display: flex;
    align-items: flex-end;
  }
  .pa-msheet-scrim[hidden] { display: none; }

  /* ── La feuille ── */
  .pa-msheet {
    width: 100%;
    max-height: 88%;
    display: flex;
    flex-direction: column;
    background: var(--pa-sand-50);
    border-radius: var(--pa-r-xl) var(--pa-r-xl) 0 0;
    overflow: hidden;
    animation: pa-msheet-up 0.26s cubic-bezier(0.2, 0.8, 0.3, 1) both;
  }
  @keyframes pa-msheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }
  @media (prefers-reduced-motion: reduce) {
    .pa-msheet { animation: none; }
  }

  /* Poignée : dit que la feuille se ferme par glissement, même si le
     glissement n'est pas implémenté — le × reste la voie sûre. */
  .pa-msheet-grab {
    flex: 0 0 auto;
    display: flex;
    justify-content: center;
    padding: var(--pa-s-2) 0 var(--pa-s-1);
  }
  .pa-msheet-grab i {
    width: 36px;
    height: 5px;
    border-radius: 3px;
    background: var(--pa-border-strong);
  }

  .pa-msheet-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--pa-s-2);
    padding: 0 var(--pa-s-2) var(--pa-s-3);
  }
  .pa-msheet-x {
    flex: 0 0 auto;
    width: 44px; height: 44px;
    border: 0;
    background: none;
    color: var(--pa-text-soft);
    font-size: 16px;
    cursor: pointer;
    border-radius: var(--pa-r-pill);
  }
  .pa-msheet-x:active { background: var(--pa-sand-100); }
  .pa-msheet-t {
    flex: 1;
    text-align: center;
    font-size: var(--pa-fs-lg);
    font-weight: 600;
    /* Compense la largeur du × pour que le titre soit optiquement
       centré, sans ajouter un élément vide à droite. */
    padding-right: 44px;
  }

  /* Corps défilant : c'est ici que va le bloc de filtres existant. */
  .pa-msheet-bd {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 var(--pa-s-4) var(--pa-s-4);
    scrollbar-width: none;
  }
  .pa-msheet-bd::-webkit-scrollbar { display: none; }

  /* ── Pied d'action ──
     Hors du corps défilant : le bouton de validation reste visible même
     quand les champs débordent. C'est la raison d'être de la feuille. */
  .pa-msheet-foot {
    flex: 0 0 auto;
    display: flex;
    gap: var(--pa-s-2);
    padding: var(--pa-s-3) var(--pa-s-4)
             calc(var(--pa-s-4) + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid var(--pa-border);
    background: var(--pa-surface);
  }
  .pa-msheet-reset {
    flex: 0 0 auto;
    width: 52px;
    min-height: 52px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--pa-border-strong);
    border-radius: var(--pa-r-md);
    background: var(--pa-surface);
    color: var(--pa-text-soft);
    font-size: 15px;
    text-decoration: none;
    cursor: pointer;
  }
  .pa-msheet-reset:hover { color: var(--pa-text-soft); text-decoration: none; }
  .pa-msheet-reset:active { background: var(--pa-sand-100); }
  .pa-msheet-go {
    flex: 1;
    min-height: 52px;
    border: 0;
    border-radius: var(--pa-r-md);
    background: var(--pa-emerald-500);
    color: #fff;
    font-family: var(--pa-font-sans);
    font-size: var(--pa-fs-md);
    font-weight: 600;
    cursor: pointer;
  }
  .pa-msheet-go:active { background: var(--pa-emerald-600); }

  /* Un compteur de puce qui ne décrit plus les champs saisis vaut moins que
     pas de compteur : il fait choisir sur un chiffre faux. Même raison que
     le libellé du bouton (`data-msheet-neutre`, base.html). */
  .pa-msheet[data-perime] .pa-mchip-n { display: none; }

  /* Les conteneurs Bootstrap passent en pleine largeur dans la feuille.
     `.pa-filter-grid` n'est PLUS ici : elle est gérée plus haut, par la
     règle des dates. Son `display: block` gagnait par l'ordre du fichier
     (spécificité égale, position plus basse) et rendait cette règle-là
     inerte — la rangée de dates ne se formait jamais, sans erreur ni
     avertissement. */
  .pa-msheet-bd .row { display: block; }
  /* 16px minimum : en dessous, iOS zoome au focus et casse la mise en
     page de la feuille. */
  .pa-msheet-bd .form-control,
  .pa-msheet-bd .form-select { font-size: 16px; min-height: 48px; }
}

@media (min-width: 768px) {
  .pa-msheet-scrim { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Lot 71 — LES MODALES TIENNENT DANS L'ÉCRAN (mobile)
   ═══════════════════════════════════════════════════════════════════════
   Le défaut, constaté sur l'aperçu de facture : la modale n'était bornée
   par rien. Son contenu l'étirait, elle débordait de la fenêtre, et
   c'était la PAGE qui défilait. Conséquence : pour atteindre les boutons
   du pied, il fallait faire défiler tout le ticket — et les trois boutons
   arrivaient à leur largeur de bureau, en deux rangs mal cassés.

   Ce n'est pas propre à cet écran. Toute modale dont le contenu dépasse
   la hauteur du téléphone se comporte ainsi : aperçu de facture
   (`reedit_facture`, `export_factures`, `gestion_caisse`), ticket de
   caisse, détails de relevé, liste de factures d'un article. Le correctif
   est donc ici, une fois, et non dans les gabarits.

   ── CE QUE ÇA FAIT ────────────────────────────────────────────────────
   Sous 768px, toute modale devient une feuille bornée à la fenêtre :
   l'en-tête et le pied restent en place, seul le CORPS défile. C'est le
   comportement de `.modal-dialog-scrollable` de bootstrap, appliqué
   d'office plutôt qu'écran par écran — aucune des huit modales du dépôt
   ne le portait.

   ── POURQUOI PAS `.modal-dialog-scrollable` DANS LES GABARITS ─────────
   Il aurait fallu le poser sur huit modales, se souvenir de le poser sur
   la neuvième, et il ne règle ni la taille des boutons ni la marge. Un
   comportement que TOUTE modale doit avoir sur un téléphone n'est pas une
   option à cocher : c'est le défaut du système.

   ── 100dvh ET NON 100vh ───────────────────────────────────────────────
   Sur iOS et Android, `100vh` compte la barre d'adresse comme si elle
   était rétractée. Une modale en `100vh` dépasse donc du bas, du montant
   exact de la barre — et le pied redevient inatteignable, ce qu'on
   corrige ici. `dvh` suit la hauteur réellement visible.
   ═══════════════════════════════════════════════════════════════════════ */

@media (max-width: 767.98px) {

  /* La boîte : bornée à l'écran, marge fine pour que le scrim reste lisible. */
  .modal-dialog {
    margin: .5rem;
    max-width: none;
    height: calc(100dvh - 1rem);
    display: flex;
    align-items: center;
  }

  /* `modal-dialog-centered` pose déjà `align-items:center` ; l'uniformiser
     ici évite qu'une modale centrée et une modale ordinaire ne se placent
     différemment une fois la hauteur contrainte. */
  .modal-content {
    max-height: 100%;
    overflow: hidden;
  }

  /* L'en-tête et le pied ne se compriment pas ; le corps prend le reste et
     défile seul. `min-height:0` est indispensable : sans lui, un enfant
     flex refuse de descendre sous sa hauteur de contenu et le corps pousse
     de nouveau la boîte. */
  .modal-header,
  .modal-footer { flex-shrink: 0; }

  .modal-body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-height: 0;
    /* Un aperçu de document (facture, ticket) est plus large que 390px et
       se lit en zoomant : le défilement latéral reste permis DANS le
       corps, jamais sur la page. */
    overflow-x: auto;
  }

  /* Lot 71a — UN RANG, pas une pile. À 390px les trois libellés tiennent
     (~330px pour 350px utiles) à condition que « Fermer » ne s'étire pas :
     c'est une sortie, pas une action, et lui donner le tiers de la barre
     le mettait au même rang que « Télécharger PDF ».
     `flex-wrap` reste : sous ~340px la dernière action passe à la ligne,
     seule et pleine largeur. On ne casse que ce qui ne rentre pas. */
  .modal-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: .5rem;
    padding: .75rem;
    position: sticky;
    bottom: 0;
    background: var(--pa-surface);
    border-top: 1px solid var(--pa-border);
  }

  .modal-footer > * { margin: 0; }

  /* Les actions se partagent la barre. `min-width` empêche qu'une
     troisième ne les comprime en boutons illisibles — au-delà, le
     `flex-wrap` du pied prend le relais. */
  .modal-footer .btn {
    flex: 1 1 auto;
    min-width: 7.5rem;
    min-height: 44px;
    justify-content: center;
  }

  /* Lot 71f — LA SORTIE QUITTE LE PIED.
     L'en-tête porte une croix, fixée en haut par le lot 71 et à 44px de
     cible : elle ne sort jamais de vue. Un « Fermer » dans le pied est
     donc un second geste de sortie à quelques centimètres du premier — et
     il prenait la place qui manquait aux actions.

     Le `:has` est la garde indispensable : la sortie n'est retirée QUE si
     le pied porte une autre action. Une modale dont l'unique bouton ferme
     (« J'ai compris ») garde le sien, sinon son pied serait vide. */
  .modal-footer:has(.btn:not([data-bs-dismiss="modal"])) [data-bs-dismiss="modal"].btn {
    display: none;
  }

  /* La croix de fermeture est une cible à part entière, souvent la
     première essayée. */
  .modal-header .btn-close {
    min-width: 44px;
    min-height: 44px;
    background-size: 1rem;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   Lot 72 — AppDialog : dialogues du design system (static/js/app-dialog.js)
   ═══════════════════════════════════════════════════════════════════════
   À APPENDRE EN FIN DE `static/css/pa-layout.css`.

   Remplacement maison de alert/confirm/prompt. Reprend la signature
   visuelle des cartes du dépôt — médaillon d'icône teinté + filet
   d'accent — pour que le dialogue appartienne visiblement à la même
   famille que le reste de l'application.

   ── z-index 3200 : MESURÉ, pas recopié ────────────────────────────────
   La pile existante de ce dépôt :
     2500  voile de recherche          2600  toasts (.pa-toast-stack)
     2700  feuille de filtres          3000  aperçu ticket 80 mm
   Le dépôt d'origine (Pharma_Admin_1.0.s) utilisait 2700 ; ici cette
   valeur est déjà prise par la feuille de filtres, et 3000 par l'aperçu
   ticket. Un dialogue est BLOQUANT — il doit toujours être le plan le
   plus proche, y compris au-dessus de l'aperçu ticket qui peut le
   déclencher. D'où 3200.
   ═══════════════════════════════════════════════════════════════════════ */

.pa-dialog-overlay {
  position: fixed; inset: 0; z-index: 3200;
  display: flex; align-items: center; justify-content: center;
  padding: var(--pa-s-4);
  background: rgba(28, 34, 32, .48);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  opacity: 0; transition: opacity var(--pa-t-base);
}
.pa-dialog-overlay.is-open { opacity: 1; }
.pa-dialog-overlay[hidden] { display: none; }

.pa-dialog {
  position: relative; overflow: hidden;
  width: 100%; max-width: 440px;
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-lg);
  box-shadow: var(--pa-shadow-lg);
  transform: translateY(10px) scale(.98);
  transition: transform var(--pa-t-base);
  outline: none;
}
.pa-dialog-overlay.is-open .pa-dialog { transform: translateY(0) scale(1); }
/* Filet d'accent haut : porte la couleur de la variante. */
.pa-dialog::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--pa-dialog-accent, var(--pa-emerald-500));
}

.pa-dialog-head {
  display: flex; align-items: center; gap: var(--pa-s-3);
  padding: var(--pa-s-5) var(--pa-s-5) var(--pa-s-3);
}
.pa-dialog-icon {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--pa-r-md);
  background: var(--pa-dialog-tint, var(--pa-emerald-50));
  color: var(--pa-dialog-accent, var(--pa-emerald-500));
  font-size: 17px;
}
.pa-dialog-title {
  font-family: var(--pa-font-display);
  font-size: var(--pa-fs-lg); font-weight: 500; letter-spacing: -0.01em;
  color: var(--pa-text); margin: 0; min-width: 0;
}

.pa-dialog-body { padding: 0 var(--pa-s-5) var(--pa-s-4); }
.pa-dialog-msg {
  margin: 0; color: var(--pa-text-soft);
  font-size: var(--pa-fs-base); line-height: 1.55; overflow-wrap: anywhere;
}
.pa-dialog-detail {
  margin: var(--pa-s-2) 0 0;
  font-size: var(--pa-fs-sm); color: var(--pa-text-muted); line-height: 1.5;
}
.pa-dialog-field { margin-top: var(--pa-s-4); }

.pa-dialog-foot {
  display: flex; justify-content: flex-end; gap: var(--pa-s-2);
  padding: var(--pa-s-3) var(--pa-s-5) var(--pa-s-5);
  background: var(--pa-surface-soft);
  border-top: 1px solid var(--pa-divider);
}

/* Variantes — seules deux variables changent, tout le reste est mutualisé. */
.pa-dialog--question { --pa-dialog-accent: var(--pa-emerald-500); --pa-dialog-tint: var(--pa-emerald-50); }
.pa-dialog--info     { --pa-dialog-accent: var(--pa-info);    --pa-dialog-tint: var(--pa-info-bg); }
.pa-dialog--success  { --pa-dialog-accent: var(--pa-success); --pa-dialog-tint: var(--pa-success-bg); }
.pa-dialog--warning  { --pa-dialog-accent: var(--pa-warning); --pa-dialog-tint: var(--pa-warning-bg); }
.pa-dialog--danger   { --pa-dialog-accent: var(--pa-danger);  --pa-dialog-tint: var(--pa-danger-bg); }

/* Mobile : le pied s'empile, et l'ACTION passe EN HAUT (column-reverse).
   Contrairement au pied de modale du lot 71 — là il y a deux ou trois
   actions et aucune sortie ; ici il y a une action et une annulation, et
   c'est l'action attendue qui doit tomber sous le pouce. */
@media (max-width: 575px) {
  .pa-dialog-foot { flex-direction: column-reverse; }
  .pa-dialog-foot .pa-btn { width: 100%; min-height: 44px; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .pa-dialog-overlay, .pa-dialog { transition: none; }
}

/* ═════ Écran hors connexion (static/offline.html) ═════
   Servi par le service worker depuis le cache quand le réseau tombe. La page
   étant autonome (elle n'étend pas base.html) et FontAwesome / Google Fonts
   étant indisponibles hors ligne, ces classes ne dépendent QUE de tokens.css :
   pas d'icône de fonte, pas de face de caractères distante — les piles de repli
   de --pa-font-* prennent le relais. */
.pa-offline {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--pa-s-4);
  /* Rappel du fond de la page de connexion : l'écran hors ligne est souvent le
     tout premier après l'installation, il doit se lire comme le même bâtiment. */
  background:
    radial-gradient(circle at 18% 22%, var(--pa-emerald-100) 0, transparent 42%),
    radial-gradient(circle at 82% 78%, var(--pa-sand-200) 0, transparent 42%),
    var(--pa-sand-50);
}
.pa-offline-card {
  width: 100%;
  max-width: 460px;
  background: var(--pa-surface);
  border: 1px solid var(--pa-border);
  border-radius: var(--pa-r-lg);
  box-shadow: var(--pa-shadow-md);
  padding: var(--pa-s-7) var(--pa-s-6);
  text-align: center;
}
.pa-offline-mark {
  width: 56px;
  height: 56px;
  border-radius: var(--pa-r-md);
  display: block;
  margin: 0 auto var(--pa-s-4);
}
.pa-offline-title {
  font-family: var(--pa-font-display);
  font-size: var(--pa-fs-2xl);
  font-weight: 600;
  color: var(--pa-text);
  margin: var(--pa-s-4) 0 var(--pa-s-2);
  line-height: 1.2;
}
.pa-offline-text {
  color: var(--pa-text-soft);
  margin: 0 0 var(--pa-s-5);
  line-height: 1.55;
}
/* Avertissement métier : en officine, savoir si une clôture ou un enregistrement
   est parti compte plus que la réassurance. Discret, mais présent. */
.pa-offline-note {
  font-size: var(--pa-fs-sm);
  color: var(--pa-text-muted);
  border-top: 1px solid var(--pa-divider);
  padding-top: var(--pa-s-4);
  margin: var(--pa-s-5) 0 0;
  line-height: 1.5;
}
/* Pastille d'état du badge : elle bat tant que le réseau manque, et s'immobilise
   dès qu'il revient (la classe est retirée par le script de la page). */
.pa-offline-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--pa-r-pill);
  background: currentColor;
  flex: 0 0 auto;
}
.pa-offline-dot--live { animation: pa-offline-pulse 1.8s ease-in-out infinite; }
@keyframes pa-offline-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}
.pa-offline-card .pa-btn { min-height: 44px; }
@media (prefers-reduced-motion: reduce) {
  .pa-offline-dot--live { animation: none; }
}
