/* =============================================================================
   components.css
   Composants UI réutilisables : boutons, cards, badges, tables, alertes, forms.
   IMPORTE : tokens.css
   RÈGLE : chaque composant est autonome (pas de dépendance à d'autres classes).
           Pas de styles de mise en page ici (margins, positions).
   ============================================================================= */


/* ---------------------------------------------------------------------------
   BOUTONS
--------------------------------------------------------------------------- */
.btn {
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  gap:             var(--space-2);
  padding:         var(--space-2) var(--space-4);
  border-radius:   var(--radius-md);
  font-size:       var(--text-sm);
  font-weight:     var(--font-medium);
  font-family:     var(--font-sans);
  line-height:     1.5;
  white-space:     nowrap;
  cursor:          pointer;
  border:          var(--border-width) solid transparent;
  text-decoration: none;
  transition:      background    var(--transition-fast),
                   border-color  var(--transition-fast),
                   color         var(--transition-fast),
                   box-shadow    var(--transition-fast),
                   opacity       var(--transition-fast);
  user-select:     none;
}

.btn:disabled,
.btn.is-disabled {
  opacity: 0.5;
  cursor:  not-allowed;
  pointer-events: none;
}

.btn:focus-visible {
  outline:        2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Variants */
.btn-primary {
  background:   var(--color-primary);
  color:        var(--color-text-inverse);
  border-color: var(--color-primary);
}
.btn-primary:hover {
  background:   var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color:        var(--color-text-inverse);
  text-decoration: none;
}

/* Secondaire neutre (texte foncé). Usage « Annuler » : à côté d'une action
   TEAL/PRIMARY (cf. doctrine .form-actions plus bas). */
.btn-secondary {
  background:   var(--color-surface);
  color:        var(--color-text-main);
  border-color: var(--color-border-strong);
  box-shadow:   var(--shadow-xs);
}
.btn-secondary:hover {
  background:   var(--color-primary-alpha-06);
  border-color: var(--color-primary);
  color:        var(--color-primary);
  text-decoration: none;
}

/* Fantôme discret (texte gris, transparent). Usage « Annuler » DISCRET : à côté
   d'un bouton DANGER/rouge ou dans une barre de SÉLECTION EN MASSE (cf. doctrine
   .form-actions). Sinon, préférer .btn-secondary. */
.btn-ghost {
  background:   transparent;
  color:        var(--color-text-muted);
  border-color: transparent;
}
.btn-ghost:hover {
  background: var(--color-bg);
  color:      var(--color-text-main);
  text-decoration: none;
}

.btn-danger {
  background:   var(--color-danger);
  color:        var(--color-text-inverse);
  border-color: var(--color-danger);
}
.btn-danger:hover {
  background:   var(--color-danger-hover);
  border-color: var(--color-danger-hover);
  color:        var(--color-text-inverse);
  text-decoration: none;
}

/* Action À FAIRE — attire l'œil sans alarmer. Distincte de `.btn-primary`
   (l'action principale d'un écran) et de `.btn-danger` (ce qui détruit).
   Cas d'origine : « Compléter » sur une spec en attente d'activation. La
   classe était POSÉE dans le gabarit mais déclarée nulle part : le bouton
   sortait sans fond, plus effacé que les boutons ordinaires de la même
   colonne — exactement l'inverse de ce qu'il demande.
   Ambre dense (--color-warning-strong) et non l'amber-500 : sur un aplat, le
   texte blanc ne passe pas sur le jaune clair. */
.btn-warning {
  background:   var(--color-warning-strong);
  color:        var(--color-text-inverse);
  border-color: var(--color-warning-strong);
}
.btn-warning:hover {
  background:   var(--color-warning-text);
  border-color: var(--color-warning-text);
  color:        var(--color-text-inverse);
  text-decoration: none;
}

/* Repos ALIGNÉ sur .btn-outline (neutre : texte muted, sans ombre) — un bouton
   destructif « ghost » se fond avec les autres au repos et ne révèle le danger
   (rouge) qu'au survol. */
/* Ghost DESTRUCTIF — discret au repos, rouge au survol.
   Il portait mot pour mot les trois déclarations de `.btn-outline` juste
   dessous : ce n'était donc pas un ghost, mais un bouton CADRÉ. Posé à côté
   d'un vrai `.btn-ghost` (« Éditer »), « Supprimer » était l'élément le plus
   visible de la ligne — l'inverse de la règle : l'action destructive ne
   s'annonce qu'au moment où on la vise.
   Le survol employait `--color-danger` en TEXTE sur `--color-danger-bg` :
   3,4:1 mesuré, sous le plancher de 4,5:1. C'est le ton d'aplat, pas le ton
   de texte — `--color-danger-text` (5,9:1) est fait pour ça. */
.btn-danger-ghost {
  background:   transparent;
  color:        var(--color-text-muted);
  border-color: transparent;
}
.btn-danger-ghost:hover {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger);
  color:        var(--color-danger-text);
  text-decoration: none;
}

.btn-outline {
  background:   var(--color-surface);
  color:        var(--color-text-muted);
  border-color: var(--color-border-strong);
}
.btn-outline:hover {
  background:   var(--color-primary-alpha-08);
  border-color: var(--color-primary);
  color:        var(--color-primary);
  text-decoration: none;
}

.btn-outline-danger {
  background:   var(--color-surface);
  color:        var(--color-danger);
  border-color: var(--color-danger-border);
}
.btn-outline-danger:hover {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger);
  color:        var(--color-danger-hover);
  text-decoration: none;
}

/* Tailles */
.btn-xs {
  padding:     var(--space-1) var(--space-2);
  font-size:   var(--text-xs);
  gap:         var(--space-1);
}

.btn-sm {
  padding:   6px var(--space-3);
  font-size: var(--text-sm);
}

.btn-lg {
  padding:   var(--space-3) var(--space-6);
  font-size: var(--text-base);
}

/* Icône seule */
.btn-icon {
  width:   36px;
  height:  36px;
  padding: 0;
}

.btn-icon.btn-sm {
  width:  30px;
  height: 30px;
}

.btn-icon.btn-xs {
  width:  24px;
  height: 24px;
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
}

/* Loading spinner */
.btn.is-loading {
  pointer-events: none;
  position:       relative;
  color:          transparent;
}

.btn.is-loading::after {
  content:       '';
  position:      absolute;
  width:         14px;
  height:        14px;
  border:        2px solid rgba(255,255,255,0.3);
  border-top-color: var(--color-text-inverse);
  border-radius: var(--radius-full);
  animation:     spin 0.6s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Groupe de boutons */
.btn-group {
  display:  inline-flex;
  gap:      0;
}

.btn-group .btn {
  border-radius: 0;
  border-right-width: 0;
}

.btn-group .btn:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); }
.btn-group .btn:last-child  { border-radius: 0 var(--radius-md) var(--radius-md) 0; border-right-width: var(--border-width); }


/* ---------------------------------------------------------------------------
   BADGES & STATUS
--------------------------------------------------------------------------- */
.badge {
  display:         inline-flex;
  align-items:     center;
  gap:             var(--space-1);
  padding:         2px var(--space-2);
  border-radius:   var(--radius-full);
  font-size:       var(--text-xs);
  font-weight:     var(--font-semi);
  white-space:     nowrap;
  border:          var(--border-width) solid transparent;
}

.badge-ok,
.badge-success {
  background:   var(--status-ok-bg);
  color:        var(--status-ok-color);
  border-color: var(--status-ok-border);
}

.badge-ko,
.badge-danger {
  background:   var(--status-ko-bg);
  color:        var(--status-ko-color);
  border-color: var(--status-ko-border);
}

.badge-warn,
.badge-warning {
  background:   var(--status-warn-bg);
  color:        var(--status-warn-color);
  border-color: var(--status-warn-border);
}

.badge-skip,
.badge-muted {
  background:   var(--status-skip-bg);
  color:        var(--status-skip-color);
  border-color: var(--status-skip-border);
}

.badge-pending,
.badge-info {
  background:   var(--status-pending-bg);
  color:        var(--status-pending-color);
  border-color: var(--status-pending-border);
}

.badge-primary {
  background:   var(--color-primary-light);
  color:        var(--color-primary-dark);
  border-color: var(--color-primary-light);
}

/* Doublon (DUP) : famille « déjà vu » — ignoré (identique) ET rejeté
   (correction refusée) partagent ce badge ; le détail tranche le motif. */
.badge-dup {
  background:   var(--color-info-bg);
  color:        var(--color-info);
  border-color: var(--color-info-border);
}

/* Renvoi : nombre de fois où le run a été rejoué (Renvoyer / Modifier et
   renvoyer). Annotation ADDITIVE au statut — cf. FlowRun.meta.corrections. */
.badge-replay {
  background:   var(--color-primary-light);
  color:        var(--color-primary-dark);
  border-color: var(--color-primary-light);
  margin-left:  var(--space-1);
}

/* Dot indicateur */
.badge::before {
  content:       '';
  width:         6px;
  height:        6px;
  border-radius: var(--radius-full);
  background:    currentColor;
  flex-shrink:   0;
}

.badge.no-dot::before {
  display: none;
}


/* ---------------------------------------------------------------------------
   CARDS
--------------------------------------------------------------------------- */
.card {
  background:    var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-sm);
  width: 100%;
}

.card-header {
  display:        flex;
  align-items:    center;
  justify-content: space-between;
  padding:        var(--space-5) var(--space-6);
  border-bottom:  var(--border-width) solid var(--color-border);
}

.card-title {
  font-size:   var(--text-base);
  font-weight: var(--font-semi);
  color:       var(--color-text-main);
}

.card-subtitle {
  font-size:  var(--text-sm);
  color:      var(--color-text-muted);
  margin-top: var(--space-1);
}

.card-body {
  padding: var(--space-6);
}

.card-footer {
  padding:       var(--space-4) var(--space-6);
  border-top:    var(--border-width) solid var(--color-border);
  background:    var(--color-bg);
  border-radius: 0 0 calc(var(--radius-xl) - 1px) calc(var(--radius-xl) - 1px);
  display:       flex;
  align-items:   center;
  justify-content: space-between;
}

/* ─── Detail card ─── page de détail : carte identité + sections ───────── */

.detail-card {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  overflow:      hidden;
  box-shadow:    var(--shadow-sm);
}

/* Ligne d'identité « CODE — Nom » du bandeau. Écrite en style="" dans les
   quatre fiches (partenaire, groupe de partenaires, organisation, groupe
   d'organisations) : quatre copies de la même intention. */
/* Ligne de table cliquable — comportement dans js/shell.js (contrat
   `tr.row-clickable[data-href]`). Le style vivait en double, sous un sélecteur
   d'ID dans files.css et nu dans flows.css. Le survol reprend la teinte de
   survol de ligne déjà employée par les listes. */
tr.row-clickable { cursor: pointer; }
tr.row-clickable:hover > td { background: var(--color-primary-alpha-04); }

.detail-card__ident {
  font-size:   var(--text-sm);
  font-weight: var(--font-semi);
  font-family: var(--font-mono);
}

/* Head — bandeau titre : badges à gauche, actions à droite */
.detail-card__head {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  gap:             var(--space-3);
  flex-wrap:       wrap;
  padding:         var(--space-4) var(--space-5);
  border-bottom:   var(--border-width) solid var(--color-border);
  background:      var(--color-bg);
}
.detail-card__badges {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
}
.detail-card__actions {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  flex-wrap:   wrap;   /* FIX RESP-R3 : boutons d'action wrappent sur petit écran */
}

/* Meta — grille horizontale avec séparateurs verticaux.
   auto-FIT et non auto-fill : auto-fill CRÉE les pistes vides nécessaires pour
   remplir la largeur, si bien qu'un bandeau de 4 cellules sur un écran large
   occupait 4 pistes sur 7 et se tassait à gauche, laissant un vide à droite.
   auto-fit effondre les pistes vides, les cellules s'étirent et se répartissent.
   Sans effet quand le bandeau remplit déjà la largeur. */
.detail-card__meta {
  display:               grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap:                   0;
  border-bottom:         var(--border-width) solid var(--color-border);
}
.detail-card__meta-item {
  display:        flex;
  flex-direction: column;
  gap:            3px;
  padding:        var(--space-4) var(--space-5);
  border-right:   var(--border-width) solid var(--color-border);
  min-width:      0;   /* laisse la grille contraindre les valeurs longues */
}
.detail-card__meta-item:last-child { border-right: none; }

/* ── PAIRE D'IDENTITÉ — un intitulé, une valeur ──────────────────────────────
   La typographie de la bande d'identité (intitulé en petites capitales denses,
   valeur en monospace) était SOUDÉE à la bande : `__meta-item` porte aussi le
   padding et les séparateurs qui n'ont de sens que là. Hors bande — dans une
   carte latérale, un encart « hérité de… » — il n'existait rien, et deux pages
   de Specs s'étaient donc fabriqué leur propre famille.

   Les mêmes déclarations portent maintenant DEUX noms : celui de la bande (14
   gabarits l'emploient déjà, ils ne bougent pas) et `.meta-pair*`, utilisable
   partout ailleurs. Une seule règle — les deux ne peuvent pas diverger.

   Le monospace n'est pas décoratif : ces valeurs sont des identifiants
   techniques (code projet, nom de flux, type de document) et une chasse fixe
   les rend comparables d'une ligne à l'autre. Une page dont les valeurs sont
   du LANGAGE (un nom de fichier, une phrase) a raison de s'en écarter — cf.
   `.files-page .detail-card__meta-val`, dérogation assumée et cadrée. */
.meta-pair {
  display:        flex;
  flex-direction: column;
  gap:            3px;
  min-width:      0;
}

.detail-card__meta-lbl,
.meta-pair__lbl {
  font-size:      var(--text-2xs);
  font-weight:    var(--font-bold);
  color:          var(--color-text-main);
  text-transform: uppercase;
  letter-spacing: 0.07em;
}
.detail-card__meta-val,
.meta-pair__val {
  font-size:      var(--text-xs);
  font-family:    var(--font-mono);
  color:          var(--color-text-main);
  text-transform: uppercase;
  overflow-wrap:  anywhere;   /* casse les noms longs (flux) au lieu de déborder */
}
/* Deuxième ligne d'une paire — la précision sous la valeur (« par Dany T. »,
   le nom long derrière le code). En SANS-SERIF et casse normale : c'est du
   langage, pas un identifiant. */
.detail-card__meta-sub,
.meta-pair__sub {
  font-size:      var(--text-2xs);
  font-family:    var(--font-sans);
  color:          var(--color-text-muted);
  font-weight:    var(--font-medium);
  text-transform: none;
}

/* « Ouvrir le detail » — pied de tiroir, PARTAGE par le monitoring et les
   transferts. Il vivait dans pages/flows.css : sur la page Fichiers, qui ne
   charge pas cette feuille, le lien sortait en texte brut, sans cadre ni
   survol. Une classe employee par deux modules appartient a la feuille
   partagee — sinon elle style une page sur deux, en silence. */
.detail-link,
.detail-link:link,
.detail-link:visited {
  display:       inline-flex;
  align-items:   center;
  text-decoration: none !important;
  border:        var(--border-width) solid transparent;
  border-radius: var(--radius-md);
  padding:       var(--space-2) var(--space-4);
  font-size:     var(--text-xs);
  font-weight:   var(--font-medium);
  font-family:   var(--font-sans);
  color:         var(--color-text-muted) !important;
  transition:    border-color .15s, color .15s, background .15s;
}
.detail-link:hover {
  border-color: var(--color-primary);
  color:        var(--color-primary) !important;
  background:   var(--color-primary-alpha-08);
}

/* Lien inline — le lien de texte ordinaire, au fil d'une phrase ou d'une
   cellule. Il était employé dans neuf gabarits et défini NULLE PART : les
   liens sortaient donc au style brut du navigateur, bleu souligné, hors du
   thème. À distinguer de `.link-name`, qui est le lien-TITRE d'une ligne de
   tableau. */
.link,
.link:link,
.link:visited {
  color:           var(--color-primary);
  text-decoration: none;
  font-weight:     var(--font-medium);
}
.link:hover { text-decoration: underline; }

/* Composant audit « Créé / Modifié » (templates/components/_audit_meta.html) :
   auteur en sourdine à la suite de la date. */
/* L'AUTEUR, à la suite de la date. Il ne fixait que la couleur et héritait
   donc du monospace ET des capitales de `.detail-card__meta-val` : « Dany T. »
   sortait en `DANY T.` monospace. Un nom de personne est du LANGAGE, pas un
   identifiant — la même règle que la sous-ligne (`.meta-pair__sub`). La date
   reste en chasse fixe, elle : c'est une donnée qu'on aligne d'une ligne à
   l'autre. */
.audit-meta__by {
  color:          var(--color-text-muted);
  font-family:    var(--font-sans);
  text-transform: none;
}

/* Description */
.detail-card__desc {
  padding:    var(--space-3) var(--space-4);
  /* Séparateur au-dessus (canon posé côté extraction, cf. .rpt-detail-desc)
     pour détacher la description du bloc précédent. */
  border-top: var(--border-width) solid var(--color-border);
}
.detail-card__desc p {
  font-size: var(--text-xs);   /* typo compacte canonique (aligné extraction) */
  color:     var(--color-text-muted);
  margin:    0;
}

/* Section header — titre uppercase gris (Complétude, Specs, Fichiers…) */
.detail-card__header {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  padding:         var(--space-3) var(--space-4);
  border-bottom:   var(--border-width) solid var(--color-border);
  background:      var(--color-bg);
}
.detail-card__title {
  font-size:      var(--text-2xs);
  font-weight:    var(--font-bold);
  color:          var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Section body */
.detail-card__body { padding: var(--space-4); }
.detail-card__body--flush { padding: 0; overflow-x: auto; }
/* Corps à hauteur bornée : une liste imbriquée (entreprises d'un groupe,
   organisations sœurs) ne doit pas allonger la fiche indéfiniment — elle
   défile chez elle. Deux fiches le faisaient en style="". */
.detail-card__body--scroll { max-height: 240px; overflow-y: auto; }

/* ---------------------------------------------------------------------------
   PANNEAU DE DÉTAIL DÉPLIÉ — .details-grid + .kv-list
   Le panneau qui s'ouvre sous une ligne de tableau (Monitoring : détail d'un
   run ; Fichiers : détail d'un transfert). Il vivait EN DOUBLE, copié dans
   pages/flows.css ET pages/files.css — le commentaire de files.css l'avouait
   (« MÊME structure que le monitoring »). Deux copies dérivent : c'était déjà
   le cas ici sur trois propriétés. Une classe employée par deux modules
   appartient à la feuille partagée ; la base est donc ici, et chaque module ne
   garde en local que ses vraies différences.

   ⚠ Ces panneaux sont injectés dans un <td> de .data-table, qui centre son
   contenu (`text-align: center`). Tout titre ou libellé qui doit rester à
   gauche doit le déclarer — c'est ce que fait `.kv-list li strong` ci-dessous.
--------------------------------------------------------------------------- */
.details-grid {
  background:            var(--color-surface);
  border:                var(--border-width) solid var(--color-border);
  border-radius:         var(--radius-lg);
  padding:               var(--space-5) var(--space-6);
  display:               grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap:                   var(--space-4) var(--space-8);
  align-items:           start;
}
.details-grid h4 {
  /* Alignement DÉCLARÉ, pas hérité : ces panneaux vivent dans un <td> qui
     centre. Le Monitoring le contrait dans sa propre feuille, les Fichiers
     comptaient sur une autre règle locale — deux modules, deux dépendances
     différentes, pour un composant unique. L'alignement appartient donc au
     composant : les deux panneaux sont identiques par construction. */
  text-align:     left;
  font-size:      var(--text-2xs);
  font-weight:    var(--font-bold);
  color:          var(--color-text-light);
  margin:         0 0 var(--space-3) 0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding-bottom: var(--space-2);
  border-bottom:  var(--border-width) solid var(--color-border);
}
.details-grid__footer {
  grid-column: 1 / -1;
  padding-top: var(--space-3);
  border-top:  var(--border-width) solid var(--color-border);
  margin-top:  var(--space-2);
}

.kv-list {
  list-style:     none;
  margin:         0;
  padding:        0;
  display:        table;
  border-spacing: 0 5px;
}
.kv-list li {
  display:     table-row;
  font-size:   var(--text-xs);
  color:       var(--color-text-main);
  font-family: var(--font-mono);
}
.kv-list li strong {
  display:        table-cell;
  color:          var(--color-text-muted);
  font-weight:    var(--font-medium);
  font-size:      var(--text-2xs);
  font-family:    var(--font-sans);
  text-align:     left;
  padding-right:  var(--space-5);
  white-space:    nowrap;
  vertical-align: baseline;
}
/* Cellule valeur d'un participant : nom + identifiant EDI empilé dessous. */
.kv-list .kv-cell     { display: table-cell; vertical-align: baseline; }
.kv-list .kv-cell__id { display: block; color: var(--color-text-muted); font-size: var(--text-2xs); }

/* Section pliable — <details> avec summary.detail-card__header */
.detail-card > details > summary.detail-card__header { cursor: pointer; list-style: none; }
.detail-card > details > summary.detail-card__header::-webkit-details-marker { display: none; }
.detail-card > details > summary .detail-card__title::before {
  content:      "▸ ";
  font-size:    var(--text-2xs);
  color:        var(--color-text-muted);
  margin-right: 2px;
}
.detail-card > details[open] > summary .detail-card__title::before {
  content: "▾ ";
}

@media (max-width: 768px) {
  .detail-card__meta { grid-template-columns: 1fr 1fr; }
  .detail-card__head { flex-direction: column; align-items: flex-start; }
}

/* ─── Detail DL ─── paires label : valeur (grille 2 colonnes) ──────────── */

.detail-dl { display: flex; flex-direction: column; gap: var(--space-2); }

/* Séparateur entre GROUPES de paires dans une même liste de définitions.
   La classe était posée par les gabarits (files/detail.html) sans exister dans
   aucune feuille : le <hr> sortait au style brut du navigateur, épais et
   marbré, au milieu d'une carte fine. */
.dl-sep {
  border:     none;
  border-top: var(--border-width) solid var(--color-border);
  margin:     var(--space-2) 0;
}

.detail-dl__row {
  display:               grid;
  grid-template-columns: 140px 1fr;
  gap:                   var(--space-3);
  align-items:           baseline;
}
.detail-dl__row dt {
  font-size:      var(--text-xs);
  color:          var(--color-text-muted);
  font-weight:    var(--font-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-family:    var(--font-sans);
}
.detail-dl__row dd {
  font-size:   var(--text-xs);
  color:       var(--color-text-main);
  margin:      0;
  font-family: var(--font-mono);
}

@media (max-width: 768px) {
  .detail-dl__row { grid-template-columns: 110px 1fr; }
}

/* Stat card */
.stat-card {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  padding:       var(--space-5) var(--space-6);
  box-shadow:    var(--shadow-sm);
  position:      relative;
  overflow:      hidden;
}

.stat-card__accent {
  position:    absolute;
  left:        0;
  top:         20%;
  bottom:      20%;
  width:       4px;
  border-radius: 0 var(--radius-full) var(--radius-full) 0;
}

.stat-card__label {
  font-size:   var(--text-sm);
  color:       var(--color-text-muted);
  font-weight: var(--font-medium);
  margin-bottom: var(--space-2);
}

.stat-card__value {
  font-size:   var(--text-3xl);
  font-weight: var(--font-bold);
  color:       var(--color-text-main);
  line-height: 1;
}

.stat-card__sub {
  font-size:  var(--text-xs);
  color:      var(--color-text-muted);
  margin-top: var(--space-2);
}





/* ---------------------------------------------------------------------------
   FORMULAIRES
--------------------------------------------------------------------------- */
.form-group {
  display:       flex;
  flex-direction: column;
  gap:           var(--space-2);
}
/* [hidden] doit l'emporter sur le display:flex ci-dessus : la règle du
   navigateur ([hidden]{display:none}) a une spécificité plus faible, si bien
   qu'un champ masqué en JS restait affiché. Même piège que .bulk-bar et
   .modal__confirm-type plus bas. */
.form-group[hidden] { display: none; }

/* DEUX FAMILLES POUR UNE SEULE INTENTION — dette en cours de résorption.
   `.form-label` (14 px, sentence case) et `.pf-label` (10 px, capitales
   espacées, plus bas dans ce fichier) habillent la même chose : le libellé
   d'un champ. Leurs conteneurs sont, eux, RIGOUREUSEMENT identiques —
   `.form-group` == `.pf-field`, `.form-hint` == `.pf-hint`. Seul le libellé
   diverge, et il suffit à faire que deux formulaires de la même application
   ne se ressemblent pas.
   `.pf-label` est la forme retenue : c'est celle des modules déjà repris
   (Specs, Partenaires, Organisations, Référentiels, Alertes, Extraction).
   Restent à convertir : Comptes (4 gabarits), Projets (2). Après eux,
   `.form-label` n'a plus de consommateur et se supprime.
   Le jour où ils y passent, `.form-label` disparaît — ne pas l'aliaser d'ici
   là : la conversion se valide À L'ŒIL, module par module. */
.form-label {
  font-size:   var(--text-sm);
  font-weight: var(--font-medium);
  color:       var(--color-text-main);
}

/* L'astérisque du champ obligatoire vaut pour les DEUX familles de libellés.
   `.pf-label` a son propre mécanisme (`<span class="req">`), mais les gabarits
   posent aussi `class="pf-label required"` : sans cette règle, l'obligation
   était annoncée dans le HTML et invisible à l'écran. */
.form-label.required::after,
.pf-label.required::after {
  content: ' *';
  color:   var(--color-danger);
}

.form-hint {
  font-size: var(--text-xs);
  color:     var(--color-text-muted);
}

.form-error {
  font-size: var(--text-xs);
  color:     var(--color-danger);
  display:   flex;
  align-items: center;
  gap:       var(--space-1);
}

/* Case à cocher + libellé — composant canonique.
   Usage : <label class="form-check"><input type="checkbox"> <span>Texte</span></label> */
.form-check {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  font-size:   var(--text-sm);
  color:       var(--color-text-main);
  cursor:      pointer;
}

.form-check input[type="checkbox"] {
  margin:      0;
  flex-shrink: 0;
}

/* Densité « compacte » d'un formulaire — modificateur réutilisable.
   Posé sur un conteneur, resserre labels + libellés de cases en text-xs
   (réglages secondaires, panneaux denses). Usage : <div class="form-compact">…</div> */
/* `.form-compact .form-label` RETIRÉE : l'éditeur d'extraction en était le
   SEUL consommateur, et ses libellés sont passés en `.pf-label` (déjà plus
   petit, et c'est la famille du reste de l'application). La règle ne servait
   plus rien. `.form-check`, lui, reste. */
.form-compact .form-check {
  font-size: var(--text-xs);
}

/* Inputs */
.form-control {
  width:         100%;
  height:        var(--input-height-md);
  padding:       0 var(--space-3);
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size:     var(--text-sm);
  color:         var(--color-text-main);
  font-family:   var(--font-sans);
  transition:    border-color var(--transition-fast),
                 box-shadow   var(--transition-fast);
  line-height:   var(--leading-normal);
}

/* Variantes de taille */
.form-control--sm {
  height:    var(--input-height-sm);
  font-size: var(--text-xs);
  padding:   0 var(--space-2);
}

.form-control--lg {
  height:    var(--input-height-lg);
  font-size: var(--text-base);
  padding:   0 var(--space-4);
}

.form-control::placeholder {
  color: var(--color-text-light);
}

.form-control:focus {
  border-color: var(--color-primary);
  box-shadow:   0 0 0 3px var(--color-primary-subtle);
  outline:      none;
}

.form-control.is-invalid {
  border-color: var(--color-danger);
}

.form-control.is-invalid:focus {
  box-shadow: 0 0 0 3px var(--color-danger-bg);
}

.form-control:disabled {
  background: var(--color-bg);
  color:      var(--color-text-muted);
  cursor:     not-allowed;
}

/* Select */
select.form-control {
  cursor:             pointer;
  appearance:         none;
  background-image:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236B7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat:  no-repeat;
  background-position: right var(--space-3) center;
  padding-right:      var(--space-8);
}

/* Textarea */
textarea.form-control {
  min-height: 100px;
  resize:     vertical;
}

/* Input group */
.input-group {
  display: flex;
}

.input-group .form-control {
  border-radius: 0;
  flex: 1;
}

.input-group .form-control:first-child {
  border-radius: var(--radius-md) 0 0 var(--radius-md);
}

.input-group .form-control:last-child {
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.input-group .btn {
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

/* Search input */
.input-search {
  position: relative;
}

.input-search__icon {
  position:  absolute;
  left:      var(--space-3);
  top:       50%;
  transform: translateY(-50%);
  color:     var(--color-text-light);
  pointer-events: none;
}

.input-search .form-control {
  padding-left: calc(var(--space-3) * 2 + 16px);
}


/* ---------------------------------------------------------------------------
   FORM-ACTIONS — barre d'actions de formulaire
   Source de vérité : specs (pipeline_profile_create.html)
   Remplace : .pj-form-actions (projects), .pf-actions (partners/tenancy),
              .tf-actions (projects transport), .pj-delete-card__actions (suppression)

   Usage standard :
     <div class="form-actions">
       <button type="submit" class="btn btn-primary">Créer</button>
       <a href="…" class="btn btn-secondary">Annuler</a>
     </div>

   Usage suppression :
     <div class="form-actions form-actions--center">
       <a href="…" class="btn btn-secondary">Annuler</a>
       <button type="submit" class="btn btn-danger">Supprimer</button>
     </div>

   Règles :
     - Annuler = toujours <a> (navigation), jamais <button>
     - Réinit. dans les filtres = .btn.btn-secondary.btn-sm
     - Position : action principale à gauche, secondaire à droite

   Couleur d'un « Annuler » (2 conventions selon le voisin) :
     - À CÔTÉ D'UNE ACTION TEAL/PRIMARY (Enregistrer, Générer, Lancer…) :
         .btn.btn-secondary  → texte FONCÉ. C'est le cas standard.
     - DISCRET (à côté d'un bouton DANGER/rouge, ou « annuler » d'une barre de
       SÉLECTION EN MASSE, pour ne pas concurrencer l'action) :
         .btn.btn-ghost      → texte GRIS.
--------------------------------------------------------------------------- */
.form-actions {
  display:     flex;
  align-items: center;
  gap:         var(--space-3);
  margin-top:  var(--space-6);
}

.form-actions--center {
  justify-content: center;
}

/* ── Actions FIGÉES au bas de l'écran ────────────────────────────────────────
   Pour les pages dont le contenu s'allonge sans limite : éditeur de spec (une
   spec fait couramment 180 lignes), assemblage des flux d'un projet (la liste
   grandit avec le projet). Le bouton d'enregistrement s'y retrouvait à
   plusieurs écrans du contenu qu'on vient de modifier.

   Choisi CONTRE le doublon en haut de page : deux commandes pour une action,
   ce sont deux états à tenir d'accord (désactivation pendant l'envoi, retour
   de validation). Une commande, joignable partout.

   DEUX PIÈGES, tous deux vécus :
   · `position: sticky` est neutralisé par TOUT ancêtre en `overflow: hidden` —
     la barre redescend alors sans rien signaler. La poser hors des cartes à
     débordement masqué (éditeur JSON, table-card…).
   · sans fond opaque, le contenu défile au travers.
   Arrivée en bas de page, la barre retrouve sa place naturelle et cesse de
   flotter : les dernières lignes du contenu restent lisibles. */
.form-actions--sticky {
  position:      sticky;
  bottom:        var(--space-4);
  z-index:       var(--z-raised);
  /* `.form-actions` pose 24 px de marge haute — juste pour un pied de page qui
     conclut un formulaire, de trop pour une barre COLLÉE : ajoutés à l'écart de
     la colonne et à la marge de la carte, ils creusaient une bande vide entre
     le contenu et la barre. C'est la colonne qui espace. */
  margin-top:    0;
  padding:       var(--space-3) var(--space-4);
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-card);
}


/* ---------------------------------------------------------------------------
   ALERTES & NOTIFICATIONS
--------------------------------------------------------------------------- */
/* Alerte — EMPILÉE par défaut.
   Elle était en flex-row : chaque enfant devenait une COLONNE. Une alerte qui
   boucle sur des erreurs (`{% for e in ... %}<p>{{ e }}</p>{% endfor %}`, le
   cas de 19 gabarits) s'étalait donc horizontalement — 12 erreurs de
   validation de spec produisaient 12 colonnes débordant la page.

   Les alertes ICÔNE + texte, elles, ont besoin de la ligne : `:has(> svg)` les
   sert sans qu'aucun gabarit ait à se déclarer. Le sélecteur est déjà employé
   ailleurs dans ce dépôt (pipeline_sandbox.css, projects.css). */
.alert {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  padding:        var(--space-4);
  border-radius:  var(--radius-lg);
  border:         var(--border-width) solid transparent;
  font-size:      var(--text-sm);
  min-width:      0;
}
.alert > * { min-width: 0; }

.alert:has(> svg) {
  flex-direction: row;
  align-items:    flex-start;
  gap:            var(--space-3);
}
.alert:has(> svg) > svg { flex: 0 0 auto; }

.alert-success {
  background:   var(--color-success-bg);
  border-color: var(--color-success-border);
  color:        var(--color-success);
}

.alert-danger {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color:        var(--color-danger);
}

.alert-warning {
  background:   var(--color-warning-bg);
  border-color: var(--color-warning-border);
  color:        var(--color-warning);
}

.alert-info {
  background:   var(--color-info-bg);
  border-color: var(--color-info-border);
  color:        var(--color-info);
}

.alert__icon {
  flex-shrink: 0;
  margin-top:  1px;
}

/* ---------------------------------------------------------------------------
   Callout — encadré d'information COMPACT (aide, note secondaire). CANONIQUE :
   à réutiliser partout (le module Rapports en fut l'inspiration, il le suit
   désormais comme les autres). Plus léger que .alert (text-xs, radius-md, sans
   icône), à la couleur de l'app (primary). Le composant ne porte PAS de marge :
   l'espacement est la responsabilité du conteneur (utiliser .mt-* au besoin).
--------------------------------------------------------------------------- */
/* ── EXEMPLE DE FORMAT — un extrait posé dans une carte d'aide ───────────────
   À distinguer du bloc de code pleine page (`pre`, base.css), qui pose un fond
   SOMBRE. Quatre exemples de Référentiels vivaient dans une carte teintée et
   forçaient leur texte en teal foncé PAR-DESSUS ce fond sombre : 3,46:1, sous
   le minimum lisible de 4,5:1. On leur rend une surface claire — la couleur du
   texte vient alors de la carte qui les porte. */
.code-sample {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-primary-light);
  border-radius: var(--radius-md);
  padding:       var(--space-3);
  font-size:     var(--text-xs);
  line-height:   1.7;
  overflow-x:    auto;
}

.callout {
  padding:       var(--space-3) var(--space-4);
  background:    var(--color-primary-subtle);
  border:        var(--border-width) solid var(--color-primary);
  border-radius: var(--radius-md);
  font-size:     var(--text-xs);
  color:         var(--color-primary-dark);
  line-height:   var(--leading-normal);
}
.callout strong { color: var(--color-primary-dark); }

/* ---------------------------------------------------------------------------
   Bulk bar — barre d'actions de SÉLECTION EN MASSE. CANONIQUE
   (cf. shared/bulk-select.js). Apparaît au-dessus d'une table quand ≥1 ligne
   est cochée ; chaque page y greffe ses propres boutons d'action.
--------------------------------------------------------------------------- */
.bulk-bar {
  display:       flex;
  align-items:   center;
  gap:           var(--space-3);
  padding:       var(--space-2) var(--space-4);
  background:    var(--color-primary-subtle);
  border-bottom: var(--border-width) solid var(--color-primary-light);
  flex-wrap:     wrap;
}
/* display:flex écraserait l'attribut [hidden] (UA) → masquage explicite. */
.bulk-bar[hidden] { display: none; }

/* Variante AUTONOME — barre posée AU-DESSUS du tableau plutôt qu'à l'intérieur
   d'une carte (listes Fichiers IN/OUT). Elle a donc besoin d'un cadre complet
   et d'un rayon, là où la base se contente d'un `border-bottom` puisqu'elle
   s'appuie sur la carte qui l'entoure.
   C'était auparavant une REDÉFINITION de `.bulk-bar` dans pages/files.css :
   même nom de classe, feuille chargée après, donc le composant partagé était
   silencieusement écrasé sur tout le module — et la copie avait perdu
   `flex-wrap: wrap` au passage. */
.bulk-bar--standalone {
  border:        var(--border-width) solid var(--color-primary-light);
  border-radius: var(--radius-md);
}
.bulk-bar__count {
  font-size:   var(--text-xs);
  font-weight: var(--font-semi);
  color:       var(--color-primary-dark);
  min-width:   100px;
  white-space: nowrap;
}

/* Motif d'un bouton bridé — nomme l'action concernée et la raison.
   Ambre DENSE : c'est un avertissement, pas une erreur ; l'ambre de fond
   serait délavé sur la barre teintée. Volontairement sans `nowrap` : le
   message doit pouvoir se replier plutôt que pousser les boutons hors champ. */
.bulk-bar__note {
  font-size:   var(--text-xs);
  color:       var(--color-warning-strong);
  line-height: 1.4;
}

.alert__body {
  flex: 1;
}

.alert__title {
  font-weight:   var(--font-semi);
  margin-bottom: var(--space-1);
}


/* .toast — source de vérité dans shell.css (.toast-stack + .toast + .toast--*) */


/* ---------------------------------------------------------------------------
   EMPTY STATE — état vide canonique (table, card, page)
   Pattern : icône + titre + description + CTA optionnel, centré verticalement.
   Partial : components/_empty_state.html
--------------------------------------------------------------------------- */
.empty-state {
  display:         flex;
  flex-direction:  column;
  align-items:     center;
  justify-content: center;
  gap:             var(--space-2);
  padding:         var(--space-12) var(--space-4);
  text-align:      center;
}

/* ── MÉDAILLON D'ICÔNE — un pictogramme dans un rond de couleur ──────────────
   La forme servait l'état vide sous ce seul nom. Trois pages de confirmation
   la redessinaient en réglages inline (dimensions, fond, bord, centrage) pour
   n'en changer que la COULEUR. Comme pour la paire d'identité : une seule
   règle, deux noms — l'état vide garde le sien, `.icon-medallion` sert
   partout ailleurs, et les deux ne peuvent pas diverger. */
.icon-medallion,
.empty-state__icon {
  display:         flex;
  align-items:     center;
  justify-content: center;
  width:           56px;
  height:          56px;
  border-radius:   var(--radius-xl);
  background:      var(--color-bg);
  border:          var(--border-width) solid var(--color-border);
  color:           var(--color-text-light);
  margin-bottom:   var(--space-2);
}
/* Ce que la page annonce : un retrait, une publication. La couleur est la
   SEULE chose qui change — le reste vient de la forme partagée. */
.icon-medallion--danger {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color:        var(--color-danger);
}
.icon-medallion--success {
  background:   var(--color-success-bg);
  border-color: var(--color-success-border);
  color:        var(--color-success);
}
.icon-medallion svg,
.empty-state__icon svg {
  width:        28px;
  height:       28px;
  stroke-width: 1.5;
}

.empty-state__title {
  font-size:    var(--text-base);
  font-weight:  var(--font-semi);
  font-family:  var(--font-sans);
  white-space:  normal;
  color:        var(--color-text-main);
  margin:       0;
}

.empty-state__text {
  font-size:    var(--text-sm);
  font-family:  var(--font-sans);
  color:        var(--color-text-muted);
  max-width:    320px;
  line-height:  var(--leading-normal);
  white-space:  normal;
  margin:       0;
}

.empty-state .btn {
  margin-top: var(--space-3);
}

/* Override .data-table tbody td a qui écrase couleur + hover des boutons dans les <td> */
.empty-state .btn.btn-primary {
  background:   var(--color-primary);
  color:        var(--color-text-inverse);
  border-color: var(--color-primary);
}
.empty-state .btn.btn-primary:hover {
  background:   var(--color-primary-hover);
  border-color: var(--color-primary-hover);
  color:        var(--color-text-inverse);
}

/* Variante compacte — dans un <td colspan> ou une card étroite */
.empty-state--inline {
  padding: var(--space-8) var(--space-4);
}

/* ---------------------------------------------------------------------------
   MODALES
--------------------------------------------------------------------------- */
.modal-overlay {
  position:        fixed;
  inset:           0;
  background:      var(--color-overlay);
  z-index:         var(--z-modal);
  display:         flex;
  align-items:     center;
  justify-content: center;
  padding:         var(--space-4);
  animation:       overlay-enter var(--transition-fast) ease;
}

@keyframes overlay-enter {
  from { opacity: 0; }
}

.modal-overlay.is-hidden {
  display: none;
}

/* Modale de confirmation déclenchée AU-DESSUS d'un slide-over (preview-drawer,
   à --z-modal + 1) : elle doit passer devant. Même palier que .bulk-modal-overlay. */
.modal-overlay--top {
  z-index: var(--z-toast);
}

/* Le focus-visible global (base.css) encadre tout élément focusé — y compris le
   corps de modale (focusable car overflow-y:auto), d'où un vilain cadre bleu à
   l'ouverture. Les conteneurs NON-interactifs de la modale ne doivent pas être
   encadrés ; les vrais contrôles (boutons, champs) gardent leur focus visible. */
.modal:focus-visible,
.modal__body:focus-visible,
.modal__confirm-msg:focus-visible {
  outline: none;
}

.modal {
  background:    var(--color-surface);
  border-radius: var(--radius-2xl);
  box-shadow:    var(--shadow-xl);
  width:         100%;
  max-width:     560px;
  max-height:    90vh;
  display:       flex;
  flex-direction: column;
  animation:     modal-enter var(--transition-normal) ease;
}

@keyframes modal-enter {
  from {
    opacity:   0;
    transform: scale(0.96) translateY(8px);
  }
}

.modal.modal-lg { max-width: 768px; }
.modal.modal-sm { max-width: 400px; }

.modal__header {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  padding:         var(--space-5) var(--space-6);
  border-bottom:   2px solid var(--color-primary);
  flex-shrink:     0;
}

.modal__title {
  font-size:   var(--text-lg);
  font-weight: var(--font-semi);
  color:       var(--color-text-main);
}

.modal__close {
  display:         flex;
  align-items:     center;
  justify-content: center;
  width:           32px;
  height:          32px;
  border:          1px solid transparent;
  background:      transparent;
  border-radius:   var(--radius-md);
  color:           var(--color-text-muted);
  transition:      background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
  cursor:          pointer;
}

.modal__close:hover {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger);
  color:        var(--color-danger);
}

.modal__body {
  padding:    var(--space-6);
  overflow-y: auto;
  flex:       1;
}

.modal__footer {
  padding:       var(--space-4) var(--space-6);
  border-top:    var(--border-width) solid var(--color-border);
  display:       flex;
  align-items:   center;
  justify-content: flex-end;
  gap:           var(--space-3);
  flex-shrink:   0;
  background:    var(--color-bg);
  border-radius: 0 0 calc(var(--radius-2xl) - 1px) calc(var(--radius-2xl) - 1px);
}


/* ---------------------------------------------------------------------------
   TABS
--------------------------------------------------------------------------- */
.tabs {
  display:       flex;
  gap:           0;
  border-bottom: var(--border-width) solid var(--color-border);
  margin-bottom: var(--space-6);
}

.tab {
  display:         flex;
  align-items:     center;
  gap:             var(--space-2);
  padding:         var(--space-3) var(--space-4);
  font-size:       var(--text-sm);
  font-weight:     var(--font-medium);
  color:           var(--color-text-muted);
  text-decoration: none;
  border-bottom:   2px solid transparent;
  margin-bottom:   -1px;
  transition:      color        var(--transition-fast),
                   border-color var(--transition-fast);
  cursor:          pointer;
  white-space:     nowrap;
}

.tab:hover {
  color:           var(--color-text-main);
  text-decoration: none;
}

.tab.is-active {
  color:        var(--color-primary);
  border-color: var(--color-primary);
  font-weight:  var(--font-semi);
}

.tab__count {
  background:    var(--color-bg);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-full);
  padding:       1px var(--space-2);
  font-size:     var(--text-xs);
  color:         var(--color-text-muted);
  min-width:     20px;
  text-align:    center;
}

.tab.is-active .tab__count {
  background:   var(--color-primary-light);
  border-color: var(--color-primary-light);
  color:        var(--color-primary-dark);
}

/* FIX RESP-R5 : sur petit écran, la barre d'onglets défile horizontalement dans
   son propre conteneur (au lieu de déborder la page) — le soulignement d'onglet
   reste aligné, les onglets gardent leur taille. */
@media (max-width: 768px) {
  .tabs { overflow-x: auto; }
  .tabs .tab { flex-shrink: 0; }
}




/* ===========================================================================
   PAGINATION — .pagination
   Utilisé via {% include 'components/_pagination.html' %}
   =========================================================================== */

.pagination {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  padding:         var(--space-3) var(--space-4);
  border-top:      1px solid var(--color-border);
  gap:             var(--space-4);
  flex-wrap:       wrap;
}

.pagination__info {
  font-size:  var(--text-xs);
  color:      var(--color-text-muted);
  white-space: nowrap;
}

.pagination__total {
  color: var(--color-text-light);
  margin-left: 4px;
}

.pagination__btns {
  display:     flex;
  align-items: center;
  gap:         2px;
}

.pagination__btn {
  display:       inline-flex;
  align-items:   center;
  justify-content: center;
  min-width:     28px;
  height:        28px;
  padding:       0 6px;
  font-size:     var(--text-xs);
  font-family:   var(--font-sans);
  font-weight:   var(--font-normal);
  color:         var(--color-text-main);
  background:    transparent;
  border:        1px solid transparent;
  border-radius: var(--radius-sm);
  cursor:        pointer;
  text-decoration: none;
  transition:    background .12s, border-color .12s, color .12s;
  user-select:   none;
}

.pagination__btn:hover:not(:disabled):not(.pagination__btn--active) {
  background:    var(--color-bg);
  border-color:  var(--color-border);
}

.pagination__btn--active {
  background:    var(--color-primary);
  color:         var(--color-text-inverse);
  font-weight:   var(--font-semi);
  cursor:        default;
}

.pagination__btn:disabled {
  color:   var(--color-text-light);
  cursor:  not-allowed;
  opacity: .5;
}

.pagination__ellipsis {
  display:     inline-flex;
  align-items: center;
  height:      28px;
  padding:     0 4px;
  font-size:   var(--text-xs);
  color:       var(--color-text-light);
  user-select: none;
}

/* .topbar-link — source de vérité déplacée dans la section TOPBAR LINKS canonique (fin de fichier) */

/* ===========================================================================
   FORMULAIRES PAGES — .pf-*
   Utilisé par : partners, tenancy, specs, et tout module avec formulaire page.
   =========================================================================== */

/* Inputs dans .pf-form
   ÉNUMÉRATION PAR TYPE — donc tout type absent de la liste retombe sur le
   rendu NATIF du navigateur, sans un mot. Vécu : le champ « Date » d'un jour
   férié sortait plus haut, plus large et avec l'icône calendrier du système,
   à dix pixels d'un champ « Libellé » impeccable — parce que `date` n'était
   pas listé. Manquaient aussi `datetime-local`, `time`, `search` et `number`.
   Un type nouveau se rajoute ICI, et dans le bloc `:focus` plus bas. */
.pf-form input[type="text"],
.pf-form input[type="email"],
.pf-form input[type="tel"],
.pf-form input[type="password"],
.pf-form input[type="date"],
.pf-form input[type="datetime-local"],
.pf-form input[type="time"],
.pf-form input[type="search"],
.pf-form input[type="number"],
.pf-form select {
  width:         100%;
  height:        34px;
  padding:       0 var(--space-3);
  background:    var(--color-surface);
  border:        1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size:     var(--text-sm);
  font-family:   var(--font-sans);
  color:         var(--color-text-main);
  transition:    border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.pf-form textarea {
  width:         100%;
  padding:       var(--space-2) var(--space-3);
  background:    var(--color-surface);
  border:        1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size:     var(--text-sm);
  font-family:   var(--font-mono);
  color:         var(--color-text-main);
  resize:        vertical;
  min-height:    100px;
  transition:    border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.pf-form input[type="text"]:focus,
.pf-form input[type="email"]:focus,
.pf-form input[type="tel"]:focus,
.pf-form input[type="password"]:focus,
.pf-form input[type="date"]:focus,
.pf-form input[type="datetime-local"]:focus,
.pf-form input[type="time"]:focus,
.pf-form input[type="search"]:focus,
.pf-form input[type="number"]:focus,
.pf-form select:focus,
.pf-form textarea:focus {
  border-color: var(--color-primary);
  box-shadow:   0 0 0 3px var(--color-primary-subtle);
  outline:      none;
}
.pf-form input[type="checkbox"] {
  width:        16px;
  height:       16px;
  accent-color: var(--color-primary);
  cursor:       pointer;
  flex-shrink:  0;
}

/* Wrappers de largeur canonique — cf. tokens (--content-max-width / --page-width-form
   / --page-width-compact). Les 3 paliers, à appliquer selon la page :
     .page-layout            = Liste     (1280)  · tables + filtres
     .pf-form-page           = Formulaire (1100) · formulaires centrés
     .pf-form-page--compact  = Compact    (900)  · pages tertiaires / form + aide */
.pf-form-page { max-width: var(--page-width-form); margin: 0 auto; }

/* Liste au palier FORMULAIRE (1100, centré). `.page-layout` est fluide par
   défaut — c'est le bon choix pour une table à huit colonnes. Une liste
   courte et étroite, elle, se perd sur grand écran : ce modificateur la
   ramène au palier centré, sans dupliquer la mise en page. */
/* Spécificité (0,2,0) DÉLIBÉRÉE : `.page-layout` déclare désormais un plafond
   (cf. plus bas) et vit APRÈS ce bloc dans le fichier. À spécificité égale,
   l'ordre source trancherait en sa faveur et ces règles seraient muettes.

   Deux wrappers de largeur sur le MÊME élément = le plus étroit gagne. C'est la
   seule lecture qui ne surprend pas : une page qui demande explicitement le
   palier Formulaire ne doit pas être élargie parce qu'elle porte aussi le
   wrapper de liste. Cas réel : extraction/detail.html (`page-layout
   pf-form-page`), dont le commentaire demande le contenu « cadré ».
   Note : `.rpt-library-page` (extraction/library*.html) n'a pas besoin de garde —
   elle vit dans extraction.css, chargée APRÈS components.css, donc elle gagne
   déjà par l'ordre source. */
.page-layout.page-layout--form { max-width: var(--page-width-form); margin: 0 auto; }
.page-layout.pf-form-page      { max-width: var(--page-width-form); }
/* FIX RESP-WIDE : variante pleine largeur pour les VUES DE DONNÉES (pages détail,
   sous-vues à tables) — elles s'adaptent à l'écran comme les listes. Les
   formulaires de saisie gardent .pf-form-page (centré, lisible).

   `margin-inline: 0` n'est PAS cosmétique — il annule le `margin: 0 auto` hérité
   de .pf-form-page, et sans lui la variante ment dès que le parent est un flex.
   Règle du spec (Flexbox §8.1) : un item n'est étiré par `align-items: stretch`
   QUE si aucune de ses marges d'axe transverse ne vaut `auto`. Sous un parent
   `.page-layout` (display:flex; flex-direction:column), `max-width: none` ne
   suffit donc pas : les marges auto désactivent l'étirement, la boîte retombe à
   la largeur de son CONTENU et se centre — l'inverse exact de « fluide ».
   Défaut vécu (détail Monitoring, régression du 2026-08-22) : la page a adopté
   `.page-layout` en remplacement d'une classe morte, et s'est repliée sur ~800px
   centrés sur tout écran. Les 9 autres pages --fluid étaient épargnées : elles
   sont directement sous `main` (contexte bloc, où les marges auto valent 0). */
.pf-form-page--fluid { max-width: none; margin-inline: 0; }
/* Palier LISTE (1600, centré) pour ces mêmes vues de données. `--fluid` ne
   plafonne rien : sur un 2560 une fiche s'étale sur ~2280px, et une table à
   sept colonnes y creuse des vides que l'œil ne franchit plus. Contre-intuitif
   mais net : moins il y a de colonnes, plus l'étirement gêne.
   Ces pages vivent directement sous `main` (contexte BLOC), où `margin-inline:
   auto` centre sans risque — le piège flex décrit ci-dessus ne s'applique
   qu'aux enfants d'un `.page-layout`. */
.pf-form-page--wide { max-width: var(--page-width-wide); margin-inline: auto; }
.pf-form-page--compact { max-width: var(--page-width-compact); margin: 0 auto; }

/* Sections dépliables */
.pf-section {
  background:    var(--color-surface);
  border:        var(--border-width-md) solid var(--color-border-strong);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-card);
  margin-bottom: var(--space-4);
  overflow:      hidden;
}
.pf-section summary {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  padding:         var(--space-3) var(--space-5);
  cursor:          pointer;
  user-select:     none;
  list-style:      none;
  background:      var(--color-bg);
}
.pf-section summary::-webkit-details-marker { display: none; }
.pf-section summary::after {
  content:     '\25BE';
  color:       var(--color-text-muted);
  font-size:   var(--text-xs);
  margin-left: var(--space-3);
  transition:  transform .2s;
  flex-shrink: 0;
}
.pf-section[open] summary::after { transform: rotate(180deg); }

/* Section portant une erreur de validation (posé par shell.js).
   La section est ouverte d'office, mais l'utilisateur peut la replier : le
   marquage doit SURVIVRE au repli, sinon l'erreur redevient invisible. */
.pf-section--has-error {
  border-color: var(--color-danger);
}
.pf-section--has-error > summary {
  background: var(--color-danger-bg);
}
.pf-section--has-error > summary .pf-section__title {
  color: var(--color-danger);
}
.pf-section--has-error > summary .pf-section__title::after {
  content:      '!';
  display:      inline-flex;
  align-items:  center;
  justify-content: center;
  width:        1.05em;
  height:       1.05em;
  margin-left:  var(--space-2);
  border-radius: var(--radius-full);
  background:   var(--color-danger);
  color:        var(--color-surface);
  font-size:    var(--text-2xs);
  font-weight:  var(--font-semi);
  line-height:  1;
}

.pf-section__title { font-size: var(--text-sm); font-weight: var(--font-semi); color: var(--color-text-main); }
.pf-section__hint  { font-size: var(--text-xs); color: var(--color-text-muted); margin-right: auto; margin-left: var(--space-4); }
.pf-section__count { font-size: var(--text-2xs); font-weight: var(--font-normal); color: var(--color-text-muted); margin-left: var(--space-1); }
.pf-section__body  {
  padding:        var(--space-4) var(--space-5);
  border-top:     1px solid var(--color-border);
  display:        flex;
  flex-direction: column;
  gap:            var(--space-3);
}
.pf-group-inline summary {
  cursor: pointer; font-size: var(--text-sm); color: var(--color-primary);
  font-weight: var(--font-semi); list-style: none; padding: var(--space-2) 0;
}
.pf-group-inline summary::-webkit-details-marker { display: none; }
.pf-group-inline__body { padding-top: var(--space-4); }

/* Pied de carte avec sous-section toggle */
.pd-card__foot {
  padding:    var(--space-3) var(--space-5);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg);
}
.pf-subsection__body { padding-top: var(--space-4); display: none; }
[data-subsection].is-open .pf-subsection__body { display: block; }

/* Grilles de champs */
.pf-row    { display: grid; gap: var(--space-3); }
.pf-row--2 { grid-template-columns: 1fr 1fr; }
.pf-row--3 { grid-template-columns: 1fr 1fr 1fr; }
.pf-row--4 { grid-template-columns: 1fr 1fr 1fr 1fr; }

/* Champ */
.pf-field { display: flex; flex-direction: column; gap: var(--space-2); }
.pf-field--error input,
.pf-field--error select,
.pf-field--error textarea { border-color: var(--color-danger) !important; }
.pf-field--action { justify-content: flex-end; }

.pf-label {
  font-size: var(--text-2xs); font-weight: var(--font-semi); color: var(--color-text-main);
  text-transform: uppercase; letter-spacing: .05em;
}
.pf-label .req  { color: var(--color-danger); margin-left: 2px; }

/* Intitulé d'un BLOC — le pendant de `.pf-label`, qui nomme un CHAMP.
   Même registre (micro, capitales espacées) ; deux différences voulues : elle
   est en gris — elle situe, elle n'étiquette pas une saisie — et elle porte sa
   marge basse, puisqu'elle vit sur un <p> qui en a une par défaut.
   Elle vivait dans `pages/files.css`, où un seul gabarit l'employait. Le
   Centre de données, ne la trouvant pas, avait écrit sa propre version en
   `text-muted text-base` : une phrase grise de 16 px, seule de son espèce sur
   toute la page. Promue ici pour qu'il n'y en ait qu'une. */
.section-label {
  font-size:      var(--text-2xs);
  font-weight:    var(--font-semi);
  color:          var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: .07em;
  margin:         0 0 var(--space-2);
}
.pf-error       { font-size: var(--text-xs); color: var(--color-danger); margin: 0; }

/* ── Formulaire à colonne d'aide ────────────────────────────────────────────
   Deux colonnes : le formulaire, et une aide contextuelle qui explique les
   notions plutôt que les champs (les libellés s'en chargent déjà).

   Promu depuis .tf-layout / .tf-help-list de pages/projects.css, où le motif
   était né sur le seul formulaire de transport. Il sert désormais toutes les
   surfaces d'édition — une définition, pas sept copies.

   Sous 720 px l'aide disparaît : sur mobile, une colonne latérale de 260 px
   repoussée sous le formulaire n'est plus une aide, c'est du bruit. */
.form-with-help {
  display:               grid;
  grid-template-columns: 1fr 260px;
  gap:                   var(--space-6);
  align-items:           start;
}
.form-with-help__main,
.form-with-help__aside {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-4);
  min-width:      0;
}
@media (max-width: 720px) {
  .form-with-help { grid-template-columns: 1fr; }
  .form-with-help__aside { display: none; }
}
/* Les cartes portent leur propre marge basse (elles s'empilent d'ordinaire dans
   un flux normal). Dans la colonne, c'est le `gap` qui espace : sans cette
   remise à zéro, les deux s'additionnent et les sections se détachent deux fois
   trop. La colonne fait l'espacement, les cartes s'en abstiennent. */
.form-with-help__main > .pf-section,
.form-with-help__main > .pj-card { margin-bottom: 0; }

.form-help-list    { margin: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.form-help-list dt { font-size: var(--text-xs); font-weight: var(--font-semi); color: var(--color-text-main); }
.form-help-list dd { font-size: var(--text-xs); color: var(--color-text-muted); margin: var(--space-1) 0 0 0; line-height: 1.5; }
.form-help-list code { font-family: var(--font-mono); font-size: .7rem; }

/* Récapitulatif d'erreurs en tête de formulaire (organisation, partenaire).
   Même écriture que .pf-error ci-dessus — graisse normale, petit, rouge : une
   erreur se lit, elle ne se crie pas. Le conteneur .alert-danger porte déjà le
   fond et la couleur, on ne rajoute donc ni gras ni corps plus gros.

   .alert est un conteneur flex : sans le wrapper .pf-error-block, l'intitulé
   et la liste devenaient deux colonnes côte à côte — d'où un bandeau large,
   haut, aux textes désalignés. */
.pf-error-block {
  display:        flex;
  flex-direction: column;
  gap:            2px;
  min-width:      0;
}
.pf-error-block__lead {
  font-size:   var(--text-xs);
  font-weight: var(--font-medium);
}
.pf-error-summary {
  margin:       0;
  padding-left: var(--space-4);
  font-weight:  var(--font-normal);
  line-height:  1.45;
}
.pf-error-summary li {
  font-size: var(--text-xs);
}

/* Bandeau compact : un récapitulatif d'erreurs n'a pas besoin de la
   respiration d'une alerte informative pleine page. */
.alert--form-errors {
  padding: var(--space-3) var(--space-4);
}
.pf-hint        { font-size: var(--text-xs); color: var(--color-text-muted); margin: 0; line-height: 1.4; }
.pf-hint-inline { font-size: var(--text-xs); color: var(--color-danger); margin-left: var(--space-1); font-weight: var(--font-normal); }
.pf-hint--note  {
  font-size: var(--text-xs);
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg);
  border-left: 3px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text-main);
  line-height: 1.5;
}
.pf-hint--note a { color: var(--color-primary); }
.pf-check { display: flex; align-items: center; gap: var(--space-2); height: 38px; }
.pf-check label { font-size: var(--text-sm); cursor: pointer; }

/* CSS-DEAD-04 — .pf-actions supprimé → utiliser .form-actions (ci-dessus) + .ml-auto (utils.css) */


/* ===========================================================================
   FICHES DÉTAIL — .pd-*
   Utilisé par : partners, tenancy, et tout module avec fiche détail.
   =========================================================================== */

.pd-card {
  background: var(--color-surface); border: var(--border-width-md) solid var(--color-border-strong);
  border-radius: var(--radius-xl); box-shadow: var(--shadow-card);
  overflow: hidden; margin-bottom: var(--space-4);
}
.pd-card__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--space-3) var(--space-5); background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}
.pd-card__title { font-size: var(--text-sm); font-weight: var(--font-semi); color: var(--color-text-main); }
.pd-card__body  { padding: var(--space-5); }
.pd-badges      { display: flex; gap: var(--space-2); align-items: center; }

/* Identité 2 colonnes */
.pd-identity {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: var(--space-6); padding: var(--space-5);
}
@media (max-width: 768px) { .pd-identity { grid-template-columns: 1fr; } }

.pd-kv {
  display: grid; grid-template-columns: 130px 1fr;
  gap: var(--space-2) var(--space-4); align-content: start; font-size: var(--text-sm);
}
.pd-kv dt { font-size: var(--text-sm); font-weight: var(--font-normal); color: var(--color-text-muted); padding-top: 1px; white-space: nowrap; }
.pd-kv dd { color: var(--color-text-main); }

.pd-section-lbl {
  font-size: var(--text-2xs); font-weight: var(--font-semi); color: var(--color-text-muted);
  text-transform: uppercase; letter-spacing: .05em; margin: 0 0 var(--space-2);
}
.pd-address  { font-style: normal; font-size: var(--text-sm); line-height: 1.7; color: var(--color-text-main); }
.pd-siblings { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* ---------------------------------------------------------------------------
   PD-TWO-COL — layout 2 colonnes pour les pages détail (identité + adresse)
   PD-RIGHT    — colonne droite dans pd-two-col
--------------------------------------------------------------------------- */
.pd-two-col {
  display:               grid;
  grid-template-columns: 1fr 1fr;
  gap:                   var(--space-4);
}
.pd-two-col > .card { display: flex; flex-direction: column; }
.pd-two-col > .card .card-table-wrap { flex: 1; }
.pd-right { /* colonne droite — pas de style propre, sert de conteneur */ }

@media (max-width: 768px) {
  .pd-two-col { grid-template-columns: 1fr; }
}


/* ---------------------------------------------------------------------------
   PF-GRID-2 / PF-GRID-3 — grilles pour formulaires inline (membres, invitations)
--------------------------------------------------------------------------- */
.pf-grid-2 {
  display:               grid;
  grid-template-columns: 1fr 1fr;
  gap:                   var(--space-3);
  align-items:           start;
}
.pf-grid-3 {
  display:               grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap:                   var(--space-3);
  align-items:           start;
}

@media (max-width: 900px) {
  .pf-grid-2,
  .pf-grid-3 { grid-template-columns: 1fr; }
}


/* ---------------------------------------------------------------------------
   PF-SUBSECTION — section rétractable inline dans une card (JS toggle)
   Usage : <div class="pf-subsection" data-subsection>
             <button data-subsection-toggle>...</button>
             <div class="pf-subsection__body">...</div>
           </div>
--------------------------------------------------------------------------- */
.pf-subsection {
  border-top:  var(--border-width) solid var(--color-border);
  padding-top: var(--space-3);
  margin-top:  var(--space-3);
}

.pf-subsection__toggle {
  background:  none;
  border:      none;
  padding:     0;
  font-size:   var(--text-sm);
  font-weight: var(--font-medium);
  color:       var(--color-primary);
  cursor:      pointer;
  text-align:  left;
}
.pf-subsection__toggle:hover { text-decoration: underline; }

/* Tables dans les cartes détail */
.pd-table-wrap { overflow-x: auto; border-top: var(--border-width-md) solid var(--color-border-strong); }
.pd-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.pd-table thead tr { border-bottom: 2px solid var(--color-primary); background: var(--color-surface); }
.pd-table th {
  padding: var(--space-2) var(--space-4); font-size: var(--text-2xs); font-weight: var(--font-bold);
  color: var(--color-text-main); text-transform: uppercase; letter-spacing: .07em;
  text-align: left; white-space: nowrap;
}
.pd-table td {
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border);
  color: var(--color-text-main); vertical-align: middle;
}
.pd-table tbody tr:last-child td { border-bottom: none; }
.pd-table tbody tr:hover td { background: var(--color-primary-subtle); }
.pd-name { font-weight: var(--font-medium); }
.pd-col-actions { text-align: right; white-space: nowrap; }

/* Responsive pf-* */
@media (max-width: 900px) { .pf-row--4 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) {
  .pf-row--2, .pf-row--3, .pf-row--4 { grid-template-columns: 1fr; }
  .pf-section summary { flex-wrap: wrap; gap: var(--space-2); }
}

/* =============================================================================
   ═══════════════════════════════════════════════════════════════════════════════
   COMPOSANTS CANONIQUES v2 — Phase 1 consolidation (2026-03-23)
   ═══════════════════════════════════════════════════════════════════════════════
   Composants unifiés remplaçant les variantes dupliquées dans les CSS de pages.
   Chaque composant ci-dessous est la SOURCE DE VÉRITÉ unique.
   Les anciennes variantes (.kpi-grid, .pd-table, .sp-table, etc.) restent
   temporairement pour backward-compat et seront supprimées en Phase 2.
   ============================================================================= */


/* Compact — inner tables dans les cards */
.data-table--compact th {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-compact);
  font-weight: var(--font-semi);
  color: var(--color-text-muted);
}
.data-table--compact td {
  padding: var(--space-2) var(--space-3);
}

/* Muted head — style simple pour pages secondaires */
.data-table--muted-head thead tr {
  background:    var(--color-bg);
  border-bottom: var(--border-width) solid var(--color-border);
}
.data-table--muted-head th {
  font-size:   var(--text-xs);
  font-weight: var(--font-semi);
  color:       var(--color-text-muted);
}
.data-table--muted-head tbody tr:hover td {
  background: var(--color-bg);
}
.data-table--muted-head tbody tr:hover td:first-child {
  box-shadow: none;
}

.data-table--muted-head tbody tr:hover td:first-child {
  box-shadow: none;
}

/* ── FIX UI-GRP-ALIGN-01 — remonté de tenancy.css (source canonique) ──────────
   GROUP ROW — lignes de section pliables d'une table regroupée.
   Générique : toute liste avec {% regroup %} + [data-grp-toggle]
   (organisations, partenaires, futures listes). Toggle géré par shell.js.
   =========================================================================== */
.grp-row {
  cursor:      pointer;
  user-select: none;
  transition:  background var(--transition-fast);
}
.grp-row td {
  padding:       var(--space-2) var(--space-4) !important;
  background:    var(--color-bg) !important;
  border-bottom: 0.5px solid var(--color-border) !important;
}
.grp-row:hover td { background: var(--color-surface) !important; }
.grp-row__inner {
  display:     flex;
  align-items: center;
  gap:         0;
}
.grp-row__bar {
  display:        inline-block;
  width:          3px;
  height:         14px;
  border-radius:  2px;
  background:     var(--color-primary);
  margin-right:   var(--space-2);
}
.grp-row__chevron {
  color:          var(--color-text-light);
  transition:     transform 0.2s ease;
  margin-right:   var(--space-2);
  flex-shrink:    0;
}
.grp-row.is-open .grp-row__chevron { transform: rotate(90deg); }
.grp-row__name {
  font-size:      var(--text-sm);
  font-weight:    var(--font-semi);
  color:          var(--color-text-main);
}
.grp-row__count {
  font-size:      var(--text-2xs);
  color:          var(--color-text-light);
  margin-left:    var(--space-2);
}
.grp-row__link {
  font-size:       var(--text-2xs);
  color:           var(--color-primary);
  text-decoration: none;
  margin-left:     auto;
  padding:         2px 8px;
  border-radius:   var(--radius-md);
  opacity:         0;
  transition:      opacity var(--transition-fast), background var(--transition-fast);
}
.grp-row:hover .grp-row__link { opacity: 1; }
.grp-row__link:hover { background: var(--color-primary-alpha-04); }


/* ── FIX UI-GRP-ALIGN-07 — remonté de tenancy.css (source canonique) ───────────
   MANAGE ITEM — ligne à cocher d'une liste de rattachement (sections/drawers
   « membres à rattacher »). Générique : groupes d'organisations ET de partenaires.
   =========================================================================== */
.manage-item {
  display:       flex;
  align-items:   center;
  gap:           var(--space-2);
  padding:       var(--space-1) 0;
  border-bottom: var(--border-width) solid var(--color-border);
  cursor:        pointer;
}
.manage-item:last-child { border-bottom: none; }
.manage-item:hover { background: var(--color-primary-alpha-04); margin: 0 calc(var(--space-3) * -1); padding-left: var(--space-3); padding-right: var(--space-3); border-radius: var(--radius-md); }
.manage-item input[type="checkbox"] {
  width:         16px;
  height:        16px;
  accent-color:  var(--color-primary);
  cursor:        pointer;
  flex-shrink:   0;
}
.manage-item__info { flex: 1; min-width: 0; }
.manage-item__name {
  font-size:     var(--text-xs);
  font-weight:   var(--font-medium);
  color:         var(--color-text-main);
  font-family:   var(--font-mono);
  white-space:   nowrap;
  overflow:      hidden;
  text-overflow: ellipsis;
}
.manage-item__warn {
  font-size:   var(--text-2xs);
  color:       var(--color-warning);
  margin-left: var(--space-2);
  font-family: var(--font-sans);
  font-weight: var(--font-normal);
}
.manage-item__meta {
  /* --text-3xs n'existe pas ; --text-2xs vaut exactement 10px. Rendu identique,
     token fantôme retiré. */
  font-size:   var(--text-2xs);
  color:       var(--color-text-light);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ===========================================================================
   STAT-STRIP — barre de KPIs cliquables unifiée
   Remplace : .kpi-grid/.kpi-card, .db-stat-strip, .stat-strip (partners),
              .pj-stat-strip, .tx-stat-strip
   Usage :
     <div class="stat-strip">
       <a href="?" class="stat-strip__item is-active">
         <span class="stat-strip__dot stat-strip__dot--ok"></span>
         <span class="stat-strip__label">Actifs</span>
         <span class="stat-strip__value">42</span>
       </a>
     </div>
   Variantes :
     .stat-strip--narrow → max-width: 560px (transco, alerts)
     .stat-strip--5      → 5 colonnes grid (dashboard)
   =========================================================================== */

.stat-strip {
  display:       flex;
  gap:           1px;
  background:    var(--color-border);
  border:        1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow:      hidden;
  box-shadow:    var(--shadow-xs);
  margin-bottom: var(--space-4);
}

.stat-strip--narrow { max-width: 560px; }

.stat-strip__item {
  flex:            1;
  background:      var(--color-surface);
  padding:         11px 16px;
  display:         flex;
  align-items:     center;
  gap:             10px;
  cursor:          pointer;
  text-decoration: none;
  transition:      background .1s;
  user-select:     none;
}

.stat-strip__item:hover { background: var(--color-primary-subtle); }
.stat-strip__item:hover { text-decoration: none; }

.stat-strip__item.is-active {
  background: var(--color-primary-subtle);
}
.stat-strip__item.is-active .stat-strip__value {
  color: var(--color-primary-dark);
}
.stat-strip__item.is-active .stat-strip__dot {
  box-shadow: 0 0 0 3px var(--color-primary-alpha-20);
}

.stat-strip__dot {
  width:         8px;
  height:        8px;
  border-radius: 50%;
  flex-shrink:   0;
  transition:    box-shadow .1s;
}

/* Couleurs de dot sémantiques */
.stat-strip__dot--total    { background: var(--color-text-light); }
.stat-strip__dot--ok,
.stat-strip__dot--active,
.stat-strip__dot--published { background: var(--color-success); }
.stat-strip__dot--ko,
.stat-strip__dot--inactive  { background: var(--color-danger); }
.stat-strip__dot--warn,
.stat-strip__dot--paused,
.stat-strip__dot--draft     { background: var(--color-warning); }
.stat-strip__dot--muted,
.stat-strip__dot--archived  { background: var(--color-text-light); }
.stat-strip__dot--info,
.stat-strip__dot--rate      { background: var(--color-primary); }
.stat-strip__dot--in        { background: #3B82F6; }
.stat-strip__dot--out       { background: #8B5CF6; }

.stat-strip__label {
  font-size:      var(--text-xs);
  color:          var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight:    var(--font-medium);
  flex:           1;
}

.stat-strip__value {
  font-size: var(--text-xl);
  font-weight:          var(--font-semi);
  color:                var(--color-text-main);
  line-height:          1;
  font-variant-numeric: tabular-nums;
  transition:           color .1s;
}

/* Variantes de couleur sur la valeur */
.stat-strip__item--ok .stat-strip__value   { color: var(--color-success); }
.stat-strip__item--ko .stat-strip__value   { color: var(--color-danger); }
.stat-strip__item--rate .stat-strip__value { color: var(--color-primary-dark); }

/* Stat-strip avec icônes (dashboard style) */
.stat-strip__icon {
  width:           34px;
  height:          34px;
  border-radius:   var(--radius-lg);
  display:         flex;
  align-items:     center;
  justify-content: center;
  flex-shrink:     0;
  background:      var(--color-bg);
}

/* Grid 5 colonnes (dashboard) */
.stat-strip--5 {
  display:               grid;
  grid-template-columns: repeat(5, 1fr);
}

/* Responsive */
@media (max-width: 1200px) {
  .stat-strip--5 { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 900px) {
  .stat-strip { flex-wrap: wrap; }
  .stat-strip__item { min-width: 120px; }
}
@media (max-width: 560px) {
  .stat-strip--5 { grid-template-columns: 1fr; }
}


/* ===========================================================================
   CARD-TOOLBAR — toolbar intégrée en haut d'une card (recherche + filtres)
   Remplace : .tx-toolbar, .pj-toolbar, .pipeline-filter-bar
   Usage :
     <div class="card">
       <form class="card-toolbar">
         <div class="search-input">…</div>
         <select class="card-toolbar__ctrl">…</select>
         <button class="btn btn-primary btn-sm">Filtrer</button>
         <span class="card-toolbar__count">42 résultats</span>
       </form>
       <table class="data-table">…</table>
     </div>
   =========================================================================== */

.card-toolbar {
  display:       flex;
  align-items:   center;
  gap:           var(--space-2);
  padding:       var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  background:    var(--color-bg);
  min-height:    48px;
  flex-wrap:     wrap;
}

.card-toolbar__ctrl {
  height:        var(--input-height-sm);
  padding:       0 24px 0 8px;
  border:        1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size:     var(--text-xs);
  font-family:   var(--font-sans);
  color:         var(--color-text-main);
  background:    var(--color-surface);
  cursor:        pointer;
  appearance:    none;
  background-image:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%236B7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat:   no-repeat;
  background-position: right 7px center;
  transition:    border-color .12s;
  white-space:   nowrap;
}

.card-toolbar__ctrl:focus {
  outline:      none;
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-12);
}

input[type="text"].card-toolbar__ctrl {
  width:         180px;
  padding:       0 10px;
  cursor:        text;
  appearance:    none;
  background-image: none;
}

.card-toolbar__count {
  margin-left: auto;
  font-size:   var(--text-xs);
  color:       var(--color-text-muted);
  white-space: nowrap;
}


/* ===========================================================================
   SEARCH-INPUT — champ de recherche avec icône (universel)
   Remplace : .input-search, .pt-search-*, .pj-search-*, .tx-search-*
   Usage :
     <div class="search-input">
       <svg class="search-input__icon">…</svg>
       <input class="search-input__field" placeholder="Rechercher…">
     </div>
   =========================================================================== */

.search-input {
  position:  relative;
  flex:      1;
  min-width: 140px;
  max-width: 280px;
}

.search-input__icon {
  position:       absolute;
  left:           9px;
  top:            50%;
  transform:      translateY(-50%);
  color:          var(--color-text-light);
  pointer-events: none;
}

.search-input__field {
  width:         100%;
  height:        var(--input-height-sm);
  padding:       0 10px 0 30px;
  border:        1px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-size:     var(--text-xs);
  font-family:   var(--font-sans);
  background:    var(--color-surface);
  color:         var(--color-text-main);
  transition:    border-color .12s, box-shadow .12s;
}

.search-input__field::placeholder { color: var(--color-text-light); }

.search-input__field:focus {
  outline:      none;
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-12);
}

/* Variante large (pour filter panels) */
.search-input--lg {
  max-width: 360px;
}
.search-input--lg .search-input__field {
  height: var(--input-height-md);
}


/* ===========================================================================
   FILTER-PANEL — panneau de filtres (source de vérité : flows)
   Remplace : .mf-panel (flows), .tx-panel (transco), .ff-panel (files),
              .sp-panel (specs), .pg-filter (partners)

   2 layouts, mêmes atomes :

   Layout A — simple row (partners, projects, specs, tenancy) :
     <div class="filter-panel" id="…" hidden>
       <form><div class="filter-panel__row">
         <div class="filter-panel__field">
           <label class="filter-panel__label">…</label>
           <input class="filter-panel__ctrl">
         </div>
         <div class="filter-panel__actions">…</div>
       </div></form>
     </div>

   Layout B — multi-colonnes centré (flows, transco, files) :
     <form class="filter-panel filter-panel--compact" id="…" hidden>
       <div class="filter-panel__cols">
         <div class="filter-panel__col">
           <div class="filter-panel__col-title">ÉMETTEUR</div>
           <div class="filter-panel__grid">
             <span class="filter-panel__label">Groupe</span>
             <select class="filter-panel__ctrl">…</select>
           </div>
         </div>
       </div>
       <div class="filter-panel__actions">…</div>
     </form>
   =========================================================================== */


/* ---------------------------------------------------------------------------
   Wrapper — base partagée
--------------------------------------------------------------------------- */
.filter-panel {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-sm);
  overflow:      visible;
  animation:     fp-slide-down var(--transition-normal) ease;
}

.filter-panel[hidden] { display: none; }

/* Espacement entre la toolbar et le panneau filtres */
.page-toolbar + .filter-panel {
  margin-top: var(--space-3);
}

@keyframes fp-slide-down {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}


/* ---------------------------------------------------------------------------
   Wrapper — variante compact (flows, transco, files)
--------------------------------------------------------------------------- */
/* ⚠ LE CENTRAGE EST DÉLIBÉRÉ — NE PAS « CORRIGER ».
   Chaque page déclare un nombre de colonnes de filtres différent, donc
   `fit-content` donne une largeur différente à chaque panneau. Alignés à
   gauche, ils dépasseraient tous d'une longueur différente vers la droite :
   le bord droit serait en dents de scie d'un écran à l'autre. Centrés, ils
   paraissent équilibrés quelle que soit leur largeur. C'est un choix visuel
   assumé, arbitré par Dany.

   Un audit signale volontiers ce `margin: 0 auto` comme un défaut : le panneau
   est le seul élément centré au milieu d'une toolbar et d'un tableau pleine
   largeur, et il s'ouvre décalé du bouton qui le déclenche. Le raisonnement se
   tient, il a déjà été appliqué une fois — et reverté. La régularité entre
   pages l'emporte ici sur l'alignement au sein d'une page. */
.filter-panel--compact {
  width:  fit-content;
  margin: 0 auto;
}


/* ---------------------------------------------------------------------------
   Layout A — simple row (partners, projects, specs, tenancy)
--------------------------------------------------------------------------- */
.filter-panel__row {
  display:     flex;
  align-items: flex-end;
  gap:         var(--space-3);
  padding:     var(--space-3) var(--space-4);
  flex-wrap:   wrap;
}

.filter-panel__row--bg {
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
}

.filter-panel__row--grid {
  display:               grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap:                   var(--space-2);
  align-items:           end;
}

.filter-panel__field {
  display:        flex;
  flex-direction: column;
  gap:            var(--filter-gap-field);
}


/* ---------------------------------------------------------------------------
   Layout B — multi-colonnes (flows, transco, files)
--------------------------------------------------------------------------- */
.filter-panel__cols {
  display: flex;
}

.filter-panel__col {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  padding:        var(--filter-col-padding);
}

.filter-panel__col + .filter-panel__col {
  border-left: var(--border-width) solid var(--color-border);
}

.filter-panel__col-title {
  font-size:      var(--filter-col-title-size);
  font-weight:    var(--font-semi);
  text-transform: uppercase;
  letter-spacing: .07em;
  color:          var(--filter-col-title-color);
}

.filter-panel__grid {
  display:               grid;
  grid-template-columns: 70px minmax(0, 180px);
  gap:                   var(--filter-gap-grid);
  align-items:           center;
}


/* ---------------------------------------------------------------------------
   Atomes — label (partagé layouts A + B)
--------------------------------------------------------------------------- */
.filter-panel__label {
  font-size:   var(--filter-label-size);
  font-weight: var(--filter-label-weight);
  color:       var(--filter-label-color);
  white-space: nowrap;
  user-select: none;
}


/* ---------------------------------------------------------------------------
   Atomes — control input/select (partagé layouts A + B)
   Cible aussi les widgets Django sans classe explicite via .filter-panel__field
--------------------------------------------------------------------------- */
.filter-panel__ctrl,
.filter-panel__field input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
.filter-panel__field select {
  display:       block;
  height:        var(--filter-field-height);
  font-size:     var(--filter-ctrl-size);
  font-family:   var(--font-sans);
  padding:       0 6px;
  border:        var(--border-width) solid var(--filter-ctrl-border);
  border-radius: var(--radius-sm);
  background:    var(--color-surface);
  color:         var(--filter-ctrl-color);
  width:         100%;
  transition:    border-color var(--transition-fast),
                 box-shadow var(--transition-fast);
}

.filter-panel__ctrl::placeholder,
.filter-panel__field input::placeholder {
  color: var(--filter-placeholder);
}

/* Combobox à l'intérieur du filter-panel : hauteur alignée sur --filter-field-height */
.filter-panel__grid .combobox__input,
.filter-panel__col  .combobox__input {
  height:       var(--filter-field-height);
  border-color: var(--filter-ctrl-border);
}

.filter-panel__grid .combobox__toggle,
.filter-panel__col  .combobox__toggle {
  height:       var(--filter-field-height);
  border-color: var(--filter-ctrl-border);
}

.filter-panel__ctrl:focus,
.filter-panel__field input:focus,
.filter-panel__field select:focus {
  outline:      none;
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-10);
}

select.filter-panel__ctrl,
.filter-panel__field select {
  cursor:              pointer;
  appearance:          none;
  background-image:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%236B7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat:   no-repeat;
  background-position: right 5px center;
  padding-right:       18px;
  color:               var(--filter-placeholder);
}

/* Options de la liste déroulée : toujours lisibles (noir #1F2937), même si le
   select affiche un placeholder gris tant qu'aucun filtre n'est choisi. */
select.filter-panel__ctrl option,
.filter-panel__field select option {
  color: var(--color-text-main);
}

/* Valeur sélectionnée (filtre actif) : noir, pas gris muet. */
select.filter-panel__ctrl.has-value {
  color: var(--color-text-main);
}

input[type="date"].filter-panel__ctrl { padding-top: 0; }


/* ---------------------------------------------------------------------------
   Atomes — actions (Filtrer + Réinit.)
   Source de vérité : flows (list.html)
   Partial canonique : components/_filter_actions.html

   Pattern :
     <div class="filter-panel__actions">
       {% include "components/_filter_actions.html" with reset_url=request.path %}
     </div>

   Règles :
     - Filtrer  = .btn.btn-primary.btn-sm + icône SVG entonnoir (14×14)
     - Réinit.  = <a> .btn.btn-secondary.btn-sm → URL sans query string
     - Texte    = "Filtrer" / "Réinit." (jamais "Réinitialiser", jamais "Reset")
--------------------------------------------------------------------------- */
.filter-panel__actions {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  flex-shrink: 0;
  margin-left: auto;
}

/* Actions en bas du panneau compact (border-top séparateur) */
.filter-panel--compact > .filter-panel__actions {
  padding:    var(--filter-col-padding);
  border-top: var(--border-width) solid var(--color-border);
}

/* Actions à l'intérieur d'une colonne (flows: Masse → Filtrer/Réinit.) */
.filter-panel__col .filter-panel__actions {
  margin-top:      8px;
  justify-content: flex-end;
}

.filter-panel__count {
  font-size:   var(--text-xs);
  color:       var(--color-text-muted);
  white-space: nowrap;
}


/* ---------------------------------------------------------------------------
   Responsive
--------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .filter-panel__cols {
    flex-wrap: wrap;
  }
  .filter-panel__col + .filter-panel__col {
    border-left:  none;
    border-top:   var(--border-width) solid var(--color-border);
  }
}
/* FIX RESP-L1 : sur mobile, le panneau de filtres multi-colonnes stacke
   proprement en 1 colonne pleine largeur (Dashboard : Organisation/Message ;
   flows garde ses overrides ≤768). */
@media (max-width: 640px) {
  .filter-panel__cols { flex-direction: column; }
}

@media (max-width: 640px) {
  .filter-panel__row {
    flex-direction: column;
    align-items:    stretch;
  }
  .filter-panel__ctrl {
    min-width: auto;
    width:     100%;
  }
}


/* ===========================================================================
   FORM-CHECK-INPUT — checkbox standardisée dans les formulaires
   Usage : <input type="checkbox" class="form-check-input">
   =========================================================================== */

.form-check-input {
  width:         16px;
  height:        16px;
  margin-right:  var(--space-2);
  accent-color:  var(--color-primary);
  vertical-align: middle;
  cursor:        pointer;
}


/* ===========================================================================
   FORM-STACK — layout vertical pour formulaires
   Remplace : déclarations ad-hoc dans pipeline_specs.css, transco.css, etc.
   Usage :
     <div class="form-stack">
       <div class="form-group">…</div>
       <div class="form-group">…</div>
     </div>
   =========================================================================== */

.form-stack {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-4);
}


/* ===========================================================================
   ROW STATES — états visuels des lignes de tableaux (partagé entre modules)
   Remplace : .pt-row--inactive, .pj-row--inactive, .row-inactive, etc.
   Usage : <tr class="row--inactive">
   =========================================================================== */

.row--inactive    td { opacity: .6; }
.row--inactive:hover td { opacity: 1; }

.row--paused      td { background: var(--color-warning-bg); }
.row--paused:hover td { background: #fde68a; }

.row--archived    td { opacity: .5; }
.row--archived:hover td { opacity: .8; background: var(--color-bg); }

.row--clickable   { cursor: pointer; }
.row--unread      td { font-weight: var(--font-medium); }


/* ===========================================================================
   BADGE-COUNT — compteur dans les headers de section
   Usage : <span>Flux <span class="badge-count">12</span></span>
   =========================================================================== */

.badge-count {
  display:       inline-flex;
  align-items:   center;
  justify-content: center;
  min-width:     20px;
  height:        18px;
  padding:       0 6px;
  border-radius: var(--radius-full);
  background:    var(--color-bg);
  border:        var(--border-width) solid var(--color-border);
  font-size:     var(--text-2xs);
  font-weight:   var(--font-semi);
  color:         var(--color-text-muted);
  margin-left:   var(--space-2);
  line-height:   1;
}


/* ===========================================================================
   DOT INDICATOR — pastille de statut inline
   Usage : <span class="dot dot--ok"></span>
   =========================================================================== */

.dot,
.status-dot {
  display:       inline-block;
  width:         8px;
  height:        8px;
  border-radius: var(--radius-full);
}
.dot--ok, .status-dot--ok  { background: var(--color-success); box-shadow: 0 0 0 3px var(--color-success-bg); }
.dot--off, .status-dot--off { background: var(--color-border-strong); box-shadow: 0 0 0 3px var(--color-bg); }
.dot--ko, .status-dot--ko  { background: var(--color-danger); box-shadow: 0 0 0 3px var(--color-danger-bg); }
.dot--warn, .status-dot--warn { background: var(--color-warning); box-shadow: 0 0 0 3px var(--color-warning-bg); }


/* ===========================================================================
   TOGGLE-BTN — bouton toggle ON/OFF dans les tableaux
   Usage :
     <button class="toggle-btn toggle-btn--on">✓</button>
     <button class="toggle-btn toggle-btn--off">—</button>
   =========================================================================== */

/* Deux définitions concurrentes existaient : celle-ci (fond blanc, bordure
   visible, la couleur portée par le TEXTE) et une autre dans pages/projects.css
   (fond coloré d'emblée, survol par transparence). La seule page qui emploie ce
   bouton — les membres d'un projet — charge projects.css, donc la version
   ci-dessous n'était RENDUE NULLE PART. On garde ce qui s'affiche vraiment :
   les valeurs sont celles de l'ex-copie locale, à l'identique. */
.toggle-btn {
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  width:           26px;
  height:          20px;
  border-radius:   var(--radius-sm);
  border:          var(--border-width) solid transparent;
  cursor:          pointer;
  font-size:       var(--text-xs);
  font-weight:     var(--font-semi);
  background:      none;
  transition:      opacity var(--transition-fast);
}
.toggle-btn--on      { background: var(--color-success-bg);  color: var(--color-success);     border-color: var(--color-success-border); }
.toggle-btn--off     { background: var(--color-bg);          color: var(--color-text-light);  border-color: var(--color-border); }
.toggle-btn--warn    { background: var(--color-warning-bg);  color: var(--color-warning);     border-color: var(--color-warning-border); }
.toggle-btn--neutral { background: var(--color-bg);          color: var(--color-text-light);  border-color: var(--color-border); }
.toggle-btn:hover    { opacity: .75; }


/* .status-dot — alias de .dot (voir DOT INDICATOR ci-dessus). Aucun gabarit ne
   l'emploie aujourd'hui ; c'est `.dot` qui sert. Alias conservé, il ne coûte
   qu'un mot dans une liste de sélecteurs. */


/* ===========================================================================
   MODAL EXTENSIONS — compléments pour le composant .modal existant
   =========================================================================== */

/* État ouvert via JS (alternative au display:none → display:flex) */
.modal-overlay--open {
  display: flex;
}

/* Grille 2 colonnes dans le body d'une modale */
.modal-grid {
  display:               grid;
  grid-template-columns: 1fr 1fr;
  gap:                   var(--space-4);
  align-items:           end;
}

/* FIX RESP-R4 : sur petit écran la modale ne fait plus que ~343px → une seule
   colonne, sinon les champs (tenant/partner/project/doctype/encodages) sont illisibles. */
@media (max-width: 560px) {
  .modal-grid { grid-template-columns: 1fr; }
}

.modal-field--full { grid-column: 1 / -1; }

/* Confirmation déclarative (shared/confirm.js) — message + saisie « type-to-confirm » */
.modal__confirm-msg {
  margin:      0;
  color:       var(--color-text-main);
  font-size:   var(--text-sm);
  line-height: var(--leading-relaxed, 1.6);
  text-align:  center;
}
.modal__confirm-type {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  margin-top:     var(--space-4);
}
/* [hidden] doit l'emporter sur `display:flex` ci-dessus (sinon le champ
   « retaper le code » s'affiche même quand aucun code n'est demandé). */
.modal__confirm-type[hidden] {
  display: none;
}
.modal__confirm-type-hint {
  font-size: var(--text-sm);
  color:     var(--color-text-muted);
}
.modal__confirm-type-input {
  width:         100%;
  height:        var(--input-height-sm);
  padding:       0 var(--space-3);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  font-family:   var(--font-mono, monospace);
  font-size:     var(--text-sm);
  color:         var(--color-text-main);
  background:    var(--color-surface);
}
.modal__confirm-type-input:focus {
  outline:      none;
  border-color: var(--color-primary);
  box-shadow:   0 0 0 3px var(--color-primary-alpha-10);
}


/* ===========================================================================
   PIPELINE-STEP — visualisation des étapes pipeline
   Factorisé depuis flows.css (utilisé aussi par pipeline_sandbox.css)
   Usage :
     <div class="pipeline-steps">
       <div class="pipeline-step pipeline-step--ok">
         <div class="pipeline-step__num">1</div>
         <div class="pipeline-step__body">
           <div class="pipeline-step__name">Identification</div>
           <div class="pipeline-step__reason">…</div>
         </div>
         <div class="pipeline-step__right">
           <span class="pipeline-step__badge">OK</span>
           <span class="pipeline-step__issues">2 issues</span>
         </div>
       </div>
     </div>
   =========================================================================== */

.pipeline-steps {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-1);
}

.pipeline-step {
  display:       flex;
  align-items:   center;
  gap:           var(--space-3);
  padding:       var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  border-left:   3px solid transparent;
  background:    var(--color-bg);
}

.pipeline-step--ok      { border-left-color: var(--color-success); }
.pipeline-step--ko      { border-left-color: var(--color-danger); background: var(--color-danger-bg); }
.pipeline-step--skipped { border-left-color: var(--color-border); opacity: .6; }

.pipeline-step__num {
  font-size:   var(--text-xs);
  font-weight: var(--font-bold);
  color:       var(--color-text-muted);
  width:       18px;
  text-align:  center;
  flex-shrink: 0;
}

.pipeline-step__body { flex: 1; min-width: 0; }

.pipeline-step__name {
  font-size:   var(--text-sm);
  font-weight: var(--font-medium);
  color:       var(--color-text-main);
}

.pipeline-step__reason {
  font-size:   var(--text-xs);
  color:       var(--color-text-muted);
  font-style:  italic;
  margin-top:  1px;
}

.pipeline-step__right {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  flex-shrink: 0;
}

.pipeline-step__badge {
  font-size:      var(--text-xs);
  font-weight:    var(--font-bold);
  letter-spacing: .04em;
}
.pipeline-step--ok      .pipeline-step__badge { color: var(--color-success); }
.pipeline-step--ko      .pipeline-step__badge { color: var(--color-danger); }
.pipeline-step--skipped .pipeline-step__badge { color: var(--color-text-muted); }

/* Badges statut nommés (pour split docs) */
.pipeline-step__badge--success,
.pipeline-step__badge--ok,
.pipeline-step__badge--OK      { color: var(--color-success); }
.pipeline-step__badge--failed,
.pipeline-step__badge--ko,
.pipeline-step__badge--KO      { color: var(--color-danger); }
.pipeline-step__badge--partial { color: var(--color-warning); }

.pipeline-step__issues {
  font-size:     var(--text-xs);
  background:    var(--color-danger-bg);
  color:         var(--color-danger);
  padding:       1px var(--space-1);
  border-radius: var(--radius-sm);
}


/* ===========================================================================
   PIPELINE ERROR BANNER
   =========================================================================== */

.pipeline-error-banner {
  background:    var(--color-danger-bg);
  color:         var(--color-danger);
  border-radius: var(--radius-md);
  padding:       var(--space-2) var(--space-3);
  font-size:     var(--text-sm);
  margin-bottom: var(--space-4);
}


/* ===========================================================================
   BADGE KIND — badges techniques dans les tableaux
   Usage : <span class="badge-kind badge-kind--edifact">EDIFACT</span>
   La variante est fabriquée par le gabarit à partir d'un TYPE DE DOCUMENT ou
   d'un MOTEUR. Sans variante, le badge reste neutre — c'est le cas le plus
   courant (Transferts, Monitoring, centre de notifications).
   =========================================================================== */

.badge-kind {
  display:       inline-flex;
  align-items:   center;
  padding:       2px 7px;
  border-radius: var(--radius-sm);
  font-size:     var(--text-2xs);
  font-weight:   var(--font-semi);
  font-family:   var(--font-mono);
  letter-spacing: .03em;
  text-transform: uppercase;
  background:    var(--color-bg);
  border:        1px solid var(--color-border);
  color:         var(--color-text-muted);
}

/* Variantes de PROTOCOLE (--sftp --ftp --api --as2 --local) SUPPRIMÉES :
   aucun gabarit ne les compose. Les templates fabriquent `badge-kind--{{ ... }}`
   à partir d'un TYPE DE DOCUMENT (monitoring) ou d'un MOTEUR (specs), jamais
   d'un protocole — la page Transports, elle, l'affiche en texte mono.
   Elles vivaient en double avec pages/projects.css, dans deux palettes
   contradictoires (SFTP bleu ici, teal là ; AS2 violet ici, ambre là), pour
   des badges que personne n'affichait. Les variantes RÉELLEMENT employées —
   types de document et moteurs — sont plus bas dans ce fichier. */


/* ===========================================================================
   OPS-PANEL — panneau dépliable opérationnel (flows detail)
   =========================================================================== */

.ops-panel {
  background:    var(--color-surface);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-sm);
  overflow:      hidden;
}

.ops-panel details > summary {
  padding:        var(--space-3) var(--space-4);
  background:     var(--color-bg);
  border-bottom:  var(--border-width) solid var(--color-border);
  font-size:      var(--text-xs);
  font-weight:    var(--font-bold);
  color:          var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor:         pointer;
  user-select:    none;
  list-style:     none;
  display:        flex;
  align-items:    center;
  gap:            var(--space-2);
  transition:     background var(--transition-fast);
}

.ops-panel details > summary:hover { background: var(--color-border); }
.ops-panel details > summary::-webkit-details-marker { display: none; }

.ops-body { padding: var(--space-4); }


/* ===========================================================================
   PAGE-TOOLBAR — barre d'outils principale des pages de listing
   Remplace : .mf-toolbar (flows), .ff-toolbar (files), .tx-toolbar (transco),
              .pipeline-toolbar / .sp-toolbar (specs), ad-hoc projets/partenaires/orgs
   Source de vérité : flows.css (monitoring) — pixel-perfect

   Usage :
     <div class="page-toolbar">
       <button class="page-toolbar__toggle" id="xxx-toggle-filters"
               aria-expanded="false" aria-controls="xxx-filters">
         <svg class="page-toolbar__toggle-icon">…</svg>
         Filtres avancés
         <svg class="page-toolbar__chevron">…</svg>
       </button>
       <div class="page-toolbar__sep"></div>
       <div class="page-toolbar__group"> <!-- pills / nav / compteurs --> </div>
       <div class="page-toolbar__spacer"></div>
       <div class="page-toolbar__search">
         <svg>…</svg>
         <input type="text" placeholder="Recherche rapide…">
       </div>
     </div>
   =========================================================================== */

.page-toolbar {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  flex-wrap:   wrap;
}

/* ── Toggle — bouton "Filtres avancés ▾" ── */
.page-toolbar__toggle {
  display:       inline-flex;
  align-items:   center;
  gap:           var(--space-2);
  padding:       var(--space-2) var(--space-3);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background:    var(--color-surface);
  color:         var(--color-text-muted);
  font-size:     var(--text-sm);
  font-weight:   var(--font-medium);
  font-family:   var(--font-sans);
  cursor:        pointer;
  transition:    background var(--transition-fast),
                 border-color var(--transition-fast),
                 color var(--transition-fast);
  white-space:   nowrap;
}
.page-toolbar__toggle:hover {
  border-color: var(--color-primary);
  color:        var(--color-primary);
}
.page-toolbar__toggle[aria-expanded="true"] {
  border-color: var(--color-primary);
  color:        var(--color-primary);
  background:   var(--color-primary-subtle);
}

.page-toolbar__toggle-icon {
  flex-shrink: 0;
  opacity:     0.6;
}
.page-toolbar__toggle:hover .page-toolbar__toggle-icon,
.page-toolbar__toggle[aria-expanded="true"] .page-toolbar__toggle-icon {
  opacity: 1;
}

.page-toolbar__chevron {
  transition:  transform var(--transition-fast);
  flex-shrink: 0;
}
.page-toolbar__toggle[aria-expanded="true"] .page-toolbar__chevron {
  transform: rotate(180deg);
}

/* ── Group — conteneur générique pour pills, nav, compteurs ── */
.page-toolbar__group {
  display:     flex;
  align-items: center;
  gap:         3px;
  flex-shrink: 0;
  flex-wrap:   wrap;   /* FIX RESP-R1 : pills/onglets wrappent au lieu de déborder (petits écrans) */
}

/* ── Séparateur vertical ── */
.page-toolbar__sep {
  width:       var(--border-width);
  height:      24px;
  background:  var(--color-border);
  flex-shrink: 0;
  margin:      0 var(--space-1);
}

/* ── Spacer — pousse le search à droite ── */
.page-toolbar__spacer { flex: 1; }

/* ── Search — champ de recherche rapide avec icône ── */
.page-toolbar__search {
  display:       flex;
  align-items:   center;
  gap:           var(--space-2);
  padding:       var(--space-2) var(--space-3);
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  transition:    border-color var(--transition-fast),
                 box-shadow var(--transition-fast);
  color:         var(--color-text-light);
}
.page-toolbar__search:focus-within {
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-10);
}
.page-toolbar__search input {
  background:  transparent;
  border:      none;
  outline:     none;
  color:       var(--color-text-main);
  font-size:   var(--text-sm);
  font-family: var(--font-sans);
  font-weight: var(--font-medium);
  width:       170px;
}
.page-toolbar__search input::placeholder {
  color:       var(--color-text-light);
  font-weight: var(--font-normal);
  font-size:   var(--text-sm);
}

/* ── Responsive ── */
@media (max-width: 768px) {
  .page-toolbar__group {
    order:      10;
    flex-basis: 100%;
    margin-top: var(--space-2);
  }
  .page-toolbar__search input { width: 120px; }
}
@media (max-width: 480px) {
  .page-toolbar__search { flex: 1; order: 20; }
  .page-toolbar__search input { width: 100%; }
}


/* ===========================================================================
   TABLE-FOOTER — footer de tableau avec per-page + pagination
   Remplace : .flows-card__footer (flows), .files-card__footer (files),
              .pj-list-footer (projets)

   Usage :
     <div class="table-footer">
       <div class="table-footer__inner">
         <div class="per-page">…</div>
         <div class="table-footer__pagination">
           {% include 'components/_pagination.html' %}
         </div>
       </div>
     </div>
   =========================================================================== */

.table-footer {
  padding:    var(--space-3) var(--space-4);
  border-top: var(--border-width) solid var(--color-border);
  background: var(--color-bg);
}

/* `.pagination` a été conçue comme pied de tableau AUTONOME : elle porte son
   propre padding et son propre `border-top`. Réemployée À L'INTÉRIEUR de
   `.table-footer`, qui déclare les mêmes règles, elle produisait un second
   filet — un trait horizontal court et flottant au-dessus des boutons de page —
   et un double padding. On neutralise dans ce contexte uniquement : employée
   seule, la pagination garde son filet. */
.table-footer .pagination {
  padding:    0;
  border-top: none;
}

.table-footer__inner {
  display:         flex;
  align-items:     center;
  justify-content: space-between;
  gap:             var(--space-4);
}

.table-footer__pagination {
  flex:            1;
  display:         flex;
  justify-content: flex-end;
}


/* ===========================================================================
   PER-PAGE — sélecteur "Lignes par page" (50 / 100 / 250 / 500 / 1k)
   Composant unique : templates/components/_per_page.html (forme <select>).
   Navigation pilotée par shell.js (data-per-page).

   Usage :
     {% include 'components/_per_page.html' %}
   =========================================================================== */

.per-page {
  display:     flex;
  align-items: center;
  gap:         var(--space-2);
  flex-shrink: 0;
}

.per-page__label {
  font-size:   var(--text-xs);
  color:       var(--color-text-light);
  font-weight: var(--font-medium);
  white-space: nowrap;
}

/* Sélecteur « lignes par page » — forme <select> (composant _per_page.html).
   Largeur FIXE et serrée (chevron custom comme .filter-panel__ctrl) : la case ne
   réserve plus la place du plus long palier → pas de vide, et ne grandit pas. */
.per-page__select {
  appearance:          none;
  -webkit-appearance:  none;
  height:              28px;
  width:               54px;
  padding:             0 18px 0 var(--space-2);
  font-size:           var(--text-xs);
  font-weight:         var(--font-semi);
  font-family:         var(--font-mono);
  color:               var(--color-text-main);
  background:          var(--color-surface)
                       url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='%236B7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
                       no-repeat right 5px center;
  border:              var(--border-width) solid var(--color-border-strong);
  border-radius:       var(--radius-md);
  cursor:              pointer;
}
.per-page__select:hover { border-color: var(--color-primary); }
.per-page__select:focus-visible {
  outline:      none;
  border-color: var(--color-primary);
  box-shadow:   0 0 0 3px var(--color-primary-alpha-06);
}


/* ===========================================================================
   PILL — composant pill unifié (filtres, navigation, compteurs)
   Remplace : .mf-pill (flows), .ff-tab + .ff-toggle (files),
              .sp-pill (specs/projets/partenaires/orgs), .tx-pill (transco)

   3 usages :

   1. Nav / Counter — lien <a> ou <span> directement stylé
      <a href="…" class="pill is-active">3 Total</a>
      <a href="…" class="pill">2 Actifs</a>
      <span class="pill">0 Inactifs</span>

   2. Toggle — <label> avec checkbox caché, coloration au :checked
      <label class="pill--toggle pill--ok">
        <input type="checkbox" …>
        <span>OK</span>
      </label>

   3. Sémantique — variante couleur sur is-active (counter filtrant)
      <a href="…" class="pill pill--success is-active">7 Publiés</a>
      <a href="…" class="pill pill--warn is-active">2 Brouillons</a>
   =========================================================================== */


/* ---------------------------------------------------------------------------
   Base — forme partagée par nav/counter ET toggle span
--------------------------------------------------------------------------- */
.pill,
.pill--toggle span {
  display:        inline-flex;
  align-items:    center;
  height:         var(--input-height-sm);
  padding:        0 var(--space-3);
  border:         var(--border-width-md) solid var(--color-border-strong);
  border-radius:  var(--radius-full);
  font-size:      var(--text-xs);
  font-weight:    var(--font-semi);
  color:          var(--color-text-muted);
  background:     var(--color-surface);
  text-decoration: none;
  cursor:         pointer;
  transition:     background var(--transition-fast),
                  border-color var(--transition-fast),
                  color var(--transition-fast);
  user-select:    none;
  letter-spacing: .03em;
  white-space:    nowrap;
}

.pill:hover,
.pill--toggle:hover span {
  border-color:    var(--color-primary);
  color:           var(--color-primary);
  text-decoration: none;
}

.pill:focus:not(:focus-visible) { outline: none; }
.pill:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }


/* ---------------------------------------------------------------------------
   Active state — nav/counter (teal par défaut)
--------------------------------------------------------------------------- */
.pill.is-active {
  border-color: var(--color-primary);
  color:        var(--color-primary-dark);
  background:   var(--color-primary-subtle);
}


/* ---------------------------------------------------------------------------
   Active color variants — counter filtrant avec couleur sémantique
--------------------------------------------------------------------------- */
.pill--success.is-active {
  border-color: var(--color-success-border);
  color:        var(--color-success);
  background:   var(--color-success-bg);
}

.pill--warn.is-active {
  border-color: var(--color-warning-border);
  color:        var(--color-warning-text);
  background:   var(--color-warning-bg);
}

.pill--danger.is-active {
  border-color: var(--color-danger-border);
  color:        var(--color-danger);
  background:   var(--color-danger-bg);
}


/* ---------------------------------------------------------------------------
   Toggle — <label> wrapper avec checkbox caché
--------------------------------------------------------------------------- */
.pill--toggle {
  position: relative;
  cursor:   pointer;
}

.pill--toggle input {
  position: absolute;
  opacity:  0;
  width:    0;
  height:   0;
}


/* ---------------------------------------------------------------------------
   Toggle color variants — coloration quand :checked
--------------------------------------------------------------------------- */
.pill--ok  input:checked + span {
  background:   var(--color-success-bg);
  border-color: var(--color-success-border);
  color:        var(--color-success);
}

.pill--ko  input:checked + span {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color:        var(--color-danger);
}

.pill--dir input:checked + span {
  background:   var(--color-primary-light);
  border-color: var(--color-primary);
  color:        var(--color-primary-dark);
}

.pill--md  input:checked + span {
  background:   var(--color-md-bg);
  border-color: var(--color-md-border);
  color:        var(--color-md);
}

/* DUP (monitoring) = doublon run-level (status ignored/rejected). */
.pill--dup input:checked + span {
  background:   var(--color-info-bg);
  border-color: var(--color-info-border);
  color:        var(--color-info);
}


/* Stop out = livraison bloquée (run vert mais fichier non parti) — danger. */
.pill--stopout input:checked + span {
  background:   var(--color-danger-bg);
  border-color: var(--color-danger-border);
  color:        var(--color-danger);
}

.pill--archived input:checked + span {
  background:   var(--color-bg);
  border-color: var(--color-border-strong);
  color:        var(--color-text-muted);
}


/* ===========================================================================
   FILTER-CHIP — pill dismissible de filtre actif
   Source de vérité : dashboard (index.html)

   Usage :
     <span class="filter-chip">
       Partenaire ALTO
       <a href="?remove=partner" class="filter-chip__close" aria-label="Retirer">×</a>
     </span>

   Peut être utilisé dans .page-toolbar__group ou tout conteneur flex.
   =========================================================================== */

.filter-chip {
  display:       inline-flex;
  align-items:   center;
  gap:           var(--space-2);
  padding:       4px var(--space-3);
  border:        var(--border-width-md) solid var(--color-primary);
  border-radius: var(--radius-full);
  font-size:     var(--text-sm);
  font-weight:   var(--font-medium);
  color:         var(--color-primary-dark);
  background:    var(--color-primary-light);
  white-space:   nowrap;
}

.filter-chip__close {
  display:         inline-flex;
  align-items:     center;
  justify-content: center;
  width:           16px;
  height:          16px;
  border-radius:   var(--radius-full);
  border:          none;
  background:      transparent;
  font-size:       var(--text-xs);
  line-height:     1;
  color:           var(--color-primary);
  text-decoration: none;
  cursor:          pointer;
  transition:      background var(--transition-fast), color var(--transition-fast);
}

.filter-chip__close:hover {
  background: var(--color-primary);
  color:      var(--color-text-inverse);
}


/* ===========================================================================
   BACK-LINK — pill retour compact (pages détail/formulaire)
   Remplace : .mf-back-link (flows), .fd-back-link (files),
              .pj-back-link (projets), .sp-back-link (specs/tenancy)

   Usage :
     <a href="…" class="back-link">← Retour à la liste</a>
   =========================================================================== */

.back-link {
  display:         inline-flex;
  align-items:     center;
  align-self:      flex-start;
  gap:             3px;
  font-size:       var(--text-xs);
  color:           var(--color-text-muted);
  text-decoration: none;
  padding:         3px 10px;
  border:          var(--border-width) solid var(--color-border-strong);
  border-radius:   var(--radius-full);
  transition:      border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
  margin-bottom:   var(--space-4);
}

/* Dans une page en flex (.page-layout), l'écart de SECTION s'ajoute à la marge
   ci-dessus : 32 + 16 = 48px sous le lien, contre 16 partout ailleurs. Le lien
   de retour n'est pas une section — il annonce la page. On annule le double
   comptage pour retrouver l'écart commun à toute l'application. */
.page-layout > .back-link { margin-bottom: calc(var(--space-4) - var(--space-8)); }

.back-link:hover {
  border-color: var(--color-primary);
  color:        var(--color-primary);
  background:   var(--color-primary-alpha-08);
  text-decoration: none;
}


/* ===========================================================================
   JSON-VIEWER — surface d'affichage d'un JSON en lecture
   Promue depuis pages/pipeline_specs.css : la fiche de spec n'est plus la
   seule à montrer un JSON, la vitrine des modèles le fait aussi. La
   SURFACE est commune ; les enrichissements de Specs (gouttière de numéros,
   coloration, retour à la ligne) restent chez elle, ils dépendent de son
   script. Une page qui n'affiche que le contenu pose `.json-viewer` +
   `.json-viewer__pre`, sans gouttière.
   =========================================================================== */
/* Fond : `--editor-bg`, celui de l'ÉDITEUR. La visionneuse empruntait
   `--color-text-main` — la couleur du TEXTE de l'application détournée en
   fond — si bien que consulter et éditer le même JSON ne donnaient pas le
   même noir. Une surface, une source. */
.json-viewer {
  display: flex;
  max-height: 600px;
  overflow: auto;
  background: var(--editor-bg);
}

.json-viewer__gutter {
  flex-shrink: 0;
  margin: 0;
  padding: var(--space-5) var(--space-3);
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--editor-gutter-text);
  background: var(--editor-bg);
  text-align: right;
  user-select: none;
  border-right: 1px solid var(--editor-gutter-border);
  overflow: hidden;
}

.json-viewer__pre {
  flex: 1;
  margin: 0;
  padding: var(--space-5) var(--space-6);
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.6;
  background: var(--editor-bg);
  color: var(--color-text-on-dark);
  white-space: pre;
  overflow-x: auto;
  border-radius: 0;
  tab-size: 2;
}

.json-viewer--wrap .json-viewer__pre {
  white-space: pre-wrap;
  word-break: break-all;
}

/* Coloration syntaxique — émise par `_jsonHL` (js/shared/json-viewer.js).
   Elle vit AVEC la surface : une visionneuse sans ses couleurs, ou des
   couleurs sans leur fond, ne veulent rien dire séparément. Les tons
   viennent de `--color-syntax-dark-*` — la palette du FOND SOMBRE, à ne
   pas confondre avec `--color-syntax-*`, celle des surfaces claires
   (aperçu JSON du monitoring). */
.jt-key     { color: var(--color-syntax-dark-key); }
.jt-str     { color: var(--color-syntax-dark-string); }
.jt-num     { color: var(--color-syntax-dark-number); }
.jt-bool    { color: var(--color-syntax-dark-bool); }
.jt-null    { color: var(--color-syntax-dark-null); }
.jt-brace   { color: var(--color-syntax-dark-punct); }
.jt-colon   { color: var(--color-syntax-dark-punct); }
.jt-comment { color: var(--color-syntax-dark-comment); font-style: italic; }

/* ===========================================================================
   LINK-NAME — lien principal dans la première colonne des tableaux de liste
   Remplace : .link-name dans pipeline_specs.css (migré en canonique)

   Usage : <a href="…" class="link-name">NOM_ENTITE</a>
   =========================================================================== */

.link-name {
  font-weight:     var(--font-semi);
  font-family:     var(--font-sans);
  color:           var(--color-text-main);
  text-decoration: none;
  transition:      color var(--transition-fast);
}
.link-name:hover {
  color:           var(--color-primary);
  text-decoration: none;
}

/* Variante INERTE — même typographie, aucune promesse de clic.
   Le nom d'une ligne reste le nom d'une ligne quand l'utilisateur n'a pas le
   droit d'ouvrir la fiche : on garde le poids visuel, on retire le survol et
   le curseur. Un <span class="link-name"> nu portait encore le survol : il
   avait les habits du lien sans en être un, ce qui est exactement le genre
   d'écran qui ment. */
.link-name--inerte,
.link-name--inerte:hover {
  color:  var(--color-text-main);
  cursor: default;
}


/* ===========================================================================
   BADGE-KIND — extensions de couleur
   Base (.badge-kind) déjà dans components.css (sftp, ftp, api, as2, local).
   On ajoute : types de document, engines, protocoles, transco.

   Remplace : .flux-tag (flows), .proto-tag (flows),
              .format-tag (specs), .tx-badge--json (transco)

   Usage :
     <span class="badge-kind badge-kind--order">ORDER</span>
     <span class="badge-kind badge-kind--edifact">EDIFACT</span>
     <span class="badge-kind badge-kind--json">JSON</span>
     <span class="badge-kind">manual</span>   ← default neutre
   =========================================================================== */

/* ── Types de document (flux EDI) ── */
.badge-kind--order   { background: #eef2ff; border-color: #c7d2fe; color: #4f46e5; }
.badge-kind--desadv  { background: #f0f9ff; border-color: #bae6fd; color: #0284c7; }
.badge-kind--invoic  { background: #fff7ed; border-color: #fed7aa; color: #c2410c; }
.badge-kind--recadv  { background: #ecfdf5; border-color: #a7f3d0; color: #059669; }

/* ── Engines (pipeline format IN → OUT) ──
   Les HUIT du modèle. IDoc, X12 et Tradacoms manquaient : ils tombaient sur
   le gris neutre, deux moteurs différents portant alors le même badge. */
.badge-kind--csv       { background: var(--engine-csv-bg);       border-color: var(--engine-csv-border);       color: var(--engine-csv-text); }
.badge-kind--fixed     { background: var(--engine-fixed-bg);     border-color: var(--engine-fixed-border);     color: var(--engine-fixed-text); }
.badge-kind--edifact   { background: var(--engine-edifact-bg);   border-color: var(--engine-edifact-border);   color: var(--engine-edifact-text); }
.badge-kind--xml       { background: var(--engine-xml-bg);       border-color: var(--engine-xml-border);       color: var(--engine-xml-text); }
.badge-kind--idoc      { background: var(--engine-idoc-bg);      border-color: var(--engine-idoc-border);      color: var(--engine-idoc-text); }
.badge-kind--x12       { background: var(--engine-x12-bg);       border-color: var(--engine-x12-border);       color: var(--engine-x12-text); }
.badge-kind--tradacoms { background: var(--engine-tradacoms-bg); border-color: var(--engine-tradacoms-border); color: var(--engine-tradacoms-text); }

/* ── Transco value type ── */
.badge-kind--json    { background: var(--color-primary-light); border-color: var(--color-primary); color: var(--color-primary-dark); }

/* ── Flèche entre engines (EDIFACT → CSV) ── */
.engine-arrow {
  display:     inline-block;
  margin:      0 var(--space-1);
  color:       var(--color-text-muted);
  font-size:   var(--text-sm);
  font-weight: var(--font-medium);
}


/* ===========================================================================
   CELL-META — cellule tableau avec ligne principale + sous-ligne muted
   Remplace : styles inline dans projects list (colonne MAJ : date + user)

   Usage :
     <td>
       <div class="cell-meta">
         <span class="cell-meta__main">28/03/2026 10:53</span>
         <span class="cell-meta__sub">admin</span>
       </div>
     </td>
   =========================================================================== */

.cell-meta {
  display:        flex;
  flex-direction: column;
  line-height:    1.3;
}

/* Cellule qui empile N valeurs de MÊME rang — les partenaires d'un projet, par
   exemple. Distincte de .cell-meta, qui hiérarchise une valeur principale et
   sa sous-ligne : ici aucune ne prime, elles se lisent comme une liste. */
.cell-stack {
  display:        flex;
  flex-direction: column;
  gap:            2px;
  line-height:    1.3;
}

.cell-meta__main {
  font-size: var(--text-2xs);
  color:     var(--color-text-muted);
}

.cell-meta__sub {
  font-size: var(--text-2xs);
  color:     var(--color-text-light);
}


/* ===========================================================================
   SYS-PREFIX — préfixe d'un code « système interne » mis en valeur (F-24)
   Pastille teintée affichée là où un partenaire commercial montrerait son
   GLN/identité EDI : sous-lignes émetteur/récepteur du monitoring, ligne
   « Code » des fiches partenaire et détail du run. Le préfixe stylé fait
   office de marqueur (pas de badge séparé). Découpe préfixe/numéro via
   Partner.code_prefix_display / code_suffix_display.
   Usage : <span class="sys-prefix">SI</span>00001
   =========================================================================== */
.sys-prefix {
  display:        inline-block;
  font-family:    var(--font-sans);
  font-weight:    var(--font-bold);
  font-size:      var(--text-2xs);
  letter-spacing: .03em;
  color:          var(--color-primary-dark);
  background:     var(--color-primary-subtle);
  border:         var(--border-width) solid var(--color-primary-light);
  border-radius:  var(--radius-sm);
  padding:        0 5px;
  margin-right:   4px;
}


/* ===========================================================================
   COMBOBOX — select filtrable avec dropdown
   Remplace : .mf-combo (flows), .ff-combo (files),
              .sp-combo (specs), .tx-combo (transco)

   2 modes d'init par combobox.js :
   A) Progressive : <select data-combobox> → JS construit le wrapper
   B) Pre-built   : <div class="combobox"> avec input + list déjà en HTML

   Structure générée / attendue :
     <div class="combobox [is-open]">
       <input type="hidden" name="…" value="…">
       <input type="text" class="combobox__input" …>
       <button class="combobox__toggle">▼</button>
       <ul class="combobox__list">
         <li class="combobox__item" data-value="…">Label</li>
       </ul>
     </div>
   =========================================================================== */


/* ── Wrapper ── */
.combobox {
  position: relative;
  display:  flex;
  align-items: center;
}


/* ── Input texte (recherche / affichage) ── */
.combobox__input {
  flex:          1;
  min-width:     0;
  height:        var(--input-height-sm);
  padding:       0 var(--space-2) 0 var(--space-2);
  border:        var(--border-width) solid var(--color-border-strong);
  border-right:  none;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  font-size:     var(--text-xs);
  font-family:   var(--font-sans);
  color:         var(--color-text-main);
  background:    var(--color-surface);
  outline:       none;
  transition:    border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.combobox__input::placeholder {
  color:       var(--color-text-light);
  font-weight: var(--font-normal);
}

.combobox__input:focus {
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-10);
}

/* Quand l'input est focus, le toggle partage le même état visuel */
.combobox__input:focus ~ .combobox__toggle {
  border-color: var(--color-primary);
  box-shadow:   0 0 0 2px var(--color-primary-alpha-10);
}

.combobox__input.has-value {
  color:       var(--color-primary-dark);
  font-weight: var(--font-semi);
}


/* ── Bouton toggle (chevron) — séparateur bordure gauche ── */
.combobox__toggle {
  display:         flex;
  align-items:     center;
  justify-content: center;
  flex-shrink:     0;
  width:           28px;
  height:          var(--input-height-sm);
  padding:         0;
  border:          var(--border-width) solid var(--color-border-strong);
  border-radius:   0 var(--radius-md) var(--radius-md) 0;
  background:      var(--color-surface);
  color:           var(--color-text-light);
  cursor:          pointer;
  transition:      color var(--transition-fast),
                   border-color var(--transition-fast),
                   background var(--transition-fast),
                   box-shadow var(--transition-fast);
}

.combobox__toggle:hover {
  color:      var(--color-primary);
  background: var(--color-primary-alpha-06);
}

.combobox.is-open .combobox__toggle {
  border-color: var(--color-primary);
  color:        var(--color-primary);
}

.combobox.is-open .combobox__toggle svg {
  transform: rotate(180deg);
}


/* ── Dropdown list — affiche 10 éléments, scroll au-delà ── */
.combobox__list {
  display:       none;
  position:      absolute;
  top:           calc(100% + 2px);
  left:          0;
  right:         0;
  z-index:       var(--z-dropdown, 100);
  margin:        0;
  padding:       4px 0;
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  box-shadow:    var(--shadow-md);
  max-height:    288px;   /* 10 items × 28px + 8px padding */
  overflow-y:    auto;
  list-style:    none;
  overscroll-behavior: contain;
}

.combobox.is-open .combobox__list {
  display: block;
}

/* Scrollbar fine */
.combobox__list::-webkit-scrollbar { width: 4px; }
.combobox__list::-webkit-scrollbar-thumb {
  background:    var(--color-border-strong);
  border-radius: 2px;
}


/* ── Item ── */
.combobox__item {
  padding:     5px 10px;
  font-size:   var(--text-xs);
  font-family: var(--font-mono);
  color:       var(--color-text-main);
  cursor:      pointer;
  white-space: nowrap;
  transition:  background .1s;
}

.combobox__item:hover,
.combobox__item.is-focused {
  background: var(--color-primary-alpha-08);
  color:      var(--color-primary-dark);
}

.combobox__item.is-selected {
  background:  var(--color-primary-light);
  color:       var(--color-primary-dark);
  font-weight: var(--font-semi);
}

.combobox__item.is-hidden {
  display: none;
}

/* ── Item interne (Tenant) — distinction visuelle tenant vs partenaire ── */
.combobox__item--internal {
  color:       var(--color-primary-dark);
  font-weight: var(--font-medium);
  border-bottom: var(--border-width) dashed var(--color-border);
}
.combobox__item--internal:last-of-type {
  border-bottom-style: solid;
}


/* ── Message "aucun résultat" ── */
.combobox__empty {
  padding:    8px 10px;
  font-size:  var(--text-xs);
  color:      var(--color-text-muted);
  font-style: italic;
}


/* ===========================================================================
   TOPBAR LINKS — boutons/liens dans la topbar (page_actions)
   Remplace : .sp-topbar-link (specs + transco), .topbar-upload-btn (4 fichiers)

   Usage :
     <a href="…" class="topbar-link">Modèle CSV</a>           ← lien ghost secondaire (sp-topbar-link = alias compat)
     <a href="…" class="btn btn-sm btn-topbar">+ Nouveau</a>  ← CTA primaire (outline→fill)
   =========================================================================== */

/* Lien ghost dans la topbar (téléchargement de modèle, liens annexes…) */
.topbar-link,
.sp-topbar-link {
  display:         inline-flex;
  align-items:     center;
  gap:             4px;
  font-size:       var(--text-xs);
  font-weight:     var(--font-medium);
  color:           var(--color-text-muted);
  text-decoration: none;
  padding:         var(--space-1) var(--space-3);
  border:          var(--border-width) solid transparent;
  border-radius:   var(--radius-md);
  transition:      border-color var(--transition-fast), color var(--transition-fast), background var(--transition-fast);
}

.topbar-link:hover,
.sp-topbar-link:hover {
  border-color: var(--color-primary);
  color:        var(--color-primary);
  background:   var(--color-primary-alpha-08);
}

/* CTA primaire dans la topbar — outline teal par défaut, filled au hover.
   Usage : class="btn btn-sm btn-topbar"
   Ex : + Nouveau X, Envoyer un fichier
   Ancienne classe : topbar-upload-btn (alias conservé pour compat) */
.btn-topbar,
.topbar-upload-btn {
  border:        var(--border-width-md) solid var(--color-primary);
  color:         var(--color-primary);
  font-weight:   var(--font-semi);
  transition:    background var(--transition-fast), color var(--transition-fast);
}

.btn-topbar:hover,
.topbar-upload-btn:hover {
  background:      var(--color-primary);
  color:           var(--color-text-inverse);
  text-decoration: none;
}

/* ===========================================================================
   COMBOBOX — extensions flows (group filtering + cross-exclusion)
   Ajout à la suite du bloc .combobox dans components.css
   =========================================================================== */

/* Masquage par groupe ou exclusion cross-combo (flows sender/receiver) */
.combobox__item--group-hidden,
.combobox__item--excluded {
  display: none;
}

/* ===========================================================================
   DATA TABLE — tableau de données canonique
   Source de vérité : flows (copie stricte)

   Sélecteurs :
     .data-table         : classe canonique (flows, transco)
     .file-table         : alias (files, specs, projets, partenaires, tenancy)
     .table-card         : wrapper card (transco, files, specs, projets, partenaires, tenancy)
     .files-table-wrap   : alias wrapper
   =========================================================================== */


/* ---------------------------------------------------------------------------
   Wrapper card
--------------------------------------------------------------------------- */
.table-card,
.files-table-wrap {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow:    var(--shadow-xs);
  overflow:      hidden;
}

/* En-tête figé pleine page — COMPOSANT CANONIQUE (.table-card--flush).
   Ajouter la classe sur le <div class="table-card"> : le <thead> (déjà sticky
   via .data-table th) reste collé sous la topbar pendant que la PAGE défile —
   pas de scroll interne, les lignes gardent leur densité pleine page.
   .table-card a `overflow:hidden` (coins arrondis) qui PIÈGE position:sticky →
   on le neutralise, et on cale le thead sous la topbar fixe. ATTENTION : si la
   card est imbriquée dans une autre carte overflow:hidden (ex. monitoring
   .flows-card), neutraliser AUSSI le parent, sinon le sticky reste piégé.
   À adopter par tous les tableaux (partenaires, specs, projets, tenancy…). */
.table-card--flush { overflow: visible; }
.table-card--flush .data-table thead th { top: var(--topbar-height); }

/* FIX RESP-R2 : sous tablette/mobile, les tables larges (nombreuses colonnes)
   défilent horizontalement DANS leur carte au lieu d'être clippées par
   l'overflow:hidden de base ou de pousser la page (cas --flush). La hauteur de
   la carte suit le contenu → aucun scroll vertical interne. Le sticky page du
   pattern --flush est relâché sous ce seuil (peu utile sur petit écran). */
@media (max-width: 1024px) {
  .table-card,
  .table-card--flush,
  .files-table-wrap { overflow: auto; }
}


/* ---------------------------------------------------------------------------
   Table base
--------------------------------------------------------------------------- */
.data-table,
.file-table {
  width:           100%;
  border-collapse: collapse;
  font-size:       var(--text-sm);
}


/* ---------------------------------------------------------------------------
   Header
--------------------------------------------------------------------------- */
.data-table th,
.file-table th {
  padding:        10px var(--space-4);
  font-size:      var(--text-2xs);
  font-weight:    var(--font-bold);
  color:          var(--color-text-main);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-bottom:  var(--border-width-md) solid var(--color-border);
  white-space:    nowrap;
  background:     var(--color-bg);
  text-align:     center;
  vertical-align: middle;
  position:       sticky;
  top:            0;
  z-index:        var(--z-raised);
}


/* ---------------------------------------------------------------------------
   Cellules
--------------------------------------------------------------------------- */
.data-table td,
.file-table td {
  padding:              11px var(--space-4);
  border-bottom:        var(--border-width) solid var(--color-border);
  font-family:          var(--font-mono);
  font-size:            var(--text-xs);
  font-variant-numeric: tabular-nums;
  color:                var(--color-text-main);
  vertical-align:       middle;
  text-align:           center;
  white-space:          nowrap;
  transition:           background var(--transition-fast);
}

.data-table tbody tr:last-child td,
.file-table tbody tr:last-child td {
  border-bottom: none;
}


/* ---------------------------------------------------------------------------
   Hover ligne
--------------------------------------------------------------------------- */
.data-table tbody tr:hover td,
.file-table tbody tr:hover td {
  background: var(--color-primary-alpha-04);
}


/* ---------------------------------------------------------------------------
   Liens dans les cellules
--------------------------------------------------------------------------- */
.data-table tbody td a,
.file-table tbody td a {
  color:           var(--color-text-main);
  font-weight:     inherit;
  text-decoration: none;
}

.data-table tbody td a:hover,
.file-table tbody td a:hover {
  color:           var(--color-primary);
  text-decoration: none;
}


/* ---------------------------------------------------------------------------
   Colonnes utilitaires
--------------------------------------------------------------------------- */
.data-table .col-center,
.file-table .col-center { text-align: center; }

.data-table .col-primary,
.data-table .col-tertiary,
.file-table .col-primary,
.file-table .col-tertiary {
  color:       var(--color-text-main);
  font-weight: inherit;
}

.data-table .col-check,
.file-table .col-check {
  width:      1%;
  padding:    0 var(--space-2);
  text-align: center;
}

.data-table .col-check input[type="checkbox"],
.file-table .col-check input[type="checkbox"] {
  cursor:       pointer;
  accent-color: var(--color-primary);
}

.data-table .col-mono,
.file-table .col-mono {
  font-family: var(--font-mono);
  font-size:   var(--text-xs);
}


/* Colonne "partie prenante" (Émetteur, Récepteur) — noms longs tronqués
   avec ellipsis + tooltip natif (title="..."). Évite le scroll horizontal
   quand un partner a un nom raisonnablement long (ex: "FRIGEX Logistique
   Froid SAS"). La largeur max est adaptée au breakpoint : étroite sur
   tablette/laptop, plus large sur écran XL. */
.data-table .col-party,
.file-table .col-party {
  max-width:     200px;
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
}

@media (min-width: 1600px) {
  .data-table .col-party,
  .file-table .col-party {
    max-width: 260px;
  }
}

@media (max-width: 1024px) {
  .data-table .col-party,
  .file-table .col-party {
    max-width: 140px;
  }
}


/* ---------------------------------------------------------------------------
   Colonne actions — pattern canonique (2026-04-01)

   Pattern A (défaut) : "Ouvrir" (detail) + "Éditer" (edit, conditionnel)
     → Specs, Projects, Transco, Tenancy orgs, Tenancy groups
   Pattern B (lecture) : "Ouvrir" (detail) + "Voir" (preview)
     → Files IN, Files OUT (+ bouton ↓ download)
   Pattern C (single) : "Ouvrir" seul
     → Partners groups

   Règles :
     - Toujours btn-xs + btn-ghost (le sélecteur ci-dessous override la taille)
     - "Ouvrir" = 1er bouton, toujours présent → page detail
     - "Éditer" = 2e bouton conditionnel → page edit
     - "Voir" = variante lecture → preview ou detail read-only
     - Boutons spécifiques métier (Basculer, ↓) en extra
--------------------------------------------------------------------------- */
.data-table .col-actions,
.file-table .col-actions {
  text-align:  center;
  white-space: nowrap;
  width:       1%;
}

/* Cellule hôte d'un état vide : le <td colspan> qui accueille .empty-state ne
   doit apporter ni padding ni filet, sinon l'état vide se retrouve encadré
   comme une donnée. Le motif était écrit en `style="padding:0;border:none"`
   dans les gabarits — 16 occurrences dans 10 modules, donc un composant, pas
   un cas particulier. */
/* Cellule d'ÉTAT VIDE d'un tableau — « Aucune règle », « Aucune alerte »…
   Employée par six tableaux (Fichiers, Projets) et définie NULLE PART :
   sa seule règle vivait sous `.pj-card .pj-inner-table`, un sélecteur dont
   plus aucun gabarit ne porte la classe. Six états vides sans mise en forme,
   qui passaient pour du texte brut mal centré.
   Sélecteur en (0,2,1) plutôt qu'un `!important` : il suffit à battre le
   `padding` de `.data-table td`.

   ⚠ La classe sert AUSSI de marqueur sémantique sur un <span> (centre de
   notifications : une route qui ne filtre rien), où elle ne doit rien peindre —
   d'où le sélecteur limité aux cellules. Une garde s'appuie sur ce marqueur ;
   le renommer casse un test, et à raison. */
.data-table td.col-empty,
.file-table td.col-empty {
  text-align: center;
  color:      var(--color-text-muted);
  padding:    var(--space-8) var(--space-4);
}

.data-table td.cell-flush,
.file-table td.cell-flush {
  padding: 0;
  border:  none;
}

/* Wrapper scrollable horizontalement — tables larges (schéma déclarable à N
   colonnes) : scroll interne plutôt que débordement de la carte. */
.data-table-scroll {
  overflow-x: auto;
  border-top: var(--border-width) solid var(--color-border);
}

.data-table .col-actions .btn,
.file-table .col-actions .btn {
  padding:     3px var(--space-2);
  font-size:   var(--text-2xs);
  font-family: var(--font-sans);
}

/* Dans une colonne d'actions, un ghost reprend un filet : sans lui, deux
   libellés nus flotteraient dans la cellule sans se lire comme des boutons.
   Ce filet vaut pour les DEUX ghosts. Il ne couvrait que `.btn-ghost` : à côté
   d'« Éditer » au filet clair, « Supprimer » gardait le sien, plus sombre —
   deux poids pour deux boutons de même rang. */
.data-table .col-actions .btn.btn-ghost,
.data-table .col-actions .btn.btn-danger-ghost,
.file-table .col-actions .btn.btn-ghost,
.file-table .col-actions .btn.btn-danger-ghost {
  color:        var(--color-text-muted);
  border-color: var(--color-border);
}

.data-table .col-actions .btn.btn-ghost:hover,
.file-table .col-actions .btn.btn-ghost:hover {
  color:        var(--color-primary);
  border-color: var(--color-primary);
  background:   var(--color-primary-alpha-08);
}

/* Seul le SURVOL sépare les deux : le voisin va au teal, celui-ci au rouge.
   Même geste, deux annonces — c'est là que la nature de l'action se dit. */
.data-table .col-actions .btn.btn-danger-ghost:hover,
.file-table .col-actions .btn.btn-danger-ghost:hover {
  color:        var(--color-danger-text);
  border-color: var(--color-danger);
  background:   var(--color-danger-bg);
}

.data-table .col-actions a,
.file-table .col-actions a,
.data-table .col-actions a:hover,
.file-table .col-actions a:hover {
  text-decoration: none;
}

/* ── Tri colonnes (lien cliquable dans <th>) ── */
.th-sort {
  display:         inline-flex;
  align-items:     center;
  gap:             4px;
  color:           inherit;
  text-decoration: none;
  white-space:     nowrap;
  cursor:          pointer;
}
.th-sort:hover  { color: var(--color-text-main); }
.th-sort .caret { font-size: var(--text-2xs); opacity: .6; }


/* ---------------------------------------------------------------------------
   Badges dans les cellules
--------------------------------------------------------------------------- */
.data-table td .badge,
.file-table td .badge {
  font-weight: var(--font-normal);
  font-size:   var(--text-xs);
  padding:     2px 8px;
}


/* ---------------------------------------------------------------------------
   États de lignes
--------------------------------------------------------------------------- */

/* Ligne sélectionnée (bulk select) */
.data-table tr.is-selected td,
.file-table tr.is-selected td {
  background: var(--color-primary-alpha-08);
}

/* Ligne inactive / draft */
.data-table tr.row-inactive td,
.file-table tr.row-inactive td,
.data-table tr.row--inactive td,
.file-table tr.row--inactive td {
  opacity: 0.55;
}

.data-table tr.row-inactive:hover td,
.file-table tr.row-inactive:hover td,
.data-table tr.row--inactive:hover td,
.file-table tr.row--inactive:hover td {
  opacity: 0.85;
}

/* ---------------------------------------------------------------------------
   Page layout — wrapper flex column pour toutes les pages list
--------------------------------------------------------------------------- */
.page-layout {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-8);
  /* FIX RESP-WIDE (2026) : les listes remplissent la largeur disponible au lieu
     de se figer à 1280px collées à gauche, ce qui laissait un grand vide à droite
     sur grand écran.

     FIX RESP-WIDE-2 : le défaut d'alors n'était pas le plafond, c'était l'absence
     de CENTRAGE — on avait retiré le mauvais des deux. Preuve : à 1440px la
     largeur utile ne vaut que 1160px, donc le plafond 1280 n'était JAMAIS atteint
     sous ~1560px de viewport ; le « vide à droite » ne pouvait venir que des
     1920/2560. Sans plafond, une liste s'étire à ~2280px sur un 2560 : les
     colonnes se diluent, l'œil ne suit plus la ligne, et toolbar/pied de tableau
     (space-between) écartèlent leurs extrémités sur toute la largeur.
     Le plafond est donc rétabli — HAUT et centré — mais porté par le
     modificateur `.page-layout--wide` ci-dessous, opt-in gabarit par gabarit :
     la bonne largeur dépend du nombre de colonnes, elle ne se décrète pas en
     bloc pour les 20 listes. `.page-layout` nu reste fluide. */
  width:          100%;
}

/* Palier 0 « Liste large » — OPT-IN, un gabarit à la fois.
   Plafond HAUT + CENTRAGE : les deux, pas l'un ou l'autre. Le plafond seul
   reproduit le défaut d'origine (contenu collé à gauche, vide à droite) ; le
   centrage seul ne borne rien. Calibré pour que 1920 reste inchangé (largeur
   utile 1640) et que seuls les 2560+ soient bornés.
   Pourquoi opt-in : la largeur qui convient dépend du NOMBRE DE COLONNES de la
   liste. Une table à 10 colonnes a besoin de 1600 ; une liste à 4 colonnes s'y
   perd et veut plutôt .page-layout--form. On l'applique donc module par module,
   après contrôle à l'œil, jamais en bloc. */
.page-layout--wide {
  max-width:     var(--page-width-wide);
  margin-inline: auto;
}

/* ===========================================================================
   POPOVER — petit panneau ancré au-dessus d'un déclencheur
   FIX FANOUT-LIST-VIS (2026-05-16) — Composant réutilisable. Premier usage :
   bouton "+N" dans la liste des fan-out de redirection / forward pour révéler
   la liste complète des destinataires (cf. pipeline_specs.css, popover.js).

   Convention alignée sur .combobox__list :
     - z-index : var(--z-dropdown, 100)
     - shadow  : var(--shadow-md)
     - hover   : var(--color-primary-alpha-08) + var(--color-primary-dark)

   Structure HTML (composant générique, cf. templates/components/_popover.html) :
     <div class="popover" hidden>
       <div class="popover__arrow"></div>
       <div class="popover__container">
         <div class="popover__header">…</div>
         <div class="popover__body">
           <a class="popover__item">…</a>
         </div>
         <div class="popover__footer">…</div>
       </div>
     </div>
   =========================================================================== */

.popover {
  position: absolute;
  z-index:  var(--z-dropdown, 100);
  /* top/left injectés par popover.js au moment de l'ouverture */
}

.popover[hidden] {
  display: none;
}


/* ── Flèche pointant vers le trigger ─────────────────────────────────── */
.popover__arrow {
  width:      12px;
  height:     6px;
  margin-left: 14px;
  background: var(--color-surface);
  clip-path:  polygon(50% 0, 100% 100%, 0 100%);
  position:   relative;
  z-index:    2;
}


/* ── Container ───────────────────────────────────────────────────────── */
.popover__container {
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  box-shadow:    var(--shadow-md);
  min-width:     220px;
  max-width:     320px;
  overflow:      hidden;
}


/* ── Header ──────────────────────────────────────────────────────────── */
.popover__header {
  padding:         8px 14px;
  display:         flex;
  justify-content: space-between;
  align-items:     center;
  border-bottom:   var(--border-width) solid var(--color-border);
}

.popover__title {
  font-size:      var(--text-2xs);
  color:          var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.popover__count {
  font-family: var(--font-mono);
  font-size:   var(--text-xs);
  color:       var(--color-text-muted);
}


/* ── Body ────────────────────────────────────────────────────────────── */
.popover__body {
  padding:    4px 0;
  max-height: 280px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Scrollbar fine — aligné sur .combobox__list */
.popover__body::-webkit-scrollbar { width: 4px; }
.popover__body::-webkit-scrollbar-thumb {
  background:    var(--color-border-strong);
  border-radius: 2px;
}


/* ── Item ────────────────────────────────────────────────────────────── */
.popover__item {
  display:         flex;
  align-items:     center;
  gap:             10px;
  padding:         7px 14px;
  text-decoration: none;
  color:           var(--color-text-main);
  border-left:     3px solid transparent;
  transition:      background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.popover__item:hover,
.popover__item:focus-visible,
.popover__item.is-focused {
  background:        var(--color-primary-alpha-08);
  color:             var(--color-primary-dark);
  border-left-color: var(--color-primary);
  outline:           none;
}

.popover__item--static {
  cursor: default;
}

.popover__item--static:hover,
.popover__item--static:focus-visible {
  background:        transparent;
  border-left-color: transparent;
  color:             var(--color-text-main);
}

.popover__item-text {
  display:        flex;
  flex-direction: column;
  flex:           1;
  min-width:      0;
}

.popover__item-name {
  font-family:    var(--font-mono);
  font-size:      var(--text-xs);
  font-weight:    var(--font-medium);
  white-space:    nowrap;
  overflow:       hidden;
  text-overflow:  ellipsis;
}

.popover__item-sub {
  font-size:     var(--text-2xs);
  color:         var(--color-text-muted);
  white-space:   nowrap;
  overflow:      hidden;
  text-overflow: ellipsis;
  margin-top:    1px;
}

.popover__item-icon {
  color:       var(--color-text-light);
  flex-shrink: 0;
}


/* ── Footer ──────────────────────────────────────────────────────────── */
.popover__footer {
  padding:         6px 14px;
  display:         flex;
  align-items:     center;
  justify-content: flex-end;
  gap:             6px;
  border-top:      var(--border-width) solid var(--color-border);
  background:      var(--color-bg);
}

.popover__kbd {
  font-family:   var(--font-mono);
  font-size:     var(--text-2xs);
  color:         var(--color-text-muted);
  background:    var(--color-surface);
  border:        var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding:       1px 5px;
  line-height:   1;
}

.popover__hint {
  font-size: var(--text-2xs);
  color:     var(--color-text-muted);
}


/* ── Trigger générique ───────────────────────────────────────────────── */
.popover-trigger {
  cursor: pointer;
}


/* ===========================================================================
   TRANSPORT PLACEHOLDER — empty-state « configurez depuis la fiche »
   Canonique, partagé : partenaires (partners/form.html)
   Promu depuis l'ex-.pf-transport-soon (partners.css),
   sans changement de rendu.
   =========================================================================== */
.transport-placeholder {
  display:         flex;
  flex-direction:  column;
  align-items:     center;
  justify-content: center;
  padding:         var(--space-8);
  gap:             var(--space-3);
  text-align:      center;
  color:           var(--color-text-light);
}
.transport-placeholder__icon  { color: var(--color-text-light); }
.transport-placeholder__title { font-size: var(--text-sm); font-weight: var(--font-medium); color: var(--color-text-muted); }
.transport-placeholder__desc  { font-size: var(--text-xs); color: var(--color-text-light); max-width: 320px; }

/* ===========================================================================
   COPIER — bouton « copier dans le presse-papiers »
   Canonique, partagé : partenaires (liste), fichiers (listes + panneaux).
   Promu depuis l'ex-.pt-copy-btn (partners.css), sans changement de rendu ;
   s'y ajoute la confirmation VISIBLE (bulle « Copié »), l'infobulle seule
   n'étant pas relue par le navigateur après le clic.
   Comportement : js/shared/copy-button.js (contrat `data-copy`).
   =========================================================================== */
.copy-cell {
  display:         inline-flex;
  align-items:     center;
  gap:             var(--space-2);
  max-width:       100%;
  min-width:       0;
}

.copy-btn {
  position:        relative;
  display:         inline-flex;
  align-items:     center;
  gap:             4px;
  flex:            0 0 auto;
  padding:         2px var(--space-2);
  font-size:       var(--text-2xs);
  font-family:     var(--font-sans);
  color:           var(--color-text-muted);
  background:      transparent;
  border:          var(--border-width) solid var(--color-border);
  border-radius:   var(--radius-sm);
  cursor:          pointer;
  transition:      color .15s, border-color .15s, background .15s;
}
.copy-btn:hover {
  color:           var(--color-primary);
  border-color:    var(--color-primary);
  background:      var(--color-primary-alpha-06);
}
.copy-btn:focus-visible {
  outline:         none;
  border-color:    var(--color-primary);
  box-shadow:      0 0 0 2px var(--color-primary-alpha-12);
}
.copy-btn.is-copied,
.copy-btn.is-copied:hover {
  color:           var(--color-success);
  border-color:    var(--color-success-border);
  background:      var(--color-success-bg);
}

/* Confirmation : bulle au-dessus du bouton, le temps du retour visuel.
   Le libellé est posé par le JS (traduit), le CSS ne fait que l'afficher. */
.copy-btn.is-copied::after {
  content:         attr(data-copied-label);
  position:        absolute;
  bottom:          calc(100% + 4px);
  left:            50%;
  transform:       translateX(-50%);
  z-index:         var(--z-tooltip, 60);
  padding:         2px var(--space-2);
  font-size:       var(--text-2xs);
  font-family:     var(--font-sans);
  line-height:     1.4;
  white-space:     nowrap;
  color:           var(--color-success);
  background:      var(--color-success-bg);
  border:          var(--border-width) solid var(--color-success-border);
  border-radius:   var(--radius-sm);
  pointer-events:  none;
}

/* ── Variante inline : icône seule, discrète.
   Effacée au repos, elle apparaît au survol de la LIGNE — une table de
   fichiers en afficherait sinon une par ligne, ce qui ferait du bruit.
   Toujours visible au focus clavier et pendant la confirmation, sinon
   l'action serait inatteignable sans souris. ── */
.copy-btn--inline {
  padding:         2px;
  border:          none;
  background:      transparent;
  opacity:         0;
  transition:      opacity .15s, color .15s, background .15s;
}
.copy-btn--inline svg {
  width:           12px;
  height:          12px;
}
.copy-btn--inline:hover {
  background:      var(--color-primary-alpha-06);
  border:          none;
}
.copy-btn--inline:focus-visible {
  opacity:         1;
  box-shadow:      none;
  border:          none;
}
tr:hover .copy-btn--inline,
.copy-btn--inline:focus-visible,
.copy-btn--inline.is-copied {
  opacity:         1;
}

/* Hors table (panneau déplié, fiche) : rien ne « survole la ligne », le
   bouton doit donc rester visible. */
.copy-cell--static .copy-btn--inline { opacity: 1; }

/* Champ tampon du repli sans API Clipboard : présent le temps d'un tick,
   ne doit ni se voir ni déplacer quoi que ce soit. */
.copy-btn__buffer {
  position:        fixed;
  top:             0;
  left:            0;
  opacity:         0;
  pointer-events:  none;
}


/* ===========================================================================
   MDL-TILE — tuile de modèle (vitrine « Modèles & formats »)

   Pourquoi une tuile et pas une ligne de table : `.data-table td` est mono,
   centré et `white-space: nowrap` — c'est un composant fait pour des codes,
   des dates et des statuts. Une DESCRIPTION y devient un mur monospace qui
   déborde en largeur et repousse la colonne d'actions hors de l'écran.
   La tuile assume l'inverse : texte en sans, replié, sur trois lignes au plus.
   =========================================================================== */
.mdl-tile {
  display:        flex;
  flex-direction: column;
  transition:     border-color var(--transition-fast);
}
.mdl-tile:hover { border-color: var(--color-primary-light); }

.mdl-tile .card-body {
  display:        flex;
  flex-direction: column;
  gap:            var(--space-2);
  height:         100%;
}

/* Trois lignes au plus, coupées proprement — la description entière se lit
   sur la fiche du modèle, ce n'est pas ici qu'on l'épuise. */
.mdl-tile__desc {
  margin:             0;
  font-family:        var(--font-sans);
  font-size:          var(--text-xs);
  line-height:        1.5;
  color:              var(--color-text-muted);
  display:            -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow:           hidden;
}

/* Collées en bas : les descriptions n'ont pas la même longueur, les boutons
   doivent malgré tout s'aligner d'une tuile à l'autre. */
.mdl-tile__actions {
  display:   flex;
  gap:       var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
}

@media (max-width: 1100px) {
  .mdl-tile { min-width: 0; }
}
