/* ============================================================
   PaysagePro — 30-components.css
   Catalogue de composants.

   Convention : .pp-<bloc>, .pp-<bloc>__<element>, .pp-<bloc>--<variante>.
   Les noms historiques (.btn, .card, .alert, .badge, .form-control,
   .form-group, .table) restent des alias exacts : ils sont utilises
   des centaines de fois dans les vues et ne seront pas renommes.

   Tout composant a : un etat :focus-visible, un etat desactive, et
   respecte prefers-reduced-motion (via la regle globale de 10-reset).

   Aucun !important dans ce fichier.
   ============================================================ */

/* ============================================================
   1. BOUTONS

   L'ancien socle n'offrait que deux niveaux, d'ou un usage de
   429 .btn-secondary pour 306 .btn-primary : tout ce qui n'etait pas
   l'action principale tombait dans "secondary". Quatre niveaux
   permettent aux boutons existants de trouver leur vrai rang.
   ============================================================ */
.btn,
.pp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--pp-space-2);
  min-height: 40px;
  padding: 0 var(--pp-space-4);
  border: 1px solid transparent;
  border-radius: var(--pp-radius);
  font-size: var(--pp-fs-400);
  font-weight: var(--pp-fw-medium);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  background: transparent;
  color: var(--pp-text);
  transition: background-color var(--pp-dur-fast) var(--pp-ease-standard),
              border-color var(--pp-dur-fast) var(--pp-ease-standard),
              color var(--pp-dur-fast) var(--pp-ease-standard),
              transform var(--pp-dur-instant) var(--pp-ease-standard);
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }

.btn:disabled,
.btn[aria-disabled="true"],
.btn.is-disabled {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* --- Niveau 1 : l'action de la page. Une seule. --- */
.btn-primary {
  background: var(--pp-primary);
  border-color: var(--pp-primary);
  color: var(--pp-primary-on);
  box-shadow: var(--pp-shadow-xs);
}
.btn-primary:hover  { background: var(--pp-primary-hover);  border-color: var(--pp-primary-hover);  color: var(--pp-primary-on); }
.btn-primary:active { background: var(--pp-primary-active); border-color: var(--pp-primary-active); }

/* --- Niveau 2 : actions de rang 2 --- */
.btn-secondary {
  background: var(--pp-surface);
  border-color: var(--pp-border-strong);
  color: var(--pp-text);
}
.btn-secondary:hover { background: var(--pp-surface-2); border-color: var(--pp-primary); color: var(--pp-text); }

/* --- Niveau 3 : barres d'outils, cellules de tableau (nouveau) --- */
.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--pp-text);
}
.btn-ghost:hover { background: var(--pp-surface-2); color: var(--pp-text); }

/* --- Niveau 4 : navigation deguisee en action (nouveau) --- */
.btn-link {
  background: transparent;
  border-color: transparent;
  color: var(--pp-link);
  padding: 0 var(--pp-space-1);
  min-height: 0;
}
.btn-link:hover { color: var(--pp-link-hover); text-decoration: underline; }

/* --- Destruction --- */
.btn-danger {
  background: var(--pp-danger-text);
  border-color: var(--pp-danger-text);
  color: #fff;
}
.btn-danger:hover { filter: brightness(0.92); color: #fff; }

.btn-danger-soft {
  background: var(--pp-danger-bg);
  border-color: var(--pp-danger-border);
  color: var(--pp-danger-text);
}
.btn-danger-soft:hover { background: var(--pp-danger-border); color: var(--pp-danger-text); }

/* --- Conserves pour compatibilite, deconseilles dans la doc --- */
.btn-success { background: var(--pp-success-text); border-color: var(--pp-success-text); color: #fff; }
.btn-success:hover { filter: brightness(0.92); color: #fff; }
.btn-warning { background: var(--pp-warning-bg); border-color: var(--pp-warning-border); color: var(--pp-warning-text); }
.btn-warning:hover { background: var(--pp-warning-border); color: var(--pp-warning-text); }

/* Filet : un .btn nu tombe au niveau 2 plutot que d'etre invisible. */
.btn:not([class*="btn-"]) {
  background: var(--pp-surface);
  border-color: var(--pp-border-strong);
  color: var(--pp-text);
}

/* --- Tailles --- */
.btn-xs { min-height: 24px; padding: 0 var(--pp-space-2); font-size: var(--pp-fs-200); gap: var(--pp-space-1); }
.btn-sm { min-height: 32px; padding: 0 var(--pp-space-3); font-size: var(--pp-fs-300); }
.btn-lg { min-height: 48px; padding: 0 var(--pp-space-6); font-size: var(--pp-fs-500); }
.btn-block { display: flex; width: 100%; }

.btn-icon {
  padding: 0;
  aspect-ratio: 1;
  min-width: 40px;
}
.btn-icon.btn-sm { min-width: 32px; }
.btn-icon.btn-xs { min-width: 24px; }

.btn-group { display: inline-flex; }
.btn-group > .btn { border-radius: 0; margin-left: -1px; }
.btn-group > .btn:first-child { border-radius: var(--pp-radius) 0 0 var(--pp-radius); margin-left: 0; }
.btn-group > .btn:last-child  { border-radius: 0 var(--pp-radius) var(--pp-radius) 0; }

/* --- Etat de chargement (nouveau) : evite la double soumission --- */
.btn[aria-busy="true"] {
  pointer-events: none;
  position: relative;
}
.btn[aria-busy="true"] > * { opacity: 0.6; }

/* --- Cible tactile : corrige les boutons de ligne a 26px, meme quand
       leur padding est pose en style inline (l'inline ne declare ni
       min-height ni min-width). --- */
@media (pointer: coarse) {
  .btn, .pp-btn { min-height: var(--pp-tap-min); min-width: var(--pp-tap-min); }
  .btn-xs, .btn-sm { min-height: var(--pp-tap-min); }
}

/* ============================================================
   2. CHAMPS DE FORMULAIRE
   ============================================================ */
.pp-field,
.form-group {
  display: block;
  margin-bottom: var(--pp-space-4);
  min-inline-size: 0;
}

.pp-label,
.form-group > label {
  display: block;
  margin-bottom: var(--pp-space-1);
  font-size: var(--pp-fs-300);
  font-weight: var(--pp-fw-medium);
  color: var(--pp-text);
}
.pp-label__req { color: var(--pp-danger-text); margin-left: 2px; }

.pp-input,
.pp-select,
.pp-textarea,
.form-control {
  display: block;
  width: 100%;
  padding: var(--pp-space-2) var(--pp-space-3);
  background: var(--pp-input-bg);
  color: var(--pp-text);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-sm);
  font-size: var(--pp-fs-400);
  transition: border-color var(--pp-dur-fast), box-shadow var(--pp-dur-fast);
}
.pp-input:hover,
.form-control:hover { background: var(--pp-input-bg-hover); }

.pp-input:focus,
.form-control:focus,
.pp-select:focus,
.pp-textarea:focus {
  outline: none;
  border-color: var(--pp-primary);
  box-shadow: var(--pp-focus-ring);
}

.pp-input:disabled,
.form-control:disabled,
.pp-input[readonly] {
  background: var(--pp-surface-2);
  color: var(--pp-text-muted);
  cursor: not-allowed;
}

.pp-input-sm, .form-control-sm { padding: var(--pp-space-1) var(--pp-space-2); font-size: var(--pp-fs-300); }

/* Chevron du select en SVG, pas en emoji : il suit la couleur du texte. */
.pp-select, select.form-control {
  appearance: none;
  padding-right: var(--pp-space-8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23536257' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--pp-space-3) center;
  background-size: 16px;
}

/* --- Etat d'erreur, porte par aria-invalid et non par une classe :
       cote PHP il n'y a qu'un attribut a ajouter. --- */
.pp-input[aria-invalid="true"],
.form-control[aria-invalid="true"],
.pp-input.is-invalid,
.form-control.is-invalid,
[aria-invalid="true"].pp-select,
[aria-invalid="true"].pp-textarea {
  border-color: var(--pp-danger-text);
  background: var(--pp-danger-bg);
}
.pp-input[aria-invalid="true"]:focus,
.form-control[aria-invalid="true"]:focus,
.form-control.is-invalid:focus {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pp-danger-text) 28%, transparent);
}

.pp-hint {
  display: block;
  margin-top: var(--pp-space-1);
  font-size: var(--pp-fs-300);
  color: var(--pp-text-muted);
}
.pp-error,
.field-error {
  display: block;
  margin-top: var(--pp-space-1);
  font-size: var(--pp-fs-300);
  font-weight: var(--pp-fw-medium);
  color: var(--pp-danger-text);
}

/* --- Groupe de champ : prefixe, suffixe, bouton accole --- */
.pp-input-group { display: flex; align-items: stretch; }
.pp-input-group > .pp-input,
.pp-input-group > .form-control { border-radius: 0; margin-left: -1px; min-width: 0; }
.pp-input-group > :first-child { border-radius: var(--pp-radius-sm) 0 0 var(--pp-radius-sm); margin-left: 0; }
.pp-input-group > :last-child  { border-radius: 0 var(--pp-radius-sm) var(--pp-radius-sm) 0; }
.pp-input-group__addon {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--pp-space-3);
  background: var(--pp-surface-2);
  border: 1px solid var(--pp-border);
  color: var(--pp-text-muted);
  font-size: var(--pp-fs-400);
  white-space: nowrap;
}
.pp-input-group > .pp-input:focus { position: relative; z-index: 1; }

/* --- Cases, radios, interrupteur --- */
.pp-check {
  display: inline-flex;
  align-items: center;
  gap: var(--pp-space-2);
  font-size: var(--pp-fs-400);
  cursor: pointer;
}

/* ============================================================
   3. CARDS  (composant le plus utilise : 640 occurrences)
   ============================================================ */
.pp-card,
.card {
  background: var(--pp-card-bg);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-lg);
  padding: var(--pp-space-7);          /* 24 -> 28 : la card respire */
  box-shadow: var(--pp-shadow-sm);
  margin-bottom: var(--pp-space-5);
  min-inline-size: 0;
}
.card > h2:first-child,
.card > h3:first-child,
.pp-card > :first-child { margin-top: 0; }

/* Titre de section : une seule taille, quel que soit le niveau ecrit dans la
   vue (v1.61.0). Les ecrans se partageaient entre h2 et h3 en tete de carte,
   soit 28 et 22 px pour deux choses identiques a l'oeil — une section de page.
   La taille retenue est celle de la fiche d'appel d'offres, la plus travaillee
   du projet. Le niveau reste libre dans le balisage : c'est la hierarchie du
   document, elle regarde le lecteur d'ecran, pas la mise en page. */
.card > h2:first-child,
.card > h3:first-child,
/* Meme titre, pose dans une barre d'en-tete avec son bouton d'action : sans
   ces deux selecteurs, deux tailles de titre cohabiteraient sur une meme page
   (fiche client, comptabilite). */
.card > div:first-child > h2:first-child,
.card > div:first-child > h3:first-child { font-size: var(--pp-fs-800); }
.pp-card > :last-child,
.card > :last-child { margin-bottom: 0; }

.pp-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--pp-space-4);
  margin-bottom: var(--pp-space-4);
}
.pp-card__title { font-size: var(--pp-fs-600); margin: 0; }
.pp-card__actions { display: flex; gap: var(--pp-space-2); flex-shrink: 0; }
.pp-card__footer {
  margin-top: var(--pp-space-5);
  padding-top: var(--pp-space-4);
  border-top: 1px solid var(--pp-border);
}

.pp-card--compact { padding: var(--pp-space-4); }
.pp-card--flush   { padding: 0; overflow: hidden; }
.pp-card--muted   { background: var(--pp-surface-2); box-shadow: none; }
.pp-card--interactive {
  cursor: pointer;
  transition: box-shadow var(--pp-dur-base) var(--pp-ease-standard),
              transform var(--pp-dur-base) var(--pp-ease-standard);
}
.pp-card--interactive:hover { box-shadow: var(--pp-shadow-md); transform: translateY(-1px); }

.pp-card--accent-success { border-left: 3px solid var(--pp-success-text); }
.pp-card--accent-warning { border-left: 3px solid var(--pp-warning-text); }
.pp-card--accent-danger  { border-left: 3px solid var(--pp-danger-text); }
.pp-card--accent-info    { border-left: 3px solid var(--pp-info-text); }

/* ============================================================
   4. TABLEAUX
   ============================================================ */
.pp-table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border-radius: var(--pp-radius);
}
.pp-table-wrap:focus-visible { outline: 2px solid var(--pp-focus-color); outline-offset: 2px; }

/* Sans hauteur maximale, position:sticky n'a aucun conteneur de
   defilement et ne colle pas : c'est le piege classique. */
.pp-table-wrap--tall {
  max-block-size: min(70dvh, 720px);
  overflow: auto;
}
.pp-table-wrap--tall th {
  position: sticky;
  top: 0;
  z-index: var(--pp-z-sticky);
}

.pp-table, .table { width: 100%; border-collapse: collapse; font-size: var(--pp-fs-400); }
.pp-table th, .table th {
  background: var(--pp-th-bg);
  color: var(--pp-th-text);
  font-weight: var(--pp-fw-semibold);
  text-align: left;
  padding: var(--pp-space-3) var(--pp-space-4);
  border-bottom: 1px solid var(--pp-border-strong);
  white-space: nowrap;
}
.pp-table td, .table td {
  padding: var(--pp-space-3) var(--pp-space-4);
  border-bottom: 1px solid var(--pp-border);
}
.pp-table tbody tr:last-child td,
.table tbody tr:last-child td { border-bottom: 0; }

/* srgb, pas "srgh" : la faute de frappe de zip15.css:325 rendait la
   declaration invalide et le zebrage ne tenait qu'au repli. */
/* La teinte se calculait depuis la bordure a 4 % : #ffffff contre #fcfdfc, deux
   valeurs qu'aucun ecran ne distingue — le zebrage existait sur le papier et
   nulle part a l'oeil. Elle se calcule desormais depuis l'ENCRE, ce qui la rend
   visible sans la rendre sale, et surtout juste dans les deux themes : en
   sombre le meme melange eclaircit la ligne au lieu de l'assombrir, ce qu'une
   teinte ecrite en dur ne saura jamais faire. */
.pp-table tbody tr:nth-child(even) td,
.table tbody tr:nth-child(even) td {
  background: color-mix(in srgb, var(--pp-text) 5%, var(--pp-card-bg));
}
.pp-table tbody tr:hover td,
.table tbody tr:hover td { background: var(--pp-primary-soft); }

.pp-table--compact th, .pp-table--compact td { padding: var(--pp-space-1) var(--pp-space-3); font-size: var(--pp-fs-300); }
.pp-table--comfortable th, .pp-table--comfortable td { padding: var(--pp-space-4); font-size: var(--pp-fs-500); }

.pp-table td[data-numeric],
.pp-table--num td:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* ============================================================
   BOUTON D'ACTION EN RELIEF — colonne « Actions » d'un tableau
   ============================================================
   Trois ou quatre actions ecrites en toutes lettres tenaient plus de place que
   la ligne qu'elles concernaient : sur les 119 vues du projet qui portent une
   colonne d'actions, c'est elle qui poussait le tableau hors de l'ecran. Le
   bouton devient une pastille, et le nom s'affiche au survol ET au focus.

       <a class="pp-btn-icone" href="…"
          data-nom="Ouvrir la fiche" aria-label="Ouvrir la fiche">📂</a>

   AUCUN ATTRIBUT title : l'infobulle native met une seconde a s'ouvrir,
   n'existe pas au clavier et pas du tout au doigt. Le nom vit dans data-nom,
   rendu par ::after, et repris a l'identique dans aria-label — c'est celui-la,
   et lui seul, que le lecteur d'ecran annonce. Les deux doivent dire la meme
   chose, sinon l'ecran et la voix ne decrivent pas le meme bouton.

   L'ETIQUETTE S'OUVRE VERS LA GAUCHE parce que la colonne d'actions est la
   derniere, et que .pp-table-wrap defile en x : il rogne tout ce qui deborde de
   sa boite. Ouverte vers la droite ou au-dessus, elle serait coupee.

   LE RELIEF tient a trois ombres et non a une image : un lisere clair sur
   l'arete haute, une tranche pleine sous le bouton, une ombre portee molle.
   Enfoncer fait tomber la tranche et rentrer l'ombre — c'est ce mouvement,
   plus que le degrade, qui donne l'objet. Une image serait figee, ignorerait
   le theme sombre et couterait une requete. */
.pp-btn-grille {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: start;
  gap: 5px;
}
.pp-btn-grille--file { display: flex; flex-wrap: wrap; }
.pp-btn-grille form { margin: 0; display: inline-flex; }

.pp-btn-icone {
  --pp-bi-fond: var(--pp-card-bg);
  --pp-bi-encre: var(--pp-text);
  --pp-bi-tranche: var(--pp-border);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 0.88rem;
  line-height: 1;
  cursor: pointer;
  color: var(--pp-bi-encre);
  text-decoration: none;
  border: 1px solid var(--pp-border);
  border-radius: 8px;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--pp-bi-fond) 88%, #fff),
      var(--pp-bi-fond));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 65%, transparent),
    0 2px 0 var(--pp-bi-tranche),
    0 3px 6px rgba(0, 0, 0, 0.16);
  transition: transform 0.08s ease, box-shadow 0.08s ease, filter 0.12s ease;
}
.pp-btn-icone:hover {
  filter: brightness(1.06);
  transform: translateY(-1px);
  text-decoration: none;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 65%, transparent),
    0 3px 0 var(--pp-bi-tranche),
    0 5px 10px rgba(0, 0, 0, 0.2);
}
/* Enfonce : la tranche disparait, l'ombre passe a l'interieur. */
.pp-btn-icone:active { transform: translateY(2px); filter: none; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.28); }
.pp-btn-icone:focus-visible { outline: 2px solid var(--pp-focus-color); outline-offset: 2px; }

.pp-btn-icone--primaire { --pp-bi-fond: var(--pp-primary); --pp-bi-encre: var(--pp-primary-on);
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-primary) 62%, #000);
                          border-color: color-mix(in srgb, var(--pp-primary) 78%, #000); }
.pp-btn-icone--danger   { --pp-bi-fond: var(--pp-danger-text); --pp-bi-encre: #fff;
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-danger-text) 62%, #000);
                          border-color: color-mix(in srgb, var(--pp-danger-text) 78%, #000); }

/* Teintes d'action de la liste des AO — pastel.
   Le fond porte la teinte, le trace porte le contraste : l'inverse (aplat
   sature, trace blanc) donnait quatre pastilles criardes par ligne, qui
   tiraient l'oeil avant les chiffres du tableau. La tranche en relief se
   melange vers l'encre et non vers le noir, sinon elle salit le pastel.
   --primaire et --danger du catalogue restent intacts : d'autres tableaux
   les attendent satures. */
.pp-btn-icone--reprendre { --pp-bi-fond: var(--pp-success-bg); --pp-bi-encre: var(--pp-success-text);
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-success-text) 26%, var(--pp-success-bg));
                          border-color: var(--pp-success-border); }
.pp-btn-icone--score    { --pp-bi-fond: var(--pp-act-score-bg); --pp-bi-encre: var(--pp-act-score);
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-act-score) 26%, var(--pp-act-score-bg));
                          border-color: var(--pp-act-score-border); }
.pp-btn-icone--archive  { --pp-bi-fond: var(--pp-act-archive-bg); --pp-bi-encre: var(--pp-act-archive);
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-act-archive) 26%, var(--pp-act-archive-bg));
                          border-color: var(--pp-act-archive-border); }
.pp-btn-icone--supprimer { --pp-bi-fond: var(--pp-danger-bg); --pp-bi-encre: var(--pp-danger-text);
                          --pp-bi-tranche: color-mix(in srgb, var(--pp-danger-text) 26%, var(--pp-danger-bg));
                          border-color: var(--pp-danger-border); }

/* Le trace herite de la couleur du bouton : un seul symbole sert sur les
   quatre teintes, et suit le theme sombre sans etre duplique. */
.pp-btn-icone .pp-i {
  display: block;
  width: 15px;
  height: 15px;
  color: currentColor;
  pointer-events: none;   /* le clic vise le bouton, jamais le trace */
}

/* En theme sombre, un lisere blanc a 65 % fait une arete de neon. */
:root:not([data-theme="light"]) .pp-btn-icone,
[data-theme="dark"] .pp-btn-icone {
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 14%, transparent),
    0 2px 0 var(--pp-bi-tranche),
    0 3px 7px rgba(0, 0, 0, 0.5);
}

/* L'etiquette ne prend le flux nulle part : ni la largeur de la colonne ni la
   hauteur de la ligne ne bougent quand elle s'ouvre. */
.pp-btn-icone::after {
  content: attr(data-nom);
  position: absolute;
  right: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
  padding: 5px 10px;
  font-size: 0.76rem;
  font-weight: var(--pp-fw-semibold);
  line-height: 1.2;
  white-space: nowrap;
  color: var(--pp-text);
  background: var(--pp-surface);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-lg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: var(--pp-z-popover);
}
.pp-btn-icone:hover::after,
.pp-btn-icone:focus-visible::after { opacity: 1; visibility: visible; }

/* Sans survol — tactile — l'etiquette ne s'ouvrirait jamais : le nom passe en
   clair a cote de l'icone. C'est plus large, et c'est le seul moyen de le lire
   au doigt. */
@media (hover: none) {
  .pp-btn-grille { grid-template-columns: 1fr; }
  .pp-btn-icone { width: auto; min-width: 30px; height: auto; padding: 6px 10px; gap: 6px; }
  .pp-btn-icone::after {
    position: static;
    transform: none;
    padding: 0;
    opacity: 1;
    visibility: visible;
    font-size: 0.78rem;
    background: none;
    border: 0;
    box-shadow: none;
    color: inherit;
  }
}

.pp-table__actions { white-space: nowrap; text-align: right; }
.pp-table__actions .btn + .btn { margin-left: var(--pp-space-1); }

/* Ligne qui sort du lot — une facture en retard de paiement, un lot infructueux.
   Les vues la teintaient a la main en rgba(220,53,69,.04), une valeur ecrite
   pour le theme clair qui disparaissait en sombre. L'etat se declare desormais
   sur la ligne, et la teinte suit la palette :

       <tr data-state="danger">

   Ces regles ont exactement la specificite du zebrage (.pp-table + une
   pseudo-classe ou un attribut) : c'est leur position PLUS BAS dans le fichier
   qui les fait gagner, sans quoi une ligne sur deux perdrait son etat. Ne pas
   les remonter au-dessus du bloc « 4. TABLEAUX ».

   Le survol garde le dernier mot — il repond au geste, l'etat ne fait que
   decrire — d'ou la derniere regle, d'une specificite superieure d'un cran. */
.pp-table tbody tr[data-state="danger"] td,
.table tbody tr[data-state="danger"] td   { background: var(--pp-danger-bg); }
.pp-table tbody tr[data-state="warning"] td,
.table tbody tr[data-state="warning"] td  { background: var(--pp-warning-bg); }
.pp-table tbody tr[data-state="success"] td,
.table tbody tr[data-state="success"] td  { background: var(--pp-success-bg); }
.pp-table tbody tr[data-state]:hover td,
.table tbody tr[data-state]:hover td      { background: var(--pp-primary-soft); }

/* ============================================================
   5. BADGES DE STATUT

   Vocabulaire UNIQUE. L'ancien socle en avait deux, incompatibles :
   app.css nommait notice/error, zip15 nommait success/danger, et les
   vues utilisaient les deux.

   La pastille ::before et le libelle texte sont toujours presents :
   aucune information n'est jamais portee par la seule couleur.
   ============================================================ */
.pp-badge,
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--pp-space-1);
  min-height: 20px;
  padding: 2px var(--pp-space-2);
  border: 1px solid transparent;
  border-radius: var(--pp-radius-full);
  font-size: var(--pp-fs-200);
  font-weight: var(--pp-fw-medium);
  line-height: 1.4;
  white-space: nowrap;
  background: var(--pp-surface-2);
  color: var(--pp-text-muted);
  border-color: var(--pp-border);
}
.pp-badge::before,
.badge::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}
.pp-badge--nodot::before, .badge--nodot::before { display: none; }

/* Badge en relief — meme langage que .pp-btn-icone : lisere clair en haut,
   tranche portante en bas, ombre courte.

   La teinte arrive par --pp-bd-ton, posee en style en ligne par la vue : les
   couleurs de statut et de recommandation sont calculees en PHP et ne peuvent
   pas vivre dans une classe. C'est la seule valeur que la vue transmet ; fond,
   bordure, tranche et pastille en decoulent.

   Tous les melanges vont vers « transparent » et non vers #fff : le badge
   garde ainsi sa teinte sur fond clair comme sur fond sombre, ce que faisait
   deja le « <couleur>20 » qu'il remplace. */
.pp-badge-3d {
  --pp-bd-ton: var(--pp-text-muted);
  /* L'encre n'est pas la teinte brute : melangee a 45 % avec la couleur de
     texte, elle fonce dans le theme clair et s'eclaircit dans le sombre.

     45 % n'est pas un reglage a l'oeil. Sur les onze teintes de statut, de
     priorite, de recommandation et d'echeance employees par la liste, c'est le
     taux le plus eleve — donc le plus colore — qui tienne 4,5:1 partout, dans
     les deux themes. La teinte brute donnait 1,89:1 pour l'ambre « A etudier »
     et 1,86:1 pour « A decider » : illisibles. Pire cas apres melange : 4,98:1
     en clair, 6,49:1 en sombre.

     Fond, bordure et tranche gardent la teinte brute : ce sont eux qui portent
     la couleur, l'encre ne porte que le texte. */
  color: color-mix(in srgb, var(--pp-bd-ton) 45%, var(--pp-text));
  border: 1px solid color-mix(in srgb, var(--pp-bd-ton) 42%, transparent);
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--pp-bd-ton) 10%, transparent),
      color-mix(in srgb, var(--pp-bd-ton) 24%, transparent));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 60%, transparent),
    0 2px 0 color-mix(in srgb, var(--pp-bd-ton) 34%, transparent),
    0 3px 5px rgba(0, 0, 0, 0.14);
}

/* En theme sombre, un lisere blanc a 60 % fait une arete de neon — meme
   correction que pour .pp-btn-icone. */
:root:not([data-theme="light"]) .pp-badge-3d,
[data-theme="dark"] .pp-badge-3d {
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent),
    0 2px 0 color-mix(in srgb, var(--pp-bd-ton) 40%, transparent),
    0 3px 6px rgba(0, 0, 0, 0.45);
}

.pp-badge--neutral { background: var(--pp-surface-2);      color: var(--pp-text-muted);    border-color: var(--pp-border); }
.pp-badge--primary { background: var(--pp-primary-soft);   color: var(--pp-primary);       border-color: var(--pp-primary-border); }
.pp-badge--success { background: var(--pp-success-bg);     color: var(--pp-success-text);  border-color: var(--pp-success-border); }
.pp-badge--warning { background: var(--pp-warning-bg);     color: var(--pp-warning-text);  border-color: var(--pp-warning-border); }
.pp-badge--danger  { background: var(--pp-danger-bg);      color: var(--pp-danger-text);   border-color: var(--pp-danger-border); }
.pp-badge--info    { background: var(--pp-info-bg);        color: var(--pp-info-text);     border-color: var(--pp-info-border); }

.pp-badge--solid { color: #fff; border-color: transparent; }
.pp-badge--solid.pp-badge--success { background: var(--pp-success-text); }
.pp-badge--solid.pp-badge--warning { background: var(--pp-warning-text); color: var(--pp-n-950, #0e1310); }
.pp-badge--solid.pp-badge--danger  { background: var(--pp-danger-text); }
.pp-badge--solid.pp-badge--info    { background: var(--pp-info-text); }
.pp-badge--solid.pp-badge--primary { background: var(--pp-primary); color: var(--pp-primary-on); }

/* ============================================================
   6. ALERTES

   Le texte reste en --pp-text : le mettre a la couleur de tonalite
   etait la cause des echecs de contraste de l'ancien socle.
   ============================================================ */
.pp-alert,
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--pp-space-3);
  padding: var(--pp-space-4);
  margin-bottom: var(--pp-space-4);
  border: 1px solid var(--pp-border);
  border-left-width: 3px;
  border-radius: var(--pp-radius);
  background: var(--pp-surface-2);
  color: var(--pp-text);
  font-size: var(--pp-fs-400);
}
.pp-alert__icon  { flex-shrink: 0; line-height: 1.4; }
.pp-alert__body  { flex: 1 1 auto; min-width: 0; }
.pp-alert__title { font-weight: var(--pp-fw-semibold); margin-bottom: var(--pp-space-1); }
.pp-alert__close { flex-shrink: 0; }

.pp-alert--success, .alert-success { background: var(--pp-success-bg); border-color: var(--pp-success-border); border-left-color: var(--pp-success-text); }
.pp-alert--warning, .alert-warning { background: var(--pp-warning-bg); border-color: var(--pp-warning-border); border-left-color: var(--pp-warning-text); }
.pp-alert--danger,  .alert-error,
.alert-danger                      { background: var(--pp-danger-bg);  border-color: var(--pp-danger-border);  border-left-color: var(--pp-danger-text); }
.pp-alert--info,    .alert-info    { background: var(--pp-info-bg);    border-color: var(--pp-info-border);    border-left-color: var(--pp-info-text); }

.pp-alert--success .pp-alert__icon { color: var(--pp-success-text); }
.pp-alert--warning .pp-alert__icon { color: var(--pp-warning-text); }
.pp-alert--danger  .pp-alert__icon { color: var(--pp-danger-text); }
.pp-alert--info    .pp-alert__icon { color: var(--pp-info-text); }

/* ============================================================
   7. TOASTS (nouveau)
   ============================================================ */
.pp-toast-region {
  position: fixed;
  inset-block-end: var(--pp-space-5);
  inset-inline-end: var(--pp-space-5);
  z-index: var(--pp-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--pp-space-2);
  max-inline-size: min(380px, calc(100vw - 2 * var(--pp-space-4)));
  pointer-events: none;
}
.pp-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--pp-space-3);
  padding: var(--pp-space-3) var(--pp-space-4);
  background: var(--pp-surface);
  color: var(--pp-text);
  border: 1px solid var(--pp-border);
  border-left: 3px solid var(--pp-text-muted);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-lg);
  font-size: var(--pp-fs-400);
  animation: pp-toast-in var(--pp-dur-slow) var(--pp-ease-spring);
}
@keyframes pp-toast-in {
  from { opacity: 0; transform: translateY(12px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
.pp-toast--success { border-left-color: var(--pp-success-text); }
.pp-toast--warning { border-left-color: var(--pp-warning-text); }
.pp-toast--danger  { border-left-color: var(--pp-danger-text); }
.pp-toast--info    { border-left-color: var(--pp-info-text); }
.pp-toast__body    { flex: 1 1 auto; min-width: 0; }
.pp-toast__action  { flex-shrink: 0; }

/* ============================================================
   8. MODALE (nouveau, sur <dialog> natif)

   <dialog> fournit gratuitement le piege de focus, la fermeture par
   Echap, l'inertie du fond et la restitution du focus : environ
   200 lignes de JS d'accessibilite en moins.
   ============================================================ */
.pp-modal {
  padding: 0;
  border: 0;
  background: transparent;
  max-inline-size: min(92vw, 640px);
  inline-size: 100%;
  max-block-size: 88dvh;
  overflow: visible;
  color: var(--pp-text);
}
.pp-modal::backdrop {
  background: rgba(9, 17, 13, 0.55);
  backdrop-filter: blur(2px);
}
.pp-modal--sm   { max-inline-size: min(92vw, 420px); }
.pp-modal--lg   { max-inline-size: min(92vw, 880px); }
.pp-modal--full { max-inline-size: 96vw; }

.pp-modal__panel {
  display: flex;
  flex-direction: column;
  max-block-size: 88dvh;
  background: var(--pp-surface);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-lg);
  box-shadow: var(--pp-shadow-xl);
  overflow: hidden;
}
.pp-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pp-space-4);
  padding: var(--pp-space-5) var(--pp-space-6);
  border-bottom: 1px solid var(--pp-border);
  flex-shrink: 0;
}
.pp-modal__title { margin: 0; font-size: var(--pp-fs-600); }
.pp-modal__body {
  padding: var(--pp-space-6);
  overflow-y: auto;
  flex: 1 1 auto;
  min-block-size: 0;
}
.pp-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--pp-space-2);
  padding: var(--pp-space-4) var(--pp-space-6);
  border-top: 1px solid var(--pp-border);
  background: var(--pp-surface-2);
  flex-shrink: 0;
}

/* Sur telephone, la modale devient une feuille remontante. */
@media (max-width: 639.98px) {
  .pp-modal--sheet { max-inline-size: 100vw; inline-size: 100vw; margin: auto auto 0; }
  .pp-modal--sheet .pp-modal__panel { border-radius: var(--pp-radius-lg) var(--pp-radius-lg) 0 0; }
}

.pp-modal__icon {
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--pp-radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.pp-modal__icon--danger  { background: var(--pp-danger-bg);  color: var(--pp-danger-text); }
.pp-modal__icon--warning { background: var(--pp-warning-bg); color: var(--pp-warning-text); }
.pp-modal__icon--info    { background: var(--pp-info-bg);    color: var(--pp-info-text); }

.pp-modal--confirm .pp-modal__body { display: flex; gap: var(--pp-space-4); align-items: flex-start; }

/* ============================================================
   9. ONGLETS (nouveau)
   ============================================================ */
.pp-tabs {
  display: flex;
  gap: var(--pp-space-1);
  border-bottom: 1px solid var(--pp-border);
  margin-bottom: var(--pp-space-5);
  overflow-x: auto;
  scrollbar-width: none;
}
.pp-tabs::-webkit-scrollbar { display: none; }
.pp-tabs > a,
.pp-tabs > button,
.pp-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: var(--pp-space-3) var(--pp-space-4);
  margin-bottom: -1px;
  font-size: var(--pp-fs-400);
  font-weight: var(--pp-fw-medium);
  color: var(--pp-text-muted);
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition: color var(--pp-dur-fast), border-color var(--pp-dur-fast);
}
.pp-tabs > a:hover,
.pp-tabs > button:hover { color: var(--pp-text); text-decoration: none; }
.pp-tabs > [aria-current="page"],
.pp-tabs > [aria-selected="true"],
.pp-tab.is-active {
  color: var(--pp-primary);
  border-bottom-color: var(--pp-primary);
}
.pp-tabpanel:focus-visible { outline: 2px solid var(--pp-focus-color); outline-offset: 4px; }

/* ============================================================
   10. PAGINATION (nouveau)
   ============================================================ */
.pp-pagination {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--pp-space-1);
  margin-top: var(--pp-space-5);
}
.pp-pagination a,
.pp-pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 36px;
  min-block-size: 36px;
  padding: 0 var(--pp-space-2);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-sm);
  font-size: var(--pp-fs-400);
  color: var(--pp-text);
  text-decoration: none;
}
.pp-pagination a:hover { background: var(--pp-surface-2); border-color: var(--pp-primary); text-decoration: none; }
.pp-pagination [aria-current="page"] {
  background: var(--pp-primary);
  border-color: var(--pp-primary);
  color: var(--pp-primary-on);
  font-weight: var(--pp-fw-semibold);
}
.pp-pagination [aria-disabled="true"] { opacity: 0.45; pointer-events: none; }
.pp-pagination__gap { border: 0; min-inline-size: auto; color: var(--pp-text-muted); }
.pp-pagination__summary {
  margin-inline-start: auto;
  font-size: var(--pp-fs-300);
  color: var(--pp-text-muted);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   11. ETAT VIDE (nouveau)

   L'application compte aujourd'hui 33 formulations differentes de
   "aucun resultat", la plupart sans action proposee.
   ============================================================ */
.pp-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--pp-space-3);
  padding: var(--pp-space-12) var(--pp-space-6);
  color: var(--pp-text-muted);
}
.pp-empty__icon  { font-size: 2.5rem; color: var(--pp-text-subtle); line-height: 1; }
.pp-empty__title { font-size: var(--pp-fs-600); font-weight: var(--pp-fw-semibold); color: var(--pp-text); margin: 0; }
.pp-empty__text  { max-inline-size: 44ch; margin: 0; }
.pp-empty__actions { display: flex; gap: var(--pp-space-2); flex-wrap: wrap; justify-content: center; margin-top: var(--pp-space-2); }
.pp-empty--inline { padding: var(--pp-space-8) var(--pp-space-4); }

/* ============================================================
   12. CHARGEMENT (nouveau)
   ============================================================ */
.pp-spinner {
  display: inline-block;
  inline-size: 1em;
  block-size: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: pp-spin 0.7s linear infinite;
  vertical-align: -0.125em;
}
@keyframes pp-spin { to { transform: rotate(360deg); } }
.pp-spinner--lg { inline-size: 1.75rem; block-size: 1.75rem; border-width: 3px; }

.pp-skeleton {
  background: linear-gradient(90deg,
      var(--pp-surface-2) 25%,
      color-mix(in srgb, var(--pp-surface-2) 60%, var(--pp-border)) 37%,
      var(--pp-surface-2) 63%);
  background-size: 400% 100%;
  animation: pp-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--pp-radius-xs);
}
@keyframes pp-shimmer { from { background-position: 100% 50%; } to { background-position: 0 50%; } }
.pp-skeleton--text  { block-size: 1em; inline-size: var(--w, 100%); margin-bottom: var(--pp-space-2); }
.pp-skeleton--title { block-size: 1.5em; inline-size: var(--w, 40%); margin-bottom: var(--pp-space-3); }
.pp-skeleton--block { block-size: var(--h, 120px); }
.pp-skeleton--circle{ inline-size: var(--w, 40px); block-size: var(--w, 40px); border-radius: 50%; }

/* ============================================================
   13. TUILE KPI (nouveau)

   Remplace .stat-card (app.css + zip15) et les ~120 lignes de
   .dash-widget* de zip14, ecrasees et utilisees par une seule vue.
   ============================================================ */
.pp-kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--pp-space-4);
  margin-bottom: var(--pp-space-5);
}
.pp-kpi {
  display: flex;
  flex-direction: column;
  gap: var(--pp-space-1);
  padding: var(--pp-space-5);
  background: var(--pp-card-bg);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius-lg);
  box-shadow: var(--pp-shadow-xs);
  min-inline-size: 0;
}
.pp-kpi__label {
  font-size: var(--pp-fs-200);
  font-weight: var(--pp-fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--pp-ls-caps);
  color: var(--pp-text-muted);
}
.pp-kpi__value {
  font-size: var(--pp-fs-800);
  font-weight: var(--pp-fw-semibold);
  line-height: var(--pp-lh-tight);
  color: var(--pp-text);
  font-variant-numeric: tabular-nums;
}
.pp-kpi__sub { font-size: var(--pp-fs-300); color: var(--pp-text-muted); }

/* Le glyphe ET le signe accompagnent toujours la couleur. */
.pp-kpi__delta { display: inline-flex; align-items: center; gap: 4px; font-size: var(--pp-fs-300); font-weight: var(--pp-fw-medium); }
.pp-kpi__delta--up   { color: var(--pp-success-text); }
.pp-kpi__delta--down { color: var(--pp-danger-text); }
.pp-kpi__delta--flat { color: var(--pp-text-muted); }
.pp-kpi__delta--up::before   { content: "\25B2"; font-size: 0.7em; }
.pp-kpi__delta--down::before { content: "\25BC"; font-size: 0.7em; }
.pp-kpi__delta--flat::before { content: "\2013"; }

.pp-kpi--compact { padding: var(--pp-space-4); }
.pp-kpi--compact .pp-kpi__value { font-size: var(--pp-fs-700); }
.pp-kpi--interactive { text-decoration: none; transition: box-shadow var(--pp-dur-base), transform var(--pp-dur-base); }
.pp-kpi--interactive:hover { box-shadow: var(--pp-shadow-md); transform: translateY(-1px); text-decoration: none; }
.pp-kpi--accent-success { border-left: 3px solid var(--pp-success-text); }
.pp-kpi--accent-warning { border-left: 3px solid var(--pp-warning-text); }
.pp-kpi--accent-danger  { border-left: 3px solid var(--pp-danger-text); }
.pp-kpi--accent-info    { border-left: 3px solid var(--pp-info-text); }

/* ============================================================
   14. MISE EN PAGE
   ============================================================ */
.pp-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--pp-space-4);
  flex-wrap: wrap;
  margin-bottom: var(--pp-space-6);
}
.pp-page-header__text { min-inline-size: 0; }
.pp-page-header h1 { margin: 0 0 var(--pp-space-1); }
.pp-page-header__desc { color: var(--pp-text-muted); margin: 0; }
.pp-page-header__actions { display: flex; gap: var(--pp-space-2); flex-wrap: wrap; }

.pp-toolbar {
  display: flex;
  align-items: center;
  gap: var(--pp-space-2);
  flex-wrap: wrap;
  margin-bottom: var(--pp-space-4);
}
.pp-stack   { display: flex; flex-direction: column; gap: var(--pp-space-4); }
.pp-cluster { display: flex; flex-wrap: wrap; gap: var(--pp-space-2); align-items: center; }
.pp-split   { display: flex; justify-content: space-between; align-items: center; gap: var(--pp-space-4); }

/* .grid-3 de zip-v31-group.css n'avait AUCUN gap : les cartes se
   touchaient dans les 7 vues qui l'utilisent. */
.pp-grid, .grid-2, .grid-3, .grid-4 {
  display: grid;
  gap: var(--pp-space-4);
}
.pp-grid--2, .grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.pp-grid--3, .grid-3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.pp-grid--4, .grid-4 { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* Barre de filtres : responsive par construction. Les grilles a
   colonnes fixes des vues (2fr 1fr 1fr 1fr auto) ecrasaient les
   filtres a ~60px de large sur telephone. */
.pp-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--pp-space-3);
  align-items: end;
}

.pp-divider { border: 0; border-top: 1px solid var(--pp-border); margin: var(--pp-space-6) 0; }

/* ============================================================
   15. SURCOUCHES LEGERES
   ============================================================ */
.pp-popover {
  position: absolute;
  z-index: var(--pp-z-popover);
  max-inline-size: 340px;
  padding: var(--pp-space-4);
  background: var(--pp-surface);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-lg);
  font-size: var(--pp-fs-400);
}

.pp-dropdown {
  position: absolute;
  z-index: var(--pp-z-dropdown);
  min-inline-size: 200px;
  padding: var(--pp-space-1);
  background: var(--pp-surface);
  border: 1px solid var(--pp-border);
  border-radius: var(--pp-radius);
  box-shadow: var(--pp-shadow-lg);
}
.pp-dropdown__item {
  display: flex;
  align-items: center;
  gap: var(--pp-space-2);
  inline-size: 100%;
  padding: var(--pp-space-2) var(--pp-space-3);
  border: 0;
  border-radius: var(--pp-radius-sm);
  background: transparent;
  color: var(--pp-text);
  font-size: var(--pp-fs-400);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.pp-dropdown__item:hover { background: var(--pp-surface-2); text-decoration: none; }
.pp-dropdown__item--danger { color: var(--pp-danger-text); }
.pp-dropdown__sep { border: 0; border-top: 1px solid var(--pp-border); margin: var(--pp-space-1) 0; }
