.vp-header {
  background: linear-gradient(180deg, #1a3a5c 0%, #14304d 100%);
  color: #fff;
  border-bottom: 3px solid #0d2438;
}

/* =================================================================
   LA BARRE DES MODULES — un lien par module, sans sous-menu.

   ELLE COIFFE LA BARRE D ONGLETS, et c est ce qui commande sa taille.
   Elle affichait du 16 px — le plus gros texte de la page apres la
   marque — au-dessus d onglets a .88rem : la hierarchie de lecture
   etait a l envers, le menu criait plus fort que l ecran ou l on se
   trouve. Elle passe donc SOUS la mesure des onglets, et leur emprunte
   tout le reste : la graisse, l arrondi, la duree de transition. Deux
   barres empilees qui ne se ressemblent pas se lisent comme deux
   applications.

   LE SELECTEUR PORTE `.navbar-nav`, ET CE N EST PAS DU BAVARDAGE.
   Bootstrap pose `.navbar-expand-lg .navbar-nav .nav-link` — trois
   classes. La regle d ici en portait deux : elle perdait, et le
   `padding: .35rem .9rem` qui y etait ecrit n a jamais ete applique.
   Mesure faite, les liens retombaient sur les 8 px de Bootstrap, sans
   la moindre erreur ni trace. A specificite egale c est l ordre de
   chargement qui tranche, et style.css vient apres bootstrap.min.css.
   ================================================================= */

.vp-header .navbar-nav {
  gap: .1rem;
  /* SUR UNE SEULE LIGNE, TOUJOURS. Mesure faite, le menu occupait
     968 px sur 1000 et repliait « Mes / modules » et « Ventes — Bières
     et / Alcomalt » sur deux lignes : la barre doublait de hauteur, et
     c est exactement ce que « plus petit » devait corriger.

     Le defilement horizontal est celui de la barre d onglets, et il est
     sans risque ICI pour une raison precise : overflow-x DECOUPE tout
     descendant en absolu, or cette barre n a deliberement aucun
     sous-menu (le catalogue MODULES ne porte de groupes que sur les
     onglets). La barre d onglets, elle, a du sortir ses sous-menus dans
     une seconde liste pour cette raison meme. */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}

/* Une barre de defilement dans un bandeau sombre se lit comme un
   defaut d affichage. Le defilement reste, sa barre disparait. */
.vp-header .navbar-nav::-webkit-scrollbar { display: none; }

.vp-header .navbar-nav .nav-link {
  padding: .3rem .62rem;
  border-radius: .4rem;
  font-size: .8rem;
  font-weight: 500;
  white-space: nowrap;
  color: rgba(255,255,255,.72);
  transition: background .15s, color .15s, box-shadow .15s;
}

/* L ICONE ACCOMPAGNE LE MOT, elle ne le concurrence pas. A egalite de
   ton, sept icones alignees se lisent avant les titres qu elles
   annoncent. Elle reprend son plein contraste des que le lien est
   survole ou courant — la ou l oeil est deja. */
.vp-header .navbar-nav .nav-link > .fas {
  font-size: .95em;
  opacity: .68;
  transition: opacity .15s;
}

.vp-header .navbar-nav .nav-link:hover {
  background: rgba(255,255,255,.1);
  color: #fff;
}

/* LE MODULE COURANT — un aplat plus discret qu avant, borde d un
   filet. Le bloc a 20 % de blanc etait franc mais plat : il se lisait
   comme un bouton pose la, alors qu il dit ou l on est. L abaisser et
   le cerner lui rend un relief, et rapproche la pastille du bandeau
   sur lequel elle vit. */
.vp-header .navbar-nav .nav-link.active {
  background: rgba(255,255,255,.15);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.18);
  color: #fff;
  font-weight: 600;
}

.vp-header .navbar-nav .nav-link:hover > .fas,
.vp-header .navbar-nav .nav-link.active > .fas {
  opacity: 1;
}

/* Meme forme de mise au point que sur les onglets — clair ici, sombre
   la-bas, parce que le fond n est pas le meme. */
.vp-header .navbar-nav .nav-link:focus-visible {
  outline: 2px solid rgba(255,255,255,.8);
  outline-offset: -2px;
}

/* « MES MODULES » N EST PAS UN MODULE : c est le retour a l accueil, et
   un filet l en detache.

   Il se pose en :first-child sans le risque que court celui des
   onglets — celui-la vient du catalogue et se serait retrouve au milieu
   d une famille des qu une page manque a un compte. Ici l element est
   ECRIT dans le layout, avant le dmx-repeat des modules : il est
   premier pour tout le monde, et il le restera. */
.vp-header .navbar-nav > .nav-item:first-child {
  margin-right: .5rem;
  padding-right: .5rem;
  border-right: 1px solid rgba(255,255,255,.16);
}

/* MENU REPLIE — sous 992 px, les liens s empilent.
   Le filet vertical n a plus rien a separer, et une cible tapee au
   doigt demande de la hauteur, pas de la finesse. */
@media (max-width: 991.98px) {
  .vp-header .navbar-nav {
    gap: 0;
    flex-wrap: wrap;
    overflow-x: visible;
  }

  .vp-header .navbar-nav .nav-link {
    padding: .45rem .6rem;
  }

  .vp-header .navbar-nav > .nav-item:first-child {
    margin-right: 0;
    padding-right: 0;
    border-right: 0;
    border-bottom: 1px solid rgba(255,255,255,.16);
    margin-bottom: .2rem;
    padding-bottom: .2rem;
  }
}

/* ===== Champs saisissables ===== */

.form-control,
.form-select,
textarea.form-control {
  background-color: #fdfcf7;
}

.form-control:focus,
.form-select:focus {
  background-color: #fffef9;
}

.form-control:disabled,
.form-control[readonly] {
  background-color: #f1f0ec;
}

/* Une invite n'est pas une valeur. Bootstrap 5.3 peint le placeholder a
   75 % de la couleur du texte : « Un mot, puis Entree » se lit alors
   exactement comme un mot qu'on aurait tape, et comme la valeur du
   select d'a cote. On ne sait plus ce qui est saisi et ce qui ne l'est
   pas. Il prend donc le gris des textes d'aide — meme registre, meme
   sens : ceci est une indication, pas une donnee — et l'italique le dit
   sans dependre de la seule nuance de gris. */
.form-control::placeholder,
.form-select::placeholder,
textarea.form-control::placeholder {
  color: #8a929c;
  font-style: italic;
  opacity: 1;
}

/* ===== Libellés de section ===== */

.vp-libelle {
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: #1a3a5c;
  margin-bottom: .25rem;
}

/* ===== Page de connexion =====

   Reprise de l'ecran de connexion WebDev : fond gris uni, la marque
   seule en haut, un champ, un bouton, et la mention legale en pied
   entre deux filets.

   PAS DE CARTE BLANCHE, ET PAS DE DEGRADE. La page ne porte qu'une
   chose a faire ; l'encadrer dans une boite posee sur un fond bleu la
   faisait ressembler aux ecrans de l'application, alors qu'on n'y est
   pas encore entre. Le gris uni la distingue.

   LE TITRE ECRIT A COTE DU LOGO A DISPARU. « Vélocité+ » est deja
   dans la marque, et « Tableau de bord » decrivait une page qu'on
   n'a pas encore atteinte.
   ===================================================================== */

.vp-login {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #f2f2f2;
  padding: 3rem 1.25rem 1.25rem;
}

/* La colonne prend toute la hauteur pour que `margin-top: auto` du
   pied le pousse en bas, quel que soit le contenu au-dessus — l'etape
   du code est plus haute que celle du courriel. */
.vp-login-box {
  width: 100%;
  max-width: 380px;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.vp-login-marque {
  text-align: center;
  margin-bottom: 3rem;
}

/* Les dimensions sont posees en CSS ET en attributs sur la balise :
   sans les attributs, la place de l'image n'est reservee qu'une fois
   le fichier charge, et le champ saute vers le bas a l'affichage. */
.vp-login-marque img {
  width: 200px;
  max-width: 60vw;
  height: auto;
  aspect-ratio: 1;
}

.vp-login-box label {
  display: block;
  font-size: 1.3rem;
  font-weight: 400;
  color: #2e2e2e;
  margin-bottom: .3rem;
}

.vp-login-box .form-control {
  width: 100%;
  font-size: 1.05rem;
  padding: .5rem .6rem;
  border: 1px solid #9a9a9a;
  border-radius: 3px;
  background: #fff;
}

.vp-login-box .form-control:focus {
  border-color: #1a3a5c;
  box-shadow: 0 0 0 .2rem rgba(26, 58, 92, .18);
  outline: 0;
}

.vp-login-bouton {
  display: block;
  width: 100%;
  margin-top: .9rem;
  padding: .6rem 1rem;
  border: 0;
  border-radius: 5px;
  background: #1a3a5c;
  color: #fff;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.3;
}

.vp-login-bouton:hover:not(:disabled) { background: #14304d; }
.vp-login-bouton:disabled            { opacity: .55; }

/* L ANNEAU D ATTENTE VIT DANS LE BOUTON. La page de connexion n a pas
   d indicateur commun : /partials/chargement n est habille que par
   cette feuille, que le layout mobile ne charge pas — il aurait donc
   signale d un seul cote. Sans lui, on clique « Demander son Code »,
   le bouton grise, et rien ne dit que ca travaille : c est exactement
   ce qui fait recliquer (section 13). */
.vp-login-attente { margin-right: .45rem; }

.vp-login-lien {
  display: block;
  width: 100%;
  margin-top: .6rem;
  padding: .3rem;
  border: 0;
  background: none;
  color: #5c5c5c;
  font-size: .9rem;
  text-decoration: underline;
}

/* LE PIED EST POUSSE EN BAS, PAS COLLE AU FORMULAIRE. Entre deux
   filets, comme sur l'ecran d'origine : c'est ce qui le donne a lire
   comme une mention et non comme une phrase de plus du formulaire. */
.vp-login-pied {
  margin-top: auto;
  padding: .85rem 0;
  border-top: 1px solid #c9c9c9;
  border-bottom: 1px solid #c9c9c9;
  text-align: center;
  font-style: italic;
  font-size: .95rem;
  line-height: 1.5;
  color: #6f6a60;
}

.vp-login-pied a {
  color: inherit;
  text-decoration: none;
}

.vp-login-pied a:hover { text-decoration: underline; }

.vp-header .dropdown-menu {
  background: #14304d;
  border: 1px solid rgba(255,255,255,.15);
}

.vp-header .dropdown-item {
  color: rgba(255,255,255,.85);
}

.vp-header .dropdown-item:hover {
  background: rgba(255,255,255,.15);
  color: #fff;
}

/* ===== Tableaux =====
   Habillage unique de TOUS les tableaux de l'application.

   Un tableau se lit toujours dans le meme ordre, de haut en bas :

       1. les statistiques portant sur son contenu
       2. la barre de filtre et de rafraichissement
       3. l'en-tete, avec le tri et les points d'aide
       4. les lignes

   Ces quatre etages sont produits par les partials tableau_debut /
   tableau_fin. Une page n'ecrit que ses <td> : elle n'a ni a repeter cet
   habillage, ni a pouvoir s'en ecarter.

   Les couleurs sont volontairement neutres. Le seul accent est le bleu
   de l'en-tete de l'application, repris sur les valeurs importantes,
   pour que les donnees, et non le cadre, retiennent l'oeil. */

.vp-tableau {
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: 10px;
  overflow: hidden;
  /* Repere pour l'indicateur de chargement, place en absolu */
  position: relative;
}

/* --- Chargement --- */

/* Rien dans le flux : ni decalage a l'apparition, ni texte recouvert */
.vp-chargement { pointer-events: none; }

/* Barre indeterminee, en haut de la fenetre. La duree n'est pas connue a
   l'avance : elle glisse au lieu de progresser. */
.vp-chargement-barre {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1080;
  height: 3px;
  background: rgba(255,255,255,.25);
  overflow: hidden;
}

.vp-chargement-barre::after {
  content: "";
  display: block;
  height: 100%;
  width: 35%;
  /* Sur le bandeau bleu fonce de l'application, un bleu ne se verrait pas */
  background: #7fb2e5;
  animation: vp-glisse 1.1s ease-in-out infinite;
}

@keyframes vp-glisse {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(385%); }
}

/* Pastille en bas a droite : visible ou que l'on soit dans la page */
.vp-chargement-message {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: 1080;
  padding: .4rem .85rem;
  border-radius: 999px;
  background: #1f2933;
  color: #fff;
  font-size: .78rem;
  font-weight: 500;
  box-shadow: 0 4px 14px rgba(0,0,0,.25);
}

/* ===============================================================
   LE CONTENU QU'ON REMPLACE S'ESTOMPE — REGLE DE TOUTE L'APPLICATION

   Elle etait ecrite PAR PAGE, et donc a moitie : les tableaux
   l'avaient par `tableau_debut`, cinq blocs de tuiles la portaient a
   la main, et tout le reste — fiches, cartes, listes, les dix ecrans
   d'administration — restait NET pendant qu'on le remplacait. On
   changeait de magasin, l'ecran ne bougeait pas, et l'on recliquait.

   Elle vit maintenant dans le layout, une seule fois, et aucune page
   ne la declare : voir « LE CONTENU QUI SE REMPLACE S'ESTOMPE » dans
   views/layouts/main.ejs pour ce qui la declenche.

   UN VOILE, ET NON UNE OPACITE SUR <main>. Ce n'est pas un gout :
   `opacity < 1` cree un BLOC CONTENEUR pour les descendants
   `position: fixed`. La barre et la pastille de `/partials/chargement`
   vivent dans la page, donc dans <main> — leur `position: fixed` se
   serait recalee sur <main> EXACTEMENT pendant qu'elles sont visibles,
   la barre glissant sous l'en-tete et la pastille se posant au bas du
   contenu au lieu de la fenetre.

   IL S'ARRETE A <main>. Le bandeau des modules, la barre d'onglets et
   l'en-tete de page restent nets : ils viennent de `sc_moi` et ne sont
   pas remplaces. Les estomper dirait que le menu est en train de
   changer.

   IL NE VA PAS SUR UNE MODALE. z-index 40 — au-dessus de tout ce
   qu'une page pose (5 au plus), sous `.vp-modale` (1050) et sous
   l'indicateur (1080). Une boite ouverte porte deja son propre masque
   (`.vp-fiche-masque`) et ses tableaux leur `vp-tableau-occupe`.
   =============================================================== */
/* La classe, et non #content : les layouts de connexion et de mobile
   portent le meme id, et ce repere n a de sens que la ou le voile est
   pose. */
.vp-page { position: relative; }

.vp-page-voile {
  position: absolute;
  inset: 0;
  z-index: 40;
  /* Le fond de l'application, jamais du gris : c'est un retrait, pas
     un bandeau. */
  background: #f4f6f8;
  opacity: 0;
  /* ON ESTOMPE, ON NE BLOQUE PAS. Meme raison qui laisse le filtre
     d'un tableau utilisable : c'est souvent le controle qu'on vient
     de changer, et parfois celui qu'on veut corriger tout de suite. */
  pointer-events: none;
  transition: opacity .18s;
}

.vp-page-occupee > .vp-page-voile { opacity: .55; }

/* Contenu en cours de remplacement : hors d'atteinte du clic.

   L'ESTOMPAGE EST PARTI AU VOILE, et il ne restait pas le choix : une
   opacite de .4 sous un voile a .55 rend un tableau illisible, ce qui
   se lit comme une panne et non comme une attente. Ce que cette regle
   garde est ce que le voile ne sait pas faire — elle sait QUEL tableau
   se recharge, et empeche de trier ou de filtrer des lignes sur le
   point d'etre remplacees. */
.vp-tableau-occupe .vp-tableau-stats,
.vp-tableau-occupe .table-responsive,
.vp-tableau-occupe .vp-tableau-vide {
  pointer-events: none;
}

/* Le filtre reste utilisable : c'est souvent lui qu'on vient de changer */

@media (prefers-reduced-motion: reduce) {
  .vp-chargement-barre::after {
    animation: none;
    width: 100%;
    opacity: .6;
  }
  .fa-spin { animation: none; }
  /* Le voile reste — c'est le seul signal que le contenu est en train
     d'etre remplace. C'est le FONDU qui disparait, pas l'estompage. */
  .vp-page-voile { transition: none; }
}

/* --- 1. Bandeau de statistiques --- */

.vp-tableau-stats {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 2.25rem;
  padding: .8rem 1rem;
  background: #f7f8fa;
  border-bottom: 1px solid #e3e6ea;
}

/* ---------------------------------------------------------------
   Le bouton d'impression du tableau

   EN ABSOLU, ET PAS EN DERNIER ENFANT DU BANDEAU. Le bandeau est un
   flex qui passe a la ligne : pousse a droite par `margin-left:auto`,
   le bouton descendrait avec les statistiques des que l'ecran se
   resserre — et « en haut a droite du tableau » cesserait d'etre vrai
   exactement quand la place manque.

   `.vp-tableau` porte deja `position: relative` pour son indicateur de
   chargement : le repere existe, on s'y accroche.

   La place est RESERVEE dans le bandeau plutot que laissee au hasard :
   sans ce retrait, une statistique de bout de ligne passerait sous le
   bouton et se lirait a moitie.
   --------------------------------------------------------------- */
/* LA PLACE DES DEUX BOUTONS EST RESERVEE, pas laissee au hasard : sans
   ce retrait, une statistique de bout de ligne passerait SOUS eux. Deux
   boutons de 2 rem, l ecart de .3 et le decalage de .7 — la valeur suit
   le nombre de boutons, elle ne s arrondit pas. */
.vp-tableau-stats-imprimable {
  padding-right: 5.7rem;
}

/* DEUX BOUTONS, ET NON UN : le PDF se lit, le CSV se calcule. Ils sont
   cote a cote parce qu ils partent du MEME formulaire — donc de la meme
   plage, du meme perimetre et des memes filtres — et qu ils ne
   different que par ce qu on fera du fichier. */
.vp-tableau-imprimer {
  position: absolute;
  top: .55rem;
  right: .7rem;
  z-index: 2;
  margin: 0;
  display: flex;
  gap: .3rem;
}

.vp-btn-imprimer {
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: .82rem;
  color: #6b7580;
  background: #fff;
  border: 1px solid #d7dce2;
  border-radius: 7px;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}

.vp-btn-imprimer:hover:not(:disabled) {
  color: #1a3a5c;
  border-color: #1a3a5c;
  background: #f2f6fa;
}

.vp-btn-imprimer:focus-visible {
  outline: 2px solid #1a3a5c;
  outline-offset: 2px;
}

/* Grise, il reste VISIBLE : un bouton qui disparait quand aucun
   magasin n'est choisi ferait chercher une fonction qu'on a deja vue. */
.vp-btn-imprimer:disabled {
  opacity: .4;
  cursor: not-allowed;
}

.vp-tableau-stat-libelle {
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #6b7580;
  line-height: 1.4;
}

.vp-tableau-stat-valeur {
  font-size: 1.1rem;
  font-weight: 600;
  line-height: 1.2;
  color: #1f2933;
  /* Chiffres de meme largeur : les valeurs restent alignees entre elles */
  font-variant-numeric: tabular-nums;
}

.vp-tableau-stat-principal .vp-tableau-stat-valeur { color: #1a3a5c; }

/* Ce qui demande une intervention : produits sans capsule, sans
   description. Une teinte ambree, pas un rouge : ce n'est pas une erreur. */
.vp-tableau-stat-alerte .vp-tableau-stat-valeur { color: #9a6700; }

/* --- 2. Barre d'outils : filtre, rafraichissement, compte filtre --- */

.vp-tableau-outils {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .6rem 1rem;
  border-bottom: 1px solid #e3e6ea;
}

/* Retour a la ligne impose dans une barre d'outils.

   La barre s'enroule deja toute seule selon la largeur — mais un
   enroulement libre coupe la ou la place manque, pas la ou le sens
   change. L'analyse par plage pose deux questions distinctes : ce que
   le tableau montre — niveau, segmentation, colonne cherchee — et le
   seuil chiffre qu'on lui applique. Cet element vide, large de toute la
   ligne, garantit qu'elles ne se melangent jamais. */
.vp-tableau-outils .vp-saut-ligne {
  flex-basis: 100%;
  height: 0;
  margin: 0;
  padding: 0;
}

/* Un critere chiffre se lit d'un bloc : la colonne, l'operateur, la
   valeur. Sans ce groupement, deux criteres cote a cote donnent six
   controles alignes ou l'oeil ne voit plus ou finit le premier. */
.vp-tableau-outils .vp-critere {
  display: flex;
  align-items: center;
  gap: .25rem;
}

/* --- 3 et 4. La grille --- */

.vp-tableau-grille {
  margin-bottom: 0;
}

.vp-tableau-grille > thead > tr > th {
  background: #eef1f4;
  color: #3b4754;
  font-size: .7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  white-space: nowrap;
  border-bottom: 2px solid #d6dbe1;
  padding: .6rem .75rem;
  /* L'en-tete reste visible quand on descend dans une longue liste */
  position: sticky;
  top: 0;
  z-index: 2;
}

/* Le titre cliquable prend la couleur de l'en-tete, pas celle des liens */
.vp-tableau-grille > thead a {
  color: inherit;
  text-decoration: none;
}

.vp-tableau-grille > thead a:hover {
  color: #1a3a5c;
}

.vp-tableau-grille > tbody > tr > td {
  padding: .55rem .75rem;
  border-top: 1px solid #eef0f3;
  border-bottom: 0;
  vertical-align: middle;
}

.vp-tableau-grille > tbody > tr:first-child > td {
  border-top: 0;
}

.vp-tableau-grille > tbody > tr:hover > td {
  background: #f5f8fb;
}

/* En-tete de ligne d'un croise : la premiere colonne porte le libelle */
.vp-tableau-grille > tbody > tr > th {
  padding: .55rem .75rem;
  border-top: 1px solid #eef0f3;
  border-bottom: 0;
  background: #f7f8fa;
  font-weight: 600;
  font-size: .875rem;
}

/* Chiffres alignes en colonne : meme largeur pour tous les chiffres */
.vp-tableau-grille .text-end {
  font-variant-numeric: tabular-nums;
}

/* Ligne de total : le meme gris que le bandeau du haut, pour qu'elle se
   lise comme une synthese et non comme une ligne de donnees de plus */
.vp-tableau-grille > tfoot > tr > th,
.vp-tableau-grille > tfoot > tr > td {
  background: #f7f8fa;
  border-top: 1px solid #e3e6ea;
  border-bottom: 0;
  padding: .55rem .75rem;
}

.vp-tableau-grille > tfoot > tr:first-child > th,
.vp-tableau-grille > tfoot > tr:first-child > td {
  border-top: 2px solid #d6dbe1;
}

/* Cellule choisie dans un croise. Une classe a nous plutot que
   .table-primary : celle de Bootstrap passe par des variables que
   l'habillage ci-dessus recouvre. */
.vp-tableau-grille > tbody > tr > td.vp-cellule-choisie,
.vp-tableau-grille > tbody > tr:hover > td.vp-cellule-choisie {
  background: #dce8f5;
  box-shadow: inset 0 0 0 2px #1a3a5c;
  font-weight: 600;
}

/* --- Barre de pagination, en pied de cadre --- */

.vp-tableau-pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .75rem;
  padding: .6rem 1rem;
  background: #f7f8fa;
  border-top: 1px solid #e3e6ea;
}

.vp-tableau-pages-compte {
  font-size: .8rem;
  color: #6b7580;
  font-variant-numeric: tabular-nums;
}

/* Le rang de page n'est pas un bouton : il ne doit pas en avoir l'air */
.vp-tableau-pages-rang {
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  background: #fff;
}

/* Etat vide : le cadre reste, pour garder le filtre accessible */
.vp-tableau-vide {
  padding: 2rem 1rem;
  text-align: center;
  color: #6b7580;
}

/* ===== Infobulles ===== */

.tooltip-inner {
  max-width: 320px;
  text-align: left;
  background: #1f2933;
  font-size: .8rem;
  line-height: 1.45;
  padding: .5rem .7rem;
  border-radius: 6px;
}

.tooltip.bs-tooltip-top .tooltip-arrow::before { border-top-color: #1f2933; }
.tooltip.bs-tooltip-bottom .tooltip-arrow::before { border-bottom-color: #1f2933; }

/* =================================================================
   ONGLETS DE MODULE
   Rendus par le layout depuis sc_moi.data.onglets.
   Ce sont des LIENS : chaque page garde son adresse et ses droits.

   Le soulignement est porte par ::after et non par une bordure :
   une bordure ajoute de la hauteur et fait sauter la ligne d'un
   pixel au changement d'onglet.
   ================================================================= */

.vp-onglets {
  background: #fff;
  border-bottom: 1px solid #e3e6ea;
  box-shadow: 0 1px 2px rgba(16, 32, 50, .04);
}

.vp-onglets .nav {
  gap: .1rem;
  border: 0;
}

/* LE FILET QUI SEPARE DEUX FAMILLES D ONGLETS.

   Il ne se pose pas en :nth-child : la barre n est pas la meme pour
   tout le monde, et un filet accroche a une position se retrouverait
   au milieu d une famille des qu une page manque a un compte. Le
   catalogue de lib/auth.js le porte donc sur la PAGE.

   Plus court que l onglet et centre : il doit se lire comme une
   respiration, pas comme une bordure de conteneur. */
.vp-onglet-filet {
  flex: 0 0 auto;
  width: 1px;
  height: 1.15rem;
  margin: 0 .55rem;
  background: #d7dce2;
}

.vp-onglets .nav-link {
  position: relative;
  border: 0;
  border-radius: .4rem .4rem 0 0;
  padding: .72rem 1.05rem;
  font-size: .88rem;
  font-weight: 600;
  color: #6b7580;
  background: transparent;
  transition: color .15s, background .15s;
}

.vp-onglets .nav-link::after {
  content: '';
  position: absolute;
  left: .55rem;
  right: .55rem;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: transparent;
  transition: background .15s;
}

.vp-onglets .nav-link:hover {
  color: #1a3a5c;
  background: #f4f6f8;
}

.vp-onglets .nav-link.active {
  color: #1a3a5c;
  background: transparent;
}

.vp-onglets .nav-link.active::after {
  background: #1a3a5c;
}

.vp-onglets .nav-link:focus-visible {
  outline: 2px solid #1a3a5c;
  outline-offset: -2px;
}

/* =================================================================
   LE SOUS-MENU D UN GROUPE D ONGLETS
   Rendu par le layout depuis sc_moi.data.groupes, et donc identique
   pour « Gestion » de la biere et « Gestion » des capsules : c'est le
   meme gabarit, il n'y a rien a habiller deux fois.

   Il etait laisse a Bootstrap : fond blanc, angles vifs, lignes
   espacees, aucune icone, et la page ouverte peinte en bleu vif —
   une couleur qui n'existe nulle part ailleurs dans l'application.
   Un menu qui ne ressemble a aucun autre ecran se lit comme une piece
   rapportee.

   Chaque entree porte l'icone de sa page, prise au catalogue de
   lib/auth.js. C'est la meme que celle de l'en-tete de l'ecran : on
   reconnait donc au menu ce que l'on retrouvera en haut de la page.
   ================================================================= */

.vp-sousmenu {
  min-width: 15.5rem;
  padding: .35rem;
  margin-top: .15rem;
  border: 1px solid #e3e6ea;
  border-radius: .65rem;
  box-shadow: 0 10px 28px rgba(16, 32, 50, .14);
}

.vp-sousmenu .dropdown-item {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .5rem .6rem;
  border-radius: .45rem;
  font-size: .86rem;
  font-weight: 500;
  color: #48525d;
  transition: background .15s, color .15s;
}

/* La pastille de l'icone donne aux quatre lignes une colonne commune :
   sans elle, des glyphes de largeurs differentes decalent chaque
   libelle d'un ou deux pixels, et la liste ne s'aligne plus. */
.vp-sousmenu-icone {
  flex: 0 0 1.7rem;
  height: 1.7rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: .4rem;
  background: #f0f3f7;
  color: #6b7580;
  font-size: .78rem;
  transition: background .15s, color .15s;
}

.vp-sousmenu .dropdown-item:hover,
.vp-sousmenu .dropdown-item:focus {
  background: #f4f6f8;
  color: #1a3a5c;
}

.vp-sousmenu .dropdown-item:hover .vp-sousmenu-icone,
.vp-sousmenu .dropdown-item:focus .vp-sousmenu-icone {
  background: #e4ebf3;
  color: #1a3a5c;
}

/* LA PAGE OUVERTE SE MARQUE, ELLE NE SE REPEINT PAS. Bootstrap peint
   .active en bleu vif et en blanc ; on garde le bleu de
   l'application, pose sur la seule pastille. */
.vp-sousmenu .dropdown-item.active,
.vp-sousmenu .dropdown-item.active:hover,
.vp-sousmenu .dropdown-item.active:focus {
  background: #eef2f7;
  color: #1a3a5c;
  font-weight: 600;
}

.vp-sousmenu .dropdown-item.active .vp-sousmenu-icone {
  background: #1a3a5c;
  color: #fff;
}

/* LES FAMILLES D'UN SOUS-MENU

   A deux ou trois entrees l'ordre suffit a grouper. A sept, non :
   « Gestion » du module biere alignait sept lignes ou rien ne separait
   la paire des marques de celle des segmentations.

   UN TITRE, ET PAS UN FILET. Un trait horizontal dirait « ce qui suit
   est autre chose » sans dire QUOI — et il faudrait de toute facon lire
   les entrees pour le deviner. Le titre repond avant qu'on lise.

   IL SE LIT SANS SE FAIRE LIRE : petit, gris, en capitales. Ce qu'on
   vient chercher dans ce menu est une destination, pas une rubrique —
   un titre aussi lourd que les entrees les mettrait au second rang. */
.vp-sousmenu-section {
  list-style: none;
  margin: 0;
  padding: 0;
}

.vp-sousmenu-famille {
  padding: .2rem .6rem .3rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #97a1ac;
}

/* SEULES LES SECTIONS SUIVANTES RESPIRENT AU-DESSUS. La premiere n'a
   rien a separer, et l'espace y creuserait un trou en haut du menu.
   `li + li` plutot que `:not(:first-child)` : App Connect laisse ses
   propres noeuds dans la liste repetee, et un selecteur de position
   absolue pourrait viser l'un d'eux. */
.vp-sousmenu > li + li > .vp-sousmenu-famille {
  padding-top: .6rem;
}

/* UN FILET DANS UN SOUS-MENU — l autre facon de dire « ce qui suit
   est autre chose ».

   UNE FAMILLE RESTE PREFERABLE : elle dit QUOI, la ou un trait ne dit
   que « autre chose ». Mais il y a un cas ou elle ne peut pas — le
   sous-menu « Réseau », dont les deux familles justes s appelleraient
   « Tableaux » et « Détail », c est-a-dire comme les deux onglets
   voisins de la barre a plat, qui ouvrent autre chose.

   C EST LE MEME DRAPEAU QUE LA BARRE A PLAT — `separateur` — et il y dit
   deja la meme chose — sauf qu il y pose un trait vertical. Un second
   drapeau aurait fait deux facons de dire « ici la barre change de
   nature ».

   Le meme filet que la deconnexion du menu du compte : deux traits
   d une meme application n ont aucune raison de ne pas se ressembler. */
.vp-sousmenu-filet {
  margin-top: .35rem;
  padding-top: .35rem;
  border-top: 1px solid #eef1f5;
}


/* =================================================================
   LE MENU DU COMPTE

   IL N'A PAS DE DESSIN A LUI. Il porte `vp-sousmenu` — le cadre
   arrondi, l'ombre, les pastilles d'icone, les familles — et ne
   declare ici que les trois choses que le sous-menu des modules n'a
   pas : l'identite en tete, la separation de la deconnexion, et
   l'etat « son coupe ».

   Une seconde feuille qui aurait redessine le meme menu aurait fini
   par ne plus l'arrondir pareil, et personne ne l'aurait vu : les
   deux menus ne s'ouvrent jamais l'un a cote de l'autre.
   ================================================================= */

/* Plus large que le sous-menu des modules : il porte un courriel, qui
   est la plus longue chaine de l'application a ne pas pouvoir se
   couper en son milieu. */
/* IL EST CLAIR, COMME LE SOUS-MENU DES MODULES. `.vp-header` peint ses
   menus en marine, et c'est juste pour un menu qui n'aurait que des
   liens. Ce ne l'est plus ici : le courriel y devenait un gris sur
   marine qu'on ne lisait pas, et les pastilles — dessinees pour du
   blanc — y perdaient tout relief. Il n'existe qu'un gabarit de menu
   dans cette application, et c'est celui du sous-menu des modules.

   LA REGLE EST PORTEE, JAMAIS RETIREE : un autre menu de la barre du
   haut, ecrit demain, restera marine comme avant. */
.vp-header .dropdown-menu.vp-menucompte {
  background: #fff;
  border: 1px solid #e3e6ea;
}

.vp-menucompte {
  min-width: 17rem;
  max-width: 20rem;
}

/* L'IDENTITE, EN TETE. La barre du haut la cache en dessous de `md` :
   sur un ecran etroit, ce bloc est le seul endroit qui dise a qui
   appartient la session. */
.vp-menucompte-identite {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .45rem .6rem .7rem;
  margin-bottom: .1rem;
  border-bottom: 1px solid #eef1f5;
}

/* Le meme degrade que l'icone d'en-tete de page : c'est la marque de
   l'application, et elle ne se dessine pas deux fois. */
.vp-menucompte-avatar {
  flex: 0 0 2.15rem;
  height: 2.15rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
  color: #fff;
  font-size: .82rem;
}

.vp-menucompte-qui {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  /* Sans `min-width: 0`, un enfant de flex refuse de retrecir sous la
     largeur de son contenu, et l'ellipse du courriel ne prend jamais :
     c'est le menu entier qui s'elargit. */
  min-width: 0;
}

.vp-menucompte-nom {
  font-size: .88rem;
  font-weight: 600;
  color: #1a3a5c;
}

/* LA FONCTION, SOUS LE NOM — l'ordre de la barre du haut, qui range
   deja « Philippe Wouters » puis « Administrateur ». Deux lectures du
   meme compte qui n'empileraient pas les memes lignes dans le meme
   ordre se liraient comme deux comptes.

   TROIS DEGRES, ET C'EST CE QUI LES SEPARE : le nom en marine et en
   gras, la fonction en gris moyen, le courriel en gris clair. Trois
   lignes du meme gris demanderaient de lire pour savoir laquelle est
   laquelle.

   ELLE SE TAIT QUAND ELLE MANQUE, et sans condition : velociteplus.com
   ne la renseigne pas sur tous les comptes (section 24). Un `!= ''`
   ecrit ici serait VRAI sur `undefined` (section 38) ; une chaine vide,
   elle, ne rend rien et n'occupe aucune hauteur. C'est deja ce que fait
   la barre du haut. */
.vp-menucompte-fonction {
  font-size: .76rem;
  font-weight: 500;
  color: #4c5761;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* IL SE LIT. Il etait en `text-muted` — #6c757d a 50 % d'opacite sur
   fond blanc — c'est-a-dire presque rien, sur la seule ligne qui
   nommait le compte. */
.vp-menucompte-courriel {
  font-size: .74rem;
  color: #6b7580;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* LA DECONNEXION SE SEPARE, ELLE NE SE REPEINT PAS. Un filet dit « ce
   qui suit n'est pas une destination » ; du rouge dirait « danger »,
   une couleur qui n'existe nulle part ailleurs dans l'application
   (section 13). */
.vp-menucompte-fin {
  margin-top: .35rem;
  padding-top: .35rem;
  border-top: 1px solid #eef1f5;
}

/* LE SON COUPE SE VOIT SANS SE LIRE. Le libelle le dit deja — c'est
   `marquer()` qui l'ecrit — mais un menu se parcourt du regard, et une
   pastille eteinte repond avant le mot. */
.vp-menucompte .dropdown-item.est-coupe .vp-sousmenu-icone {
  background: #f2f4f7;
  color: #b3bbc4;
}

.vp-menucompte .dropdown-item.est-coupe .vp-sousmenu-titre {
  color: #8a939d;
}


/* =================================================================
   EN-TETE DE PAGE
   Titre, sous-titre et icone. Declares dans lib/auth.js, rendus par
   le layout : une page n'ecrit plus son propre <h1>.
   ================================================================= */

.vp-page-entete {
  display: flex;
  align-items: flex-start;
  gap: .85rem;
  margin-bottom: 1rem;
}

.vp-page-icone {
  flex: 0 0 auto;
  width: 2.6rem;
  height: 2.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .7rem;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
  color: #fff;
  font-size: 1.1rem;
  box-shadow: 0 2px 5px rgba(16, 32, 50, .18);
}

.vp-page-titre {
  margin: 0;
  font-size: 1.3rem;
  font-weight: 700;
  line-height: 1.25;
  color: #16283c;
}

.vp-page-soustitre {
  margin: .15rem 0 0;
  font-size: .88rem;
  color: #6b7580;
  max-width: 80ch;
}


/* =================================================================
   LE MODE D'EMPLOI D'UN ECRAN

   « Un titre qui demande une explication recoit un point
   d'interrogation, pas un sous-titre » — la regle valait deja pour les
   colonnes d'un tableau ; elle vaut ici pour l'ecran entier.

   LE BOUTON, LUI, N'A PAS DE STYLE PROPRE : il vit dans la barre du
   haut et prend `btn-outline-light`, comme l'enveloppe et le compte
   qui l'entourent. Ce qui suit n'habille que la FENETRE.
   ================================================================= */

/* La boite d'aide est un peu plus etroite que celle du fil : c'est de
   la prose, et une ligne trop longue se relit mal. */
.vp-aide .vp-modale-boite { max-width: 40rem; padding: 1.4rem 1.5rem; }

.vp-aide-entete {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding-right: 2rem; /* la croix */
  margin-bottom: 1.1rem;
}

.vp-aide-blason {
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .7rem;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
  color: #fff;
  font-size: 1rem;
}

.vp-aide-sur {
  margin: 0;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: #97a3af;
}

.vp-aide-titre {
  margin: 0;
  font-size: 1.12rem;
  font-weight: 700;
  line-height: 1.25;
  color: #16283c;
}

/* UN POINT PAR IDEE, et l'icone tient la colonne de gauche : l'oeil
   descend la liste sans relire les titres. */
.vp-aide-point {
  display: flex;
  align-items: flex-start;
  gap: .8rem;
  padding: .75rem 0;
  border-top: 1px solid #eef1f5;
}

.vp-aide-icone {
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .5rem;
  background: #eef3f8;
  color: #1a3a5c;
  font-size: .78rem;
  margin-top: .1rem;
}

.vp-aide-corps { display: block; }

.vp-aide-point-titre {
  display: block;
  font-weight: 600;
  font-size: .93rem;
  color: #16283c;
  margin-bottom: .15rem;
}

.vp-aide-texte {
  display: block;
  font-size: .88rem;
  line-height: 1.5;
  color: #5a6673;
}

.vp-aide-refus {
  margin: .5rem 0 0;
  font-size: .88rem;
  color: #6b7580;
}

/* -----------------------------------------------------------------
   CHERCHER DANS L'AIDE

   LE CHAMP FAIT 1rem, ET CE N'EST PAS UN CHOIX D'ESTHETIQUE. En
   dessous de 16 px, iOS ZOOME la page a la mise au point, et il faut
   repincer apres chaque saisie (section 38). Le layout de bureau se lit
   aussi sur une tablette.

   LE CADRE PORTE LA BORDURE, PAS L'INPUT : c'est ce qui permet a
   l'icone et au bouton d'etre dedans, comme le sélecteur mobile
   (section 38).
   ----------------------------------------------------------------- */
.vp-aide-chercher {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .35rem .35rem .7rem;
  margin-bottom: 1rem;
  border: 1px solid #d6dde5;
  border-radius: .6rem;
  background: #fff;
}

.vp-aide-chercher:focus-within {
  border-color: #1a3a5c;
  box-shadow: 0 0 0 .15rem rgba(26, 58, 92, .12);
}

.vp-aide-chercher-icone {
  flex: 0 0 auto;
  color: #97a3af;
  font-size: .85rem;
}

.vp-aide-champ {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font-size: 1rem;
  color: #16283c;
  padding: .3rem 0;
}

.vp-aide-champ::placeholder { color: #9aa6b2; }

/* 2,75 rem de haut : on vise ce bouton au doigt sur une tablette, et la
   cible de 48 px est celle que le projet tient partout (section 38). */
.vp-aide-go {
  flex: 0 0 auto;
  border: 0;
  border-radius: .45rem;
  background: #1a3a5c;
  color: #fff;
  font-size: .85rem;
  font-weight: 600;
  padding: .45rem .85rem;
  min-height: 2.25rem;
  cursor: pointer;
}

.vp-aide-go:hover:not(:disabled) { background: #234c76; }
.vp-aide-go:disabled { opacity: .55; cursor: default; }

/* LA RUBRIQUE — « Le vocabulaire », « Les écrans ». Elle reprend le
   sur-titre de l'en-tete plutot que d'inventer un troisieme rang de
   titre dans une fenetre qui en porte deja deux. */
.vp-aide-rubrique {
  margin: 1rem 0 .1rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: #97a3af;
}

/* UN RESULTAT D'ECRAN EST UN LIEN, et il le dit au survol — sans
   prendre le bleu : la fenetre entiere deviendrait une page de liens,
   alors que la moitie de ce qu'on y lit ne mene nulle part. C'est la
   regle de l'adresse cliquable (section 13). */
.vp-aide-ecran {
  text-decoration: none;
  color: inherit;
}

.vp-aide-ecran:hover {
  background: #f6f9fc;
  color: inherit;
}

.vp-aide-ecran:hover .vp-aide-point-titre { text-decoration: underline; }

.vp-aide-fil {
  display: block;
  font-size: .72rem;
  color: #97a3af;
  margin-bottom: .1rem;
}

/* -----------------------------------------------------------------
   ECRIRE A L'ADMINISTRATEUR
   ----------------------------------------------------------------- */
.vp-aide-contact {
  margin-top: 1.2rem;
  border-top: 1px solid #eef1f5;
  padding-top: .9rem;
}

.vp-aide-contact-titre {
  display: flex;
  align-items: center;
  gap: .5rem;
  cursor: pointer;
  font-size: .88rem;
  font-weight: 600;
  color: #1a3a5c;
  list-style: none;
}

.vp-aide-contact-titre::-webkit-details-marker { display: none; }
.vp-aide-contact-titre:hover { color: #234c76; }

.vp-aide-contact-corps { padding-top: .8rem; }

.vp-aide-contact-note {
  margin: 0 0 .55rem;
  font-size: .8rem;
  color: #6b7580;
}

.vp-aide-message {
  width: 100%;
  border: 1px solid #d6dde5;
  border-radius: .6rem;
  padding: .6rem .75rem;
  font-size: 1rem;
  line-height: 1.45;
  color: #16283c;
  resize: vertical;
}

.vp-aide-message:focus {
  outline: 0;
  border-color: #1a3a5c;
  box-shadow: 0 0 0 .15rem rgba(26, 58, 92, .12);
}

.vp-aide-contact-pied {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: .6rem;
  flex-wrap: wrap;
}

/* UN SUCCES EST FUGACE, UNE ERREUR EST UN ETAT (section 13) — mais ni
   l'un ni l'autre ne s'efface ici : la fenetre se referme, et c'est
   elle qui emporte le message. Une pastille qui fanerait dans une boite
   qu'on vient d'ouvrir se lirait comme un envoi perdu. */
.vp-aide-etat {
  font-size: .82rem;
  color: #6b7580;
}

.vp-aide-etat.est-succes { color: #1e7d4f; font-weight: 600; }

/* DE L'AMBRE, JAMAIS DU ROUGE : ce qui demande une intervention est en
   ambre dans tout le projet (section 13). */
.vp-aide-etat.est-refus { color: #9a6a10; font-weight: 600; }


@media (max-width: 40rem) {
  .vp-aide .vp-modale-boite { padding: 1rem; }
}


/* =================================================================
   CADRE DE CONTEXTE — les selecteurs de haut de page
   Magasin, periode, etendue : ce qui definit CE QUE la page montre.

   Ouvert par '/partials/contexte_debut', ferme par '_fin'.
   Les libelles reprennent la casse et l'espacement du bandeau de
   statistiques : les deux se lisent comme une meme famille.
   ================================================================= */

.vp-contexte {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: .85rem 1.6rem;
  padding: .9rem 1.1rem;
  margin-bottom: 1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .6rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
}

.vp-champ {
  display: flex;
  flex-direction: column;
  min-width: 9.5rem;
}

.vp-champ-large { min-width: 15rem; }
.vp-champ-auto  { min-width: 0; }

.vp-champ > label {
  font-size: .68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #6b7580;
  line-height: 1.4;
  margin-bottom: .22rem;
  white-space: nowrap;
}

.vp-champ-aide {
  font-size: .75rem;
  color: #8a929c;
  margin-top: .22rem;
  max-width: 22rem;
}

.vp-champ-aide.text-danger { color: #b02a37 !important; }

/* Valeur en lecture seule presentee comme un champ, sans bordure */
.vp-champ-valeur {
  font-size: .9rem;
  color: #16283c;
  padding-top: .3rem;
  line-height: 1.35;
}

/* Le cadre s'estompe pendant un rechargement, comme les tableaux */
.vp-contexte-occupe { opacity: .55; transition: opacity .15s; }

/* Un formulaire qui porte les champs du cadre ne doit pas casser sa
   disposition : display:contents le fait disparaitre de la mise en page,
   et ses .vp-champ s'alignent comme ceux des autres ecrans. Sans cela,
   le formulaire formerait un bloc unique et la largeur ne viendrait plus
   de la classe du champ. */
.vp-contexte-form {
  display: contents;
}

/* La meme rangee de champs, mais sans le cadre : pour un formulaire
   pose dans une carte, qui n a pas a redessiner une bordure autour de
   lui. Sans cela ses .vp-champ s empilent et le bouton prend toute la
   largeur. */
.vp-champs {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: .85rem 1.6rem;
}

@media (max-width: 575.98px) {
  .vp-champ, .vp-champ-large { min-width: 100%; }
  .vp-page-entete { gap: .6rem; }
  .vp-page-icone { width: 2.2rem; height: 2.2rem; font-size: .95rem; }
}


/* =================================================================
   ACCUEIL — cartes de module

   Le survol suit le pointeur : un script delegue pose --mx / --my sur
   la carte survolee, et la lueur les lit. La delegation est necessaire —
   les cartes sont creees par App Connect APRES le chargement, un
   ecouteur pose au demarrage les manquerait toutes.

   Toute animation est desactivee sous prefers-reduced-motion.
   ================================================================= */

.vp-resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem 1.5rem;
  padding: .85rem 1.1rem;
  margin-bottom: 1.1rem;
  background: linear-gradient(120deg, #1a3a5c 0%, #2b5b8c 100%);
  border-radius: .7rem;
  color: #fff;
  box-shadow: 0 3px 10px rgba(16, 32, 50, .18);
}

.vp-resume-chiffre {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
}

.vp-resume-libelle {
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  opacity: .8;
}

.vp-resume-barre {
  flex: 1 1 12rem;
  height: .4rem;
  min-width: 8rem;
  background: rgba(255, 255, 255, .22);
  border-radius: 1rem;
  overflow: hidden;
}

.vp-resume-barre > span {
  display: block;
  height: 100%;
  border-radius: 1rem;
  background: linear-gradient(90deg, #7ec8f5, #fff);
  transition: width .9s cubic-bezier(.2, .8, .2, 1);
}

/* ---------------------------------------------------------------
   LA VITRINE DE L'ACADEMIE — une bande qui defile, en tete de la
   grille

   Elle ne se dessine PAS comme une carte de module, et c'est tout
   l'objet : ce sont des articles, pas des ecrans. Fond bleute, pas
   d'ombre portee, pas de survol qui souleve — la grille garde ces
   signes pour ce qui s'ouvre.

   `overflow-x: auto` sur la PISTE, jamais sur la page : le corps ne
   defile jamais a l'horizontale (regle d'affichage du projet).
   --------------------------------------------------------------- */
/* L'ESPACE AU-DESSUS EST PLUS GRAND QUE LA GOUTTIERE DE LA GRILLE
   (1 rem), et c'est ce qui la separe. Colle aux cartes, la vitrine se
   lisait comme une septieme ligne de modules : elle en est autre
   chose, et le blanc doit le dire avant la couleur du fond. */
.vp-vitrine {
  margin: 2rem 0 1.1rem;
  padding: .9rem 1rem 1rem;
  background: #f2f6fb;
  border: 1px solid #dde6f1;
  border-radius: .8rem;
}

.vp-vitrine-tete {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-bottom: .8rem;
}

.vp-vitrine-icone {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: .6rem;
  background: #2b5b8c;
  color: #fff;
  font-size: 1rem;
}

/* Le titre prend la place restante ; le lien « tout voir » se cale a
   droite sans qu'on ait a le pousser. */
.vp-vitrine-titres { flex: 1 1 auto; min-width: 0; }

.vp-vitrine-titre {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
}

.vp-vitrine-titre a {
  color: #16324f;
  text-decoration: none;
}

.vp-vitrine-titre a:hover { text-decoration: underline; }

.vp-vitrine-sous {
  margin-top: .1rem;
  font-size: .78rem;
  color: #5b6b7c;
}

/* AMBRE, comme la marque d'un article neuf : c'est le meme fait, dit
   d'un cran plus haut pour qu'il se lise sans defiler. */
.vp-vitrine-neuves {
  flex: none;
  padding: .12rem .45rem;
  border-radius: .3rem;
  background: #f0ad4e;
  color: #3a2a06;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  white-space: nowrap;
}

.vp-vitrine-fleche {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 1px solid #cfdcea;
  border-radius: 50%;
  background: #fff;
  color: #2b5b8c;
  font-size: .72rem;
  cursor: pointer;
  transition: background .15s, border-color .15s, opacity .15s;
}

.vp-vitrine-fleche:hover:not(:disabled) {
  background: #e8f0f8;
  border-color: #b9cde0;
}

/* Elle reste EN PLACE quand elle ne sert plus, plutot que de
   disparaitre : une rangee dont les boutons vont et viennent fait
   sauter le titre a chaque defilement. */
.vp-vitrine-fleche:disabled {
  opacity: .35;
  cursor: default;
}

.vp-vitrine-tout {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .78rem;
  font-weight: 600;
  color: #2b5b8c;
  text-decoration: none;
  white-space: nowrap;
}

.vp-vitrine-tout:hover { text-decoration: underline; }

.vp-vitrine-piste {
  display: flex;
  gap: .8rem;
  overflow-x: auto;
  /* Une carte coupee au bord DIT qu'il y en a d'autres : c'est ce qui
     fait comprendre que la bande defile, sans fleche a maintenir. */
  padding-bottom: .35rem;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}

.vp-vitrine-piste::-webkit-scrollbar { height: 6px; }
.vp-vitrine-piste::-webkit-scrollbar-thumb {
  background: #c6d4e3;
  border-radius: 3px;
}

.vp-vitrine-carte {
  position: relative;
  flex: 0 0 14rem;
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .7rem;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  scroll-snap-align: start;
  transition: transform .18s cubic-bezier(.2, .8, .2, 1), border-color .18s;
}

.vp-vitrine-carte:hover {
  transform: translateY(-2px);
  border-color: #b9cde0;
}

/* La hauteur est FIXE et le recadrage se fait ici : les couvertures
   n'ont pas toutes le meme format, et une bande dont chaque carte
   commence a une hauteur differente ne se lit plus comme une bande. */
.vp-vitrine-image {
  display: block;
  width: 100%;
  height: 6.2rem;
  object-fit: cover;
  object-position: top left;
  background: #eef2f7;
}

.vp-vitrine-corps {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: .6rem .7rem .7rem;
}

.vp-vitrine-cat {
  display: flex;
  align-items: center;
  gap: .35rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #7a8896;
}

.vp-vitrine-nom {
  font-size: .88rem;
  font-weight: 600;
  line-height: 1.25;
  color: #16324f;
}

/* Trois lignes au plus : au-dela, les cartes cessent d'avoir la meme
   hauteur et la bande se lit de travers. */
.vp-vitrine-chapeau {
  font-size: .76rem;
  line-height: 1.35;
  color: #5b6b7c;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.vp-vitrine-date {
  margin-top: .15rem;
  font-size: .72rem;
  color: #8a97a4;
}

/* AMBRE, comme tout ce qui demande un regard dans ce projet — jamais
   du rouge, qui dit qu'une lecture a echoue. */
.vp-vitrine-neuf {
  position: absolute;
  top: .45rem;
  right: .45rem;
  padding: .1rem .4rem;
  border-radius: .3rem;
  background: #f0ad4e;
  color: #3a2a06;
  font-size: .65rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

.vp-modules {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: 1rem;
}

.vp-module {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: .55rem;
  padding: 1.1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .8rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
  overflow: hidden;
  transition: transform .18s cubic-bezier(.2, .8, .2, 1),
              box-shadow .18s, border-color .18s;
  animation: vp-module-entree .35s cubic-bezier(.2, .8, .2, 1) both;
}

/* Lueur qui suit le pointeur. Sous la carte, jamais au-dessus du texte. */
.vp-module::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(16rem circle at var(--mx, 50%) var(--my, 0%),
              rgba(43, 91, 140, .13), transparent 65%);
  opacity: 0;
  transition: opacity .2s;
  pointer-events: none;
}

.vp-module-ouvert:hover {
  transform: translateY(-3px);
  border-color: #b9cde0;
  box-shadow: 0 10px 24px rgba(16, 32, 50, .13);
}

.vp-module-ouvert:hover::before { opacity: 1; }

.vp-module-icone {
  width: 2.9rem;
  height: 2.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .75rem;
  font-size: 1.25rem;
  color: #fff;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
  box-shadow: 0 3px 8px rgba(16, 32, 50, .22);
  transition: transform .25s cubic-bezier(.2, .8, .2, 1);
}

.vp-module-ouvert:hover .vp-module-icone { transform: scale(1.06) rotate(-4deg); }

.vp-module-titre {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  color: #16283c;
  line-height: 1.3;
}

.vp-module-meta {
  font-size: .78rem;
  color: #6b7580;
  margin-top: -.2rem;
}

/* Le lien du titre, ETIRE sur toute la carte.

   La carte n'est plus un <a> enveloppant : chaque fait porte son propre
   lien, et un <a> dans un <a> n'est pas du HTML. Le ::after rend la
   surface entiere cliquable ; les faits repassent au-dessus par
   z-index et restent de vrais liens. */
.vp-module-cible {
  color: inherit;
  text-decoration: none;
}

.vp-module-cible::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* --- Les apercus : ce que la carte annonce avant qu'on l'ouvre --- */

.vp-module-faits {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  margin-top: -.1rem;
}

/* z-index au-dessus de la cible etiree, sinon le lien du titre
   recouvrirait le fait et l'on partirait toujours au meme endroit. */
.vp-module-fait {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  padding: .3rem .5rem;
  border-radius: .4rem;
  background: #f5f7f9;
  text-decoration: none;
  transition: background .15s;
}

.vp-module-fait:hover { background: #e8edf2; text-decoration: none; }

.vp-module-fait-libelle {
  font-size: .72rem;
  color: #6b7580;
  line-height: 1.25;
}

.vp-module-fait-valeur {
  font-size: .85rem;
  font-weight: 700;
  white-space: nowrap;
  color: #16283c;
}

/* La valeur qui date le module — la derniere periode transmise. */
.vp-module-fait-principal .vp-module-fait-valeur { color: #1a3a5c; }

/* Ce qui demande une intervention. L'ambre de .vp-tableau-stat-alerte,
   et jamais du rouge : ce n'est pas une erreur, c'est du travail en
   attente (CLAUDE.md section 13). */
.vp-module-fait-alerte { background: #fdf6e7; }
.vp-module-fait-alerte:hover { background: #f9edd3; }
.vp-module-fait-alerte .vp-module-fait-valeur { color: #9a6700; }

.vp-module-pied { margin-top: auto; padding-top: .5rem; }

/* Sur quel magasin portent les chiffres des cartes. Une ligne, au-dessus
   de la grille : repetee sur chaque carte, elle prendrait plus de place
   que les faits qu'elle qualifie. */
.vp-accueil-magasin {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .9rem;
  font-size: .82rem;
  color: #6b7580;
}

.vp-accueil-magasin i { color: #97a0aa; }

/* Module non disponible : present, mais visiblement inerte */
.vp-module-ferme {
  background: #f7f8fa;
  border-style: dashed;
  box-shadow: none;
}

.vp-module-ferme .vp-module-icone {
  background: #dfe3e8;
  color: #97a0aa;
  box-shadow: none;
}

.vp-module-ferme .vp-module-titre { color: #6b7580; }

.vp-module-etat {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  align-self: flex-start;
  padding: .2rem .55rem;
  border-radius: 1rem;
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .03em;
  background: #eceff2;
  color: #6b7580;
}

.vp-module-etat-ouvert { background: #e4f1e6; color: #2f6b3c; }

/* Fermé par un administrateur — l'ambre de .vp-tableau-stat-alerte, et
   jamais du rouge : ce n'est pas une panne, c'est une décision, et elle
   se lève d'un clic. Elle se distingue ainsi du gris de « à développer »
   et de « accès non autorisé », qui, eux, ne se lèvent pas d'ici. */
.vp-module-etat-ferme { background: #fdf1d8; color: #9a6700; }

/* Le lien « Découvrir ce module » d'une carte non attribuée (section 126).
   AUCUN `display` ICI : il porte un `dmx-show`, et App Connect ecrit son
   `display` en clair — les deux s'ecraseraient (section 38). Marine, le
   lien d'une offre, jamais l'ambre d'une intervention (section 13). */
.vp-module-decouvrir {
  margin-left: .5rem;
  font-size: .78rem;
  font-weight: 600;
  color: #1a3a5c;
  text-decoration: none;
  border-bottom: 1px dotted #1a3a5c;
}
.vp-module-decouvrir:hover { color: #0f2740; border-bottom-style: solid; }
.vp-module-decouvrir .fas { margin-right: .3rem; }

.vp-accueil-decouvrir { margin: .75rem 0 0; font-size: .88rem; }

@keyframes vp-module-entree {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vp-module { animation: none; transition: none; }
  .vp-module::before { display: none; }
  .vp-module-ouvert:hover { transform: none; }
  .vp-module-ouvert:hover .vp-module-icone { transform: none; }
  .vp-resume-barre > span { transition: none; }
}


/* =================================================================
   ACCUEIL — LA CARTE DU RACCOURCI DE GESTION

   Elle vit dans la meme grille que les modules et ne doit pas se lire
   comme l'un d'eux : le bandeau de tete compte « 4 modules ouverts sur
   7 », et une cinquieme carte d'apparence identique se lirait comme une
   erreur d'affichage (CLAUDE.md sections 34 et 45).

   D'ou le fond et le liseré marine clair, et la pastille « Raccourci »
   au pied : ce n'est pas un module, c'est un chemin vers ce qu'on a
   deja.
   ================================================================= */

.vp-module-raccourci {
  background: linear-gradient(160deg, #f4f8fc 0%, #eaf1f8 100%);
  border-color: #cfdded;
}

.vp-module-raccourci .vp-module-icone {
  background: linear-gradient(180deg, #3d7ab5 0%, #2b5b8c 100%);
}

.vp-module-etat-raccourci { background: #dfeaf5; color: #234c76; }


/* =================================================================
   /gestion — LES OUTILS, RANGES PAR MODULE

   Une section par module, puis ses outils en cartes. Chaque carte porte
   le sous-titre du catalogue : c'est ce qui permet de choisir sans
   ouvrir, la ou un onglet n'a la place que d'un titre.
   ================================================================= */

.vp-gestion-groupe { margin-bottom: 1.6rem; }

.vp-gestion-module {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 .7rem;
  font-size: 1rem;
  font-weight: 700;
  color: #16283c;
}

.vp-gestion-module a { color: inherit; text-decoration: none; }
.vp-gestion-module a:hover { color: #2b5b8c; text-decoration: underline; }

.vp-gestion-module-icone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  flex: 0 0 auto;
  border-radius: .5rem;
  font-size: .85rem;
  color: #fff;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
}

/* LES SECTIONS D'UN MODULE, une par famille.

   Le decoupage est celui du sous-menu « Gestion », et son titre en
   reprend la forme : petites capitales grises, la meme graisse et le
   meme interlettrage que .vp-sousmenu-famille. Deux dessins pour le
   meme decoupage auraient fait douter que ce soit le meme. */

/* SEULES LES SECTIONS SUIVANTES RESPIRENT AU-DESSUS. La premiere n'a
   rien a separer du titre du module, et l'espace y creuserait un trou.
   `+` plutot que `:not(:first-child)` : App Connect laisse ses propres
   noeuds dans une liste repetee, et un selecteur de position absolue
   pourrait viser l'un d'eux. */
.vp-gestion-section + .vp-gestion-section { margin-top: .85rem; }

.vp-gestion-famille {
  margin: 0 0 .35rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #97a1ac;
}

/* LA LARGEUR D'UNE CARTE DECIDE DE LA LISIBILITE DU DECOUPAGE.

   Les familles sont presque toutes des PAIRES — les marques, les
   segmentations, les capsules, les aliments. A 19rem la piste tombait a
   trois colonnes sur un ecran ordinaire : chaque paire laissait donc un
   tiers de rangee vide, et le decoupage se lisait comme des trous
   plutot que comme des sections. A 24rem, une paire remplit sa rangee.

   Et la carte y gagne par ailleurs : son sous-titre est une PHRASE, pas
   une etiquette. A 19rem il tombait sur trois lignes ; il en prend deux
   ici, et la page raccourcit MALGRE les titres ajoutes.

   `min(100%, ...)` et non `24rem` seul : une piste plus large que son
   conteneur deborde au lieu de se replier, et l'on ne s'en apercoit
   qu'a la fenetre etroite, ou personne ne regarde. */
.vp-gestion-outils {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 24rem), 1fr));
  gap: .6rem;
}

.vp-gestion-outil {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  padding: .7rem .85rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .6rem;
  text-decoration: none;
  transition: transform .15s cubic-bezier(.2, .8, .2, 1),
              box-shadow .15s, border-color .15s;
}

.vp-gestion-outil:hover {
  transform: translateY(-2px);
  border-color: #b9cde0;
  box-shadow: 0 6px 16px rgba(16, 32, 50, .1);
  text-decoration: none;
}

.vp-gestion-outil-icone {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  border-radius: .5rem;
  font-size: .85rem;
  color: #2b5b8c;
  background: #eef3f8;
}

.vp-gestion-outil-texte { display: block; min-width: 0; }

.vp-gestion-outil-titre {
  display: block;
  font-size: .92rem;
  font-weight: 600;
  color: #16283c;
  line-height: 1.3;
}

.vp-gestion-outil-sous {
  display: block;
  margin-top: .15rem;
  font-size: .76rem;
  color: #6b7580;
  line-height: 1.35;
}

@media (prefers-reduced-motion: reduce) {
  .vp-gestion-outil { transition: none; }
  .vp-gestion-outil:hover { transform: none; }
}


/* =================================================================
   LEGENDE SOUS UN TABLEAU
   Le mode d'emploi des colonnes. Pleine largeur : la classe
   vp-champ-aide, dont elle heritait, borne a 22 rem — utile sous un
   selecteur, absurde pour un paragraphe.
   ================================================================= */

.vp-legende {
  width: 100%;
  max-width: none;
  padding: .7rem .9rem;
  margin-bottom: 1rem;
  background: #f7f8fa;
  border-left: 3px solid #b9cde0;
  border-radius: 0 .4rem .4rem 0;
  font-size: .8rem;
  line-height: 1.55;
  color: #5c6670;
}

.vp-legende strong { color: #16283c; }


/* =================================================================
   TABLEAU DE BORD DES CAPSULES SENSATIONS

   Graphiques en SVG ecrit a la main : beignet par stroke-dasharray,
   courbe par polyline. Aucune bibliotheque exterieure — le projet ne
   charge que Bootstrap, Font Awesome et App Connect, et un graphique
   qui depend d'un CDN ne s'affiche plus hors ligne.

   Toute la geometrie vient de lib/metier/vue.js.
   ================================================================= */

.vp-bord {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 1rem;
  align-items: start;
  margin-bottom: 1rem;
}

.vp-carte {
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .8rem;
  padding: 1.1rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
}

/* Les blocs a lire d'un trait prennent toute la largeur */
.vp-carte-couleurs,
.vp-carte-absentes,
.vp-carte-mouvement { grid-column: 1 / -1; }

.vp-carte-titre {
  margin: 0 0 .9rem;
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: #6b7580;
}

.vp-carte-note {
  margin: .9rem 0 0;
  font-size: .78rem;
  line-height: 1.5;
  color: #8a929c;
}

.vp-carte-note strong { color: #16283c; }

/* --- Beignet de couverture --- */

.vp-beignet { position: relative; width: 12rem; max-width: 100%; margin: 0 auto; }
.vp-beignet svg { display: block; width: 100%; height: auto; }

.vp-beignet-fond {
  fill: none;
  stroke: #eceff2;
  stroke-width: 12;
}

.vp-beignet-trait {
  fill: none;
  stroke: url(#grad_courbe);
  stroke: #2b5b8c;
  stroke-width: 12;
  stroke-linecap: round;
  transition: stroke-dasharray .9s cubic-bezier(.2, .8, .2, 1);
}

.vp-beignet-centre {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.vp-beignet-chiffre {
  font-size: 2.1rem;
  font-weight: 700;
  line-height: 1;
  color: #16283c;
}

.vp-beignet-chiffre span { font-size: 1.1rem; color: #6b7580; margin-left: .1rem; }

.vp-beignet-detail {
  margin-top: .3rem;
  font-size: .74rem;
  color: #6b7580;
  max-width: 8rem;
}

/* --- Comparaisons POP / YOY --- */

.vp-compare {
  display: grid;
  grid-template-columns: 3rem 1fr;
  grid-template-rows: auto auto;
  column-gap: .7rem;
  padding: .6rem 0;
  border-bottom: 1px solid #eceff2;
}

.vp-compare:last-of-type { border-bottom: 0; }

.vp-compare-cle {
  grid-row: 1 / 3;
  align-self: center;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .06em;
  color: #fff;
  background: #6b7580;
  border-radius: .3rem;
  text-align: center;
  padding: .2rem 0;
}

.vp-compare-valeur {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.1;
  color: #6b7580;
}

.vp-compare-valeur.est-hausse { color: #2f6b3c; }
.vp-compare-valeur.est-baisse { color: #b02a37; }

.vp-compare-note { font-size: .74rem; color: #8a929c; }

/* --- Courbe de tendance --- */

.vp-courbe svg { display: block; width: 100%; height: 5.5rem; }

.vp-courbe-trait {
  fill: none;
  stroke: #2b5b8c;
  stroke-width: 2;
  /* Sans cela, preserveAspectRatio="none" etire aussi l'epaisseur */
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.vp-courbe-aire { fill: url(#grad_courbe); stroke: none; }

.vp-courbe-point {
  fill: #fff;
  stroke: #2b5b8c;
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.vp-courbe-bornes,
.vp-courbe-legende {
  display: flex;
  justify-content: space-between;
  font-size: .7rem;
  color: #8a929c;
}

.vp-courbe-bornes { margin-top: .2rem; }

.vp-courbe-legende {
  margin-top: .5rem;
  gap: .15rem;
  font-variant-numeric: tabular-nums;
}

.vp-courbe-legende span { cursor: help; }

/* --- Repartition par couleur --- */

.vp-couleur { margin-bottom: .75rem; }
.vp-couleur:last-of-type { margin-bottom: 0; }

.vp-couleur-tete {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: .25rem;
  font-size: .82rem;
}

.vp-couleur-pastille {
  width: .8rem;
  height: .8rem;
  border-radius: 50%;
  flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18);
}

.vp-couleur-nom { font-weight: 600; color: #16283c; min-width: 5rem; }

.vp-couleur-compte {
  font-size: .7rem;
  font-weight: 600;
  padding: .1rem .4rem;
  border-radius: 1rem;
  background: #eceff2;
  color: #6b7580;
  cursor: help;
}

.vp-couleur-compte.est-complete { background: #e4f1e6; color: #2f6b3c; }

.vp-couleur-produits { margin-left: auto; color: #8a929c; font-size: .76rem; }

.vp-couleur-barre {
  height: .5rem;
  background: #f1f3f5;
  border-radius: 1rem;
  overflow: hidden;
}

.vp-couleur-barre > span {
  display: block;
  height: 100%;
  border-radius: 1rem;
  transition: width .8s cubic-bezier(.2, .8, .2, 1);
}

/* --- Mouvement et absences --- */

.vp-mouvement { margin-bottom: .8rem; }
.vp-mouvement:last-of-type { margin-bottom: 0; }

.vp-mouvement-cle {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .76rem;
  font-weight: 600;
  margin-bottom: .35rem;
}

.vp-mouvement-cle.est-hausse { color: #2f6b3c; }
.vp-mouvement-cle.est-baisse { color: #b02a37; }

.vp-puces { display: flex; flex-wrap: wrap; gap: .35rem; }

.vp-puce {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .22rem .55rem;
  border-radius: 1rem;
  background: #f4f6f8;
  border: 1px solid #e3e6ea;
  font-size: .74rem;
  color: #16283c;
}

.vp-puce-absente { background: #fff; border-style: dashed; color: #6b7580; }

/* Les deux etats que porte une proposition de brasserie. Les couleurs
   sont celles de .vp-alerte est-succes et est-attention : deux jeux de
   verts et d'ambres dans la meme application se liraient comme deux
   applications. */
.vp-puce.est-sur   { background: #f3f9f4; border-color: #cfe6d8; color: #1f5132; }
.vp-puce.est-doute { background: #fdf8f0; border-color: #ecd9b8; color: #7a5312; }

/* --- UN BLOC, SA LISTE, SA PREUVE REPLIEE ---------------------------
   Deux ecrans portent cet idiome, et pour la meme raison : un mur de
   cartes produit par produit ne se lit pas, un bloc qui NOMME ce que
   ses lignes ont en commun se lit.

     « Marques a associer »      un bloc par brasserie proposee
     le briefing d'une visite    un bloc par marque qui bouge

   Ils ne partagent aucun code — seulement cette forme. La recopier
   aurait donne deux familles de classes pour un meme dessin, et la
   seconde aurait fini par ne plus s'aligner sur la premiere. */

.vp-lot-entete {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem .75rem;
}

.vp-lot-portee { font-size: .78rem; color: #6b7580; }

.vp-lot-produits { list-style: none; margin: .85rem 0 0; padding: 0; }

.vp-lot-produit {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  padding: .45rem 0;
  border-top: 1px solid #eef1f4;
}

.vp-lot-produit:first-child { border-top: 0; }

/* La case ne retrecit pas quand le libelle est long : sans cela elle
   devient une fente de deux pixels qu'on vise mal. */
.vp-lot-case { flex: 0 0 auto; margin-top: .25rem; }

.vp-lot-corps { flex: 1 1 auto; min-width: 0; }

.vp-lot-ligne {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem .6rem;
  font-size: .86rem;
}

/* On compare un code chiffre a chiffre avec une etiquette : deux
   suites voisines se confondent en chasse proportionnelle. */
.vp-lot-cup { font-size: .78rem; color: #55606c; }

.vp-lot-chiffres { flex: 0 0 auto; font-size: .76rem; color: #6b7580; white-space: nowrap; }

.vp-lot-preuve { margin-top: .2rem; }

.vp-lot-preuve > summary {
  font-size: .76rem;
  color: #2b5b8c;
  cursor: pointer;
  list-style: none;
}

.vp-lot-preuve > summary::-webkit-details-marker { display: none; }

.vp-lot-preuve > summary::before {
  content: "\f0da";
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  margin-right: .35rem;
  font-size: .7rem;
}

.vp-lot-preuve[open] > summary::before { content: "\f0d7"; }

.vp-lot-preuve > * { margin-left: 1rem; }

.vp-lot-pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .85rem;
}

.vp-lot-bascule { font-size: .78rem; text-decoration: none; padding: .25rem .4rem; }

/* --- Le briefing d'une visite : ce qui s'y ajoute -------------------

   LE CHIFFRE DIT SA NATURE PAR SA COULEUR, et les deux ne
   s'additionnent jamais : la perte est un FAIT mesure ici, le potentiel
   une ESTIMATION prise chez les pairs. Les peindre pareil ferait lire
   deux grandeurs differentes comme une seule.

   L'AMBRE ET NON LE ROUGE : ce qui recule demande une intervention, il
   n'est pas en panne (section 13). Le rouge de cette application dit
   qu'une lecture a echoue. */

.vp-constat-chiffre {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.15;
  font-size: .95rem;
  font-weight: 700;
  white-space: nowrap;
  margin-left: auto;
}

.vp-constat-chiffre.est-perte     { color: #7a5312; }
.vp-constat-chiffre.est-potentiel { color: #2b5b8c; }

/* L'ETIQUETTE DIT DE QUOI LE MONTANT EST LE MONTANT — « perdu ici »,
   « chez ses pairs » — et c'est elle qui a permis a la phrase de cesser
   de le repeter. Deux nombres cote a cote sans etiquette se lisent comme
   deux nombres comparables. */
.vp-constat-libelle {
  display: block;
  font-size: .68rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #8b96a2;
}

/* La phrase EST le constat : c'est elle qu'on emporte, et elle se lit
   avant les chiffres qui la justifient. */
.vp-constat-phrase {
  margin: .5rem 0 0;
  font-size: .9rem;
  color: #16283c;
  line-height: 1.45;
}

.vp-constat-section {
  font-size: .82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #55606c;
  margin: 1.4rem 0 .6rem;
}

.vp-constat-section:first-of-type { margin-top: 1rem; }

/* LE RESTE NE DISPARAIT PAS, IL SE REPLIE — et le compte total est dit
   au-dessus : une troncature muette se lirait comme une liste complete
   (sections 13 et 29). */
.vp-constat-reste > summary {
  font-size: .82rem;
  color: #2b5b8c;
  cursor: pointer;
  list-style: none;
  padding: .35rem 0;
}

.vp-constat-reste > summary::-webkit-details-marker { display: none; }

.vp-constat-reste > summary::before {
  content: "\f0da";
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  margin-right: .35rem;
  font-size: .72rem;
}

.vp-constat-reste[open] > summary::before { content: "\f0d7"; }

/* --- Jauge d'association --- */

.vp-jauge-barre {
  height: .7rem;
  background: #f1f3f5;
  border-radius: 1rem;
  overflow: hidden;
  margin-bottom: .5rem;
}

.vp-jauge-barre > span {
  display: block;
  height: 100%;
  border-radius: 1rem;
  background: linear-gradient(90deg, #2b5b8c, #4d90cd);
  transition: width .9s cubic-bezier(.2, .8, .2, 1);
}

.vp-jauge-chiffres { font-size: .82rem; color: #5c6670; }
.vp-jauge-chiffres strong { color: #16283c; font-size: 1rem; }

@media (prefers-reduced-motion: reduce) {
  .vp-beignet-trait,
  .vp-couleur-barre > span,
  .vp-jauge-barre > span { transition: none; }
}


/* =================================================================
   TROIS REPARTITIONS COTE A COTE — couleur, sensation, capsule
   Sur bureau elles se lisent ensemble : on compare une couleur a une
   sensation d'un coup d'oeil. Sous 62 rem, elles s'empilent.
   ================================================================= */

.vp-carte-repartition { grid-column: auto; }

@media (min-width: 62rem) {
  .vp-bord { grid-template-columns: repeat(3, 1fr); }
  .vp-carte-couverture   { grid-column: span 1; }
  .vp-carte-comparaisons { grid-column: span 1; }
  .vp-carte-tendance     { grid-column: span 1; }
  .vp-carte-repartition  { grid-column: span 1; }
  .vp-carte-mouvement,
  .vp-carte-absentes,
  .vp-carte-association  { grid-column: 1 / -1; }
}

/* La liste des 63 capsules defile dans sa carte : sans hauteur bornee,
   elle ferait trois fois la hauteur de ses deux voisines. */
.vp-capsules {
  max-height: 22rem;
  overflow-y: auto;
  padding-right: .3rem;
}

.vp-capsule {
  display: grid;
  grid-template-columns: 1.6rem 1fr 3.5rem 2rem;
  align-items: center;
  gap: .4rem;
  padding: .18rem 0;
  font-size: .76rem;
}

.vp-capsule-nom { color: #16283c; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.vp-capsule-barre {
  height: .4rem;
  background: #f1f3f5;
  border-radius: 1rem;
  overflow: hidden;
}

.vp-capsule-barre > span { display: block; height: 100%; border-radius: 1rem; }

.vp-capsule-compte { text-align: right; color: #6b7580; font-variant-numeric: tabular-nums; }

/* --- La vignette de la capsule ---

   L'image, et non la pastille seule : les neuf sensations d'une couleur
   partagent exactement la même teinte, et c'est la capsule qui est en
   tablette — son nom y est imprimé.

   AUCUN CADRE : le badge est déjà une pastille dessinée, avec son
   contour noir et ses coins arrondis. L'enfermer dans un cercle blanc
   lui coupait les coins et lui ajoutait un second contour.

   object-fit: contain, jamais cover : les 63 fichiers ne sont pas tous
   cadrés pareil, et recadrer couperait précisément le nom qu'on vient y
   lire.

   La source est la VIGNETTE de 64 px, pas l'original de 450 px : rendus
   à 26 px, les 63 visuels du catalogue coûtaient 7,4 Mo au lieu de
   207 Ko. Voir vignettes_capsules.js.

   Sur les capsules ABSENTES, l'image reste en couleur : c'est elle qu'on
   part chercher en tablette. Le pointillé de la puce et le gris du texte
   portent déjà l'absence. */
.vp-capsule-vignette,
.vp-puce-vignette {
  display: block;
  flex: 0 0 auto;
  object-fit: contain;
}

.vp-capsule-vignette { width: 1.6rem; height: 1.6rem; }
.vp-puce-vignette { width: 1.25rem; height: 1.25rem; }

.vp-capsule.est-absente { opacity: .45; }
.vp-capsule.est-absente .vp-capsule-nom { font-style: italic; }

/* Barre des sensations : pas de teinte propre, elles traversent les
   couleurs. Le bleu de l'application les distingue des couleurs. */
.vp-barre-sensation { background: linear-gradient(90deg, #2b5b8c, #4d90cd); }

.vp-hors {
  margin-top: .9rem;
  padding-top: .8rem;
  border-top: 1px solid #eceff2;
}

.vp-hors-titre {
  font-size: .76rem;
  font-weight: 600;
  color: #6b7580;
  margin-bottom: .4rem;
}


/* Portee d'une carte quand elle ignore le filtre en cours */
.vp-carte-portee {
  margin-left: .5rem;
  padding: .1rem .45rem;
  border-radius: 1rem;
  background: #eceff2;
  color: #6b7580;
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: none;
}

/* Carte qui SUIT le filtre, par opposition a celles prefixees « Magasin » */
.vp-portee-filtre { background: #e3edf6; color: #2b5b8c; }


/* =================================================================
   HAUTEUR EGALE POUR LES CARTES D'UNE MEME RANGEE

   align-items: stretch au lieu de start, et chaque carte devient une
   colonne flex : sa note se colle en bas par margin-top auto. Sans cela
   « Par couleur », « Par sensation » et « Capsule par capsule » avaient
   trois hauteurs differentes, et la rangee paraissait bancale.

   La liste des capsules occupe la place restante et defile dedans :
   min-height 0 est indispensable, sinon un enfant flex refuse de
   retrecir sous sa hauteur de contenu et deborde la carte.
   ================================================================= */

.vp-bord { align-items: stretch; }

.vp-carte {
  display: flex;
  flex-direction: column;
}

.vp-carte-note { margin-top: auto; padding-top: .9rem; }

.vp-capsules {
  flex: 1 1 auto;
  min-height: 8rem;
  max-height: none;
}

/* Le beignet reste centre verticalement dans une carte etiree */
.vp-carte-couverture .vp-beignet { margin-top: auto; margin-bottom: auto; }


/* =================================================================
   CHIFFRE DE TETE D'UNE CARTE, BORNES, ECHELLE RESEAU
   ================================================================= */

.vp-kpi {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  flex-wrap: wrap;
  margin-bottom: .6rem;
  padding-bottom: .6rem;
  border-bottom: 1px solid #eceff2;
}

.vp-kpi-chiffre {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
  color: #16283c;
}

.vp-kpi-pct { font-size: 1.1rem; color: #6b7580; margin-left: .05rem; }
.vp-kpi-unite { font-size: .78rem; color: #6b7580; }

.vp-bornes {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  margin-top: .6rem;
  padding-top: .55rem;
  border-top: 1px solid #eceff2;
  font-size: .74rem;
  color: #8a929c;
}

/* --- Echelle du reseau --- */

.vp-echelle { margin: .3rem 0 .7rem; }

.vp-echelle-piste {
  position: relative;
  height: .5rem;
  border-radius: 1rem;
  background: linear-gradient(90deg, #eceff2, #c9d6e2);
}

.vp-echelle-mediane {
  position: absolute;
  top: -.2rem;
  width: 2px;
  height: .9rem;
  background: #6b7580;
  transform: translateX(-1px);
}

.vp-echelle-moi {
  position: absolute;
  top: -.25rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: #2b5b8c;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(16, 32, 50, .3);
  transform: translateX(-.5rem);
  transition: left .8s cubic-bezier(.2, .8, .2, 1);
}

.vp-echelle-bornes {
  display: flex;
  justify-content: space-between;
  margin-top: .35rem;
  font-size: .7rem;
  color: #8a929c;
}

.vp-reseau-verdict {
  font-size: .82rem;
  font-weight: 600;
  color: #6b7580;
}

.vp-reseau-verdict.est-hausse { color: #2f6b3c; }

@media (prefers-reduced-motion: reduce) {
  .vp-echelle-moi { transition: none; }
}


/* =================================================================
   LA LISTE DES CAPSULES NE DICTE PLUS LA HAUTEUR DE SA RANGEE

   flex-basis 0 : la liste ne compte pas dans la hauteur NATURELLE de sa
   carte. La rangee se cale donc sur la plus haute des deux autres —
   « Par sensation », neuf lignes — et la liste occupe ensuite la place
   obtenue, en defilant.

   Avec flex-basis auto, ses 43 lignes imposaient leur hauteur aux trois
   cartes et la rangee faisait trois ecrans.

   min-height: 0 reste indispensable : sans lui un enfant flex refuse de
   retrecir sous sa hauteur de contenu et deborde.
   ================================================================= */

.vp-capsules {
  flex: 1 1 0;
  min-height: 0;
  max-height: none;
  overflow-y: auto;
}


/* Les trois repartitions ont leur propre grille : la grille generale les
   separait des qu'on ajoutait une carte plus haut. */
.vp-repartitions {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  align-items: stretch;
}

@media (min-width: 62rem) {
  .vp-repartitions { grid-template-columns: repeat(3, 1fr); }
}


/* =================================================================
   MATRICE 7 COULEURS x 9 SENSATIONS

   Les 63 cases du catalogue. Ce que « 20 capsules absentes » dit en
   liste, la matrice le montre : une ligne ou une colonne claire signale
   d'un coup d'oeil ou l'assortiment est mince.

   Trois paliers d'opacite plutot qu'un degrade continu : sur des carres
   de 1 rem, un degrade ne se distingue pas.
   ================================================================= */

.vp-matrice { font-size: .7rem; }

.vp-matrice-entete,
.vp-matrice-ligne {
  display: grid;
  grid-template-columns: 7.5rem repeat(9, 1fr);
  gap: .18rem;
  align-items: center;
}

.vp-matrice-entete { margin-bottom: .3rem; }

.vp-matrice-sens {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  height: 4.2rem;
  font-size: .62rem;
  color: #6b7580;
  white-space: nowrap;
  justify-self: center;
}

.vp-matrice-ligne { margin-bottom: .18rem; }

.vp-matrice-couleur {
  display: flex;
  align-items: center;
  gap: .3rem;
  overflow: hidden;
}

.vp-matrice-nom {
  color: #16283c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vp-matrice-compte {
  margin-left: auto;
  font-size: .62rem;
  font-weight: 600;
  padding: .05rem .3rem;
  border-radius: 1rem;
  background: #eceff2;
  color: #6b7580;
}

.vp-matrice-compte.est-complete { background: #e4f1e6; color: #2f6b3c; }

.vp-matrice-case {
  aspect-ratio: 1;
  border-radius: .2rem;
  cursor: help;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1);
}

/* Une case pleine s'assombrit avec le nombre de produits */
.vp-matrice-case.palier-1 { opacity: .38; }
.vp-matrice-case.palier-2 { opacity: .68; }
.vp-matrice-case.palier-3 { opacity: 1; }

.vp-matrice-case.est-vide {
  background: transparent;
  box-shadow: inset 0 0 0 1px #dfe3e8;
}

/* ===============================================================
   VUE D'ENSEMBLE DES VENTES — /ventes/bieres/periode

   Reprend le vocabulaire deja pose pour le tableau de bord des
   capsules : .vp-bord, .vp-carte, .vp-kpi, .vp-compare, .vp-beignet.
   Ce qui suit n'ajoute que ce qui lui manquait — les alertes, le
   beignet a plusieurs parts, et la liste par segmentation.
   =============================================================== */

/* Quatre chiffres de tete tiennent sur une ligne en grand ecran, et se
   replient d'eux-memes en dessous. Une largeur mini plus basse que
   .vp-bord : ces cartes portent un chiffre, pas un graphique. */
.vp-bord-kpi { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }

/* La carte des rapports a deposer se signale en AMBRE, jamais en rouge :
   un rapport qui n'est pas encore arrive est une chose a faire, pas une
   baisse de ventes. Le rouge reste aux baisses et aux pannes (section 13). */
.vp-carte-manque {
  border-color: #e0c37a;
  background: linear-gradient(180deg, #fdf8ec 0%, #fff 60%);
}

.vp-kpi-chiffre.est-hausse { color: #2f6b3c; }
.vp-kpi-chiffre.est-baisse { color: #b02a37; }
.vp-kpi-chiffre.est-attente { color: #7a5200; }

/* Un montant tient plus de place qu'un pourcentage : sans cela
   « 62 621,58 » debordait du centre du beignet. */
.vp-beignet-montant { font-size: 1.35rem; }

/* --- Beignet a plusieurs parts --- */

.vp-part {
  fill: none;
  stroke-width: 12;
  transition: stroke-dasharray .9s cubic-bezier(.2, .8, .2, 1);
}

.vp-legende-parts {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  margin-top: 1rem;
  font-size: .78rem;
  color: #4a5560;
}

.vp-legende-part { display: inline-flex; align-items: center; gap: .35rem; }
.vp-legende-pct { color: #8a929c; font-variant-numeric: tabular-nums; }

/* --- Ce qui merite votre attention --- */

.vp-alertes { grid-column: 1 / -1; margin-bottom: 1rem; }

.vp-alerte {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  column-gap: .8rem;
  padding: .75rem .9rem;
  margin-bottom: .5rem;
  border-radius: .5rem;
  /* Le liseré gauche porte la gravité : quatre teintes de fond pleines
     donneraient un écran bariolé où plus rien ne ressort. */
  border-left: 3px solid #c8ced5;
  background: #f6f8fa;
}

.vp-alerte:last-child { margin-bottom: 0; }

.vp-alerte-icone { font-size: 1rem; line-height: 1.35; color: #6b7580; }

.vp-alerte-titre {
  font-weight: 650;
  font-size: .92rem;
  color: #16283c;
  margin-bottom: .1rem;
}

.vp-alerte-texte { font-size: .82rem; line-height: 1.5; color: #55606c; }

/* L'action est ce qui distingue une alerte d'un constat : elle se
   detache du texte au lieu de s'y fondre. */
.vp-alerte-action {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  margin-top: .4rem;
  font-size: .8rem;
  font-weight: 600;
  color: #2b5b8c;
}

.vp-alerte-action i { font-size: .7rem; }

.vp-alerte.est-critique { border-left-color: #b02a37; background: #fdf3f4; }
.vp-alerte.est-critique .vp-alerte-icone { color: #b02a37; }

.vp-alerte.est-attention { border-left-color: #c8811a; background: #fdf8f0; }
.vp-alerte.est-attention .vp-alerte-icone { color: #c8811a; }

.vp-alerte.est-information { border-left-color: #2b5b8c; background: #f3f7fb; }
.vp-alerte.est-information .vp-alerte-icone { color: #2b5b8c; }

.vp-alerte.est-succes { border-left-color: #2f6b3c; background: #f3f9f4; }
.vp-alerte.est-succes .vp-alerte-icone { color: #2f6b3c; }

/* --- Pastille d'etat dans un tableau --- */

.vp-etat {
  display: inline-block;
  padding: .16rem .55rem;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 650;
  line-height: 1.35;
  white-space: nowrap;
  border: 1px solid transparent;
}

.vp-etat.est-succes    { color: #2f6b3c; background: #f3f9f4; border-color: #cfe3d4; }
.vp-etat.est-attention { color: #c8811a; background: #fdf8f0; border-color: #ecdcbf; }
.vp-etat.est-neutre    { color: #6b7684; background: #f4f6f8; border-color: #e2e6ea; }
/* Ce qui vaut pour tout le reseau, et non pour ce seul magasin — la
   portee GLOBALE d'une categorie. Bleu et non ambre : ce n'est pas une
   alerte, c'est une portee, et l'ambre est reserve a ce qui demande une
   intervention (CLAUDE.md section 13). */
.vp-etat.est-reseau    { color: #2b5c8a; background: #f2f7fb; border-color: #d2e2ef; }

/* Un produit reclasse depuis la fenetre des produits d'une marque. La
   ligne RESTE, grisee : elle dit ce qu'on vient de faire et garde la
   place qu'on lisait — recharger la liste la faisait remonter en tete.
   Sa cellule de geste porte la marque d'arrivee en pastille, et le
   bouton est parti avec : un second clic ecrirait deux fois. */
tr.est-reclassee > td { color: #6b7684; }

/* --- Liste par segmentation --- */

.vp-carte-segments { grid-column: span 2; }

.vp-segment {
  display: grid;
  grid-template-columns: minmax(8rem, 1fr) 6.5rem 3.6rem 4.6rem 4.6rem 4.2rem;
  align-items: center;
  gap: .5rem;
  padding: .5rem 0;
  border-bottom: 1px solid #eceff2;
  font-size: .84rem;
}

.vp-segment:last-of-type { border-bottom: 0; }

.vp-segment-entete {
  font-size: .68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #8a929c;
  padding-top: 0;
}

.vp-segment-nom { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.vp-segment-nom small { color: #8a929c; font-size: .72rem; width: 100%; padding-left: 1.1rem; }
.vp-segment-entete .vp-segment-nom { padding-left: 0; }

.vp-segment-vente,
.vp-segment-part,
.vp-segment-pop,
.vp-segment-yoy,
.vp-segment-marge {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.vp-segment-vente { font-weight: 600; color: #16283c; }
.vp-segment-part,
.vp-segment-marge { color: #6b7580; }

.vp-segment-pop,
.vp-segment-yoy { font-weight: 600; color: #8a929c; }
.vp-segment-pop.est-hausse,
.vp-segment-yoy.est-hausse { color: #2f6b3c; }
.vp-segment-pop.est-baisse,
.vp-segment-yoy.est-baisse { color: #b02a37; }
.vp-segment-marge.est-baisse { color: #b02a37; font-weight: 600; }

/* Sur un ecran etroit, la grille a cinq colonnes serre les chiffres au
   point de les rendre illisibles : les compteurs passent dessous. */
@media (max-width: 767.98px) {
  .vp-carte-segments { grid-column: 1 / -1; }
  .vp-segment { grid-template-columns: 1fr 6rem 4rem; }
  .vp-segment-part,
  .vp-segment-entete .vp-segment-part,
  .vp-segment-pop,
  .vp-segment-entete .vp-segment-pop { display: none; }
}

/* --- Mesures secondaires sous un chiffre de tete --- */

.vp-mesures {
  display: flex;
  flex-wrap: wrap;
  gap: 1.2rem;
  margin-top: .9rem;
  padding-top: .8rem;
  border-top: 1px solid #eceff2;
}

.vp-mesure { display: flex; flex-direction: column; }
.vp-mesure-valeur { font-size: 1.05rem; font-weight: 650; color: #16283c; font-variant-numeric: tabular-nums; }
.vp-mesure-libelle { font-size: .72rem; color: #8a929c; }

/* --- Tuile de comparaison : trois mesures, une reference --- */

.vp-ecarts { display: flex; flex-direction: column; }

.vp-ecart {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  column-gap: .8rem;
  padding: .5rem 0;
  border-bottom: 1px solid #eceff2;
}

.vp-ecart:last-child { border-bottom: 0; }

.vp-ecart-libelle { font-size: .78rem; color: #55606c; align-self: center; grid-row: 1 / 3; }

.vp-ecart-pct {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.1;
  text-align: right;
  color: #6b7580;
  font-variant-numeric: tabular-nums;
}

.vp-ecart-pct.est-hausse { color: #2f6b3c; }
.vp-ecart-pct.est-baisse { color: #b02a37; }
.vp-ecart-pct i { font-size: .8rem; }

.vp-ecart-ref { font-size: .72rem; color: #8a929c; text-align: right; font-variant-numeric: tabular-nums; }

/* Titre de carte : le badge de portee ne doit jamais se retrouver seul
   sur une deuxieme ligne. Il arrivait sur « YOY — MÊME PÉRIODE L'AN
   DERNIER » + « P7 2025 », et la tuile decalait ses trois rangees d'une
   ligne : POP et YOY ne s'alignaient plus.

   La hauteur minimale reserve la place de deux lignes pour toutes les
   cartes d'une meme rangee. Les titres restent alignes quelle que soit
   leur longueur — un titre plus long ne repousse plus son contenu. */
.vp-bord-kpi .vp-carte-titre {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .35rem;
  min-height: 2.2rem;
}

.vp-bord-kpi .vp-carte-titre .vp-carte-portee { margin-left: 0; }

/* --- Filtre de l'historique croise --- */
/* Barre du tableau et non cadre de contexte : ce qui decide QUELLES
   lignes garder est un filtre, et vit avec son tableau. Le cadre du haut
   porte ce qui definit les donnees de l'ecran entier (CLAUDE.md §13). */
.vp-histo-filtre {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem;
  margin-bottom: .8rem;
}

.vp-histo-filtre label {
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #6b7580;
  margin: 0;
}

.vp-histo-filtre select { width: auto; min-width: 14rem; }
.vp-histo-filtre-aide { font-size: .74rem; color: #8a929c; }

/* =================================================================
   MON PROFIL — la liste des modules autorises

   L'icone est peinte et espacee par son CONTENEUR, jamais par le <i> :
   dmx-bind:class REMPLACE l'attribut, et « me-3 text-primary » posee
   sur la balise liee disparaissait au profit de « fas fa-beer » — d'ou
   des icones noires collees au titre.

   Pastille de largeur fixe pour que les titres s'alignent : les glyphes
   de Font Awesome n'ont pas tous la meme chasse. Meme forme et memes
   couleurs que la carte de l'accueil : c'est la meme donnee.
   ================================================================= */

.vp-profil-module {
  display: flex;
  align-items: center;
  gap: .85rem;
  padding: .8rem 1rem;
  text-decoration: none;
  color: inherit;
  transition: background .15s;
}

.vp-profil-module + .vp-profil-module { border-top: 1px solid #eef0f3; }

a.vp-profil-module:hover { background: #f5f8fb; text-decoration: none; }

.vp-profil-icone {
  flex: 0 0 2.1rem;
  height: 2.1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .5rem;
  font-size: .95rem;
  color: #fff;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
}

/* min-width: 0 — sans lui, un libelle long deborde du flex au lieu de
   se couper proprement. */
.vp-profil-corps { flex: 1 1 auto; min-width: 0; }

.vp-profil-titre {
  display: block;
  font-weight: 600;
  color: #16283c;
  line-height: 1.35;
}

.vp-profil-pages {
  display: block;
  font-size: .78rem;
  color: #6b7580;
  line-height: 1.4;
}

.vp-profil-fleche { flex: 0 0 auto; color: #b6bec7; font-size: .85rem; }
a.vp-profil-module:hover .vp-profil-fleche { color: #234c76; }

/* Module attribue mais pas encore construit : present, visiblement inerte */
.vp-profil-ferme { background: #fafbfc; }
.vp-profil-ferme .vp-profil-icone { background: #dfe3e8; color: #97a0aa; }
.vp-profil-ferme .vp-profil-titre { color: #6b7580; font-weight: 500; }

.vp-profil-module .vp-module-etat { align-self: center; }


/* =================================================================
   CAPSULES À ASSOCIER — /ventes/bieres/gestion/capsules

   Deux colonnes : la liste des bières à classer, et la matrice qui
   les classe. Le vocabulaire reprend celui de la matrice 7x9 du
   tableau de bord des capsules — mêmes lignes, mêmes colonnes — à une
   différence près, et elle change tout : ici les cases se CLIQUENT.

   D'où des cases plus hautes, un curseur de lien plutôt que d'aide, et
   un état de survol. Une case de 1 rem en `cursor: help` ne se clique
   pas : on ne vise pas ce qu'on ne peut pas attraper.
   ================================================================= */

/* ---------- La liste des bières à classer ---------- */

/* Bornée en hauteur et défilante : la matrice de droite doit rester
   visible pendant qu'on parcourt la liste. Sans cela, choisir le
   vingtième produit obligerait à remonter pour cliquer.

   Au-dessus de lg, cette borne cède la place à celle de la colonne
   voisine — voir `.vp-associer-colonnes` plus bas. */
.vp-attente {
  max-height: 32rem;
  overflow-y: auto;
  margin: 0 -.4rem;
}

/* =================================================================
   LES DEUX COLONNES ONT LA MÊME HAUTEUR

   La liste et la matrice se lisent ensemble : on descend l'une en
   cliquant dans l'autre. Tant que chacune prenait la hauteur de son
   contenu, les deux cartes se fermaient à des niveaux différents et la
   barre de pagination de gauche flottait au milieu du vide.

   C'EST LA MATRICE QUI DONNE LA HAUTEUR, et elle suit la largeur de la
   fenêtre — c'est voulu : ses cases sont ce qu'on vient cliquer. La
   liste défile pour occuper exactement ce qui reste, sa barre de
   pagination reste collée en bas de la carte, et les deux cartes se
   ferment au même niveau quelle que soit la largeur.

   Borner les cases aurait figé cette hauteur, mais au prix d'une
   matrice réduite au tiers du panneau sur un grand écran. On préfère
   une hauteur qui varie à des cases qu'on vise mal.

   `min-height: 0` n'est pas décoratif : sans lui, un enfant défilant
   d'un conteneur flex refuse de rétrécir sous la hauteur de son
   contenu, et la carte déborde au lieu de faire défiler la liste.

   Rien de tout cela au-dessous de lg : les colonnes s'empilent, il n'y
   a plus deux hauteurs à accorder.
   ================================================================= */
@media (min-width: 992px) {
  .vp-associer-colonnes > [class*="col-"] { display: flex; }
  .vp-associer-colonnes > [class*="col-"] > * { flex: 1 1 auto; min-width: 0; }

  .vp-associer-colonnes .vp-carte {
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  .vp-associer-colonnes .vp-attente {
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;
  }
}

/* =================================================================
   ÉCRAN DES CATÉGORIES — les deux colonnes se ferment au même niveau

   C'est la colonne de GAUCHE qui donne la hauteur : le formulaire de
   création, puis la liste des catégories. Les deux panneaux de droite —
   « Contenu » et « Ajouter » — se partagent ce qui reste et défilent
   chacun dans sa carte, au lieu de pousser la page vers le bas.

   C'est l'inverse de `.vp-associer-colonnes`, où la matrice donne la
   hauteur et la liste défile : là-bas ce qu'on vient cliquer est à
   droite, ici c'est à gauche. La mécanique est la même.

   POUR QUE LA GAUCHE COMMANDE, IL FAUT QU'ELLE SOIT LA SEULE À NE PAS
   POUVOIR RÉTRÉCIR. Les panneaux de droite sont en `flex: 1 1 0` avec
   `min-height: 0` : ils acceptent de descendre sous la hauteur de leur
   contenu, donc ils ne pèsent rien dans le calcul, et la rangée prend la
   hauteur de la colonne voisine.

   `min-height: 0` n'est pas décoratif : sans lui, un enfant défilant
   d'un conteneur flex refuse de rétrécir sous la hauteur de son
   contenu, et la carte déborde au lieu de faire défiler la liste.

   La liste des catégories est bornée elle aussi — cent catégories la
   feraient courir sur quatre écrans, et la droite suivrait.

   Rien de tout cela au-dessous de lg : les colonnes s'empilent, il n'y
   a plus deux hauteurs à accorder, et de simples bornes suffisent.
   ================================================================= */
.vp-cat-catalogue,
.vp-cat-liste {
  overflow-y: auto;
  margin: 0 -.2rem;
  padding: 0 .2rem;
}

/* LA BORNE VAUT PARTOUT, Y COMPRIS SOUS LA RÉPARTITION EN FLEX.

   Première version : `max-height: none` au-dessus de lg, en confiant la
   hauteur au seul calcul flex. Le panneau débordait — et il débordait
   SANS ASCENSEUR, ce qui est le pire des deux : la carte s'allongeait
   sous la page au lieu de faire défiler sa liste.

   La cause était une faute de spécificité (voir plus bas), mais la
   leçon dépasse le correctif : une mise en page qui n'a de garde-fou
   que sa propre justesse n'en a pas. La borne reste donc active au-
   dessus de lg, un cran plus haute que la part que le flex attribue
   normalement — elle ne se voit jamais quand tout va bien, et elle
   garde l'ascenseur le jour où la chaîne flex casse. */
.vp-cat-catalogue { max-height: 26rem; }
.vp-cat-liste     { max-height: 20rem; }

@media (min-width: 992px) {
  .vp-cat-colonnes > [class*="col-"] { display: flex; flex-direction: column; }
  .vp-cat-colonnes > [class*="col-"] > * { min-width: 0; }

  /* La pile de droite occupe toute la colonne : c'est elle qui répartit
     les trois cartes. Elle porte le dmx-show de la sélection, donc elle
     doit rester un bloc quand elle est masquée — d'où le sélecteur sur
     la classe et non sur l'enfant direct de la colonne. */
  .vp-cat-droite .vp-cat-pile {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  /* L'en-tête de la catégorie garde sa taille ; les deux panneaux se
     partagent le reste.

     LA CIBLE EST LA CLASSE DE L'EN-TÊTE, PAS « toute carte de la pile ».
     Première version : `.vp-cat-pile > .vp-carte { flex: 0 0 auto }`.
     Les deux panneaux SONT des cartes de la pile, et ce sélecteur à
     trois classes l'emportait sur `.vp-cat-droite .vp-cat-panneau`, qui
     n'en porte que deux. Les panneaux ne pouvaient donc plus rétrécir,
     et « Contenu » débordait sous la page — la règle qui devait les
     faire défiler était écrasée par celle d'à côté, sans que rien ne le
     signale : une feuille de style n'a pas d'erreur, elle a un gagnant. */
  .vp-cat-droite .vp-cat-entete { flex: 0 0 auto; }

  .vp-cat-droite .vp-cat-panneau {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
  }

  /* La liste occupe la part attribuée au panneau, sans jamais dépasser
     sa borne : celle-ci ne se voit pas quand le calcul flex aboutit, et
     garde l'ascenseur quand il n'aboutit pas. */
  .vp-cat-panneau .vp-cat-liste {
    max-height: 24rem;
    flex: 1 1 auto;
    min-height: 0;
  }

  /* La liste des catégories occupe ce qui reste sous le formulaire, sans
     dépasser sa borne : c'est ce qui fixe la hauteur des deux colonnes. */
  .vp-cat-gauche > .vp-carte:last-child {
    display: flex;
    flex-direction: column;
  }
  .vp-cat-gauche .vp-cat-catalogue { max-height: 32rem; }
}

.vp-attente-ligne {
  display: block;
  padding: .5rem .6rem;
  border-radius: .3rem;
  border-left: 3px solid transparent;
  text-decoration: none;
  color: inherit;
}

.vp-attente-ligne:hover { background: #f4f6f8; }

/* La ligne choisie porte la couleur de l'action, pas un simple gris :
   c'est elle que la matrice de droite va classer, et la confusion
   ferait écrire sur le mauvais produit. */
.vp-attente-ligne.est-choisie {
  background: #eaf1fb;
  border-left-color: #2f6fed;
}

.vp-attente-nom {
  font-size: .8rem;
  font-weight: 600;
  color: #16283c;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vp-attente-detail {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  font-size: .68rem;
  color: #6b7580;
  margin-top: .1rem;
}

/* La période d'apparition est le TRI de la liste : elle s'affiche, sinon
   l'ordre serait un ordre auquel on ne peut pas se fier. */
.vp-attente-arrivee {
  font-size: .68rem;
  font-weight: 600;
  color: #2f6fed;
  margin-top: .15rem;
}

/* ---------- Le panneau de choix ---------- */

/* Collé en haut : on descend la liste de gauche sans perdre de vue ce
   sur quoi on clique. Au-dessous de lg, les deux colonnes s'empilent et
   coller n'aurait plus de sens. */
@media (min-width: 992px) {
  .vp-choix-panneau { position: sticky; top: 1rem; }
}

/* ---------- La matrice cliquable ---------- */

.vp-choix { font-size: .7rem; }

/* LES CASES PRENNENT LA LARGEUR DISPONIBLE.

   Elles ont été bornées à 2,4 rem un moment, pour rendre la hauteur de
   la matrice indépendante de l'écran. Le résultat ne tient pas : sur un
   écran large, la matrice n'occupait plus que le tiers du panneau et
   laissait un vide que rien ne justifiait — alors que ces cases sont
   précisément ce qu'on vient cliquer, cinquante fois de suite.

   `1fr` : la matrice suit la largeur de la fenêtre. C'est la colonne de
   gauche qui s'aligne sur elle, pas l'inverse — voir
   `.vp-associer-colonnes`. */
.vp-choix-entete,
.vp-choix-ligne {
  display: grid;
  grid-template-columns: 6.5rem repeat(9, 1fr);
  gap: .2rem;
  align-items: center;
}

.vp-choix-entete { margin-bottom: .35rem; }

.vp-choix-sens {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  height: 4.4rem;
  font-size: .62rem;
  color: #6b7580;
  white-space: nowrap;
  justify-self: center;
}

.vp-choix-ligne { margin-bottom: .2rem; }

.vp-choix-couleur {
  display: flex;
  align-items: center;
  gap: .35rem;
  overflow: hidden;
}

.vp-choix-pastille {
  width: .7rem;
  height: .7rem;
  border-radius: 50%;
  flex: none;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15);
}

.vp-choix-nom {
  color: #16283c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* FOND BLANC DERRIÈRE L'IMAGE.
   La teinte était en fond de case, héritée de l'époque où la case ÉTAIT
   la couleur. Depuis que l'image porte la capsule — et sa couleur, et
   son nom — cette teinte se superpose au blanc du visuel et le salit.
   Le blanc laisse l'image dire ce qu'elle a à dire. */
.vp-choix-case {
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  aspect-ratio: 1;
  border-radius: .25rem;
  cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
  transition: transform .08s ease, box-shadow .08s ease;
}

.vp-choix-case:hover {
  transform: scale(1.14);
  box-shadow: 0 0 0 2px #2f6fed, 0 2px 6px rgba(0, 0, 0, .25);
  position: relative;
  z-index: 2;
}


/* Une combinaison qui ne figure pas au catalogue. Elle tient sa colonne
   — décaler la ligne ferait cliquer à côté — mais ne se clique pas :
   il n'y a aucune capsule à écrire. */
.vp-choix-case.est-absente {
  background: transparent !important;
  box-shadow: inset 0 0 0 1px #dfe3e8;
  cursor: default;
}

.vp-choix-case.est-absente:hover {
  transform: none;
  box-shadow: inset 0 0 0 1px #dfe3e8;
}

/* L'autre sortie : la capsule 99, hors programme. Séparée de la matrice
   par un filet — ce n'est pas une 64e case, c'est un autre geste. */
.vp-choix-hors {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: .9rem;
  padding-top: .8rem;
  border-top: 1px solid #eceff2;
}

/* Caisse mixte soupçonnée, dans la liste. Ambre — c'est un signal, pas
   une erreur : le libellé évoque une caisse mixte, il ne le prouve pas. */
.vp-attente-mixte {
  color: #9a6700;
  font-size: .7rem;
  margin-left: .3rem;
  cursor: help;
}

/* =================================================================
   DEUX ONGLETS DE TRAVAIL — /capsules/gestion/associer

   À ne pas confondre avec la barre d'onglets du module, qui est en
   haut de page et porte des LIENS vers d'autres adresses. Ceux-ci
   restent sur la même page : ils changent le geste, pas l'écran.
   D'où un dessin volontairement plus discret, sous le cadre de
   contexte et au-dessus du contenu qu'ils pilotent.
   ================================================================= */

.vp-onglets {
  display: flex;
  gap: .25rem;
  margin-bottom: .9rem;
  border-bottom: 1px solid #e3e6ea;
}

.vp-onglet {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .45rem .9rem;
  font-size: .82rem;
  font-weight: 600;
  color: #6b7580;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.vp-onglet:hover { color: #16283c; background: #f7f8fa; }

.vp-onglet.est-actif {
  color: #1a3a5c;
  border-bottom-color: #2f6fed;
}

/* Le compte vient du serveur et de la même réponse que la liste : un
   onglet qui annoncerait 72 pour en montrer 40 ne serait plus lu. */
.vp-onglet-compte {
  font-size: .68rem;
  font-weight: 700;
  padding: .05rem .4rem;
  border-radius: 1rem;
  background: #eceff2;
  color: #6b7580;
  font-variant-numeric: tabular-nums;
}

.vp-onglet.est-actif .vp-onglet-compte { background: #e4ecfb; color: #2f6fed; }

/* =================================================================
   LA LISTE SOBEYS — une ligne, une capsule, un clic

   Chaque ligne porte déjà sa réponse : le rôle de la mise en forme est
   de rendre le rapprochement immédiat entre les deux codes lus, à
   gauche, et la capsule qu'ils désignent, à droite.
   ================================================================= */

.vp-sobeys { display: flex; flex-direction: column; }

.vp-sobeys-ligne {
  display: grid;
  grid-template-columns: 4.2rem 1fr auto auto;
  gap: .8rem;
  align-items: center;
  padding: .55rem .6rem;
  border-radius: .35rem;
  border-bottom: 1px solid #f1f3f5;
  text-decoration: none;
  color: inherit;
}

.vp-sobeys-ligne:hover { background: #f7f9fb; }

/* Les codes réellement lus. En monospace et encadrés : ce sont eux qui
   permettent de vérifier ce que l'écran a compris, plutôt que d'avoir
   à le deviner. */
.vp-sobeys-codes {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .72rem;
  font-weight: 700;
  text-align: center;
  padding: .15rem 0;
  border-radius: .25rem;
  background: #eceff2;
  color: #3b4754;
}

.vp-sobeys-produit { overflow: hidden; }

.vp-sobeys-nom {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vp-sobeys-detail {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  font-size: .68rem;
  color: #6b7580;
}

.vp-sobeys-capsule {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .78rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
}

/* ON CLIQUE SUR DES BOUTONS. Le bouton porte son verbe : « Associer »
   dit ce qui va s'ecrire, ce qu'une coche ne disait pas. */
.vp-sobeys-bouton { white-space: nowrap; }

/* Deux boutons dans la même cellule de la grille : « Associer » écrit
   la capsule lue, « Modifier » ouvre le produit dans la matrice pour en
   choisir une autre. Une cellule par bouton aurait décalé la colonne
   de la capsule sur toutes les lignes. */
.vp-sobeys-boutons { display: inline-flex; gap: .4rem; }

/* CHERCHER LA BIÈRE SUR LE WEB. Une petite planète à côté du nom, qui
   garde la couleur de son voisinage : peinte en bleu, elle ferait de
   chaque ligne une page de liens, alors que ce qu'on vient lire est le
   produit. Le lien est composé par le serveur (associations.lienWeb). */
.vp-web {
  display: inline-block;
  margin-left: .35rem;
  font-size: .8em;
  color: #8a94a0;
  text-decoration: none;
  vertical-align: middle;
}
.vp-web:hover, .vp-web:focus { color: #1a3a5c; }

@media (max-width: 575px) {
  .vp-sobeys-ligne { grid-template-columns: 3.6rem 1fr; }
  .vp-sobeys-capsule { grid-column: 1 / -1; }
  .vp-sobeys-boutons { grid-column: 1 / -1; justify-self: start; }
}

/* L'image de la capsule dans la liste Sobeys. Bornée en hauteur pour
   que la ligne garde son gabarit quelles que soient les dimensions du
   fichier — les 63 images ne sont pas toutes calibrées pareil. */
.vp-sobeys-image {
  width: 2.1rem;
  height: 2.1rem;
  object-fit: contain;
  flex: none;
}

/* =================================================================
   LES DERNIÈRES ASSOCIÉES — le rétroviseur

   Même gabarit que la liste Sobeys, et ce n'est pas de l'économie :
   les deux répondent à la même question de lecture — quel produit,
   quelle capsule, un bouton. Les dessiner différemment obligerait à
   réapprendre la ligne en changeant d'onglet.

   Ce qui change est la première colonne. Sobeys y met les deux codes
   lus, qui servent à VÉRIFIER ce que l'écran a compris ; ici c'est le
   moment de l'association, qui sert à SE SITUER — c'est lui qui ordonne
   la liste, et un ordre qu'on ne voit pas est un ordre auquel on ne
   peut pas se fier.
   ================================================================= */

.vp-recentes { display: flex; flex-direction: column; }

.vp-recente-ligne {
  display: grid;
  grid-template-columns: 9.5rem 1fr auto auto;
  gap: .8rem;
  align-items: center;
  padding: .55rem .6rem;
  border-radius: .35rem;
  border-bottom: 1px solid #f1f3f5;
  color: inherit;
}

.vp-recente-ligne:hover { background: #f7f9fb; }

.vp-recente-quand {
  font-size: .68rem;
  color: #6b7580;
  font-variant-numeric: tabular-nums;
}

.vp-recente-produit { overflow: hidden; }

.vp-recente-nom {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vp-recente-detail {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  font-size: .68rem;
  color: #6b7580;
}

.vp-recente-capsule {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-size: .78rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
}

.vp-recente-image {
  width: 2.1rem;
  height: 2.1rem;
  object-fit: contain;
  flex: none;
}

/* LA PLACE DE L'IMAGE, PAS UN APLAT DE COULEUR. La capsule 99 n'a pas
   de visuel : la migration 018 l'a créée sans couleur ni sensation. Une
   pastille de teinte tenait la colonne, et elle doublait la vignette des
   autres lignes ; ce qu'il faut ici n'est pas une couleur, c'est la
   largeur — sans elle, « HORS PROGRAMME » se décale d'une ligne à
   l'autre. Même largeur que la vignette, et rien à voir. */
.vp-recente-place {
  width: 2.1rem;
  flex: none;
}

/* RETIRER EST EN ROUGE SOMBRE, PAS EN AMBRE. L'ambre du projet signale
   ce qui demande une intervention ; ici rien n'en demande — c'est
   l'utilisateur qui décide d'annuler ce qu'il a écrit. Le contour dit
   que le geste défait quelque chose, sans en faire une alerte. */
.vp-recente-bouton { white-space: nowrap; }

@media (max-width: 767px) {
  .vp-recente-ligne { grid-template-columns: 1fr; }
  .vp-recente-bouton { justify-self: start; }
}

/* =================================================================
   L'IMAGE DE LA CAPSULE DANS LA MATRICE

   C'est elle qui est en tablette : on la reconnaît sans avoir à
   déduire quoi que ce soit d'un aplat de couleur.

   `contain` et non `cover` : les 63 fichiers ne sont pas tous
   calibrés pareil, et recadrer couperait le nom de la sensation
   imprimé sur la capsule — précisément ce qu'on vient y lire.

   La teinte reste en fond de case : si un fichier manque, la case
   garde sa couleur plutôt que de devenir un trou blanc au milieu
   de la matrice.
   ================================================================= */
.vp-choix-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* =================================================================
   UNE LIGNE TRAITÉE S'EFFACE SUR PLACE

   Associer ne recharge plus la liste sous le curseur : la ligne cliquée
   se replie, et rien d'autre ne bouge. C'est ce qui permet d'enchaîner
   sans viser à nouveau à chaque clic.

   L'animation collapse la HAUTEUR autant que l'opacité — s'estomper
   sans se replier laisserait un trou blanc, et la ligne suivante ne
   remonterait jamais.

   `pointer-events: none` dès le départ : pendant les 320 ms, la ligne
   est encore là et son bouton encore cliquable. Un second clic
   écrirait deux fois.
   ================================================================= */
.vp-attente-ligne.est-partie,
.vp-sobeys-ligne.est-partie,
.vp-recente-ligne.est-partie {
  animation: vp-partir .32s ease forwards;
  pointer-events: none;
  overflow: hidden;
}

@keyframes vp-partir {
  0%   { opacity: 1; transform: none;              max-height: 8rem; }
  40%  { opacity: .35; transform: translateX(1.5rem); max-height: 8rem; }
  100% { opacity: 0; transform: translateX(2.5rem); max-height: 0;
         padding-top: 0; padding-bottom: 0; margin: 0; border-width: 0; }
}

/* Le mouvement est un confort, pas une information : qui le refuse voit
   la ligne disparaître d'un coup, ce qui dit exactement la même chose. */
@media (prefers-reduced-motion: reduce) {
  .vp-attente-ligne.est-partie,
  .vp-sobeys-ligne.est-partie,
  .vp-recente-ligne.est-partie { animation: none; display: none; }
}

/* =================================================================
   LE BANDEAU DE CONFIRMATION NE POUSSE RIEN

   Posé dans le flux, le message de succès décalait toute la page vers
   le bas à chaque association — et le clic suivant tombait à côté.
   Fixé en bas de fenêtre, il dit la même chose sans rien déplacer.

   Même position que la pastille de chargement, mais au-dessus d'elle :
   les deux ne s'affichent jamais en même temps.
   ================================================================= */
.vp-flash {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: 1090;
  max-width: min(28rem, calc(100vw - 2.5rem));
  padding: .55rem 1rem;
  border-radius: 999px;
  background: #1f5132;
  color: #fff;
  font-size: .82rem;
  font-weight: 500;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .28);
  animation: vp-flash-entree .18s ease;
}

@keyframes vp-flash-entree {
  from { opacity: 0; transform: translateY(.5rem); }
  to   { opacity: 1; transform: none; }
}

/* Le bandeau s'efface après quelques secondes. Sans cela il reste
   jusqu'à l'association suivante, et finit par décrire un produit
   qu'on a quitté depuis longtemps — un message qui ment par retard. */
.vp-flash.est-fanee {
  opacity: 0;
  pointer-events: none;
  transition: opacity .45s ease;
}

/* ============================================================
   MODULE ADMINISTRATION — les deux bandeaux de la vue d'ensemble

   Un CHANTIER est une tache : un chiffre, une phrase, et un lien vers
   l'ecran ou on la fait. Un compteur sans destination obligerait a
   chercher soi-meme ou l'on va.

   Un chantier a zero reste affiche, en gris. C'est l'inverse de la
   regle masquerSiZero des tableaux, et c'est voulu : sur un tableau, un
   compteur a zero encombre une liste qu'on lit deja ; ici, « rien a
   faire de ce cote » est precisement ce qu'un administrateur vient
   verifier.

   L'ambre — la meme que .vp-tableau-stat-alerte — et jamais du rouge :
   ce n'est pas une erreur, c'est du travail en attente.
   ============================================================ */

.vp-chantier {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, box-shadow .15s;
}

.vp-chantier:hover {
  border-color: #c8d0d8;
  box-shadow: 0 2px 8px rgba(16, 32, 50, .08);
  color: inherit;
}

.vp-chantier-valeur {
  font-size: 2rem;
  font-weight: 650;
  line-height: 1.1;
  color: #aeb6be;
  font-variant-numeric: tabular-nums;
}

.vp-chantier.est-a-faire { border-left: 3px solid #c8811a; }
.vp-chantier.est-a-faire .vp-chantier-valeur { color: #9a6700; }

.vp-chantier-etat {
  font-size: .72rem;
  color: #8a929c;
  margin-bottom: .5rem;
}

.vp-chantier-detail {
  font-size: .78rem;
  line-height: 1.5;
  color: #55606c;
  margin-top: auto;
  padding-top: .7rem;
  /* Soixante numeros de magasin : la carte defile plutot que de
     pousser tout le bandeau vers le bas. */
  max-height: 5.4rem;
  overflow-y: auto;
  word-break: break-word;
}

.vp-chantier-aller {
  font-size: .72rem;
  color: #2b5b8c;
  margin-top: .6rem;
}

/* --- Sante technique : des faits, sans action attendue --- */

.vp-sante-lignes { display: flex; flex-direction: column; gap: .35rem; }

.vp-sante-ligne {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: .8rem;
  align-items: baseline;
  font-size: .82rem;
  padding: .2rem 0;
  border-bottom: 1px solid #f2f4f6;
}

.vp-sante-ligne:last-child { border-bottom: 0; }
.vp-sante-cle { color: #55606c; }
.vp-sante-valeur { font-weight: 600; color: #16283c; font-variant-numeric: tabular-nums; }
.vp-sante-appoint { color: #8a929c; font-variant-numeric: tabular-nums; }

/* --- Les derniers refus et erreurs, dans la grille des chantiers ---

   Cette carte detaille le dernier chantier, et elle est posee juste a
   cote de lui : elle a donc la largeur d'une colonne, pas celle de la
   page. Ce qui change tout pour ses lignes — une grille a trois
   colonnes y ecraserait la description, qui est la seule chose qu'on y
   lit vraiment.

   Qui et quand passent donc SOUS la phrase, en petit et en gris. On ne
   compare pas ces dates d'une ligne a l'autre : on lit une phrase, et
   l'on regarde ensuite quand elle a eu lieu.

   La carte peut monter plus haut que les tuiles voisines — cinq
   phrases entieres contre un chiffre. C'est assume : ce sont les
   phrases qu'on vient lire, et .vp-bord etire deja les tuiles a la
   hauteur de leur rangee.
   --- */

.vp-refus-lignes {
  display: flex;
  flex-direction: column;
  gap: .55rem;
}

.vp-refus-ligne {
  padding-bottom: .55rem;
  border-bottom: 1px solid #f2f4f6;
}

.vp-refus-ligne:last-child { border-bottom: 0; padding-bottom: 0; }

.vp-refus-phrase {
  font-size: .82rem;
  line-height: 1.4;
  color: #55606c;
  /* Un message de PostgreSQL porte des identifiants sans espace :
     sans cela, il deborde de la colonne au lieu de se replier. */
  overflow-wrap: anywhere;
}

.vp-refus-appoint {
  font-size: .72rem;
  color: #8a929c;
  margin-top: .15rem;
  font-variant-numeric: tabular-nums;
}

/* =================================================================
   LA CARTE D'UNE FICHE

   Un iframe n'a pas de ratio naturel : sans hauteur explicite il rend
   150 px, la hauteur par defaut du navigateur, et la carte devient une
   bande illisible. aspect-ratio la fait suivre la largeur du bloc, et
   la borne de hauteur l'empeche d'occuper tout l'ecran sur un moniteur
   large — une fiche reste une fiche, pas un atlas.
   ================================================================= */
.vp-carte-plan {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 22rem;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  /* L'attribut frameborder est obsolete : la bordure se retire en CSS. */
  border-width: 1px;
}

@media (max-width: 575.98px) {
  /* Sur telephone, un 16/9 devient une bande trop basse pour situer
     quoi que ce soit : on rend la carte plus carree. */
  .vp-carte-plan { aspect-ratio: 4 / 3; }
}

/* =================================================================
   RAPPORTS ET RESUMES — /ventes/bieres/resumes

   Quatre blocs propres a cet ecran : les vignettes de rapports,
   l'atelier de reglages, l'apercu du courriel, et la confirmation de
   suppression.

   L'APERCU EST DANS UNE IFRAME, et rien ici ne cherche a le decorer :
   ce qu'elle montre doit etre exactement ce que la boite de reception
   montrera. Le cadre exterieur porte la seule bordure ; l'interieur
   n'est pas notre affaire.
   ================================================================= */

/* --- Les vignettes de rapports --- */

.vp-vignettes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: .75rem;
  margin-bottom: 1rem;
}

.vp-vignette {
  display: flex;
  gap: .75rem;
  padding: .9rem 1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  cursor: pointer;
  transition: border-color .12s, box-shadow .12s;
}

.vp-vignette:hover { border-color: #c3ccd6; }

/* La vignette choisie se distingue par sa bordure ET son fond : la
   bordure seule passe inapercue a cote d'une carte survolee. */
.vp-vignette.est-choisie {
  border-color: #1a3a5c;
  background: #f6f9fc;
  box-shadow: inset 3px 0 0 #1a3a5c;
}

.vp-vignette-icone {
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .4rem;
  background: #eef3f8;
  color: #1a3a5c;
  font-size: 1.05rem;
}

.vp-vignette-titre { font-weight: 650; color: #1a3a5c; line-height: 1.3; }

.vp-vignette-texte {
  margin-top: .2rem;
  font-size: .82rem;
  line-height: 1.45;
  color: #6b7684;
}

/* --- L'atelier de reglages --- */

.vp-atelier {
  padding: 1rem 1.1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  margin-bottom: 1rem;
}

.vp-atelier-entete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding-bottom: .8rem;
  border-bottom: 1px solid #eef1f4;
}

.vp-atelier-titre { font-weight: 650; color: #1a3a5c; }

.vp-atelier-texte {
  margin-top: .15rem;
  font-size: .85rem;
  line-height: 1.5;
  color: #6b7684;
  /* 78ch plutot que 60 : la description tient alors en deux lignes au
     lieu de trois, sous un en-tete qui fait toute la largeur de l'ecran.
     Au-dela, la ligne devient trop longue pour se relire sans peine. */
  max-width: 78ch;
}

/* Le lien vers l'ecran d'ou viennent les chiffres. Il n'est pas
   decoratif : c'est la que se verifie ce que le courriel affirme. */
.vp-atelier-lien {
  flex: 0 0 auto;
  font-size: .82rem;
  color: #1a3a5c;
  text-decoration: none;
  white-space: nowrap;
}

.vp-atelier-lien:hover { text-decoration: underline; }

.vp-atelier-champs {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1rem;
  padding: .9rem 0;
}

/* Les trois gestes. « Programmer » est separe par un trait : il n'est
   pas du meme ordre que les deux autres — voir et s'envoyer ne laissent
   rien derriere eux, programmer ecrit une ligne qui partira seule. */
.vp-atelier-gestes {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .6rem;
  padding-top: .8rem;
  border-top: 1px solid #eef1f4;
}

/* LE TRAIT SEPARE ; LA DISTANCE, ELLE, DESUNIT.
   Un `margin-left: auto` renvoyait ce bloc contre le bord droit : sur un
   ecran large, les deux boutons de gauche et le bloc de programmation se
   retrouvaient a un metre l'un de l'autre, alors que la legende au-dessus
   les enonce dans la foulee, de gauche a droite. Le trait vertical dit
   deja qu'ils ne sont pas du meme ordre. */
.vp-atelier-programmer {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .6rem .75rem;
  margin-left: .4rem;
  padding-left: 1.1rem;
  border-left: 1px solid #eef1f4;
}

/* L'aide de la destination : sur sa propre ligne, sous les trois
   controles de la zone, et large comme eux. Le `max-width: 22rem` de
   .vp-champ-aide est fait pour un champ isole ; ici il decouperait la
   phrase en colonne etroite. */
.vp-atelier-destinataire {
  flex: 1 1 100%;
  margin-top: .1rem;
  font-size: .75rem;
  line-height: 1.5;
  color: #8a929c;
}

@media (max-width: 767.98px) {
  /* Sur telephone, la barre verticale et le renvoi a droite ne veulent
     plus rien dire : les gestes s'empilent. */
  .vp-atelier-programmer {
    margin-left: 0;
    padding-left: 0;
    /* rappel : le renvoi a droite n'existe plus, seul le trait reste */
    border-left: none;
    padding-top: .6rem;
    border-top: 1px solid #eef1f4;
    width: 100%;
  }
}

/* --- L'apercu du courriel --- */

.vp-apercu {
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  padding: 1rem 1.1rem;
  margin-bottom: 1.25rem;
}

.vp-apercu-entete {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .8rem;
  border-bottom: 1px solid #eef1f4;
  margin-bottom: .8rem;
}

.vp-apercu-sujet { font-weight: 650; color: #1a3a5c; }

.vp-apercu-couverture {
  margin-top: .15rem;
  font-size: .82rem;
  color: #6b7684;
}

/* Hauteur fixe et defilement interne : le courriel peut etre long, et
   le laisser pousser la page ferait disparaitre les deux tableaux du
   bas sans que rien ne l'annonce. */
.vp-apercu-cadre {
  display: block;
  width: 100%;
  height: 32rem;
  border: 1px solid #e3e6ea;
  border-radius: .4rem;
  background: #f5f5f5;
}

/* --- La confirmation de suppression ---

   Elle NOMME ce qui va disparaitre. « Supprimer la 47 ? » ne permet a
   personne de verifier qu'il vise la bonne ligne. */

.vp-confirmation {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .9rem 1.1rem;
  margin-bottom: .75rem;
  background: #fdf6f6;
  border: 1px solid #eccfd0;
  border-left: 3px solid #b02a37;
  border-radius: .5rem;
}

.vp-confirmation-titre { font-weight: 650; color: #b02a37; }

.vp-confirmation-texte {
  margin-top: .15rem;
  font-size: .85rem;
  line-height: 1.5;
  color: #6b5b5b;
}

.vp-confirmation-gestes { display: flex; gap: .5rem; }

/* --- Titre de section, entre deux tableaux d'une meme page --- */

.vp-section-titre {
  font-size: 1.05rem;
  font-weight: 650;
  color: #1a3a5c;
  margin: 1.5rem 0 .6rem;
}

/* Les boutons d'une cellule d'action restent sur une ligne : replies,
   ils font grandir la ligne et le tableau se lit moins bien. */
.vp-cellule-gestes {
  white-space: nowrap;
  display: flex;
  gap: .35rem;
}

/* --- Explications de l'ecran des resumes ---

   Elles ne sont pas decoratives : les rapports s'adressent a des
   gerants et a du personnel de plancher, pas a des analystes. Une
   vignette qui dit seulement « analyse par plage » n'apprend rien a
   qui n'a jamais vu l'application. */

.vp-intro {
  max-width: 78ch;
  margin: 0 0 .9rem;
  font-size: .9rem;
  line-height: 1.6;
  color: #4a5560;
}

.vp-vignette-note {
  margin-top: .35rem;
  font-size: .76rem;
  font-weight: 600;
  color: #6b7684;
}

/* Rappel de ce que fait chaque bouton, juste au-dessus d'eux. Sur une
   seule ligne : trois phrases empilees se liraient comme un paragraphe
   d'aide qu'on saute. */
.vp-atelier-explique {
  flex: 1 1 100%;
  margin-bottom: .2rem;
  font-size: .82rem;
  line-height: 1.6;
  color: #6b7684;
}

.vp-atelier-explique strong { color: #1a3a5c; }

/* ============================================================
   LE FIL D'UNE ACTION — /operations

   TROIS NATURES DE LIGNE, ET ELLES NE SE LISENT PAS PAREIL.

   Un message est une prise de parole : il prend la forme d'une bulle,
   et celle qu'on a ecrite soi-meme se detache a droite. Un changement
   de statut et une correction ne sont pas des paroles — ce sont des
   FAITS. Ils se rangent au centre, en petit, sans bulle : les mettre
   en bulle ferait croire que quelqu'un les a dits.

   La classe complete est servie par le serveur (lib/metier/actions.js),
   jamais composee dans la page : dmx-bind:class REMPLACE l'attribut, et
   un fragment effacerait le reste.
   ============================================================ */
/* -----------------------------------------------------------------
   LE FIL SE LIT DU PLUS ANCIEN AU PLUS RECENT, L'ASCENSEUR EN BAS

   Demande de Philippe du 2026-10-02, et c'est un renversement : le fil
   s'est lu un temps du plus recent au plus ancien, l'enonce en bas. Au
   bureau, la saisie de la reponse est SOUS le fil : le dernier message
   doit etre juste au-dessus d'elle, comme dans toute conversation.

   `column-reverse` FAIT LES DEUX A LA FOIS. Le serveur sert toujours
   le plus recent en tete (la version mobile le lit ainsi) ; la boite
   l'affiche a l'envers, donc l'enonce en haut. Et dans une boite
   inversee, la position de depart de l'ascenseur EST le bas : la
   fiche s'ouvre sur le dernier message sans une ligne de script, et
   y reste quand le fil se recharge. Un `scrollTop` pose par du code
   apres le rendu d'App Connect arriverait tantot trop tot, tantot
   jamais (section 8).

   L'ORDRE NE S'ANNONCE PLUS : c'est celui qu'on attend d'une
   conversation, et une note qui dit l'evidence est du bruit.
   ----------------------------------------------------------------- */
.vp-provenance-guide {
  display: block;
  font-size: .72rem;
  line-height: 1.35;
  color: #5c6670;
  margin-top: .15rem;
}
.vp-fiche-entete .vp-provenance-guide { margin: .1rem 0 .5rem; }

.vp-fil {
  display: flex;
  flex-direction: column-reverse;
  gap: .55rem;
  max-height: 22rem;
  overflow-y: auto;
  padding: .25rem;
  margin-bottom: .5rem;
}

.vp-fil-ligne { max-width: 82%; }

/* Un message : une bulle, avec son en-tete et son texte.

   CELLES QU'ON A ECRITES SOI-MEME PARTENT A DROITE, en bleu. Sans cela
   il faut lire le nom de chaque ligne pour savoir qui parle ; alignees,
   elles se reconnaissent sans etre lues. La classe complete est servie
   par le serveur, jamais composee dans la page. */
.vp-fil-message {
  background: #f2f5f8;
  border: 1px solid #e3e6ea;
  border-radius: .7rem .7rem .7rem .15rem;
  padding: .45rem .75rem .55rem;
  align-self: flex-start;
}

.vp-fil-moi {
  background: #e7f1ff;
  border-color: #cfe2ff;
  border-radius: .7rem .7rem .15rem .7rem;
  align-self: flex-end;
}

/* L'en-tete : qui, sa fonction, puis quand. La date est poussee au
   bout de la ligne — on la cherche rarement, et jamais avant le nom. */
.vp-fil-entete {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .3rem;
  font-size: .72rem;
  color: #6b7885;
  margin-bottom: .15rem;
}

/* Nom en gras, fonction en italique, magasin, date a la suite — dans
   cet ordre, parce que c'est celui dans lequel on lit une
   conversation : qui parle, a quel titre, d'ou, et quand. Les
   separateurs sont poses par le CSS et non ecrits dans la vue : un
   « · » tape a la main resterait quand la fonction est vide. */
.vp-fil-qui { font-weight: 700; color: #33414f; }

.vp-fil-fonction { font-style: italic; }

/* LE MAGASIN NE SE COUPE PAS. C'est un numero, et « 79 » suivi d'un
   retour a la ligne se lit comme un autre magasin. */
.vp-fil-magasins { white-space: nowrap; }

.vp-fil-quand { white-space: nowrap; }

.vp-fil-entete > span + span::before {
  content: '·';
  margin-right: .3rem;
  color: #aab4be;
}

/* Le texte respecte les retours a la ligne saisis, et coupe les mots
   trop longs plutot que d'elargir la bulle hors du cadre. */
.vp-fil-texte {
  font-size: .88rem;
  color: #1f2b38;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Le motif d'un refus, sous la phrase de statut. */
.vp-fil-motif {
  font-size: .78rem;
  font-style: italic;
  color: #6b7885;
  margin-top: .15rem;
}

/* ============================================================
   LES LIGNES DE L'APPLICATION — ni bulle, ni parole

   Un changement de statut et une correction ne sont pas des messages :
   personne ne les a ecrits, ils sont CONSTATES. Leur donner la forme
   d'une bulle faisait croire a une prise de parole, et le nom de la
   personne y paraissait deux fois — une fois en en-tete, une fois dans
   la phrase.

   Forme propre, donc : une pastille CENTREE, bornee a une largeur de
   lecture, qui ne penche ni a gauche ni a droite. Elle n'appartient a
   personne, c'est tout l'objet.
   ============================================================ */
.vp-fil-statut,
.vp-fil-correction {
  align-self: center;
  max-width: 88%;
  text-align: center;
  /* AUCUN FOND, AUCUN CADRE — et c'est ce qui les distingue.
     Une bulle a un fond parce que quelqu'un l'a ecrite ; une annonce
     de Velocite+ n'en a pas. La difference se voit avant meme d'avoir
     lu, et c'est tout ce qu'on lui demande. */
  background: none;
  border: 0;
  padding: .35rem 0 .3rem;
  font-size: .84rem;
  color: #33414f;
}

/* Qui parle : l'application. En petites capitales, comme le repere de
   l'enonce — c'est une etiquette, pas une phrase. */
.vp-fil-source {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #7a8794;
  margin-bottom: .25rem;
}

/* UN REGLEMENT ET UN REFUS FERMENT TOUS LES DEUX L'ACTION, mais l'un a
   ete fait et l'autre a ete decline. Les peindre pareil obligeait a lire
   la phrase pour savoir lequel des deux on regardait.

   LE ROUGE MARQUE ICI UNE DECISION, PAS UNE ALERTE. C'est ce qui le
   distingue de l'ambre de la section 13, reserve a ce qui demande une
   intervention : un refus n'en demande aucune, il est pris.

   Une correction, elle, reste grise — elle informe, elle ne conclut
   rien. Et une reouverture aussi : elle rouvre, elle ne tranche pas. */
/* LES TROIS ISSUES, ET ELLES SE RECONNAISSENT SANS ETRE LUES.

     regle    vert    c est fait
     refuse   rouge   c est decline
     rouvert  bleu    rien n est tranche

   Les couleurs sont celles DEJA employees par l application — #2f6b3c,
   #b02a37, #1a3a5c — et non des teintes inventees pour cet ecran. */
/* LES TROIS ISSUES SE RECONNAISSENT A LEUR TITRE.

     reglé    vert    c'est fait
     refusé   rouge   c'est décliné
     rouvert  bleu    rien n'est tranché

   Les couleurs sont celles DEJA employees par l'application — #2f6b3c,
   #b02a37, #1a3a5c — et non des teintes inventees pour cet ecran.

   Seul le titre change : la phrase, elle, reste lisible en gris fonce.
   Peindre tout le texte en rouge ferait crier un refus qui est une
   decision, pas une alerte. */
.vp-fil-statut.est-regle   .vp-fil-source { color: #2f6b3c; }
.vp-fil-statut.est-refuse  .vp-fil-source { color: #b02a37; }
.vp-fil-statut.est-rouvert .vp-fil-source { color: #1a3a5c; }

/* DEUX TRAITS, UN DE CHAQUE COTE. Avec le seul trait du haut, l'annonce
   se collait a la bulle qui la suivait et se lisait comme son en-tete —
   « Philippe Test a corrigé cette action » paraissait appartenir au
   message d'en dessous. Encadree, elle se lit pour ce qu'elle est : une
   constatation de Velocite+, entre deux prises de parole.

   Des traits, et toujours pas de cadre ni de fond : c'est ce qui la
   distingue d'une bulle, et cette regle-la ne bouge pas. */
.vp-fil-statut,
.vp-fil-correction {
  border-top: 1px solid #eceff2;
  border-bottom: 1px solid #eceff2;
}

/* Un reglement ou un refus se voit d'un coup d'oeil : c'est ce qu'on
   cherche en parcourant un fil. Une correction, elle, reste grise —
   elle informe, elle ne conclut rien. */

/* L'ENONCE DE DEPART — la premiere bulle du fil.

   Une bulle comme les autres, mais MARQUEE : sans signe distinctif on
   la lit comme un message ordinaire et on cherche a qui il repond. Le
   liseré et le repere disent « c'est la, que tout a commence ».

   Elle garde l'alignement de son auteur — a droite si elle est de vous
   — parce que c'est bien quelqu'un qui l'a ecrite. */
.vp-fil-ouverture {
  background: #fff;
  border: 1px solid #d7dee6;
  border-left: 3px solid #33414f;
  border-radius: .15rem .7rem .7rem .15rem;
}

.vp-fil-ouverture.vp-fil-moi {
  background: #fff;
  border-color: #cfe2ff;
  border-left-width: 1px;
  border-right: 3px solid #33414f;
  border-radius: .7rem .15rem .15rem .7rem;
}

/* Le repere. En petites capitales : il nomme la nature de la ligne, il
   ne fait pas partie de la phrase. */
.vp-fil-repere {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #33414f;
  background: #eef1f5;
  border-radius: .25rem;
  padding: .05rem .3rem;
}

/* Le separateur « · » de l'en-tete ne suit jamais le repere : c'est une
   etiquette, pas un element de la liste qui suit. */
.vp-fil-entete > .vp-fil-repere + span::before { content: none; }

/* ============================================================
   LA BOITE CENTRALE DU FIL — /operations

   Une conversation ne se lit pas sur onze cents pixels : l'oeil doit
   revenir trop loin a chaque ligne. La boite est bornee a une largeur
   de lecture et centree dans la fenetre.

   z-index sous le bandeau flottant (1090) : le message de succes doit
   rester visible par-dessus, sinon on ne sait pas si son geste a pris.
   ============================================================ */
.vp-modale {
  position: fixed;
  inset: 0;
  z-index: 1050;
  background: rgba(16, 32, 50, .45);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 3rem 1rem;
  overflow-y: auto;
}

/* L'ATTRIBUT `hidden` PERD CONTRE UNE CLASSE. Il pose `display: none`
   sur la balise, et la regle ci-dessus le recouvre : sans cette ligne,
   une fenetre pilotee en JavaScript serait OUVERTE au chargement de
   chaque page.

   Elle etait ecrite TROIS FOIS — `.vp-aide[hidden]`, `.vp-rafale[hidden]`
   et rien pour la troisieme rafale — chacune dans le bloc de son ecran,
   avec la meme explication recopiee. Une quatrieme fenetre l'aurait
   oubliee, et le defaut se serait vu sur toutes les pages a la fois.
   Elle vit donc sur `.vp-modale`, une fois. */
.vp-modale[hidden] { display: none; }

/* UN SEUL VOILE, QUEL QUE SOIT L'ETAGE. Les fenetres s'empilent — la
   fiche d'une visite, puis « Corriger » par-dessus — et deux voiles a
   45 % en font un a 70 %. Le layout ne laisse le sien qu'a la fenetre
   du dessus ; celles du dessous gardent leurs boites visibles derriere,
   ce qui est exactement ce qu'un empilement doit montrer. */
.vp-modale.est-dessous { background: transparent; }

/* ============================================================
   LE FOND NE DEFILE PAS SOUS LA FENETRE

   Sans ce verrou, la roue fait defiler la PAGE derriere la boite : on
   referme, et le tableau n'est plus la ou on l'avait laisse. C'est
   exactement ce que la fenetre centrale existe pour eviter.

   LA BARRE DE DEFILEMENT EST COMPENSEE, sinon toute la page saute d'une
   quinzaine de pixels vers la droite a l'ouverture — un mouvement qui
   se lit comme un defaut d'affichage. Sa largeur est mesuree une fois
   par le layout et posee en variable : elle vaut zero sur les systemes
   dont la barre se superpose au contenu.

   La classe est posee par le layout, pour toutes les fenetres a la
   fois : une page n'a rien a ecrire.
   ============================================================ */
body.vp-modale-ouverte {
  overflow: hidden;
  padding-right: var(--vp-barre-defilement, 0px);
}

.vp-modale-boite {
  /* Repere du masque de chargement : sans lui, l absolu se
     calerait sur la fenetre et non sur la boite. */
  position: relative;
  background: #fff;
  border-radius: .8rem;
  box-shadow: 0 12px 40px rgba(16, 32, 50, .3);
  padding: 1.25rem;
  width: 100%;
  /* 46rem : assez large pour une bulle confortable, assez etroit pour
     que la ligne se lise d'un seul mouvement des yeux. */
  max-width: 46rem;
  /* La boite ne depasse jamais la fenetre : c'est le FIL qui defile a
     l'interieur, pas la page derriere. */
  max-height: calc(100vh - 6rem);
  overflow-y: auto;
  animation: vp-modale-entree .16s ease;
}

@keyframes vp-modale-entree {
  from { opacity: 0; transform: translateY(-.75rem); }
  to   { opacity: 1; transform: none; }
}

/* Dans la boite, le fil respire davantage : il n'a plus a partager la
   largeur avec le tableau. */
.vp-modale-boite .vp-fil { max-height: 26rem; }

@media (max-width: 40rem) {
  .vp-modale { padding: 1rem .5rem; }
  .vp-modale-boite { padding: .9rem; max-height: calc(100vh - 2rem); }
  .vp-fil-ligne { max-width: 94%; }
}

/* ============================================================
   L'EN-TETE D'UNE FICHE D'ACTION — il ne defile jamais

   La boite centrale defile : le fil peut faire trente messages. Mais
   « de quelle action parle-t-on » doit rester lisible pendant qu'on
   descend, sinon on lit une conversation sans savoir de quoi elle
   parle. L'en-tete est donc COLLE en haut de la boite.

   C'est aussi ce qui permet d'ouvrir la fenetre directement en BAS du
   fil, sur le dernier message : sans en-tete fixe, on arriverait sur
   une conversation dont le sujet serait deja hors ecran.
   ============================================================ */
.vp-fiche-entete {
  position: sticky;
  top: -1.25rem;              /* compense le padding de la boite */
  z-index: 2;
  background: #fff;
  margin: -1.25rem -1.25rem .75rem;
  padding: 1.1rem 1.25rem .75rem;
  border-bottom: 1px solid #e3e6ea;
  border-radius: .8rem .8rem 0 0;
}

/* Le titre est la PHRASE QUI DIT QUOI FAIRE. Il etait rendu en petites
   capitales grises, comme un libelle de carte : on ne le lisait pas. */
.vp-fiche-titre {
  font-size: 1.12rem;
  font-weight: 600;
  color: #1f2b38;
  margin: 0 0 .5rem;
  line-height: 1.35;
}

/* Les details, en etiquettes separees plutot qu'en une ligne de gris
   uniforme ou magasin, portee, type et statut avaient le meme poids. */
.vp-fiche-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
}

.vp-fiche-etiquette {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: .76rem;
  color: #4a5866;
  background: #f2f5f8;
  border: 1px solid #e3e6ea;
  border-radius: 999px;
  padding: .15rem .6rem;
  white-space: nowrap;
}

.vp-fiche-etiquette.est-magasin { font-weight: 600; color: #1f2b38; }
.vp-fiche-etiquette.est-statut  { font-weight: 600; color: #1f5132;
                                  background: #e8f3ec; border-color: #cfe6d8; }

/* D'OU VIENT L'ACTION. C'est la seule etiquette qui SOIT UN LIEN — elle
   mene a la visite dans le calendrier — d'ou le survol, que les autres
   n'ont pas : sans lui, rien ne dirait qu'on peut cliquer. */
a.vp-fiche-etiquette.est-visite { color: #1a3a5c; text-decoration: none; }
a.vp-fiche-etiquette.est-visite:hover { background: #eef2f7; border-color: #c3d0de; }

/* L'AVANT-PROPOS D'UNE VISITE — `pre-line` garde ses paragraphes.
   Sans elle, HTML avale les retours a la ligne et un texte ecrit en
   trois paragraphes s'affiche en un bloc — alors que le courriel, lui,
   les respecte. Deux lectures du meme texte qui ne se ressemblent pas.
   `pre-line` et non `pre` : les espaces multiples restent replies, et
   le texte se recoupe a la largeur de la carte. */
/* =================================================================
   LA FICHE D'UNE VISITE — un document, pas une carte

   Elle ouvre une fenêtre qui ne contient rien d'autre : ce qu'on y
   lit est un COMPTE RENDU, et il se compose comme tel.
   ================================================================= */

/* LE TITRE. `.vp-carte-titre` est un intitulé de bloc — .72 rem en
   capitales grises — fait pour nommer une carte parmi d'autres dans
   une page. Ici il n'y a pas d'autres cartes : c'est le titre du
   document, et il doit s'annoncer comme tel. La classe est propre à
   cet écran plutôt que posée sur `.vp-carte-titre`, qui habille une
   trentaine de cartes ailleurs. */
.vp-titre-visite {
  margin-bottom: .35rem;
  font-size: 1.32rem;
  font-weight: 700;
  text-transform: none;
  letter-spacing: -.01em;
  line-height: 1.25;
  color: #16283c;
}
.vp-titre-visite .vp-carte-portee { vertical-align: middle; }

/* =================================================================
   LES BLOCS D'UNE FICHE DE VISITE

   Avant-propos, Envoi du rapport, Actions : trois choses de natures
   differentes — un texte, un formulaire, une liste — qui se suivaient
   dans une seule colonne, separees par rien d'autre qu'un intitule en
   petites capitales grises. On lisait une pile.

   Chacune prend donc sa BOITE. Ce n'est pas de la decoration : elles
   ne demandent pas la meme chose au lecteur — l'une se lit, la
   deuxieme s'actionne, la troisieme se parcourt — et une frontiere
   visible dit ou l'on passe de l'une a l'autre.

   `.vp-bloc` PLUTOT QUE `.vp-carte-note`. Celle-la est une NOTE : un
   fond ambre, un ton d'avertissement, faite pour dire « attention a
   ceci ». L'envoi du rapport la portait, et le formulaire se lisait
   donc comme une alerte. Ce sont deux registres, et ils ne se
   confondent pas.
   ================================================================= */
.vp-bloc {
  margin: 0 0 1.1rem;
  padding: 1.05rem 1.25rem 1.15rem;
  background: #fff;
  border: 1px solid #dfe5ec;
  border-radius: .5rem;
}

/* L'INTITULE D'UN BLOC. Le meme que celui d'une section du rapport, et
   il nomme sans concurrencer : c'est le contenu qu'on vient lire. Le
   filet le detache de ce qu'il ouvre — sans lui, un titre en petites
   capitales se lit comme la premiere ligne du texte. */
.vp-bloc-titre {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin: 0 0 .85rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid #eaeff4;
  font-size: .74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: #1a3a5c;
}

/* =================================================================
   L'AVANT-PROPOS — la prose de la fiche

   Il a d'abord ete borne a 64 caracteres, puis pose en deux colonnes.
   Les deux ont ete retires, et il vaut mieux ecrire pourquoi que de
   laisser croire a une hesitation.

   LA MESURE LAISSAIT UN VIDE. La fenetre fait 72 rem ; une boite de
   64 caracteres y occupe la moitie gauche et abandonne le reste — un
   cadre a moitie vide se lit comme un defaut d'affichage.

   LES COLONNES ONT LEUR PLACE DANS UN DOCUMENT IMPRIME, pas dans un
   panneau de travail : on y descend une colonne, on remonte, et le
   texte cesse d'etre une suite pour devenir une mise en pages.

   ET LE TEXTE GARDE LA POLICE DE LA PAGE. Aucune taille, aucune
   interligne, aucune encre n'est posee ici : il se lit comme tout le
   reste de l'ecran. Il ne reste donc qu'une chose a regler — l'ecart
   ENTRE les paragraphes, que `pre-line` ne permettait pas de distinguer
   d'une interligne.
   ================================================================= */
.vp-avant-propos p { margin: 0 0 .9em; }
.vp-avant-propos p:last-child { margin-bottom: 0; }

/* =================================================================
   LES DEUX REGISTRES — ce qui se voit en allée, et ce qui vient des
   chiffres

   Ils se lisaient mêlés, et un bouton « Type » servait à les démêler.
   Un tri qui range par registre fait le travail d'un groupe en moins
   lisible : on obtient la même liste, sans rien qui dise où l'un
   finit et où l'autre commence. Le bouton a donc disparu — un
   contrôle dont l'effet est déjà acquis est pire qu'un contrôle
   absent (section 34).

   LE TITRE VIENT DU MÉTIER, par la même fonction que le rapport.
   ================================================================= */
.vp-registre-titre {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin: 1.35rem 0 .55rem;
  padding-bottom: .35rem;
  border-bottom: 1px solid #e3e9ef;
  font-size: .74rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: #1a3a5c;
}

/* Le compte tient dans le titre : il dit la taille du groupe sans
   qu'on ait à compter les lignes. */
.vp-registre-nb {
  min-width: 1.35rem;
  padding: .05rem .4rem;
  border-radius: .75rem;
  background: #eef2f6;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: 0;
  color: #4a5866;
  text-align: center;
}

/* Le premier groupe ne se détache pas du reste : le bandeau de tri le
   précède déjà. */
.vp-registre-titre:first-of-type { margin-top: .6rem; }

/* ============================================================
   LE FIL RESPIRE

   Les bulles se touchaient presque : 0,55 rem entre deux prises de
   parole, et 0,45 rem de padding vertical. On lisait un bloc, pas une
   conversation.
   ============================================================ */
.vp-fil { gap: .9rem; padding: .35rem .25rem; }

.vp-fil-message { padding: .65rem .95rem .7rem; }

.vp-fil-entete { font-size: .75rem; margin-bottom: .3rem; gap: .35rem; }

.vp-fil-texte { font-size: .92rem; line-height: 1.5; }

.vp-fil-statut,
.vp-fil-correction { padding: .55rem 0; margin: .35rem 0; }

/* La mention qui rappelle qu'un message envoye ne se reprend pas. */
.vp-fil-avis {
  font-size: .75rem;
  color: #6b7885;
  font-style: italic;
  margin: .5rem 0 .35rem;
}

/* Le fond cliquable, DERRIERE la boite. Un element a part plutot qu'un
   ecouteur sur le cadre : App Connect n'expose pas event.target, donc
   un clic dans la boite remonterait et fermerait aussi. */
.vp-modale-fond {
  position: absolute;
  inset: 0;
  cursor: pointer;
}

/* La boite passe devant le fond, sinon le clic la traverserait. */
.vp-modale-boite { position: relative; z-index: 1; }

/* Le layout pose le focus SUR LA BOITE a l'ouverture, pour que le
   clavier entre dans la fenetre plutot que de rester sur le tableau
   qu'elle couvre. C'est une cible programmatique, pas un controle : son
   anneau de focus n'apprendrait rien et se lirait comme une bordure de
   trop. Les controles DANS la boite, eux, gardent le leur. */
.vp-modale-boite:focus { outline: none; }

/* La croix de fermeture, en haut a droite de la boite. Posee au-dessus
   de l'en-tete collant, sinon elle defilerait avec le contenu. */
.vp-modale-fermer {
  position: absolute;
  top: .55rem;
  right: .6rem;
  z-index: 3;
  width: 1.9rem;
  height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #6b7885;
  font-size: .95rem;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}

.vp-modale-fermer:hover { background: #eef1f5; color: #1f2b38; }

/* =================================================================
   LE FOND NE FERME PAS UNE FENETRE OU L'ON A COMMENCE A ECRIRE

   Le refus se LIT : un clic qui ne ferait rien est un controle sans
   effet, et l'on reclique (section 34). Ce bandeau dit ce qui bloque
   et la sortie qui reste — la croix, qu'il DESIGNE en meme temps.

   EN AMBRE : c'est un geste qu'on vient de refuser, donc quelque
   chose qui demande une intervention. Le rouge dit qu'une ecriture a
   echoue, ce qui n'est pas le cas ici (section 13).

   IL FANE APRES CINQ SECONDES, comme toute confirmation du projet, et
   pour la meme raison : un message qui reste finit par decrire un
   geste qu'on a quitte depuis longtemps. Mais il se REARME a chaque
   clic — un second essai doit revoir le meme refus, sinon le geste
   parait ignore.
   ================================================================= */
.vp-modale-retenue {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin: 0 0 .8rem;
  padding: .55rem .7rem;
  background: #fdf4e6;
  border: 1px solid #e2c48f;
  border-left: 3px solid #a8620a;
  border-radius: .35rem;
  font-size: .82rem;
  line-height: 1.45;
  color: #6b4207;
  transition: opacity .35s ease;
}

.vp-modale-retenue i { margin-top: .15rem; }

.vp-modale-retenue.est-fanee {
  opacity: 0;
  pointer-events: none;
}

/* LA CROIX SE DESIGNE PENDANT CE TEMPS. Le bandeau la nomme — « en
   haut a droite » — et la marque la montre : dire ou est la sortie
   vaut mieux que de la decrire, et une boite de 46 rem porte assez
   d'elements pour qu'on la cherche. */
.vp-modale-fermer.est-designee {
  background: #fdf4e6;
  color: #a8620a;
  box-shadow: 0 0 0 2px #e2c48f;
}

@media (prefers-reduced-motion: reduce) {
  .vp-modale-retenue { transition: none; }
}


/* Le titre laisse la place a la croix, sinon un titre long passe
   dessous. */
.vp-fiche-titre { padding-right: 2rem; }

/* ============================================================
   LA BOITE DE RECEPTION — /operations, onglet Conversations

   UNE LISTE, PAS UN TABLEAU. On la parcourt du regard pour trouver ou
   ca a bouge ; on ne compare pas des colonnes. Le tableau repond a
   l'autre question — quelles taches existent — et il la garde.

   Chaque ligne porte la DERNIERE REPLIQUE, parce que c'est a cela
   qu'on reconnait une conversation : qui a parle, ses premiers mots,
   et quand.
   ============================================================ */
.vp-boite {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-bottom: 1rem;
}

.vp-boite-ligne {
  background: #fff;
  border: 1px solid #e3e6ea;
  border-left: 3px solid #e3e6ea;
  border-radius: .5rem;
  padding: .7rem .95rem .75rem;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .15rem .75rem;
  align-items: start;
}

/* Ce qui a du neuf se signale par un liseré, pas par un fond : une
   liste ou la moitie des lignes sont peintes ne signale plus rien. */
.vp-boite-ligne.est-neuve { border-left-color: #c8951f; background: #fffdf7; }

.vp-boite-tete {
  grid-column: 1;
  display: flex;
  align-items: baseline;
  gap: .5rem;
  flex-wrap: wrap;
}

.vp-boite-titre { font-weight: 600; color: #1f2b38; font-size: .95rem; }

.vp-boite-quand { font-size: .72rem; color: #8a929c; white-space: nowrap; }

.vp-boite-lieu {
  grid-column: 1;
  font-size: .74rem;
  color: #6b7580;
}

/* L'extrait : une seule ligne, coupee proprement. Deux lignes feraient
   perdre le balayage vertical qui fait tout l'interet de la liste. */
.vp-boite-extrait {
  grid-column: 1;
  margin-top: .3rem;
  font-size: .85rem;
  color: #33414f;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.vp-boite-qui { font-weight: 600; margin-right: .35rem; }

.vp-boite-extrait.est-muette { color: #8a929c; font-style: italic; }

/* Le bouton et la pastille, colles a droite sur toute la hauteur. */
.vp-boite-action {
  grid-column: 2;
  grid-row: 1 / span 3;
  display: flex;
  align-items: center;
  gap: .5rem;
  align-self: center;
}

/* La barre de marquage de l'onglet « Non lus » — l'administrateur seul.
   Elle se pose au-dessus de la boite, et la case de chaque ligne vit a
   droite, apres « Ouvrir ». */
.vp-marquer {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  padding: .5rem .75rem;
  margin-bottom: .6rem;
  background: #f6f8fa;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
}

.vp-marquer-tout { display: inline-flex; align-items: center; gap: .4rem;
                   font-size: .85rem; font-weight: 600; color: #1f2b38; margin: 0; cursor: pointer; }
.vp-marquer-tout .form-check-input { margin: 0; }
.vp-marquer-compte { font-size: .8rem; color: #6b7580; margin-right: auto; }
.vp-marquer-case { margin: 0; width: 1.1rem; height: 1.1rem; cursor: pointer; }

@media (max-width: 40rem) {
  .vp-boite-ligne { grid-template-columns: 1fr; }
  .vp-boite-action { grid-column: 1; grid-row: auto; margin-top: .5rem; }
}

/* ===============================================================
   LES PHOTOS D'UNE ACTION

   Une grille qui se remplit toute seule : `auto-fill` avec une
   largeur MINIMALE plutot qu'un nombre de colonnes fixe. Une photo
   seule ne s'etale donc pas sur toute la boite, et huit ne debordent
   pas — la meme regle vaut sur un telephone et sur un grand ecran,
   sans requete de media a tenir.

   `object-fit: cover` ici, et non `contain` comme sur les capsules :
   ce qu'on regarde est une PHOTO, pas un pictogramme calibre. Un
   cadre de meme taille pour toutes se parcourt d'un coup d'oeil, et
   l'image entiere reste a un clic — la passerelle la sert `inline`,
   donc elle s'affiche au lieu de se telecharger.
   =============================================================== */
/* UNE PHOTO RETIREE LAISSE SA BULLE, ET LA BULLE LE DIT.
   Discret et gris : ce n'est ni une alerte ni un contenu, c'est un
   fait — l'image a existe, quelqu'un l'a retiree, et le journal en
   garde la trace (section 23). */
.vp-photo-retiree {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-top: .5rem; padding: .3rem .7rem;
  border: 1px dashed #dfe3e8; border-radius: .35rem;
  background: #f7f8fa; color: #8a929c;
  font-size: .85rem; font-style: italic;
}
.vp-photo-retiree i { font-size: .8rem; opacity: .7; }

.vp-photos { margin-top: .6rem; }

/* DES PISTES DE LARGEUR FIXE, ET SURTOUT PAS `1fr`.

   Une bulle du fil se serre sur son contenu — `align-self` dans une
   colonne flex — et ne s'arrête qu'à 82 %. Mais `minmax(5.5rem, 1fr)`
   demande à la grille de REMPLIR ce qu'on lui donne : elle réclamait
   donc les 82 %, et une bulle portant UNE vignette de 5,5 rem
   s'étirait sur toute la largeur permise.

   Le résultat se voyait tout de suite, et sans qu'on sache pourquoi :
   deux bulles du même auteur, à une minute d'écart, n'avaient pas la
   même largeur — l'une tenait une photo, l'autre une ligne de texte.

   Avec une piste fixe, la grille ne demande que ce qu'elle occupe :
   une photo fait 5,5 rem, quatre en font quatre, et la bulle suit. */
.vp-photos-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, 5.5rem);
  gap: .4rem;
}

/* Le conteneur est en `relative` pour que la croix de retrait se pose
   dans le coin de SA vignette, et non dans celui de la grille. */
.vp-photo {
  position: relative;
  aspect-ratio: 1;
  border-radius: .35rem;
  overflow: hidden;
  background: #eef1f4;
  border: 1px solid #d9dee4;
}

/* Un BOUTON et non un lien : la vignette ouvre la visionneuse de
   la page, elle ne mene nulle part. Un <a> promettrait une
   destination, et le clic du milieu ouvrirait un onglet vide. */
.vp-photo-ouvrir {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

.vp-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* La croix ne parait qu'au survol sur un poste, mais reste TOUJOURS
   visible au doigt : `hover: none` couvre les tablettes, ou un
   controle qui attend un survol est un controle inatteignable. */
.vp-photo-retirer {
  position: absolute;
  top: .2rem;
  right: .2rem;
  width: 1.4rem;
  height: 1.4rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(20, 28, 38, .72);
  color: #fff;
  font-size: .7rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s ease;
}

.vp-photo:hover .vp-photo-retirer,
.vp-photo-retirer:focus { opacity: 1; }

@media (hover: none) {
  .vp-photo-retirer { opacity: 1; }
}

/* Le champ de fichier et son bouton sur une seule ligne. Le champ
   prend ce qui reste ; le bouton garde sa largeur. */
.vp-photo-depot {
  display: flex;
  gap: .4rem;
  align-items: center;
}

.vp-photo-depot input[type="file"] { flex: 1 1 auto; min-width: 0; }
.vp-photo-depot button { flex: 0 0 auto; white-space: nowrap; }

/* ===============================================================
   LA VISIONNEUSE — une photo au milieu de l'ecran

   Au-dessus de TOUT : la boite de discussion est a 1050, le bandeau
   flottant a 1090. Une photo qu'on vient d'ouvrir doit passer devant
   les deux, sinon elle s'ouvre derriere ce qu'on regardait.

   MILLE PIXELS AU PLUS. Une photo de telephone fait 4 000 px de
   large : affichee telle quelle, elle deborderait de l'ecran et il
   faudrait la faire defiler pour la voir. La borne est en `min()`
   avec 92vw — sur un telephone, c'est la fenetre qui commande, pas
   la valeur fixe.
   =============================================================== */
.vp-visionneuse {
  position: fixed;
  inset: 0;
  z-index: 1100;
  background: rgba(10, 18, 28, .82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem 1rem;
  cursor: zoom-out;
}

.vp-visionneuse img {
  max-width: min(1000px, 92vw);
  max-height: 88vh;
  width: auto;
  height: auto;
  border-radius: .4rem;
  box-shadow: 0 16px 48px rgba(0, 0, 0, .5);
  /* L'image ne referme pas : on peut vouloir la pointer du doigt sans
     la faire disparaitre. C'est le FOND qui ferme, et la croix. */
  cursor: default;
}

.vp-visionneuse-fermer {
  position: absolute;
  top: 1rem;
  right: 1rem;
  width: 2.4rem;
  height: 2.4rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.vp-visionneuse-fermer:hover { background: rgba(255, 255, 255, .3); }

/* ===============================================================
   LE BAS D'UNE FICHE D'ACTION — trois blocs, trois gestes

   REPONDRE, DECIDER, CORRIGER. Ils etaient empiles sans hierarchie :
   un avis, un champ, un second champ, un formulaire, une legende de
   cinq lignes, puis six boutons sur une seule rangee. Rien ne disait
   qu'« Envoyer » et « Réglé » ne font pas la meme chose.

   LE TITRE DE BLOC EST CELUI D'UNE CARTE — petites capitales grises
   a 0,72 rem, la meme regle que `.vp-carte-titre`. Ce n'est pas de la
   coquetterie : c'est ce qui fait qu'un bloc de fiche se lit comme le
   reste de l'application plutot que comme une piece rapportee.

   LA SEPARATION EST UN FILET, PAS UNE BOITE. Une carte dans une carte
   ajouterait une bordure et un fond a l'interieur d'une boite qui en a
   deja ; la ligne suffit a dire « autre chose commence ici ».
   =============================================================== */
.vp-fiche-bloc {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid #eceff2;
}

.vp-fiche-bloc-titre {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: 0 0 .6rem;
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: #6b7580;
}

/* L'icone du titre ne porte aucune information : elle repere le bloc
   du coin de l'oeil. Elle reste donc discrete — le titre se lit, elle
   se remarque. */
.vp-fiche-bloc-titre > i:first-child { color: #9aa3ad; font-size: .8rem; }

/* Le point d'aide ne suit pas la casse du titre : il est deja aligne
   sur ceux des en-tetes de tableau, et sa taille vient de la. */
.vp-fiche-bloc-titre .fa-question-circle { text-transform: none; }

/* --- JOINDRE UN TABLEAU : le selecteur, puis le bouton ---------- */

/* UNE RANGEE, ET LE SELECTEUR PREND LA PLACE. Le nom d'une
   compilation est long — titre, plage, nombre de lignes — et le
   bouton, lui, porte un mot. En `.vp-champs`, les deux recevaient une
   colonne chacun et l'etiquette du bouton etait un `&nbsp;` pose pour
   aligner : une case vide dans le HTML pour un probleme de mise en
   page.

   `flex: 1` sur le selecteur dit la meme chose sans mentir, et sur
   320 pixels la rangee se replie d'elle-meme. */
.vp-fiche-joindre {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}

.vp-fiche-joindre .form-select { flex: 1 1 16rem; min-width: 0; }
.vp-fiche-joindre .btn { white-space: nowrap; }

/* L'aide sous la rangee, jamais a cote : elle nomme un ECRAN ou aller,
   ce qui se lit apres avoir vu le controle, pas pendant. */
.vp-fiche-bloc-aide {
  margin-top: .4rem;
  font-size: .78rem;
  color: #6b7885;
}

/* --- REPONDRE : le champ, puis une barre d'outils -------------- */

/* Une seule rangee : joindre a gauche, envoyer a droite. Le champ de
   fichier prenait toute la largeur pour un geste qu'on fait une fois
   sur dix, quand « Envoyer », qu'on fait a chaque tour, tenait dans une
   rangee de six boutons. Les proportions disaient l'inverse de l'usage. */
.vp-composer-barre {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}

/* Borne, et non pleine largeur : c'est un accessoire de la barre. */
.vp-composer-fichier {
  flex: 0 1 16rem;
  min-width: 0;
  font-size: .78rem;
}

/* La marge automatique pousse le geste principal au bout de la rangee :
   il se trouve sans etre cherche, et rien ne le confond avec « Joindre ». */
.vp-composer-envoyer { margin-left: auto; }

@media (max-width: 40rem) {
  /* Sur telephone la rangee se replie, et « Envoyer » repasse a gauche :
     une marge automatique sur une ligne a soi ne pousse plus rien, elle
     ne fait que decaler le bouton hors de portee du pouce. */
  .vp-composer-fichier { flex: 1 1 100%; }
  .vp-composer-envoyer { margin-left: 0; }
}

/* --- DECIDER : deux boutons, et le motif qu'ils lisent ---------- */

/* Le motif flottait entre le champ de reponse et le formulaire de
   photo, ou rien ne disait a quoi il servait. Sa place est SUR LA MEME
   RANGEE que les deux boutons qui le lisent. */
.vp-fiche-decisions {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}

.vp-fiche-decisions .form-control {
  flex: 1 1 14rem;
  min-width: 0;
  font-size: .82rem;
}

/* --- CORRIGER : ce que son auteur seul peut faire --------------- */

/* Le dernier bloc est le plus discret des trois : ce sont des gestes
   de reprise, pas la conversation. */
.vp-fiche-reprise {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}

.vp-fiche-reprise-libelle {
  font-size: .72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: #9aa3ad;
}

/* --- UN FIL SANS MESSAGE ---------------------------------------- */

/* « Aucun message pour l'instant » etait du texte nu, aligne a gauche,
   qui se lisait comme une phrase de l'application. C'est un ETAT, et un
   etat se dit au centre, en discret — comme le « vide » d'un tableau. */
.vp-fil-vide {
  margin: .8rem 0;
  padding: .9rem;
  text-align: center;
  font-size: .84rem;
  color: #8a929c;
  background: #f7f8fa;
  border-radius: .5rem;
}

/* Le point d'aide de la fiche a la MEME teinte que ceux des en-tetes de
   tableau. Il portait la couleur du texte courant — donc un disque noir
   plein au milieu d'une barre blanche, quand ceux d'un en-tete sont
   gris et discrets. Meme forme, meme classe, meme opacite : il ne
   restait que la couleur, que l'en-tete de tableau donne par heritage
   et qu'une barre d'outils ne donne pas. */
.vp-fiche-bloc .fa-question-circle,
.vp-composer-barre .fa-question-circle {
  color: #9aa3ad;
  font-size: .8rem;
}

/* ===============================================================
   SUIVI OPERATIONNEL — LA VUE D'ENSEMBLE

   DEUX BANDEAUX, ET ILS NE SE RESSEMBLENT PAS EXPRES.

   « Ce qui vous attend » porte des TACHES : chaque tuile est un lien,
   elle se survole, et celle qui a du travail se detache. « L'etat du
   parc » porte des FAITS : rien ne s'y clique, rien ne s'y allume.
   Leur donner la meme forme ferait lire un chiffre de volumetrie
   comme une tache — c'est la separation de la section 19.
   =============================================================== */
.vp-chantiers,
.vp-etats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .7rem;
}

/* --- CE QUI VOUS ATTEND — cliquable, et qui se remarque --------- */

.vp-chantier {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  padding: .9rem 1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .8rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.vp-chantier:hover {
  border-color: #c9d0d8;
  box-shadow: 0 4px 12px rgba(16, 32, 50, .09);
  transform: translateY(-1px);
  text-decoration: none;
  color: inherit;
}

.vp-chantier-nb {
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.vp-chantier-titre {
  font-size: .78rem;
  font-weight: 600;
  color: #4a5560;
  line-height: 1.35;
}

/* CE QUI DEMANDE UNE INTERVENTION EST EN AMBRE, jamais en rouge : le
   rouge est reserve a ce qui est DECIDE — un refus (section 13). */
.vp-chantier.est-a-faire { border-left: 3px solid #d98c1f; }
.vp-chantier.est-a-faire .vp-chantier-nb { color: #a86a12; }

/* UN CHANTIER A ZERO RESTE AFFICHE, en gris. C'est l'inverse de
   `masquerSiZero` : sur un tableau, un compteur a zero encombre une
   liste qu'on lit deja ; ici, « rien a faire de ce cote » est
   precisement ce qu'on vient verifier. */
.vp-chantier.est-vide { border-left: 3px solid #e3e6ea; }
.vp-chantier.est-vide .vp-chantier-nb { color: #b6bec6; }
.vp-chantier.est-vide .vp-chantier-titre { color: #8a929c; }

/* --- L'ETAT DU PARC — des faits, rien a cliquer ---------------- */

.vp-etat {
  padding: .8rem 1rem;
  background: #f7f8fa;
  border-radius: .6rem;
}

.vp-etat-valeur {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1;
  color: #16283c;
  font-variant-numeric: tabular-nums;
}

.vp-etat-titre {
  margin-top: .25rem;
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #6b7580;
}

/* --- CE QUI VIENT DE BOUGER ------------------------------------ */

/* Une GRILLE et non un tableau : `test_tableaux.js` refuse tout
   <table> dans une page, et dix lignes ne demandent ni tri, ni filtre,
   ni pagination — ce que le cadre `vp-tableau` apporterait pour rien. */
.vp-recentes {
  display: flex;
  flex-direction: column;
  border: 1px solid #e3e6ea;
  border-radius: .8rem;
  overflow: hidden;
  background: #fff;
}

.vp-recente {
  display: grid;
  grid-template-columns: 6.5rem minmax(10rem, 1fr) 14rem 10rem 5.5rem;
  align-items: center;
  gap: .8rem;
  padding: .6rem .9rem;
  border-bottom: 1px solid #eceff2;
  font-size: .84rem;
  text-decoration: none;
  color: inherit;
}

.vp-recente:last-child { border-bottom: 0; }
.vp-recente:hover { background: #f7f8fa; text-decoration: none; color: inherit; }

.vp-recente-etat {
  font-size: .68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
}

/* Les memes teintes que le fil, et pour les memes raisons : le vert
   conclut, le rouge marque une decision prise, le gris est en cours. */
.vp-recente-etat.est-regle   { color: #2f6b3c; }
.vp-recente-etat.est-refuse  { color: #b02a37; }
.vp-recente-etat.est-ouverte { color: #6b7580; }

.vp-recente-titre { font-weight: 600; color: #16283c; }
.vp-recente-ou    { color: #6b7580; }

.vp-recente-quand {
  color: #8a929c;
  font-size: .78rem;
  white-space: nowrap;
}

@media (max-width: 52rem) {
  /* Sur ecran etroit la ligne se replie en deux etages plutot que de
     comprimer cinq colonnes jusqu'a l'illisible. */
  .vp-recente {
    grid-template-columns: 6.5rem 1fr 5.5rem;
    row-gap: .2rem;
  }
  .vp-recente-ou    { grid-column: 2 / -1; }
  .vp-recente-quand { grid-column: 2 / -1; }
}

/* La pastille est cadree a droite dans sa piste : sans cela, une piste
   fixe ferait flotter le badge a gauche de la place reservee, et on
   verrait le trou plutot que l'alignement. */
.vp-recente .badge { justify-self: end; }

/* ===============================================================
   L'ETAT D'UNE ACTION, PARTOUT PAREIL

   Il etait simplement grise des que l'action n'etait plus ouverte :
   « Réglé » et « Refusé » se lisaient donc identiquement, alors qu'ils
   ne disent pas du tout la meme chose — l'un conclut, l'autre ecarte.

   Les memes teintes que le fil et que la vue d'ensemble. Le rouge
   marque une DECISION prise, jamais une alerte : ce qui demande une
   intervention est en ambre (section 13).
   =============================================================== */
.vp-etat-mot { font-weight: 600; }

.vp-etat-mot.est-regle   { color: #2f6b3c; }
.vp-etat-mot.est-refuse  { color: #b02a37; }
.vp-etat-mot.est-ouverte { color: #4a5560; }

/* ===============================================================
   BROUILLON OU DIFFUSEE — le fait qui decide de QUI LIT

   Treize colonnes, et pas une ne le portait : un brouillon se lisait
   exactement comme une action que le magasin suit depuis trois jours.
   On repondait donc dans un fil que personne ne voit.

   MEME DESSIN QUE LE BADGE MOBILE, et pour la meme raison qu ailleurs :
   un etat qui change de forme entre les deux surfaces se relit a chaque
   ecran. Les valeurs sont ecrites en clair — style.css ne porte aucun
   jeton, ses couleurs le sont une par une.

   UN SEUL DES DEUX EST PEINT. Le brouillon est l etat ou personne ne
   vous lit ; la diffusee est le cas normal, et une colonne de badges
   pleins ne laisserait rien ressortir. Le MARINE et non l ambre :
   l ambre dit ce qui demande une intervention (section 13), et un
   brouillon n est pas une alerte — on ecrit en brouillon expres.
   =============================================================== */

.vp-diffusion {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  border-radius: .35rem;
  padding: .12rem .4rem;
  font-size: .78rem;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
}

.vp-diffusion.est-brouillon {
  background: #eef3f8;
  border: 1px solid #cddbe8;
  color: #1a3a5c;
}

.vp-diffusion.est-diffusee {
  background: transparent;
  border: 1px solid transparent;
  color: #8b96a2;
  font-weight: 500;
  padding-left: 0;
  padding-right: 0;
}

/* ===============================================================
   CE QUI RESSERRE LA LISTE SE DIT — et se defait d'un clic

   Un tableau filtre sans rien qui l'annonce se lit comme un tableau
   vide, et on cherche des lignes qui sont bien la. Le bandeau nomme la
   vue, dit combien elle ecarte, et porte le bouton qui l'annule.

   BLEU ET NON AMBRE : ce n'est pas une alerte, c'est un etat de
   lecture qu'on a soi-meme demande en cliquant une tuile. L'ambre est
   reserve a ce qui demande une intervention (section 13).
   =============================================================== */
.vp-vue-active {
  display: flex;
  align-items: center;
  gap: .7rem;
  flex-wrap: wrap;
  margin: .9rem 0;
  padding: .7rem .9rem;
  background: #eef4fb;
  border: 1px solid #cfe0f2;
  border-left: 3px solid #1a3a5c;
  border-radius: .5rem;
  font-size: .84rem;
  color: #16283c;
}

.vp-vue-active > i:first-child { color: #1a3a5c; }

/* L'aide passe a la ligne sous le nom sur ecran etroit plutot que de
   comprimer le bandeau jusqu'a l'illisible. */
.vp-vue-aide {
  display: block;
  font-size: .78rem;
  color: #4a5560;
}

/* La marge automatique pousse le compte et le bouton au bout : on lit
   le nom a gauche, on sort a droite, et la position ne bouge pas d'une
   vue a l'autre. */
.vp-vue-ecartees {
  margin-left: auto;
  font-size: .78rem;
  color: #4a5560;
  white-space: nowrap;
}

/* ===============================================================
   LES GROUPES DE MAGASINS — deux colonnes qui se lisent ensemble

   La liste a gauche, les magasins du groupe choisi a droite : on
   descend l'une en cliquant dans l'autre. C'est la forme de « Capsules
   a associer », et pour la meme raison.
   =============================================================== */
.vp-groupes {
  display: grid;
  grid-template-columns: minmax(18rem, 22rem) minmax(0, 1fr);
  gap: .9rem;
  align-items: start;
}

@media (max-width: 62rem) {
  /* Sous cette largeur, deux colonnes donnent deux moities trop
     etroites pour l'une comme pour l'autre : on empile. */
  .vp-groupes { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------
   TRIER UNE LISTE QUI N'EST PAS UN TABLEAU

   Un tableau se trie par ses en-tetes ; une liste de cartes n'en a
   pas. Ces boutons sont l'equivalent, et ils empruntent la forme des
   pastilles de la version mobile : des PARTS qu'on bascule, pas des
   controles a remplir.

   Ils tiennent sur une ligne au-dessus de la liste, en petit : on les
   pose une fois et l'on ne les regarde plus.
   --------------------------------------------------------------- */
.vp-tris {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
  margin-bottom: .5rem;
}

.vp-tris-libelle {
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: #8a94a0;
  margin-right: .15rem;
}

.vp-tris-bouton {
  border: 1px solid #dde3e9;
  background: #fff;
  color: #4a5764;
  border-radius: 1rem;
  padding: .12rem .6rem;
  font-size: .74rem;
  line-height: 1.5;
  cursor: pointer;
}

.vp-tris-bouton:hover { border-color: #b9c4cf; color: #1a3a5c; }

/* L'ORDRE EN COURS SE VOIT. Sans cette marque, on ne sait pas selon
   quoi la liste est rangee — et l'on reclique celui qui est deja pose. */
.vp-tris-bouton.est-actif {
  background: #1a3a5c;
  border-color: #1a3a5c;
  color: #fff;
}

.vp-groupe-liste {
  display: flex;
  flex-direction: column;
  max-height: 30rem;
  overflow-y: auto;
  /* Un enfant defilant d'un conteneur flex refuse de retrecir sous la
     hauteur de son contenu sans ceci — la carte deborderait au lieu de
     faire defiler (lecon de « Capsules a associer »). */
  min-height: 0;
}

.vp-groupe {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .1rem 0;
  border-bottom: 1px solid #eceff2;
}

.vp-groupe:last-child { border-bottom: 0; }

/* Le groupe regarde se marque : sans cela, rien dans la liste ne dirait
   a quoi correspond le panneau de droite. */
.vp-groupe.est-choisi { background: #eef4fb; border-radius: .4rem; }

/* UN BOUTON, ET NON UNE LIGNE CLIQUABLE. « La ligne se lit, elle ne se
   clique pas » : un controle nomme dit ou commence l'action, ce qu'une
   ligne entiere ne disait pas. */
.vp-groupe-nom {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .55rem .6rem;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}

.vp-groupe-titre  { font-weight: 600; font-size: .88rem; color: #16283c; }
.vp-groupe-detail { font-size: .74rem; color: #8a929c; }
.vp-groupe-desc   { font-size: .76rem; color: #6b7580; }

.vp-groupe-etat {
  flex: 0 0 auto;
  padding: .1rem .45rem;
  border-radius: 1rem;
  background: #eceff2;
  color: #6b7580;
  font-size: .62rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.vp-groupe-gestes { flex: 0 0 auto; display: flex; gap: .1rem; }
.vp-groupe-gestes .btn { padding: .2rem .35rem; }

/* --- LES MAGASINS DU GROUPE ------------------------------------ */

/* Cent douze cases : une grille qui se remplit toute seule, avec une
   largeur MINIMALE plutot qu'un nombre de colonnes fixe. La liste
   defile plutot que d'etirer la page sur trois ecrans. */
.vp-groupe-magasins {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: .1rem .8rem;
  max-height: 26rem;
  overflow-y: auto;
}

.vp-groupe-magasin {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .4rem;
  border-radius: .3rem;
  font-size: .82rem;
  cursor: pointer;
}

.vp-groupe-magasin:hover { background: #f7f8fa; }
.vp-groupe-magasin input { flex: 0 0 auto; }

/* LA CASE ET SON RAYON (migration 115). La ligne porte les deux : la
   case avec son nom, et — sur un magasin coché — le rayon suivi. Le
   nom prend la place, le sélecteur reste étroit et ne pousse pas la
   grille : « Bières et Alcomalt » y tient sans le déplier. */
.vp-suivi-case {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: 1 1 auto;
  min-width: 0;
  cursor: pointer;
}
.vp-suivi-rayon {
  flex: 0 0 auto;
  width: auto;
  max-width: 11rem;
  font-size: .76rem;
  padding-top: .15rem;
  padding-bottom: .15rem;
}

/* CE QUE LE GROUPE DOIT — la ligne de tete du panneau, et la pastille
   d'un magasin en attente. La pastille est poussee a droite de sa case :
   on balaie la colonne de droite pour trouver les retards, comme le
   badge d'une action (section 24). Le nom garde la place. */
.vp-groupe-rapports {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  margin: 0 0 .6rem;
  padding: .5rem .7rem;
  border-radius: .4rem;
  background: #f7f8fa;
  font-size: .8rem;
  color: #3d4a58;
}
.vp-groupe-rapports > i { margin-top: .15rem; color: #8a929c; }
.vp-groupe-rapports-corps { flex: 1 1 auto; min-width: 0; }
.vp-groupe-rapports-gestes { margin-top: .45rem; }

/* LE DETAIL, MAGASIN PAR MAGASIN — le texte tel qu'il sera colle, en
   chasse fixe pour que les dates s'alignent, et son bouton a cote. */
.vp-groupe-rapports-detail { margin-top: .5rem; }
.vp-groupe-rapports-detail > summary { cursor: pointer; color: #3d4a58; font-size: .78rem; }

.vp-groupe-rapport-magasin {
  display: flex;
  align-items: flex-start;
  gap: .4rem;
  margin-top: .45rem;
  padding: .4rem .5rem;
  background: #fff;
  border: 1px solid #e6e9ee;
  border-radius: .35rem;
}
.vp-groupe-rapport-texte {
  flex: 1 1 auto;
  margin: 0;
  font-size: .74rem;
  white-space: pre-wrap;
  color: #16283c;
}
.vp-groupe-rapport-magasin .btn { padding: .1rem .35rem; }

/* ===============================================================
   LE MASQUE D'UNE FICHE QUI CHARGE

   La boite s'ouvrait a l'instant du clic, avec le contenu de l'action
   qu'on venait de quitter : son titre, ses etiquettes, son fil.
   Pendant une seconde on lisait la mauvaise conversation, et rien ne
   disait que ce n'etait pas la bonne.

   POSE DANS LA BOITE, en absolu : elle garde sa taille, et rien ne
   saute quand le masque disparait. Pose dans le flux, il aurait
   decale tout ce qui suit a chaque ouverture.

   OPAQUE, ET NON UN VOILE. Un fond translucide laisserait deviner
   l'ancienne conversation dessous — c'est justement ce qu'on cache.
   =============================================================== */
.vp-fiche-masque {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  background: #fff;
  border-radius: .8rem;
  color: #8a929c;
  font-size: .86rem;
}

.vp-fiche-masque i { font-size: 1.4rem; color: #c3ccd6; }

/* Sans mouvement, un rond qui tourne est une decoration qui fatigue.
   On le fige, et le mot suffit. */
@media (prefers-reduced-motion: reduce) {
  .vp-fiche-masque i { animation: none; }
}

/* Les portes de la vue d'ensemble : elles mènent toutes deux a la
   liste, mais pas au meme endroit. En rangee, avec du souffle entre
   elles — collees, on cliquerait l'une pour l'autre. */
.vp-portes { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }

/* « aucun choisi » n'est pas un compte : il ne prend donc pas la forme
   d'une pastille, qui se lirait comme un nombre. */
.vp-porte-note { font-size: .74rem; opacity: .75; }

/* ===========================================================
   UNE CELLULE QUI PORTE PLUSIEURS FAITS — /administration/relances

   Douze periodes manquantes servies dans leur propre colonne
   faisaient des lignes de quatre cents pixels : la colonne etait
   etroite, chaque periode passait a la ligne, et le tableau devenait
   illisible des le troisieme magasin.

   Elles vivent donc SOUS le nom du magasin, dans une cellule large ou
   elles se rangent en grille — plusieurs par ligne plutot qu'une seule.
   La hauteur d'une ligne cesse de dependre du nombre de periodes.
   =========================================================== */
.vp-cellule-empilee { display: flex; flex-direction: column; gap: .25rem; }

.vp-cellule-titre { font-weight: 600; }

.vp-cellule-appoint { font-size: .8rem; color: #6b7684; }

/* Les periodes elles-memes. `wrap` fait le travail : sur une cellule
   large elles tiennent sur une ou deux lignes, et la ligne du tableau
   reste basse. */
.vp-periodes {
  display: flex;
  flex-wrap: wrap;
  gap: .2rem .3rem;
  margin-top: .15rem;
}

.vp-periode {
  font-size: .72rem;
  line-height: 1.5;
  padding: 0 .35rem;
  border-radius: 3px;
  white-space: nowrap;
  color: #8a6100;
  background: #fdf8f0;
  border: 1px solid #ecdcbf;
}

/* ===========================================================
   UNE FENETRE PLUS LARGE POUR UN TABLEAU

   `.vp-modale-boite` est bornee a 46 rem, et c'est le bon choix pour
   la boite d'une action : c'est une largeur de LECTURE, celle ou une
   ligne se parcourt d'un seul mouvement des yeux.

   Un tableau ne se lit pas comme un paragraphe. Cinq colonnes dans
   46 rem, ce sont des cellules qui se replient sur trois lignes et une
   ligne de tableau haute de cent pixels — le defaut qu'on venait de
   corriger sur l'ecran des relances.

   72 rem : assez pour les cinq colonnes, et toujours borne par
   `width: 100%` sur un ecran plus etroit.
   =========================================================== */
.vp-modale-boite.vp-modale-large { max-width: 72rem; }


/* =================================================================
   L APERCU D UN RAPPORT DE VISITE — un document dans un cadre isole

   CE QU ON MONTRE N EST PAS DE LA PAGE, C EST UN COURRIEL. Il porte sa
   propre feuille de style, ecrite pour des clients de courriel : des
   tables, des largeurs en pixels, des couleurs posees en clair. Verse
   dans la page il repeindrait ce qui l entoure ; dans un iframe il a
   son propre document, et l on voit exactement ce que le destinataire
   verra.

   LA BOITE NE DEFILE PAS, LE CADRE DEFILE. Une boite qui defilerait
   emporterait l en-tete « rien n est parti » hors de l ecran des le
   premier mouvement — or c est justement ce qu on doit lire en
   regardant un document qui a tout l air d etre envoye.
   ================================================================= */
.vp-apercu {
  display: flex;
  flex-direction: column;
  /* Le cadre prend ce qui reste : c est lui qui defile. */
  overflow: hidden;
}

.vp-apercu-entete {
  padding-right: 2rem;        /* la croix */
  margin-bottom: .75rem;
  font-size: .84rem;
  color: #48525d;
}

.vp-apercu-note {
  margin-top: .3rem;
  font-size: .78rem;
  color: #6b7580;
}

/* LE FOND EST GRIS, ET C EST VOULU : il dit que ce rectangle blanc est
   un document POSE dans l ecran, et non l ecran lui-meme. */
.vp-apercu-cadre {
  flex: 1 1 auto;
  min-height: 24rem;
  width: 100%;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  background: #f4f6f8;
}

@media (max-width: 40rem) {
  .vp-apercu-cadre { min-height: 18rem; }
}


/* =================================================================
   FERMER UN MODULE — /administration/modules

   Une liste de huit lignes, pas un tableau : aucun tri, aucun filtre,
   aucune pagination. Le cadre de lib/metier/tableaux.js existe pour
   des listes qui en ont besoin.
   ================================================================= */

.vp-fermetures {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: 1rem;
}

.vp-fermeture {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: .9rem;
  align-items: center;
  padding: .8rem 1rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .7rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
}

/* Un module fermé se voit AVANT qu'on lise son état : la bordure
   ambrée fait le travail que l'étiquette confirme. L'ambre, jamais du
   rouge — ce n'est pas une erreur, c'est une décision. */
.vp-fermeture.est-ferme {
  background: #fdf6e7;
  border-color: #e8d3a0;
}

.vp-fermeture-icone {
  width: 2.4rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .6rem;
  font-size: 1rem;
  color: #fff;
  background: linear-gradient(180deg, #234c76 0%, #1a3a5c 100%);
}

/* L'icône du module perd son bleu : elle dit d'un coup d'œil que ce
   module n'est plus une destination. */
.vp-fermeture.est-ferme .vp-fermeture-icone {
  background: #d8c48f;
  color: #6b5518;
}

/* Un module pas encore construit — le cas principal de cet écran. Ce
   n'est pas une fermeture : c'est un fait sur le code, et il se
   distingue donc du gris ordinaire sans emprunter l'ambre de la
   décision. Le trait interrompu est celui des cartes « À développer »
   de l'accueil : le même état s'y lit de la même façon. */
.vp-fermeture.est-a-venir {
  border-style: dashed;
}

.vp-fermeture.est-a-venir:not(.est-ferme) {
  background: #f7f8fa;
}

.vp-fermeture.est-a-venir:not(.est-ferme) .vp-fermeture-icone {
  background: #dfe3e8;
  color: #97a0aa;
}

.vp-fermeture-corps { min-width: 0; }

.vp-fermeture-titre {
  font-weight: 700;
  color: #16283c;
  line-height: 1.3;
}

.vp-fermeture-detail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem;
  margin-top: .15rem;
  font-size: .78rem;
  color: #6b7580;
}

.vp-fermeture.est-ferme .vp-fermeture-detail { color: #9a6700; }

/* Le motif est LU PAR L'UTILISATEUR sur le refus : il se relit ici tel
   qu'il partira, et les guillemets disent que c'est une citation. */
.vp-fermeture-motif {
  margin-top: .2rem;
  font-size: .8rem;
  font-style: italic;
  color: #55606c;
}

/* Pourquoi ce module ne se ferme pas. Un bouton absent sans explication
   se lit comme un bogue. */
.vp-fermeture-raison {
  margin-top: .2rem;
  font-size: .76rem;
  color: #8a929c;
}

.vp-fermeture-boutons {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  justify-content: flex-end;
}

/* Ce que chaque état fait, une fois, sous la liste. « Désactivé » et
   « caché » se ressemblent, et la différence décide de ce que les
   utilisateurs comprendront. */
.vp-etats-legende {
  margin-top: 1.2rem;
  padding-top: .9rem;
  border-top: 1px solid #e9ecef;
  display: flex;
  flex-direction: column;
  gap: .45rem;
}

.vp-etats-legende-ligne {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  font-size: .8rem;
  color: #55606c;
}

.vp-etats-legende-ligne i { color: #97a0aa; width: 1rem; text-align: center; }

@media (max-width: 640px) {
  .vp-fermeture { grid-template-columns: auto 1fr; }
  .vp-fermeture-boutons { grid-column: 1 / -1; justify-content: flex-start; }
}

/* ===============================================================
   COMPARATIF DE MAGASINS — /ventes/bieres/comparatif
   =============================================================== */

/* LES CINQ SELECTEURS EN COLONNES, et non a la suite.
   .vp-champs range ses champs en ligne, chacun a sa largeur : les cinq
   selecteurs de magasin s'y suivaient et se repliaient au hasard de la
   fenetre. Une grille de cinq parts EGALES les aligne comme les
   colonnes qu'ils composent — on voit du premier coup d'oeil lequel
   pilote quelle colonne du tableau.

   minmax(11rem, 1fr) : en dessous de 11 rem un numero suivi d'un nom
   de magasin ne tient plus, et auto-fit replie alors la grille sur
   moins de colonnes plutot que d'ecraser les libelles. */
.vp-magasins-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: .75rem 1rem;
  align-items: start;
}

/* Le champ occupe sa colonne entiere : sans cela il garderait la
   largeur de son contenu et la grille se lirait de travers. */
.vp-magasins-grille .vp-champ { width: 100%; min-width: 0; }

/* Le nom du magasin, sous son numero en tete de colonne. Il peut etre
   long — « 2855-4228 Québec inc (IGA RÉGNIER METABETCHOUAN) » — et
   c'est voulu qu'il passe a la ligne plutot que d'elargir la colonne :
   c'est le chiffre qu'on vient comparer, pas le nom. */
.vp-comp-nom {
  font-size: .72rem;
  line-height: 1.2;
  white-space: normal;
}

/* LA LIGNE SECONDAIRE — les trois autres mesures, sous la valeur.
   Elle doit se lire sans se disputer la valeur principale : plus
   petite, plus pale, et d'un seul tenant. white-space: nowrap la garde
   sur une ligne — repliee, elle doublerait la hauteur de chacune des
   mille cellules du tableau. */
.vp-comp-kpi {
  font-size: .68rem;
  line-height: 1.3;
  white-space: nowrap;
  opacity: .85;
}

/* Sur ecran etroit on laisse la ligne se replier : mieux vaut deux
   lignes qu'un tableau qui defile horizontalement pour un detail. */
@media (max-width: 900px) {
  .vp-comp-kpi { white-space: normal; }
}


/* =================================================================
   LA RAFALE — /administration/relances

   Relancer cinquante magasins un apres l'autre, en regardant partir
   chaque demande. Trois choses, et aucune n'est de l'ornement :

     LA JAUGE dit ou l'on en est. Sans elle, une fenetre qui met une
     minute a se remplir se lit comme une fenetre bloquee.

     LA LIGNE EN COURS se distingue de celles qui attendent et de
     celles qui sont parties. Trois etats, trois teintes, et une
     quatrieme pour le refus — qui n'est pas une panne : c'est le
     serveur qui repond, et sa phrase dit ou corriger.

     LE MOUVEMENT EST BREF. Une animation qui dure se remarque la
     premiere fois et gene les quarante-neuf suivantes.
   ================================================================= */


/* L'APPEL, au-dessus du tableau. Il n'ecrit rien — il ouvre. */
.vp-rafale-appel {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: .7rem .9rem;
  margin-bottom: .75rem;
  border: 1px solid #dbe3ec;
  border-radius: .55rem;
  background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
}

.vp-rafale-appel-texte { font-size: .9rem; color: #3d4c5c; }

.vp-rafale-titre {
  font-size: 1.2rem;
  font-weight: 600;
  color: #1f2b38;
  margin: 0 2rem .35rem 0;
}

.vp-rafale-intro {
  font-size: .92rem;
  color: #4a5866;
  margin-bottom: .75rem;
  /* La phrase change trois fois — file lue, course, bilan — et sa
     hauteur avec elle. Un minimum evite que tout le contenu remonte
     d'une ligne au moment ou l'on regarde la premiere partir. */
  min-height: 2.6rem;
}

/* La troncature, et elle seule : un avis qui paraitrait a chaque
   ouverture n'apprendrait plus rien (CLAUDE.md section 14). */
.vp-rafale-avis {
  font-size: .88rem;
  color: #7a5a12;
  background: #fdf6e3;
  border: 1px solid #ecdcbf;
  border-radius: .45rem;
  padding: .5rem .7rem;
  margin-bottom: .75rem;
}

/* LA JAUGE. La largeur est posee en JavaScript ; la transition la
   rend continue plutot que saccadee. Les rayures glissent tant que la
   course dure — c'est ce qui distingue une jauge qui avance d'une
   jauge arretee au meme endroit. */
.vp-rafale-jauge {
  height: .55rem;
  border-radius: 1rem;
  background: #e6ecf3;
  overflow: hidden;
  margin-bottom: .8rem;
}

.vp-rafale-jauge-barre {
  height: 100%;
  width: 0;
  border-radius: 1rem;
  background: linear-gradient(90deg, #2f6fb0 0%, #4a92d8 100%);
  transition: width .3s ease;
}

.est-en-course .vp-rafale-jauge-barre {
  background-image: linear-gradient(90deg, #2f6fb0 0%, #4a92d8 100%),
    repeating-linear-gradient(115deg,
      rgba(255,255,255,.22) 0 .6rem, rgba(255,255,255,0) .6rem 1.2rem);
  background-blend-mode: overlay;
  animation: vp-rafale-rayures 1s linear infinite;
}

@keyframes vp-rafale-rayures {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 0 0, 2.65rem 0; }
}

/* LES COMPTEURS. Quatre chiffres qui repondent chacun a une question
   differente : combien c'est fait, combien sont parties, combien ont
   ete refusees, combien restent. */
.vp-rafale-compteurs {
  display: flex;
  gap: 1.6rem;
  flex-wrap: wrap;
  margin-bottom: .85rem;
}

.vp-rafale-compteur { display: flex; align-items: baseline; gap: .35rem; }

.vp-rafale-chiffre {
  font-size: 1.35rem;
  font-weight: 600;
  color: #1f2b38;
  font-variant-numeric: tabular-nums;
  display: inline-block;
}

.vp-rafale-compteur.est-succes .vp-rafale-chiffre { color: #1e7a4d; }
.vp-rafale-compteur.est-attention .vp-rafale-chiffre { color: #a9761a; }

.vp-rafale-etiquette {
  font-size: .8rem;
  color: #6b7885;
  text-transform: lowercase;
}

/* Un chiffre qui change sans bouger ne se remarque pas. Il bat une
   fois — 220 ms — et la classe est retiree puis reposee en JavaScript,
   sinon l'animation ne repartirait jamais. */
.vp-rafale-chiffre.est-bat { animation: vp-rafale-bat .22s ease; }

@keyframes vp-rafale-bat {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}

/* LA LISTE. Elle defile a l'interieur de la fenetre : la fenetre elle
   meme ne doit pas grandir avec cinquante lignes, sinon le bouton
   d'arret sort de l'ecran — le seul controle qu'on veuille atteindre
   pendant la course. */
.vp-rafale-liste {
  max-height: 22rem;
  overflow-y: auto;
  border: 1px solid #e6ecf3;
  border-radius: .5rem;
  margin-bottom: .9rem;
}

.vp-rafale-ligne {
  display: flex;
  align-items: flex-start;
  gap: .65rem;
  padding: .5rem .7rem;
  border-bottom: 1px solid #f0f4f8;
  transition: background .2s ease, opacity .2s ease;
}

.vp-rafale-ligne:last-child { border-bottom: 0; }

.vp-rafale-puce {
  flex: 0 0 auto;
  width: 1.35rem;
  text-align: center;
  padding-top: .12rem;
  font-size: .78rem;
}

.vp-rafale-corps { min-width: 0; flex: 1 1 auto; }

.vp-rafale-nom {
  font-size: .92rem;
  color: #1f2b38;
  line-height: 1.3;
}

.vp-rafale-num { font-weight: 600; }

.vp-rafale-appoint {
  font-size: .8rem;
  color: #6b7885;
  overflow-wrap: anywhere;
}

.vp-rafale-issue {
  font-size: .82rem;
  line-height: 1.35;
  margin-top: .1rem;
}

.vp-rafale-issue:empty { display: none; }

/* QUATRE ETATS, ET ILS NE DISENT PAS LA MEME CHOSE :

     attente    rien n'est parti — la ligne s'efface a demi
     en cours   c'est celle-la qu'on regarde
     envoyee    le courriel est parti, et ne se reprend pas
     refusee    le serveur a repondu non, et sa phrase dit pourquoi

   Le refus est en AMBRE, jamais en rouge : c'est la teinte du projet
   pour ce qui demande une intervention (CLAUDE.md section 13). */
.vp-rafale-ligne.est-attente { opacity: .55; }
.vp-rafale-ligne.est-attente .vp-rafale-puce { color: #b6c1cd; }

.vp-rafale-ligne.est-en-cours {
  background: #eef5fd;
  opacity: 1;
}
.vp-rafale-ligne.est-en-cours .vp-rafale-puce { color: #2f6fb0; }
.vp-rafale-ligne.est-en-cours .vp-rafale-issue { color: #2f6fb0; }

.vp-rafale-ligne.est-envoyee { background: #f4fbf7; }
.vp-rafale-ligne.est-envoyee .vp-rafale-puce { color: #1e7a4d; }
.vp-rafale-ligne.est-envoyee .vp-rafale-issue { color: #1e7a4d; }

.vp-rafale-ligne.est-refusee { background: #fdf9f0; }
.vp-rafale-ligne.est-refusee .vp-rafale-puce { color: #a9761a; }
.vp-rafale-ligne.est-refusee .vp-rafale-issue { color: #7a5a12; }

/* La puce parait a chaque changement d'etat. Elle grossit une fois,
   brievement : c'est le seul signal qu'une ligne vient de basculer,
   et il ne dure pas assez pour lasser sur cinquante lignes. */
.vp-rafale-ligne.est-envoyee .vp-rafale-puce i,
.vp-rafale-ligne.est-refusee .vp-rafale-puce i {
  animation: vp-rafale-pop .26s ease;
}

@keyframes vp-rafale-pop {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.25); opacity: 1; }
  100% { transform: scale(1); }
}

.vp-rafale-actions {
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  margin-bottom: .75rem;
}

.vp-rafale-note {
  font-size: .82rem;
  color: #6b7885;
  line-height: 1.5;
  border-top: 1px solid #eef1f5;
  padding-top: .6rem;
}

/* Une animation qui se repete cinquante fois est exactement ce qu'un
   reglage systeme demande d'eteindre. On garde les couleurs, qui
   PORTENT l'information, et on retire le mouvement, qui ne fait que
   la souligner. */
@media (prefers-reduced-motion: reduce) {
  .vp-rafale-jauge-barre,
  .vp-rafale-chiffre.est-bat,
  .est-en-course .vp-rafale-jauge-barre,
  .vp-rafale-ligne.est-envoyee .vp-rafale-puce i,
  .vp-rafale-ligne.est-refusee .vp-rafale-puce i {
    animation: none;
    transition: none;
  }
}

@media (max-width: 40rem) {
  .vp-rafale-compteurs { gap: 1rem; }
  .vp-rafale-liste { max-height: 16rem; }
}


/* =================================================================
   LA RELANCE AUTOMATIQUE — /administration/relances

   C'est le seul reglage du projet qui fasse partir des courriels chez
   des commercants sans que personne ne soit devant. La carte est donc
   au-dessus de tout le reste, et son ETAT se lit avant son reglage :
   la pastille dit en un mot ce qui va se passer, le formulaire ne
   parait qu'a la demande.

   Deux tons, et jamais du rouge : desarme n'est pas une panne, c'est
   l'etat par defaut du projet.
   ================================================================= */
.vp-auto {
  padding: .8rem .95rem;
  margin-bottom: .75rem;
  border: 1px solid #dbe3ec;
  border-left: 3px solid #b6c1cd;
  border-radius: .55rem;
  background: #fbfcfd;
}

/* Arme, la carte le DIT par sa bordure : on doit pouvoir le voir sans
   lire, en traversant l'ecran. */
.vp-auto.est-armee {
  border-left-color: #1e7a4d;
  background: #f7fbf9;
}

.vp-auto-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.vp-auto-etat {
  display: inline-block;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  padding: .12rem .45rem;
  border-radius: .3rem;
  background: #eef1f5;
  color: #6b7885;
  margin-right: .5rem;
  vertical-align: .05rem;
}

.vp-auto-etat.est-armee { background: #e3f3ea; color: #1e7a4d; }

.vp-auto-titre { font-weight: 600; color: #1f2b38; }

.vp-auto-basculer {
  font-size: .85rem;
  padding: 0;
  text-decoration: none;
}

.vp-auto-phrase {
  font-size: .88rem;
  color: #4a5866;
  margin-top: .3rem;
}

/* La preuve de vie et la derniere salve. Petites : ce sont des faits
   de controle, pas ce qu'on vient lire. */
.vp-auto-faits {
  display: flex;
  gap: 1.2rem;
  flex-wrap: wrap;
  font-size: .8rem;
  color: #6b7885;
  margin-top: .35rem;
}

.vp-auto-faits:empty { display: none; }

.vp-auto-reglage {
  margin-top: .8rem;
  padding-top: .75rem;
  border-top: 1px solid #eef1f5;
}

.vp-auto-actions {
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  margin-top: .25rem;
}

.vp-auto-note {
  font-size: .82rem;
  color: #6b7885;
  line-height: 1.5;
  margin-top: .7rem;
}

/* Le champ de delai est un nombre a deux chiffres : lui donner la
   largeur d'un champ de texte ferait croire qu'on y saisit autre
   chose. */
#auto_delai_champ { max-width: 7rem; }

/* ===============================================================
   LE SUIVI DES OPERATIONS — /administration/suivi

   Douze cases a cocher, et elles ne sont pas des `.form-check`.

   Une case Bootstrap porte un libelle court sur une ligne ; chaque
   suivi porte un TITRE et la PHRASE qui dit ce qu'il couvre — « les
   accords rediges, corriges, supprimes ou repris ». Sans cette
   phrase, « Accords mets et bieres » ne dit pas si les corrections en
   font partie, et l'on coche a l'aveugle.

   LA TUILE ENTIERE EST LA CIBLE, comme la carte d'un module de
   l'accueil : on vise une surface, pas un carre de 16 px.

   C'est un <button>, jamais un <div> cliquable : il se prend au
   clavier et s'annonce comme un controle. `aria-pressed` porte l'etat,
   parce qu'une bordure ne se lit pas au lecteur d'ecran.
   =============================================================== */
.vp-suivis {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: .5rem;
}

.vp-suivi {
  display: block;
  width: 100%;
  text-align: left;
  padding: .55rem .7rem;
  border: 1px solid #dfe4ea;
  border-left: 3px solid #dfe4ea;
  border-radius: .5rem;
  background: #fff;
  transition: background .15s, border-color .15s;
}

.vp-suivi:hover { background: #f5f7f9; border-color: #c6ced7; }

/* Coche : le bord gauche marque, et le fond suit de loin. Un aplat
   plein ferait de douze tuiles cochees un bloc de couleur ou l'on ne
   distingue plus celle qui ne l'est pas. */
.vp-suivi.est-coche {
  background: #eef4fb;
  border-color: #b9cfe6;
  border-left-color: #1a3a5c;
}

.vp-suivi.est-coche:hover { background: #e3edf8; }

.vp-suivi-tete {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-bottom: .2rem;
}

/* La marque de la coche. Elle occupe sa place meme decochee — sinon le
   titre se decale d'un caractere a chaque clic, et douze tuiles
   bougeraient sous le curseur. */
.vp-suivi-marque {
  flex: 0 0 auto;
  width: 1rem;
  font-size: .8rem;
  color: #c6ced7;
}

.vp-suivi.est-coche .vp-suivi-marque { color: #1a3a5c; }

.vp-suivi-titre {
  font-size: .82rem;
  font-weight: 600;
  color: #16283c;
  line-height: 1.2;
}

.vp-suivi-aide {
  font-size: .7rem;
  color: #6b7580;
  line-height: 1.3;
}

/* Le compte des passerelles rattachees. Il ne dit pas une tache — il
   dit l'ampleur de ce qu'on coche, et sa discretion le dit aussi. */
.vp-suivi-nb {
  margin-left: auto;
  font-size: .68rem;
  color: #8b95a1;
  white-space: nowrap;
}

/* ============================================================
   VERROUILLER L'APPLICATION — CLAUDE.md section 39
   ============================================================ */

/* LE BANDEAU DU LAYOUT, sur chaque page, et pour les seuls exemptés.
   Du ROUGE, contrairement à l'ambre d'un module fermé : une fermeture
   de module est une décision qui laisse l'application debout, un verrou
   arrête tout le réseau. La distinction se lit avant le texte.

   Il est en pleine largeur, sous la barre de simulation et au-dessus du
   menu : c'est la première chose qu'on voit en arrivant, et il ne
   défile pas avec le contenu puisqu'il vit en tête de page. */
.vp-bandeau-verrou {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  padding: .45rem 1rem;
  background: #b02a37;
  color: #fff;
  font-size: .82rem;
  line-height: 1.35;
}

.vp-bandeau-verrou i { font-size: .9rem; opacity: .9; }

.vp-bandeau-verrou strong { font-weight: 700; }

.vp-bandeau-verrou a {
  margin-left: auto;
  color: #fff;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: .15rem;
  white-space: nowrap;
}

.vp-bandeau-verrou a:hover { color: #ffe9eb; }

/* LE BLOC D'ÉTAT, sur les deux écrans — celui de l'administrateur et
   celui que les autres reçoivent. Le même cadre des deux côtés : ce
   qu'un administrateur lit avant de cliquer doit ressembler à ce que
   son réseau lira après. */
.vp-verrou {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 46rem;
  margin: 1rem 0;
  padding: 1.4rem;
  background: #fff;
  border: 1px solid #e3e6ea;
  border-radius: .8rem;
  box-shadow: 0 1px 3px rgba(16, 32, 50, .05);
}

.vp-verrou.est-verrouille {
  background: #fdf3f4;
  border-color: #e6b8bd;
}

.vp-verrou-etat {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
}

.vp-verrou-icone {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: .7rem;
  font-size: 1.3rem;
  color: #fff;
  background: linear-gradient(180deg, #2f7d5a 0%, #226044 100%);
}

.vp-verrou.est-verrouille .vp-verrou-icone {
  background: linear-gradient(180deg, #c0424f 0%, #b02a37 100%);
}

.vp-verrou-titre {
  font-size: 1.05rem;
  font-weight: 700;
  color: #16283c;
  line-height: 1.3;
}

.vp-verrou-texte {
  margin-top: .3rem;
  font-size: .84rem;
  line-height: 1.5;
  color: #55606c;
}

/* CE QUE LES AUTRES LISENT, mot pour mot. En italique et entre
   guillemets : c'est une citation, pas une phrase de l'écran. */
.vp-verrou-phrase {
  margin-top: .5rem;
  font-size: .84rem;
  font-style: italic;
  color: #7a2b34;
}

/* LE GROS BOUTON. Il est gros parce que c'est un geste qu'on vient
   faire, pas un réglage qu'on ajuste — et parce qu'il n'y a pas de
   confirmation derrière lui : ce qu'il annonce, il le fait. Son
   deuxième niveau dit la CONSÉQUENCE, ce qu'un libellé d'un mot ne
   peut pas faire. */
.vp-verrou-bouton {
  display: flex;
  align-items: center;
  gap: .9rem;
  width: 100%;
  padding: 1rem 1.2rem;
  border: 0;
  border-radius: .7rem;
  color: #fff;
  text-align: left;
  cursor: pointer;
  transition: filter .12s ease, transform .06s ease;
}

.vp-verrou-bouton:hover { filter: brightness(1.08); }
.vp-verrou-bouton:active { transform: translateY(1px); }

.vp-verrou-bouton i { font-size: 1.5rem; flex: 0 0 auto; }

.vp-verrou-bouton span { display: flex; flex-direction: column; min-width: 0; }

.vp-verrou-bouton strong { font-size: 1rem; font-weight: 700; line-height: 1.25; }

.vp-verrou-bouton small { font-size: .78rem; opacity: .85; line-height: 1.35; }

.vp-verrou-bouton.est-fermer {
  background: linear-gradient(180deg, #c0424f 0%, #b02a37 100%);
}

.vp-verrou-bouton.est-ouvrir {
  background: linear-gradient(180deg, #2f7d5a 0%, #226044 100%);
}

/* =================================================================
   UNE ADRESSE AFFICHEE EST UNE ADRESSE CLIQUABLE

   Le courriel de ventes est celui a qui l'on reclame un rapport
   manquant (section 29) ; celui d'un compte est celui a qui l'on ecrit
   quand une session traine. Les deux s'affichaient en texte : on les
   selectionnait a la souris pour les coller dans un client de
   courriel.

   IL GARDE LA COULEUR DE SON VOISINAGE. Peint en bleu, il ferait de
   chaque tableau une page de liens — or ce qu'on vient lire est le
   magasin, pas l'adresse. Le pointille dit qu'il y a quelque chose a
   faire ; il ne le crie pas.

   UNE ADRESSE ABSENTE NE DEVIENT JAMAIS UN LIEN : les pages posent
   deux branches, et « mailto:null » ne s'ecrit nulle part.
   ================================================================= */
.vp-courriel {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: .18em;
  text-decoration-color: #b9c2cc;
}

.vp-courriel:hover,
.vp-courriel:focus-visible {
  color: #1a3a5c;
  text-decoration: underline solid;
}


/* =================================================================
   RECULER OU AVANCER D'UNE PERIODE

   Deux fleches encadrent le selecteur de periode. Comparer P5, P6
   puis P7 est le geste le plus courant des ecrans de plage, et il
   demandait a chaque fois d'ouvrir la liste et d'y chercher la ligne.

   ELLES ENCADRENT LE SELECTEUR, elles ne le suivent pas. A cote l'une
   de l'autre, « precedente » et « suivante » se visent au jugement ;
   de part et d'autre, le sens est dans la position.

   LE CADRE PORTE LA LARGEUR, et le <select> prend ce qui reste : sans
   `min-width: 0`, un enfant flex refuse de retrecir sous la largeur de
   son contenu et le champ deborde de sa colonne.
   ================================================================= */
.vp-periode-nav {
  display: flex;
  align-items: stretch;
  gap: .3rem;
}

.vp-periode-nav > .form-select {
  flex: 1 1 auto;
  min-width: 0;
}

.vp-periode-fleche {
  flex: 0 0 auto;
  width: 2.1rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid #dde3e9;
  border-radius: .375rem;
  background: #fff;
  color: #6b7580;
  font-size: .72rem;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}

.vp-periode-fleche:hover:not(:disabled),
.vp-periode-fleche:focus-visible:not(:disabled) {
  background: #eef2f7;
  border-color: #c7d0da;
  color: #1a3a5c;
}

/* AU BOUT DE L'HISTORIQUE, LA FLECHE SE TAIT. Elle reste visible —
   disparue, elle ferait chercher une fonction qu'on a deja vue — mais
   elle ne promet plus rien. */
.vp-periode-fleche:disabled {
  opacity: .4;
  cursor: default;
}


/* ============================================================
   Une cellule qui TRONQUE plutot que de pousser sa colonne

   `min-width: 0` — sans lui, un enfant de grille refuse de retrecir
   sous la largeur intrinseque de son texte. Mesure sur le journal :
   « 5fea1fd5-….pl-waw-3.baremetal.scw.cloud » fait passer la ligne de
   63 px a 123 px, et une poignee d'hotes infonuagiques suffit a faire
   sauter cent lignes.

   LE TEXTE ENTIER RESTE DANS LE DOM. Le survol le porte, et le filtre
   de la barre travaille de toute facon cote serveur sur la valeur
   complete : tronquer la DONNEE aurait casse la recherche sur ce qui
   est cache. La coupe est donc ici, et nulle part ailleurs.
   ============================================================ */
.vp-cellule-tronquee { min-width: 0; }


/* ============================================================
   COMPILER UN TABLEAU — la fenetre qui le nomme

   Elle vit dans `/partials/imprimer`, avec le bouton qui l'ouvre :
   trois ecrans de plage l'incluent, et trois copies de ces regles
   auraient fini par ne plus la dessiner pareil.

   `.vp-modale` porte deja le cadre, le fond et la boite. Ce qui suit
   n'ajoute que ce qui lui est propre — l'intro, l'avis, et la rangee
   de boutons.
   ============================================================ */
.vp-compiler-titre {
  font-size: 1.15rem;
  font-weight: 600;
  color: #16283c;
  margin: 0 0 .6rem;
}

.vp-compiler-intro {
  font-size: .82rem;
  color: #55606c;
  line-height: 1.5;
  margin: 0 0 1.1rem;
}

/* UN REFUS EST UN ETAT, PAS UNE PASTILLE : il ne se fane pas
   (section 13). L'ambre dit « quelque chose demande une
   intervention » ; le vert dit que c'est range. */
/* LES TEINTES SONT CELLES DE `.vp-alerte`, et la gravite se porte
   sur le LISERE GAUCHE : un cadre plein de plus aurait fait une
   troisieme facon de dire « attention » dans la meme
   application. */
.vp-compiler-avis {
  margin: 1rem 0 0;
  padding: .6rem .8rem;
  border-radius: .5rem;
  border-left: 3px solid #c8811a;
  background: #fdf8f0;
  color: #55606c;
  font-size: .82rem;
  line-height: 1.5;
}

.vp-compiler-avis.est-bon {
  border-left-color: #2f6b3c;
  background: #f3f9f4;
}

.vp-compiler-boutons {
  display: flex;
  justify-content: flex-end;
  gap: .6rem;
  margin-top: 1.2rem;
}


/* ============================================================
   UNE COMPILATION, TELLE QU'ELLE EST LUE

   Le meme bloc sert l'ecran des compilations et le fil d'une action :
   deux dessins du meme document auraient fini par ne plus dire la
   meme chose du meme tableau.

   LE TABLEAU DEFILE DANS SON PROPRE CADRE. Une compilation de deux
   cents lignes posee dans un fil de discussion pousserait la reponse
   suivante a deux ecrans de la, et l'on ne lirait plus la
   conversation.
   ============================================================ */
.vp-compilation {
  border: 1px solid #dde3ea;
  border-radius: .5rem;
  background: #fff;
  overflow: hidden;
}

.vp-compilation-entete {
  padding: .7rem .9rem;
  background: #f7f9fb;
  border-bottom: 1px solid #e6ebf0;
}

.vp-compilation-titre {
  font-size: .92rem;
  font-weight: 650;
  color: #16283c;
  margin: 0;
}

/* LE MOT-CLE EN CHASSE FIXE : on le compare caractere a caractere avec
   celui d'une autre compilation, et deux suites voisines se confondent
   autrement (section 38). */
.vp-compilation-motcle {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .76rem;
  color: #6b7885;
  word-break: break-all;
}

.vp-compilation-faits {
  font-size: .8rem;
  color: #55606c;
  margin-top: .25rem;
}

/* CE QUI QUALIFIE LES CHIFFRES voyage avec eux, comme sur le papier et
   dans le CSV (section 36). Detache de sa plage et de son perimetre,
   un tableau devient une colonne de nombres dont plus personne ne sait
   ce qu'ils mesurent. */
.vp-compilation-contexte {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .9rem;
  padding: .55rem .9rem;
  font-size: .78rem;
  color: #55606c;
  border-bottom: 1px solid #eef2f6;
}

.vp-compilation-contexte b {
  font-weight: 650;
  color: #16283c;
}

/* CE QUI AFFAIBLIT LES CHIFFRES — plage incomplete, comparaison
   ecartee, troncature. En ambre, jamais en rouge : rien n'est casse,
   quelque chose demande une intervention (section 13). */
.vp-compilation-avertissement {
  padding: .5rem .9rem;
  border-left: 3px solid #c8811a;
  background: #fdf8f0;
  border-bottom: 1px solid #e6ebf0;
  color: #55606c;
  font-size: .8rem;
  line-height: 1.5;
}

/* --- COPIER UN CODE PRODUIT ------------------------------------- */

/* LE CODE ET SON BOUTON NE SE COUPENT PAS. La colonne declare 130 a
   150 px, ce qui suffit — mais `table-layout: auto` ne traite une
   largeur declaree que comme une indication, et sur un tableau de dix
   colonnes serrees l'icone passait A LA LIGNE, sous le code. Constate
   a l'ecran.

   `inline-flex` les tient ensemble, et `nowrap` interdit la coupure :
   si la place manque vraiment, c'est la colonne qui s'elargit. Un code
   ne se coupe pas. */
.vp-cup {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
}

/* IL NE SE VOIT QU'AU SURVOL DE SA LIGNE. Deux cents lignes portant
   chacune une icone pleine feraient une colonne de boutons, et l'oeil
   ne trouverait plus le code — qui est ce qu'on vient lire. Au clavier
   il reparait au focus : ce qui ne se voit qu'au survol est
   inatteignable sans souris. */
.vp-copier {
  border: 0;
  background: none;
  padding: 0;
  margin-left: .3rem;
  color: #aab4bf;
  /* PLUS PETIT QUE LE CODE : il l'accompagne, il ne le concurrence
     pas. C'est le code qu'on vient lire. */
  font-size: .68rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease;
}

tr:hover .vp-copier,
.vp-copier:focus-visible { opacity: 1; }
.vp-copier:hover { color: #1a3a5c; }

/* LA CONFIRMATION EST SUR LE BOUTON, pas dans une pastille : on copie
   une ligne au milieu d'un tableau de deux cents, et un message en
   tete d'ecran ne se verrait pas (section 31). */
/* Le glyphe est echange par /partials/copier ; le bouton reste visible
   le temps de se lire, meme quand la souris a quitte la ligne. */
.vp-copier:has(.fa-check), .vp-copier:has(.fa-times) { opacity: 1; }
.vp-copier .fa-check { color: #1e7d4f; }

/* UN ECHEC SE DIT. Un bouton qui ne fait rien et ne dit rien se lit
   comme un bouton casse, et l'on reclique. En ambre : rien n'est
   casse, quelque chose demande une intervention (section 13). */
.vp-copier .fa-times { color: #c8811a; }

/* SUR UN ECRAN TACTILE il n'y a pas de survol : le bouton reste
   visible, sinon il serait inatteignable. */
@media (hover: none) {
  .vp-copier { opacity: .55; }
}

/* --- COMPILER UNE LISTE DE PRODUITS ----------------------------- */

/* LE BLOC SE REPLIE. On vient sur cet ecran pour LIRE ses
   compilations ; en creer une est le geste rare. Deploye, le
   formulaire aurait pousse la liste sous le pli a chaque ouverture.

   Meme forme que le repli d'un tableau dans un fil : une balise
   DETAILS native, un chevron qui tourne, aucun marqueur du
   navigateur — ils different d'un navigateur a l'autre. */
.vp-produits { padding: 0; overflow: hidden; }

.vp-produits-tete {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .8rem 1rem;
  cursor: pointer;
  font-size: .92rem;
  font-weight: 650;
  color: #16283c;
  user-select: none;
}

.vp-produits[open] .vp-produits-tete { border-bottom: 1px solid #e6ebf0; }
.vp-produits-tete:hover { background: #f7f9fb; }
.vp-produits-tete::-webkit-details-marker { display: none; }
.vp-produits-tete::marker { content: ''; }

.vp-produits-chevron {
  font-size: .75rem;
  color: #8b96a2;
  transition: transform .15s ease;
}

.vp-produits[open] .vp-produits-chevron { transform: rotate(90deg); }

/* Le chevron TOURNE QUAND MEME sous `prefers-reduced-motion` : c'est
   lui qui dit si le bloc est ouvert, et le figer priverait du seul
   repere quelqu'un qui a demande moins d'animation (section 38). */
@media (prefers-reduced-motion: reduce) {
  .vp-produits-chevron { transition: none; }
}

.vp-produits-corps { padding: 1rem; }

/* LES CODES EN CHASSE FIXE : on les compare chiffre a chiffre avec une
   etiquette, et deux suites voisines se confondent autrement
   (section 38). */
#f_prod_codes {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .85rem;
  line-height: 1.6;
}

/* --- LE TABLEAU SE REPLIE DANS UN FIL --------------------------- */

/* On ouvre une action pour lire ce qu'on demande et la discussion.
   Deploye, un tableau de deux cents lignes occupait les deux tiers de
   la fiche AVANT l'enonce : la conversation passait sous le pli, et
   l'on defilait dans trois cadres imbriques — la modale, le corps du
   tableau, et sa largeur.

   CE QUI RESTE VISIBLE est ce qui dit de quoi on parle ; ce qui se
   replie est le detail. C'est une balise DETAILS native : elle survit
   aux rendus d'App Connect, s'ouvre au clavier, et la recherche du
   navigateur y entre.

   L'ECRAN DES COMPILATIONS NE REPLIE RIEN : on y vient lire les
   tableaux, et un clic de plus par compilation serait un controle qui
   ne sert a rien (section 34). */
.vp-compilation-repli-tete {
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .5rem .9rem;
  cursor: pointer;
  font-size: .8rem;
  font-weight: 600;
  color: #2d4a6b;
  background: #fbfcfd;
  border-bottom: 1px solid #eef2f6;
  user-select: none;
}

.vp-compilation-repli-tete:hover { background: #f2f6fa; }

/* Le marqueur natif differe d'un navigateur a l'autre — triangle ici,
   fleche la, rien sur certains. On pose le notre, du meme jeu que le
   reste de l'application (section 13). */
.vp-compilation-repli-tete::-webkit-details-marker { display: none; }
.vp-compilation-repli-tete::marker { content: ''; }

.vp-compilation-chevron {
  font-size: .7rem;
  color: #8b96a2;
  transition: transform .15s ease;
}

.vp-compilation-repli[open] .vp-compilation-chevron { transform: rotate(90deg); }

/* SOUS `prefers-reduced-motion` le chevron TOURNE QUAND MEME, il ne
   fait que ne pas s'animer : c'est lui qui dit si le bloc est ouvert,
   et le figer priverait du seul repere quelqu'un qui a demande moins
   d'animation (section 38). */
@media (prefers-reduced-motion: reduce) {
  .vp-compilation-chevron { transition: none; }
}

/* Replie, le tableau n'a plus de filet au-dessus : le resume le porte
   deja, et deux traits colles se lisent comme un defaut. */
.vp-compilation-repli .vp-compilation-corps { border-top: 0; }

.vp-compilation-corps {
  max-height: 24rem;
  overflow: auto;
}

/* LE TABLEAU PORTE `vp-tableau-grille`, la grille commune : son
   en-tete, sa taille et ses filets viennent du meme endroit que
   ceux du comparatif. Les redessiner ici aurait fait un troisieme
   tableau dans une application qui en a deja deux formes.

   MAIS IL NE S'ECRASE PLUS DANS LA LARGEUR DE LA BOITE.

   Bootstrap pose `width: 100%` sur `.table`, et huit colonnes dans les
   46 rem d'une fiche d'action donnaient « 13 039,76 $ » SUR TROIS
   LIGNES. Un montant coupe en trois ne se lit plus comme un montant :
   on compte les chiffres au lieu de lire la somme, et deux lignes
   voisines cessent de se comparer d'un coup d'oeil.

   Le tableau prend donc la largeur qu'il lui faut — `min-width: 100%`
   pour occuper la boite quand il est etroit — et le cadre defile
   horizontalement. C'est la regle de la section 13 : un contenu large
   defile DANS SON PROPRE CADRE, jamais en poussant la page. */
.vp-compilation-corps table {
  width: auto;
  min-width: 100%;
}

/* --- LA TAILLE DU CORPS, ET ELLE ETAIT L'ENVERS ------------------ */

/* MESURE AVANT DE CHOISIR : l'en-tete rendait a 11,2 px et le corps a
   16 — LE CONTENU ETAIT 43 % PLUS GROS QUE SES TITRES. La grille
   commune declare la taille de ses `th` et pas celle de ses `td`, qui
   heritaient donc des 16 px de la page.

   C'est juste sur un ECRAN de ventes, ou le tableau occupe la largeur
   entiere. Ce n'est pas juste dans une CARTE : neuf lignes y coutaient
   546 px, et un libelle de deux mots passait a la ligne des que la
   colonne se resserrait — c'est ce qui faisait des lignes de cent
   pixels dans une fiche d'action.

   La reduction est bornee a `.vp-compilation-corps` : la grille
   continue de servir les vrais tableaux d'ecran sans changer d'un
   pixel. */
.vp-compilation-corps table { font-size: .78rem; line-height: 1.35; }

.vp-compilation-corps > table > thead > tr > th {
  font-size: .66rem;
  padding: .4rem .6rem;
}

.vp-compilation-corps > table > tbody > tr > td { padding: .32rem .6rem; }

/* LES CHIFFRES S'ALIGNENT CHIFFRE A CHIFFRE. Une colonne de montants
   se lit en comparant des rangs — dizaines sous dizaines — et une
   police proportionnelle donne au 1 une chasse plus etroite qu'au 8 :
   les colonnes cessent de se comparer d'un coup d'oeil. Cadrer a
   droite ne suffit pas, il faut la chasse tabulaire. */
.vp-compilation-corps .est-nombre { font-variant-numeric: tabular-nums; }

/* UN CHIFFRE NE SE COUPE JAMAIS. C'est le defaut qu'on vient de
   corriger, et c'est la seule regle qui le tienne : cadrer a droite ne
   sert a rien si la valeur passe a la ligne. */
.vp-compilation-corps .est-nombre {
  text-align: right;
  white-space: nowrap;
}

/* UN LIBELLE, LUI, SE REPLIE — mais pas jusqu'a une lettre par ligne.
   « MACRO IMPORTÉE » sur deux lignes se lit ; le meme sur six ne se lit
   plus. Le plancher vaut ce qu'il faut a un mot de segmentation. */
.vp-compilation-corps td:not(.est-nombre),
.vp-compilation-corps th:not(.est-nombre) {
  min-width: 7rem;
}

.vp-compilation-vide {
  padding: 1rem .9rem;
  color: #6b7885;
  font-size: .85rem;
}

.vp-compilation-pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .9rem;
  padding: .55rem .9rem;
  background: #f7f9fb;
  border-top: 1px solid #e6ebf0;
  font-size: .78rem;
  color: #6b7885;
}

/* SUR 320 PIXELS le tableau glisse dans son propre cadre, jamais la
   page : le corps porte deja `overflow: auto`, et c'est lui qui doit
   defiler. */
@media (max-width: 640px) {
  .vp-compilation-corps { max-height: 18rem; }
  .vp-compilation-contexte { gap: .3rem .6rem; }
}

/* Une action ou la compilation ouverte se lit deja. La ligne porte
   son etat — un brouillon n'est lu par personne d'autre que son
   auteur, et le taire ferait croire que le magasin a deja les
   chiffres (section 24). */
.vp-jointe {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem 0;
  border-bottom: 1px solid #f0f3f6;
  font-size: .88rem;
}

.vp-jointe:last-child { border-bottom: 0; }
.vp-jointe-titre { flex: 1 1 auto; min-width: 0; color: #2b3641; }


/* =================================================================
   LA FICHE D'UN PRODUIT — /ventes/bieres/produit

   LE PORT DES BARRES DU MOBILE, ET RIEN D'AUTRE. Les hauteurs, les
   trous et l'ordre chronologique viennent du serveur — `ventesCup`
   les calcule une fois pour les deux surfaces (CLAUDE.md section 38).
   Ce fichier ne fait que les peindre a la taille d'un ecran.

   POURQUOI UN DESSIN PLUTOT QU'UN TABLEAU : le tableau des six
   periodes dit tout, SAUF le trou. Une periode sans vente y est une
   ligne a zero qu'on ne remarque pas ; ici c'est un fut vide, et cela
   se voit sans lire un chiffre. C'est la seule chose que le dessin
   ajoute, et c'est pour elle qu'il existe.
   ================================================================= */

.vp-produit-recherche { display: flex; gap: .5rem; align-items: stretch; }
.vp-produit-recherche .form-control { flex: 1 1 auto; }

/* Le code se compare chiffre a chiffre avec une etiquette : en chasse
   proportionnelle, deux suites voisines se confondent. */
.vp-produit-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .92rem;
  letter-spacing: .02em;
  color: #55606c;
}

.vp-produit-tete {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}

.vp-produit-identite { flex: 1 1 22rem; min-width: 0; }

.vp-produit-nom {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.25;
  color: #16283c;
}

.vp-produit-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem .75rem;
  margin-top: .45rem;
  font-size: .82rem;
  color: #6b7580;
}

/* La vignette de capsule ne parait que si le module Capsules est
   ouvert : la garde est posee cote serveur, la classe ne fait que la
   loger. */
.vp-produit-capsule {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .7rem;
  border: 1px solid #e3e6ea;
  border-radius: .6rem;
  background: #f7f8fa;
}

.vp-produit-capsule img { width: 3rem; height: 3rem; object-fit: contain; }
.vp-produit-capsule-nom { font-weight: 600; color: #16283c; font-size: .9rem; }
.vp-produit-capsule-sous { font-size: .76rem; color: #6b7580; }

/* --- Les six dernieres periodes DU MAGASIN --- */

.vp-graphe {
  display: flex;
  align-items: flex-end;
  gap: .5rem;
  padding: .3rem 0 0;
}

.vp-graphe-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .3rem;
}

.vp-graphe-valeur {
  font-size: .76rem;
  line-height: 1.15;
  color: #6b7580;
  text-align: center;
  word-break: break-word;
}

.vp-graphe-valeur.est-rien { color: #aab4be; }

.vp-graphe-fut {
  display: flex;
  align-items: flex-end;
  width: 100%;
  height: 7rem;
  background: #f2f4f7;
  border-radius: .3rem;
  overflow: hidden;
}

.vp-graphe-barre {
  width: 100%;
  background: #97a0aa;
  border-radius: .3rem .3rem 0 0;
}

/* LA DERNIERE PERIODE EST CELLE QU'ON REGARDE EN PREMIER. Elle est en
   marine, les autres en gris : sans cela l'oeil cherche laquelle est la
   plus recente, alors que la reponse est toujours « la derniere a
   droite ». */
.vp-graphe-col.est-derniere .vp-graphe-barre { background: #2b5b8c; }
.vp-graphe-col.est-derniere .vp-graphe-valeur,
.vp-graphe-col.est-derniere .vp-graphe-periode {
  color: #16283c;
  font-weight: 600;
}

.vp-graphe-periode {
  font-size: .76rem;
  line-height: 1.15;
  color: #8a929c;
  text-align: center;
}

/* --- Ou il se situe : trois horizons cote a cote --- */

/* LE BUREAU LES MONTRE ENSEMBLE, ET C'EST SON SEUL AVANTAGE ICI. En
   allee on bascule d'un horizon a l'autre faute de place ; devant un
   grand ecran, « 2e sur la derniere periode et 40e sur six » se lit
   d'un coup — et c'est cette lecture-la qui dit qu'un produit demarre
   ou s'eteint. */
.vp-horizons {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
}

.vp-horizon-titre {
  font-size: .82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: #6b7580;
}

.vp-horizon-total {
  font-size: .9rem;
  color: #16283c;
  margin: .15rem 0 .7rem;
}

.vp-rang { margin-bottom: .8rem; }

.vp-rang-ligne {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
}

.vp-rang-nom { min-width: 0; }
.vp-rang-titre { font-weight: 600; color: #16283c; font-size: .88rem; }
.vp-rang-sous { display: block; font-size: .72rem; color: #8a929c; }
.vp-rang-chiffre { font-size: .84rem; color: #55606c; white-space: nowrap; }

.vp-rang-piste {
  display: block;
  height: .45rem;
  margin-top: .3rem;
  border-radius: 1rem;
  background: #eceff2;
  overflow: hidden;
}

.vp-rang-barre { display: block; height: 100%; border-radius: 1rem; }

/* LES QUATRE TONS SONT CEUX DU METIER — `ecrans.POSITIONS` — et la
   classe entiere est SERVIE : `dmx-bind:class` remplace l'attribut
   (section 8). L'ambre est celui de .vp-tableau-stat-alerte. */
.vp-rang-barre.est-vive    { background: #2f6b3c; }
.vp-rang-barre.est-bonne   { background: #2b5b8c; }
.vp-rang-barre.est-tiede   { background: #9a6700; }
.vp-rang-barre.est-eteinte { background: #97a0aa; }

.vp-rang-pied {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  margin-top: .2rem;
  font-size: .74rem;
  color: #8a929c;
}

/* --- Les resultats d'une recherche par nom --- */

.vp-produit-resultat {
  display: flex;
  align-items: baseline;
  gap: .75rem;
  width: 100%;
  text-align: left;
  padding: .55rem .7rem;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  background: #fff;
  margin-bottom: .35rem;
}

.vp-produit-resultat:hover { background: #f2f6fa; border-color: #b6c1cd; }
.vp-produit-resultat-nom { flex: 1 1 auto; min-width: 0; color: #16283c; }

/* =================================================================
   OU LA BARRE D ONGLETS BASCULE A DROITE

   A GAUCHE CE QU ON CONSULTE, A DROITE CE QU ON REGLE ET CE QU ON
   TIENT. Le bloc de droite est epingle au bord : il ne suit pas les
   onglets de lecture, il leur fait face.

   UN SEUL ELEMENT PORTE LA CLASSE, le premier du bloc : `margin-left:
   auto` pousse celui qui la porte ET tout ce qui le suit. Deux
   marqueurs se disputeraient la meme poussee, et le second n aurait
   plus d espace a prendre.

   La frontiere vient du catalogue de lib/auth.js — `a_droite` sur la
   PREMIERE page du bloc — et non d une position : elle suit la page
   meme quand celle-ci manque a ce compte. C est la regle de
   `separateur`, et les deux ne se confondent pas : le filet dit que la
   barre change de nature, la bascule dit qu elle change de cote.
   ================================================================= */
.vp-onglet-droite { margin-left: auto; }

/* ===============================================================
   UN PRODUIT OU UNE MARQUE, VUS DU RESEAU
     /ventes/bieres/reseau/produit  ·  /ventes/bieres/reseau/marque

   LES DEUX ECRANS PARTAGENT LEUR CORPS — /partials/reseau_detail —
   donc ils partagent leurs classes. Deux jeux auraient fini par ne
   plus peindre le meme fait de la meme facon.

   `.vp-rang-*` n'est pas repris ici : il vit plus haut, avec la fiche
   d'un produit, et c'est le meme barreau a une autre echelle.
   =============================================================== */

.vp-detail-attente {
  text-align: center;
  padding: 3rem 1rem;
  color: #8a929c;
}
.vp-detail-attente i { font-size: 2.4rem; opacity: .35; }
.vp-detail-attente p { margin: .8rem 0 0; font-size: .95rem; }

/* L'identite du produit, au-dessus de ses chiffres : on verifie d'un
   coup d'oeil qu'on regarde la bonne bouteille. */
.vp-detail-identite {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem .9rem;
  margin-bottom: 1rem;
}
.vp-detail-identite-nom {
  font-size: 1.05rem;
  font-weight: 600;
  color: #16283c;
}
/* Un code se compare chiffre a chiffre avec une etiquette : deux
   suites voisines se confondent en chasse proportionnelle. */
.vp-detail-identite-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .85rem;
  color: #8a929c;
}
.vp-detail-identite-lien { font-size: .85rem; text-decoration: none; }
.vp-detail-identite-lien i { font-size: .7rem; }

.vp-detail-tuiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .8rem;
  margin-bottom: 1rem;
}

.vp-detail-tuile {
  background: #fff;
  border: 1px solid #e3e7eb;
  border-radius: .5rem;
  padding: .8rem 1rem;
}
.vp-detail-tuile-titre {
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #8a929c;
}
.vp-detail-tuile-valeur {
  font-size: 1.5rem;
  font-weight: 600;
  color: #16283c;
  line-height: 1.25;
}
.vp-detail-tuile-detail { font-size: .78rem; color: #55606c; }

/* L'AMBRE, JAMAIS DU ROUGE : « ne le portent pas » demande une
   intervention, ce n'est pas une panne (section 13). */
.vp-detail-tuile.est-alerte { border-color: #e0c37a; background: #fdf8ec; }
.vp-detail-tuile.est-alerte .vp-detail-tuile-valeur { color: #7a5200; }

.vp-detail-colonnes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
  gap: .9rem;
}

.vp-detail-carte {
  background: #fff;
  border: 1px solid #e3e7eb;
  border-radius: .5rem;
  padding: .9rem 1rem 1rem;
  margin-bottom: .9rem;
}
.vp-detail-carte-titre {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #8a929c;
  margin-bottom: .6rem;
}
.vp-detail-compte {
  font-size: .78rem;
  font-weight: 600;
  color: #16283c;
  background: #eef1f4;
  border-radius: 1rem;
  padding: .05rem .5rem;
  letter-spacing: 0;
}
.vp-detail-compte.est-alerte { background: #f6e6bd; color: #7a5200; }

.vp-detail-liste { list-style: none; margin: 0; padding: 0; }
.vp-detail-liste li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 .8rem;
  padding: .35rem 0;
  border-top: 1px solid #f0f2f5;
}
.vp-detail-liste li:first-child { border-top: 0; }
.vp-detail-nom { font-size: .88rem; color: #16283c; min-width: 0; }
.vp-detail-chiffre {
  font-size: .88rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
}
.vp-detail-sous {
  grid-column: 1 / -1;
  font-size: .73rem;
  color: #8a929c;
}

/* TRENTE-TROIS ABSENTS EN LISTE NE SE LISENT PAS. En pastilles qui
   s'enroulent, ils se parcourent — et chacune porte son NOM, pas son
   seul numero (section 15). */
.vp-detail-pastilles { display: flex; flex-wrap: wrap; gap: .35rem; }
.vp-detail-pastille {
  font-size: .78rem;
  color: #55606c;
  background: #f4f6f8;
  border: 1px solid #e3e7eb;
  border-radius: 1rem;
  padding: .12rem .6rem;
}

.vp-detail-vide { font-size: .88rem; color: #2f6b3c; }
.vp-detail-vide i { margin-right: .35rem; }

.vp-detail-note {
  margin-top: .6rem;
  font-size: .73rem;
  color: #8a929c;
}

/* ===============================================================
   LA VUE D'ENSEMBLE DU RESEAU — /ventes/bieres/reseau
   =============================================================== */

/* CE QUE L'ECRAN COMPTE, ET CE QU'IL ECARTE. Il parait toujours :
   l'etat qu'il decrit est permanent, ce n'est pas une alerte, c'est
   ce qui qualifie ce qu'on lit (sections 33 et 46). En marine et non
   en ambre — l'ambre dit « quelque chose demande une intervention »,
   et il n'y a rien a corriger ici. */
.vp-panel {
  background: #f2f6fa;
  border: 1px solid #d5e0ea;
  border-radius: .5rem;
  padding: .7rem 1rem;
  margin-bottom: 1rem;
}
.vp-panel-titre {
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #4b6478;
  margin-bottom: .25rem;
}
.vp-panel-titre i { margin-right: .3rem; opacity: .6; }
.vp-panel-corps { font-size: .86rem; color: #2b4257; }
.vp-panel-brut { margin-top: .35rem; font-size: .8rem; color: #55707f; }

/* CE QUE LE LOT N'A PAS TRANSMIS.

   Le bandeau du dessus est en marine : il DECRIT ce qu'on lit. Celui-ci
   est en AMBRE, et c'est la difference — il nomme des rapports a
   reclamer, donc quelque chose qui demande une intervention. C'est la
   teinte de .vp-tableau-stat-alerte, la seule du projet pour cela
   (section 13). Jamais du rouge : rien n'est casse. */
.vp-retards {
  background: #fdf6e7;
  border: 1px solid #f0e0bd;
  border-radius: .5rem;
  padding: .7rem 1rem;
  margin-bottom: 1rem;
}
.vp-retards-titre {
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #9a6700;
  margin-bottom: .25rem;
}
.vp-retards-titre i { margin-right: .3rem; opacity: .7; }
.vp-retards-phrase { font-size: .86rem; color: #5c4a1f; margin-bottom: .5rem; }

/* Une grille et non un tableau : le magasin et ses periodes s'alignent
   d'une ligne a l'autre, et `auto` sur le magasin donne a la colonne
   la largeur du plus long nom plutot qu'une valeur ecrite ici.

   LE COMPTE DES PERIODES NE S'AFFICHE PAS : elles sont la, et les
   compter est immediat. Il range la liste — le plus en retard en
   tete — et cela se voit sans le dire. */
.vp-retards-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .12rem .9rem;
  font-size: .82rem;
}
.vp-retards-liste li { display: contents; }
.vp-retards-magasin { color: #2b4257; font-weight: 500; white-space: nowrap; }
.vp-retards-periodes { color: #6b5a30; }
.vp-retards-note { margin-top: .45rem; font-size: .78rem; color: #7d6a3c; }

.vp-ratios {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: .8rem;
  margin-bottom: 1.2rem;
}
.vp-ratio {
  background: #fff;
  border: 1px solid #e3e7eb;
  border-radius: .5rem;
  padding: .75rem .9rem;
}
.vp-ratio-titre {
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #8a929c;
  /* Le titre porte son aide au survol : un ratio dont on ne sait pas
     ce qu'il divise ne dit rien (section 13). */
  cursor: help;
}
.vp-ratio-valeur {
  font-size: 1.35rem;
  font-weight: 600;
  color: #16283c;
  line-height: 1.3;
  /* Les chiffres s'alignent chiffre a chiffre d'une tuile a l'autre. */
  font-variant-numeric: tabular-nums;
}
.vp-ratio-detail { font-size: .76rem; color: #55606c; }

.vp-palmares-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
  gap: .9rem;
}

.vp-palmares {
  background: #fff;
  border: 1px solid #e3e7eb;
  border-radius: .5rem;
  padding: .85rem 1rem 1rem;
}
.vp-palmares-titre {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .92rem;
  font-weight: 600;
  color: #16283c;
}
.vp-palmares-compte {
  font-size: .74rem;
  font-weight: 600;
  color: #16283c;
  background: #eef1f4;
  border-radius: 1rem;
  padding: .05rem .5rem;
}
.vp-palmares-aide {
  font-size: .75rem;
  color: #8a929c;
  margin: .1rem 0 .6rem;
}

.vp-palmares-liste { list-style: none; margin: 0; padding: 0; }
.vp-palmares-liste li {
  display: grid;
  grid-template-columns: 1.4rem 1fr auto;
  align-items: baseline;
  gap: 0 .6rem;
  padding: .3rem 0;
  border-top: 1px solid #f0f2f5;
}
.vp-palmares-liste li:first-child { border-top: 0; }

.vp-palmares-rang {
  font-size: .74rem;
  color: #97a0aa;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.vp-palmares-nom { min-width: 0; font-size: .86rem; color: #16283c; }
/* Le lien garde la couleur de son voisinage : peint en bleu, il ferait
   de chaque palmares une page de liens — or ce qu'on vient lire est le
   classement, pas la destination (section 13). */
.vp-palmares-nom a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted #b7c0c9;
}
.vp-palmares-nom a:hover { border-bottom-color: #1a3a5c; }
.vp-palmares-sous { display: block; font-size: .72rem; color: #8a929c; }

.vp-palmares-chiffre {
  font-size: .86rem;
  font-weight: 600;
  color: #16283c;
  white-space: nowrap;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* LES DEUX COMPARAISONS, ETIQUETEES. Un pourcentage seul ne dit pas
   face a quoi — et deux references sous le meme chiffre se lisent
   comme une seule (section 13). Les sigles sont expliques une fois par
   palmares, pas repetes sur chaque ligne. */
.vp-palmares-vars {
  display: flex;
  gap: .55rem;
  justify-content: flex-end;
  margin-top: .1rem;
}
.vp-var {
  display: inline-flex;
  align-items: baseline;
  gap: .22rem;
  font-size: .72rem;
  font-weight: 400;
}
.vp-var em {
  font-style: normal;
  color: #97a0aa;
  font-size: .64rem;
  letter-spacing: .03em;
}
.vp-var b { font-weight: 500; color: #55606c; font-variant-numeric: tabular-nums; }
.vp-var b.est-absente { color: #b7c0c9; }

.vp-palmares-cles {
  display: flex;
  flex-wrap: wrap;
  gap: .1rem .9rem;
  font-size: .7rem;
  color: #97a0aa;
  margin: -.3rem 0 .6rem;
}
.vp-palmares-cles b { color: #6b7580; font-weight: 600; }

.vp-palmares-var {
  display: block;
  font-size: .72rem;
  font-weight: 400;
  color: #8a929c;
}
.vp-palmares-note {
  margin-top: .55rem;
  font-size: .73rem;
  color: #8a929c;
}

/* UNE COLONNE D'IMAGE REND SA VIGNETTE, PAS SON CHEMIN.

   Le tableau d'un releve porte parfois une colonne d'images — la
   capsule d'une lecture par programme. Elle s'imprimait en toutes
   lettres, « /images/capsules/capsule_2.png », une chaine que personne
   ne lit et qui ne dit rien de la capsule qu'elle designe.

   36 px : la ligne du tableau fait 0,32 rem de marge haute et basse,
   et une vignette plus grande la ferait grandir de moitie sur toute la
   colonne. C'est la taille a laquelle on RECONNAIT une capsule sans la
   lire — celle de la gamme mobile (section 38), pas celle de la fiche
   d'un produit. */
.vp-compilation-corps .vp-tab-vignette {
  display: block;
  width: 36px;
  height: 36px;
  /* `contain` et non `cover` : les 63 fichiers ne sont pas tous
     calibres pareil, et recadrer couperait le nom de la sensation
     imprime dessus (section 18). */
  object-fit: contain;
}

/* LE RELEVÉ QU'UNE ACTION PORTE, sur sa carte dans la fiche d'une
   visite. On lisait « Haut · Opérationnel · En cours » sans savoir
   qu'un tableau de ventes l'appuyait, et il fallait ouvrir la fiche
   pour l'apprendre.

   SOUS LE MÉMO, ET PLUS DISCRET : ce qu'on vient lire est ce qu'on
   demande au magasin ; le relevé dit d'où l'argument vient. */
.vp-groupe-releve {
  display: block;
  margin-top: .2rem;
  font-size: .74rem;
  color: #6b7580;
}

.vp-groupe-releve > i {
  margin-right: .35rem;
  opacity: .65;
}


/* ===============================================================
   LA BULLE DE L'ASSISTANT — sur toutes les pages du bureau

   Elle remplace la carte de l'accueil et l'entree de menu : deux
   chemins vers une conversation qu'on ouvre maintenant sans quitter
   l'ecran qu'on regarde (section 74). C'est ce que la version mobile
   fait depuis la section 64, et le dessin en reprend la position —
   en bas a droite, la ou la main revient.
   =============================================================== */
.vp-ia-bulle {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  /* Sous une fenetre centrale (1050) : ouverte, elle la couvre. Une
     bulle qui flotterait par-dessus le voile se lirait comme un
     bouton du dialogue. */
  z-index: 1040;
  width: 3.25rem;
  height: 3.25rem;
  border: 0;
  border-radius: 50%;
  background: #1a3a5c;
  color: #fff;
  font-size: 1.15rem;
  box-shadow: 0 4px 14px rgba(16, 32, 50, .35);
  cursor: pointer;
  transition: transform .12s, box-shadow .12s;
}

.vp-ia-bulle:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgba(16, 32, 50, .4);
}

/* Un mouvement qui n'apporte rien a qui a demande moins d'animation.
   Le bouton, lui, reste : c'est le seul chemin vers l'assistant. */
@media (prefers-reduced-motion: reduce) {
  .vp-ia-bulle, .vp-ia-bulle:hover { transition: none; transform: none; }
}

/* LA BOITE A UNE HAUTEUR, ET C'EST CE QUI FAIT DEFILER LE FIL PLUTOT
   QUE LA BOITE. Sans elle, la saisie descend avec la conversation et
   il faut remonter pour poser la question suivante — c'est la regle
   de la feuille mobile (section 64). */
.vp-ia-fenetre .vp-modale-boite {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 6rem);
  /* La boite ne defile pas : le fil defile dedans. */
  overflow: hidden;
}

.vp-ia-cadre {
  display: flex;
  flex-direction: column;
  min-height: 0;   /* sans lui, un enfant defilant refuse de retrecir */
  flex: 1 1 auto;
}

.vp-ia-titre {
  padding-right: 2rem;        /* la croix */
  margin-bottom: .75rem;
  font-weight: 600;
  color: #1a3a5c;
}

.vp-ia-titre > i { opacity: .75; margin-right: .4rem; }

/* LE FIL PREND CE QUI RESTE, et lui seul defile. */
.vp-ia-fil {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid #e2e8f0;
  border-radius: .5rem;
  padding: 1rem;
  background: #fff;
}

.vp-ia-vide { text-align: center; color: #94a3b8; padding: 1.5rem 1rem; }
.vp-ia-vide > i { font-size: 1.8rem; opacity: .5; }
.vp-ia-vide p { margin: .75rem 0 0; font-size: .9rem; }

.vp-ia-exemples {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  margin-top: .9rem;
}

/* AMBRE, ET NON ROUGE. Le rouge du verrou dit « tout est arrete » ;
   ici rien ne casse — une porte est ouverte plus grand qu'elle ne
   devrait (section 13). */
.vp-ia-ouvert {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  background: #fdf6ec;
  border: 1px solid #e8c99b;
  border-radius: .5rem;
  padding: .6rem .8rem;
  margin-bottom: .75rem;
  color: #7a4f1d;
  font-size: .84rem;
  line-height: 1.45;
}

/* LE REFUS VIT DANS LE FLUX ET NE S'EFFACE PAS : un refus est un
   etat, pas une confirmation (section 13). */
.vp-ia-refus {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  background: #fff8e6;
  border: 1px solid #f0d9a0;
  border-radius: .5rem;
  padding: .6rem .8rem;
  margin-top: .75rem;
  color: #7a4f1d;
  font-size: .86rem;
}

.vp-ia-poser { margin-top: .75rem; flex: 0 0 auto; }

.vp-ia-barre {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .6rem;
  flex-wrap: wrap;
}

.vp-ia-note { font-size: .8rem; color: #64748b; }
.vp-ia-boutons { display: flex; gap: .5rem; align-items: center; }

@media (max-width: 40rem) {
  .vp-ia-fenetre .vp-modale-boite { height: calc(100vh - 2rem); }
}

/* ELLE S'EFFACE QUAND UNE FENETRE EST OUVERTE — la sienne comme une
   autre. Le layout pose « vp-modale-ouverte » sur le body des qu'une
   fenetre est a l'ecran (section 61) : un bouton rond qui flotte
   par-dessus un dialogue se lit comme un de ses controles, et le
   sien serait un bouton qui ouvre ce qui est deja ouvert.

   « display », et non l'opacite : ce qu'on ne voit pas ne doit pas se
   cliquer. */
body.vp-modale-ouverte .vp-ia-bulle { display: none; }


/* --- LE VERDICT DE SANTE D'UNE LIGNE (CLAUDE.md section 75) ---------

   Un visage, un pointage, et les criteres replies. Il repond a « est-ce
   que ca va ? » sans demander de lire un tableau.

   LE ROUGE EST UNE EXCEPTION DECIDEE. La section 13 le refuse partout
   ailleurs — « ce qui demande une intervention, en ambre, jamais du
   rouge » — et cette regle ne bouge pas pour .vp-tableau-stat-alerte ni
   pour .vp-module-fait-alerte, qui gardent leur #9a6700.

   Ici le rouge ne signale pas une tache : il rend un VERDICT, et un
   verdict a trois degres ne se lit pas sur une seule teinte graduee,
   debout, a bout de bras. Philippe a tranche le 3 septembre 2026.

   LES VERTS ET LES AMBRES SONT CEUX DU PROJET — ceux de .vp-puce
   est-sur et est-doute, eux-memes repris de .vp-alerte. Deux jeux de
   verts dans la meme application se liraient comme deux applications.
   Seul le rouge est neuf, parce qu'aucun autre bloc n'en portait. */

.vp-sante {
  border: 1px solid #dde3ea;
  border-left-width: 4px;
  border-radius: .5rem;
  background: #fff;
  padding: .9rem 1.1rem;
}

.vp-sante-tete {
  display: flex;
  align-items: flex-start;
  gap: .9rem;
}

/* Le visage porte le verdict a lui seul : c'est ce qu'on lit de loin,
   et c'est pourquoi il est bien plus gros que le texte. */
.vp-sante-visage {
  font-size: 2.1rem;
  line-height: 1;
  flex: none;
  margin-top: .1rem;
}

.vp-sante-dit { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }

.vp-sante-verdict { font-size: 1.05rem; font-weight: 600; }

/* Le pointage se lit sous le verdict, pas a cote : « 4 sur 6 » precise
   le visage, il ne le concurrence pas. */
.vp-sante-note { font-size: .82rem; color: #5a6472; }

/* Ce qui a plafonne le verdict. Sans cette ligne, un visage qui ne
   sourit pas au-dessus de « 5 sur 6 » se lit comme une erreur
   d'affichage, et l'on cherche longtemps. */
/* L'espace apres l'icone vient d'ICI, pas d'un `me-1` de Bootstrap : ce
   bloc doit se dessiner pareil partout ou il sera repris, et la version
   mobile ne charge pas Bootstrap (section 38). */
.vp-sante-plafond { font-size: .78rem; font-weight: 600; }
.vp-sante-plafond > i { margin-right: .3rem; }

.vp-sante-raison { font-size: .78rem; color: #6b7580; font-style: italic; }

/* --- LES QUATRE VERDICTS ------------------------------------------- */

.vp-sante-bien    { border-left-color: #3f8f5f; background: #f7fbf8; }
.vp-sante-bien    .vp-sante-visage,
.vp-sante-bien    .vp-sante-verdict { color: #1f5132; }

.vp-sante-moyen   { border-left-color: #d9a441; background: #fdfaf3; }
.vp-sante-moyen   .vp-sante-visage,
.vp-sante-moyen   .vp-sante-verdict,
.vp-sante-moyen   .vp-sante-plafond { color: #7a5312; }

.vp-sante-mauvais { border-left-color: #b4342c; background: #fdf6f5; }
.vp-sante-mauvais .vp-sante-visage,
.vp-sante-mauvais .vp-sante-verdict,
.vp-sante-mauvais .vp-sante-plafond { color: #9c2b24; }

/* « Trop peu pour juger » n'est pas une graduation des trois autres :
   il dit qu'on ne se prononce pas. Il est donc GRIS, et non un ambre
   pale qui se lirait comme un demi-avertissement. */
.vp-sante-indecis { border-left-color: #aab3bd; background: #fafbfc; }
.vp-sante-indecis .vp-sante-visage,
.vp-sante-indecis .vp-sante-verdict { color: #6b7580; }

/* --- LE POINTAGE, REPLIE ------------------------------------------- */

.vp-sante-detail { margin-top: .7rem; }

.vp-sante-detail > summary {
  cursor: pointer;
  font-size: .82rem;
  color: #5a6472;
  list-style: none;
}
.vp-sante-detail > summary::-webkit-details-marker { display: none; }

/* Le chevron tourne a l'ouverture. `\f078` est fa-angle-down en Font
   Awesome 5 — la version que le layout charge. */
.vp-sante-detail > summary::before {
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  content: "\f054";
  display: inline-block;
  margin-right: .45rem;
  font-size: .7rem;
  transition: transform .15s;
}
.vp-sante-detail[open] > summary::before { transform: rotate(90deg); }

.vp-sante-liste {
  list-style: none;
  margin: .6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.vp-sante-liste > li {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  font-size: .82rem;
}

/* La marque de chaque critere. Elle est dessinee en CSS et non servie :
   c'est de l'habillage, et l'etat, lui, vient du serveur. */
.vp-sante-marque {
  flex: none;
  width: 1.1rem;
  text-align: center;
  font-weight: 700;
  line-height: 1.4;
}

.vp-sante-reussi     .vp-sante-marque::before { content: "\2713"; color: #3f8f5f; }
.vp-sante-echoue     .vp-sante-marque::before { content: "\2715"; color: #b4342c; }
/* « Sans objet » n'est pas un echec : on a regarde, il n'y avait rien a
   regarder. Un tiret le dit ; une croix pale se lirait comme un demi-
   echec. */
.vp-sante-sans_objet .vp-sante-marque::before { content: "\2014"; color: #aab3bd; }

.vp-sante-corps { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }

.vp-sante-titre { font-weight: 600; }
.vp-sante-echoue .vp-sante-titre { color: #9c2b24; }
.vp-sante-sans_objet .vp-sante-titre,
.vp-sante-sans_objet .vp-sante-constat { color: #8a939d; font-weight: 500; }

.vp-sante-constat { color: #5a6472; }

/* QUOI FAIRE. C'est ce qui distingue un critere d'un chiffre rouge. */
.vp-sante-agir { color: #7a5312; font-style: italic; }

@media (max-width: 575.98px) {
  .vp-sante { padding: .75rem .85rem; }
  .vp-sante-visage { font-size: 1.7rem; }
}

/* --- LA PUCE DE LA COLONNE « SANTÉ » -------------------------------

   Un bouton, et non un simple <span> : il s'atteint au clavier, il
   annonce qu'il ouvre quelque chose, et il porte son infobulle. Une
   cellule cliquable qui ne serait pas un bouton ne dirait ni ou
   commence l'action, ni qu'il y en a une (section 18). */

.vp-sante-puce {
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  padding: .1rem .3rem;
  line-height: 1;
  font-size: 1.15rem;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}

.vp-sante-puce:hover,
.vp-sante-puce:focus-visible { border-color: currentColor; }

/* Les memes quatre teintes que le bloc deplie : deux jeux de couleurs
   pour un meme verdict se liraient comme deux verdicts. */
.vp-sante-puce.vp-sante-bien    { color: #1f5132; }
.vp-sante-puce.vp-sante-moyen   { color: #7a5312; }
.vp-sante-puce.vp-sante-mauvais { color: #9c2b24; }

/* « Trop peu pour juger » s'efface : ce n'est pas un verdict, c'est une
   absence de verdict, et deux cents points d'interrogation a pleine
   couleur dans une colonne noieraient les lignes qui, elles, disent
   quelque chose (section 14). */
.vp-sante-puce.vp-sante-indecis { color: #c2c9d1; }

/* --- LA MENTION « PROJET TEST » ------------------------------------

   Elle vit DANS la fenetre, pas sous le tableau : c'est la qu'on lit le
   verdict en detail, donc la qu'il faut savoir ce qu'il vaut. Sous le
   tableau, elle aurait ete lue une fois puis oubliee. */

.vp-sante-test {
  margin: .9rem 0 0;
  padding-top: .7rem;
  border-top: 1px solid #e9edf2;
  font-size: .76rem;
  font-style: italic;
  color: #6b7580;
}

/* =================================================================
   UNE LIGNE QUI SE COCHE

   « Marques à rattacher » est le seul écran du projet où l'on choisit
   PLUSIEURS lignes avant d'écrire : Les Brasseurs du Nord portent
   BORÉALE et ses déclinaisons, et les cocher ensemble est le geste.
   Ailleurs, une ligne se choisit seule et la classe est-choisie suffit.

   Le reste vient de .vp-attente-ligne : c'est la même liste que
   « Capsules à associer », et elle doit se lire pareil.
   ================================================================= */
.vp-attente-ligne.est-cochable {
  display: flex;
  gap: .55rem;
  align-items: flex-start;
  cursor: pointer;
}

/* min-width: 0 — sans lui, un enfant flex refuse de rétrécir sous la
   largeur de son contenu, et l'ellipsis du nom ne se déclenche jamais. */
.vp-attente-ligne.est-cochable .vp-attente-corps {
  flex: 1 1 auto;
  min-width: 0;
}

/* .vp-attente-nom est un <div> ailleurs ; ici c'est un <span>, et il
   lui faut son display pour que l'ellipsis tienne. */
.vp-attente-ligne.est-cochable .vp-attente-nom { display: block; }

.vp-attente-ligne.est-cochable .vp-coche {
  flex: 0 0 auto;
  margin-top: .15rem;
}

/* Le montant se compare d'une ligne à l'autre : chasse tabulaire, et
   la couleur du texte plutôt que le gris du détail. */
.vp-attente-detail .est-chiffre {
  font-weight: 600;
  color: #16283c;
  font-variant-numeric: tabular-nums;
}

/* Creer un producteur sans quitter l ecran de rattachement. Replie,
   parce qu on vient rattacher — creer est le geste rare. */
.vp-creer-fournisseur {
  margin-top: .9rem;
  padding-top: .8rem;
  border-top: 1px solid #e3e8ee;
}

.vp-creer-fournisseur > summary {
  cursor: pointer;
  font-size: .78rem;
  font-weight: 600;
  color: #2f6fed;
  list-style: none;
}

.vp-creer-fournisseur > summary::-webkit-details-marker { display: none; }

/* Le chevron vient de Font Awesome 5 — la version 6 n est pas chargee,
   et un nom introduit en 6 ne rend AUCUN glyphe (section 8). */
.vp-creer-fournisseur > summary::before {
  content: "\f0da";
  font-family: "Font Awesome 5 Free";
  font-weight: 900;
  font-size: .6rem;
  margin-right: .4rem;
  display: inline-block;
}

.vp-creer-fournisseur[open] > summary::before { content: "\f0d7"; }

.vp-creer-fournisseur[open] > summary { margin-bottom: .7rem; }

/* ------------------------------------------------------------------
   Les marques dont on a DÉCIDÉ qu'elles n'ont pas de producteur

   Une ligne, son motif, et le bouton qui défait. Elle emprunte
   .vp-attente-ligne — c'est la même liste que celle du haut, et en
   inventer une seconde donnerait deux listes qui ne se ressemblent
   pas (le défaut de cet écran, déjà payé une fois).

   Une CLASSE et non l'identifiant du conteneur : c'est la convention
   de .est-cochable juste au-dessus, et une seconde liste tranchée
   écrite demain la porterait sans qu'on y pense.

   Ce qui change est le bouton : il vit à DROITE, où l'œil le cherche
   quand il balaie une colonne de gestes.
   ------------------------------------------------------------------ */
.vp-attente-ligne.est-tranchee {
  display: flex;
  align-items: center;
  gap: .6rem;
}

.vp-attente-ligne.est-tranchee .vp-attente-corps {
  flex: 1;
  min-width: 0;   /* sans lui, un long motif pousse le bouton dehors */
}

.vp-attente-ligne.est-tranchee .vp-attente-nom { display: block; }

/* Le bouton ne se coupe pas : c'est le motif qui cède la place. */
.vp-attente-ligne.est-tranchee .vp-reprendre {
  flex: none;
  white-space: nowrap;
  font-size: .7rem;
}

/* =================================================================
   MARQUES À RATTACHER — c'est la colonne de GAUCHE qui est bornée

   L'inverse de `.vp-associer-colonnes`, où la matrice de droite donne
   la hauteur et la liste s'y ajuste. Ici la liste EST le contenu long
   — soixante-dix marques, deux cents au plus — et la colonne de
   droite tient en trois blocs pliés.

   ELLE NE TOUCHE JAMAIS LE BAS DE LA FENÊTRE. La borne est posée par
   le script de la page, qui MESURE d'où la carte part : écrite en
   pixels ici, elle serait fausse dès qu'une ligne s'ajoute au cadre de
   contexte, et personne ne le verrait. C'est la règle du cadrage sur
   les lentilles (section 38) — la hauteur d'un en-tête se mesure,
   jamais ne s'écrit.

   `min-height: 0` n'est pas décoratif : sans lui, un enfant défilant
   d'un conteneur flex refuse de rétrécir sous la hauteur de son
   contenu, et la carte déborde au lieu de faire défiler la liste.

   Rien au-dessous de lg : les colonnes s'empilent, la liste reprend
   sa borne de 32 rem, et il n'y a plus deux hauteurs à accorder.
   ================================================================= */
@media (min-width: 992px) {
  #carte_attente {
    display: flex;
    flex-direction: column;
    /* Un repli tant que le script n'a pas mesuré : sans lui, la carte
       s'affiche à sa hauteur naturelle une fraction de seconde, et la
       page saute. */
    max-height: calc(100vh - 12rem);
  }

  #carte_attente .vp-attente {
    max-height: none;
    flex: 1 1 auto;
    min-height: 0;
  }

  /* Deux cartes empilées dans la même colonne ne se touchent pas.
     .vp-carte ne porte aucune marge basse — elle vit d ordinaire seule
     dans sa colonne — et sans cette règle les deux blocs de droite se
     lisent comme un seul. */
  .vp-fournisseurs-colonnes .vp-carte + .vp-carte { margin-top: 1rem; }

  /* La colonne de droite prend sa hauteur naturelle : ses trois blocs
     sont pliés, et le quatrième — les marques tranchées — défile dans
     sa propre carte plutôt que de pousser la page. */
  #liste_tranchees { max-height: 22rem; }
}

/* ------------------------------------------------------------------
   « En tablette » — les deux seules teintes de .vp-detail-pastille

   La pastille neutre existe déjà et sert partout ; ce qui manquait
   est de distinguer ce que ce magasin porte de ce qu'il ne porte pas.
   C'est la question de la fiche d'un producteur, et une cellule grise
   parmi des cellules grises ne la pose pas.

   AMBRE, JAMAIS ROUGE. Ce n'est pas une panne : c'est ce qu'il
   pourrait commander, donc une décision (section 13).
   ------------------------------------------------------------------ */
.vp-detail-pastille.est-porte {
  color: #2f6b3c;
  background: #eef6f0;
  border-color: #cfe4d6;
}

.vp-detail-pastille.est-absent {
  color: #8a5a00;
  background: #fdf3e0;
  border-color: #f0dcb8;
}

/* ------------------------------------------------------------------
   La carte du réseau — trois cents points

   Elle prend la hauteur qu'on peut lui donner sans pousser le reste
   sous le pli : la fiche d'un magasin borne son iframe à 22 rem, une
   carte du Québec entier a besoin de davantage.

   `aspect-ratio` plutôt qu'une hauteur fixe : sur un écran étroit la
   carte se resserre au lieu de déborder, et le Québec reste entier
   parce que `fitBounds` recalcule le zoom.
   ------------------------------------------------------------------ */
.vp-carte-reseau {
  width: 100%;
  aspect-ratio: 16 / 10;
  max-height: 34rem;
  min-height: 20rem;
  border: 1px solid #e3e6ea;
  border-radius: .5rem;
  background: #f4f6f8;
}

@media (max-width: 48rem) {
  .vp-carte-reseau { aspect-ratio: 3 / 4; max-height: none; }
}

/* La bulle d'un point. Google pose son propre cadre : on n'écrit que
   ce qu'il y a dedans. */
.vp-carte-bulle-nom {
  font-size: .86rem;
  font-weight: 600;
  color: #16283c;
}

.vp-carte-bulle-detail {
  font-size: .74rem;
  color: #6b7580;
  margin-top: .15rem;
}

.vp-carte-bulle a {
  display: inline-block;
  margin-top: .4rem;
  font-size: .76rem;
}

/* ---------------------------------------------------------------
   LES ECHANGES D ASSORTIMENT — /marche/ensemble

   UNE PAIRE PAR LIGNE : ce qu on retire a gauche, ce qu on met a
   droite, le gain au bout. Deux listes cote a cote obligeraient a
   apparier de tete — et c est justement le travail qu on veut eviter.

   LA GRILLE PLUTOT QU UN FLEX : les trois colonnes doivent s aligner
   d une ligne a l autre, sinon l oeil ne peut pas balayer la colonne
   des gains. `auto-fit` n a rien a faire ici — les roles sont fixes.
   --------------------------------------------------------------- */
.vp-echanges {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.vp-echange {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto;
    gap: .75rem;
    align-items: center;
    padding: .6rem .75rem;
    border: 1px solid var(--vp-bord, #e3e6ea);
    border-radius: .4rem;
    background: #fff;
}

/* Sous 900 px la paire ne tient plus en ligne : elle s empile, et la
   fleche devient horizontale — un chevron vertical entre deux blocs
   empiles se lit comme une decoration. */
@media (max-width: 900px) {
    .vp-echange { grid-template-columns: 1fr; gap: .4rem; }
    .vp-echange-fleche { transform: rotate(90deg); justify-self: start; }
}

.vp-echange-cote { min-width: 0; }

.vp-echange-role {
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #7b8794;
}

/* Ce qu on retire est efface, ce qu on met est net : la hierarchie se
   lit sans avoir a lire les mots. */
.vp-echange-cote.est-retire .vp-echange-nom { color: #7b8794; }
.vp-echange-cote.est-mis .vp-echange-nom { color: #172e42; font-weight: 600; }

.vp-echange-nom {
    font-size: .9rem;
    line-height: 1.25;
    overflow-wrap: anywhere;
}

.vp-echange-detail {
    font-size: .76rem;
    color: #7b8794;
    margin-top: .15rem;
}

.vp-echange-detail code {
    font-size: .74rem;
    color: #55606c;
}

/* LE RANG DE LA SOURCE se lit sans etre crie : il qualifie le chiffre,
   il ne l alerte pas. */
.vp-echange-source {
    font-style: italic;
}

/* LE CALCUL, en chasse fixe : on y compare trois facteurs, et une
   police proportionnelle les desaligne d une ligne a l autre. */
.vp-echange-calcul {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .7rem;
    color: #8a95a1;
    margin-top: .2rem;
    overflow-wrap: anywhere;
}

.vp-echange-fleche { color: #b6bec7; }

.vp-echange-gain {
    text-align: right;
    white-space: nowrap;
}

.vp-echange-gain-chiffre {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: #1f7a4d;
}

.vp-echange-gain-note {
    font-size: .72rem;
    color: #7b8794;
}

/* Le calcul d’un échange se replie : répété en chasse fixe sur
   vingt-cinq lignes, il noyait le nom de la marque. */
.vp-echange-comment { margin-top: .25rem; }

.vp-echange-comment > summary {
    font-size: .72rem;
    color: #5b6b7d;
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: .3rem;
}

.vp-echange-comment > summary::-webkit-details-marker { display: none; }

/* Le chevron dit qu’il y a quelque chose à ouvrir ; il ne le crie
   pas — le nom de la marque reste ce qu’on vient lire (section 13). */
.vp-echange-comment > summary::before {
    content: "\f105";
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    font-size: .68rem;
    opacity: .7;
    transition: transform .15s ease;
}

.vp-echange-comment[open] > summary::before { transform: rotate(90deg); }
.vp-echange-comment > summary:hover { color: #16283c; }

/* --- Une carte qui se replie ---
   Un inventaire de soixante-dix-huit marques n’est pas une tâche : il
   se replie, et son résumé porte le compte. Ce qu’on vient chercher se
   lit plus haut. */
.vp-carte-repli > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: baseline;
    gap: .2rem;
}

.vp-carte-repli > summary::-webkit-details-marker { display: none; }

.vp-carte-repli > summary::before {
    content: "\f105";
    font-family: "Font Awesome 5 Free";
    font-weight: 900;
    color: #8a95a1;
    margin-right: .4rem;
    transition: transform .15s ease;
}

.vp-carte-repli[open] > summary::before { transform: rotate(90deg); }

/* Le titre garde sa graisse et perd sa marge : dans un résumé
   d’une ligne, elle poussait le compte en dessous. */
.vp-carte-repli > summary .vp-carte-titre { margin: 0; }

/* UN CODE EST EN CHASSE FIXE, JAMAIS EN ROUGE. Bootstrap peint <code>
   en rose : soixante-dix-huit marques × six codes faisaient un mur
   rouge, et le rouge dit une erreur dans cette application (section
   13). */
.vp-carte-repli code,
.vp-echange-detail code { color: #5b6b7d; }

/* ================================================================
   LA FENETRE DE BIENVENUE — au premier chargement (section 87)

   Elle parait une fois, a la premiere venue d'un compte que la salve
   d'accueil a reellement accueilli. La lire la marque, et elle ne
   revient plus.

   LE dmx-show VA SUR UN ENVELOPPEUR, jamais sur .vp-bienvenue : cette
   classe pose un display, et App Connect ecrit le sien en clair — les
   deux s'ecrasent sans la moindre erreur (section 38).
   ================================================================ */
.vp-bienvenue {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.vp-bienvenue-fond {
    position: absolute;
    inset: 0;
    background: rgba(23, 46, 66, .45);
}

.vp-bienvenue-boite {
    position: relative;
    background: #fff;
    border-radius: .6rem;
    box-shadow: 0 1.2rem 3rem rgba(0, 0, 0, .28);
    padding: 2rem 2.2rem;
    max-width: 34rem;
    text-align: center;
}

.vp-bienvenue-icone {
    font-size: 2.2rem;
    color: #1a3a5c;
    margin-bottom: .75rem;
}

.vp-bienvenue-titre {
    font-size: 1.35rem;
    font-weight: 600;
    margin-bottom: .75rem;
}

.vp-bienvenue-texte {
    color: #4a5b6c;
    margin-bottom: .6rem;
}

.vp-bienvenue-envois {
    font-size: .88rem;
    color: #6c757d;
    margin-bottom: 1.2rem;
}

/* ===============================================================
   LA BOUTIQUE DES CAPSULES — /capsules/boutique et
   /administration/boutique (CLAUDE.md section 99)

   L'etat d'une commande a son propre vocabulaire : ce n'est ni une
   alerte (l'ambre dit « quelque chose demande une intervention ») ni
   une portee. Recue est ce qui attend l'administrateur, et c'est la
   seule teinte qui appelle un geste ; livree est le cas ferme, annulee
   le cas gris.
   =============================================================== */
/* LE CONTENU D'UNE COMMANDE — une ligne par couleur, servie en texte
   (section 99). Vingt-sept « Couleur · Sensation × n » a la file dans
   une colonne etroite faisaient une ligne de deux ecrans de haut ; groupes
   par couleur, ils tiennent en sept lignes, dans l ordre de l etagere. */
.vp-boutique-contenu { min-width: 22rem; }
/* Sur la gestion, le tableau porte sept colonnes : le contenu se replie plus tot. */
.vp-boutique-contenu.est-serre { min-width: 14rem; }
.vp-boutique-contenu-resume { display: block; color: #6b7684; font-size: .74rem; margin-bottom: .15rem; }
.vp-boutique-contenu-texte { display: block; white-space: pre-line; line-height: 1.45; }

.vp-boutique-statut {
  display: inline-block;
  padding: .16rem .55rem;
  border-radius: 999px;
  font-size: .76rem;
  font-weight: 650;
  line-height: 1.35;
  white-space: nowrap;
  border: 1px solid transparent;
}
.vp-boutique-statut.est-recue    { color: #c8811a; background: #fdf8f0; border-color: #ecdcbf; }
.vp-boutique-statut.est-preparation { color: #6a4c93; background: #f7f4fb; border-color: #e0d6ee; }
.vp-boutique-statut.est-expediee { color: #2b5c8a; background: #f2f7fb; border-color: #d2e2ef; }
.vp-boutique-statut.est-livree   { color: #2f6b3c; background: #f3f9f4; border-color: #cfe3d4; }
.vp-boutique-statut.est-annulee  { color: #6b7684; background: #f4f6f8; border-color: #e2e6ea; }

/* Le catalogue : une carte par capsule, la vignette qu'on reconnait en
   tablette, et une quantite. `min(100%, ...)` : une piste plus large
   que son conteneur deborde au lieu de se replier. */
.vp-boutique-cartes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: .6rem;
}

.vp-boutique-capsule {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .6rem;
  border: 1px solid #e3e6ea;
  border-radius: .6rem;
  background: #fff;
}
.vp-boutique-capsule img { flex: 0 0 auto; object-fit: contain; }
.vp-boutique-capsule.est-rupture { opacity: .55; background: #f7f8f9; }
.vp-boutique-capsule-corps { flex: 1 1 auto; min-width: 0; }
.vp-boutique-capsule-nom {
  font-size: .82rem;
  font-weight: 650;
  color: #16283c;
  line-height: 1.25;
}
.vp-boutique-capsule-dispo {
  font-size: .74rem;
  color: #6b7580;
  margin-top: .1rem;
}
.vp-boutique-qte { flex: 0 0 4.4rem; width: 4.4rem; text-align: right; font-variant-numeric: tabular-nums; }
.vp-boutique-etagere { width: 5.5rem; text-align: right; font-variant-numeric: tabular-nums; }

/* La confirmation : une ligne par capsule, puis les totaux, les
   montants cadres a droite en chasse tabulaire. */
.vp-boutique-lignes { margin-bottom: .6rem; }
.vp-boutique-totaux { border-top: 1px solid #e3e6ea; padding-top: .5rem; }
.vp-boutique-ligne {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .18rem 0;
  font-size: .88rem;
  color: #16283c;
}
.vp-boutique-ligne.est-total {
  border-top: 1px solid #e3e6ea;
  margin-top: .3rem;
  padding-top: .45rem;
  font-weight: 700;
  font-size: .95rem;
}
.vp-boutique-montant { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* POUR LE MAGASIN (migration 134) — la remise, qui l'a autorisee, et la
   note, sous le catalogue, pour qui commande au nom du reseau. Les
   champs se rangent comme ceux du cadre de contexte, et se replient sur
   un ecran etroit. La remise sur place porte un vert d'etat termine. */
.vp-boutique-pour {
  display: flex;
  flex-wrap: wrap;
  gap: .8rem 1.2rem;
  align-items: flex-start;
  padding-top: .8rem;
  border-top: 1px solid #e3e6ea;
}
.vp-boutique-pour .vp-champ-pleine { flex: 1 1 100%; }
.vp-boutique-remises { display: flex; flex-wrap: wrap; gap: .4rem; }
.vp-boutique-remise {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .75rem;
  border: 1px solid #d2dbe4;
  border-radius: 999px;
  background: #fff;
  font-size: .86rem;
  cursor: pointer;
}
.vp-boutique-remise:has(input:checked) { border-color: #1a3a5c; background: #eef3f8; font-weight: 650; }
.vp-boutique-note { text-align: right; white-space: normal; max-width: 70%; }
.vp-boutique-statut.est-remise { color: #1e6b5c; background: #f1f8f6; border-color: #cbe3dc; }

/* ---------------------------------------------------------------
   UNE DEMANDE D'INSCRIPTION, LUE (section 101). L'apercu se lit avant
   d'ecrire : une etiquette a gauche, la valeur lue a droite, et une
   valeur absente en tiret — jamais un blanc, qui se lirait comme un
   defaut d'affichage. Les personnes, elles, sont des comptes de
   velociteplus.com : une carte chacune, avec ce qu'il reste a y creer.
   --------------------------------------------------------------- */
.vp-demande-apercu {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) 1fr;
  gap: .18rem 1rem;
  margin: 0;
  font-size: .86rem;
}
.vp-demande-apercu dt { color: #6b7580; font-weight: 500; margin: 0; }
.vp-demande-apercu dd { color: #16283c; margin: 0; overflow-wrap: anywhere; }
.vp-demande-apercu dd.est-vide { color: #8a929c; }
.vp-demande-titre {
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #6b7580;
  margin: 1rem 0 .4rem;
}
.vp-demande-personnes { display: flex; flex-wrap: wrap; gap: .6rem; }
.vp-demande-personne {
  flex: 1 1 16rem;
  border: 1px solid #e3e6ea;
  border-radius: .4rem;
  padding: .55rem .75rem;
  font-size: .84rem;
  background: #fbfcfd;
}
.vp-demande-personne-nom { font-weight: 650; color: #16283c; }
.vp-demande-personne-fonction { color: #6b7580; }
.vp-demande-personne-courriel { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: .8rem; }
.vp-demande-ecart { font-size: .84rem; }
.vp-demande-ecart b { color: #16283c; }
#f_demande_texte { font-size: .82rem; line-height: 1.35; }

/* ===============================================================
   LE BANDEAU DE LA CONSIGNE — /partials/consigne
   En tete des ecrans de ventes et de reseau. Un constat, pas une
   alerte : ni ambre ni rouge (section 13). Le liseré marine dit que
   la consigne est comptee ; gris, qu'elle est retiree.
   =============================================================== */
.vp-consigne {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .85rem;
  padding: .5rem .9rem;
  margin-bottom: .75rem;
  background: #f6f8fa;
  border: 1px solid #e3e6ea;
  border-left: 4px solid #98a4b1;
  border-radius: .5rem;
  font-size: .88rem;
  color: #33475b;
}
.vp-consigne.est-incluse { border-left-color: #1a3a5c; background: #f1f5fa; }
.vp-consigne-icone { color: #6b7a89; font-size: .95rem; }
.vp-consigne.est-incluse .vp-consigne-icone { color: #1a3a5c; }
.vp-consigne-texte { flex: 1 1 16rem; min-width: 0; }
.vp-consigne-texte strong { font-weight: 600; color: #1a3a5c; margin-right: .35rem; }
.vp-consigne-detail { color: #6b7a89; }
.vp-consigne-bouton {
  border: 1px solid #1a3a5c;
  color: #1a3a5c;
  background: #fff;
  white-space: nowrap;
}
.vp-consigne-bouton:hover:not(:disabled) { background: #1a3a5c; color: #fff; }
.vp-consigne-refus { margin: -.4rem 0 .75rem; font-size: .82rem; color: #9a3412; }
