/* 
=========================
Style de Base & Réinitialisation
=========================
Ces styles de base servent à normaliser le design pour éviter les incohérences entre navigateurs et à établir une palette de couleurs neutres dans le cadre de WB 2.0
*/

/* 
=========================
Style Générique pour .show-info
=========================
Ce style est conçu pour des modules d'affichage de données,
comme les informations client ou tout autre type d'affichage d'informations.
Il est pensé pour être réutilisable et adaptable.
*/

.show-info {
    background-color: #f5f5f5;
    /* Fond blanc pour contraster avec le fond global */
    border-radius: 8px;
    /* Coins arrondis pour un look moderne et épuré */
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    /* Ombre douce pour donner de la profondeur */
    padding: 18px;
    /* Espacement intérieur généreux pour l'aération */
    margin: 8px 0;
    /* Espacement extérieur pour séparer des autres éléments */
    font-family: Arial, sans-serif;
    /* Police sans-serif moderne et facile à lire */
    color: #333;
    /* Couleur de texte foncée pour un bon contraste */
    line-height: 1.3;
    /* Espacement des lignes pour une meilleure lisibilité */
    max-width: 600px;
    /* Largeur maximale pour éviter l'étirement sur les grands écrans */
}

.show-info h2 {
    font-size: 1.2rem;
    /* Taille de titre accentuée pour attirer l'attention */
    color: #4CAF50;
    /* Couleur verte pour un effet dynamique et moderne */
    margin-bottom: 15px;
    /* Espace en dessous du titre pour le séparer du contenu */
}

.show-info table {
    width: 100%;
    /* Utilise toute la largeur disponible */
    border-collapse: collapse;
    /* Enlève les espaces entre les bordures pour un style unifié */
    margin-top: 10px;
    /* Espace entre le titre et le tableau */
}

.show-info th,
.show-info td {
    padding: 8px 10px;
    /* Padding interne pour une meilleure lisibilité */
    text-align: left;
    /* Texte aligné à gauche pour un look professionnel */
    border-bottom: 1px solid #ddd;
    /* Ligne de séparation subtile entre les lignes */
    font-size: 15px;
}

.show-info td a {
    color: #333 !important;
}

/**Hover link for vehicule **/
.show-info td a:hover {
    color: #327135 !important;
}

.show-info th {
    background-color: #f9f9f9;
    /* Fond clair pour démarquer les en-têtes */
    font-weight: bold;
    /* Texte en gras pour accentuer les titres */
    color: #555;
    /* Couleur de texte légèrement plus foncée pour les en-têtes */
}

.show-info tr:hover {
    background-color: #dfdfdf;
    /* Fond gris clair au survol pour un retour visuel agréable */
}

.show-info td {
    color: #333;
    /* Couleur de texte principale */
}

/* 
=========================
Boutons d'Action dans .show-info
=========================
Styles pour les boutons d'action situés dans les modules d'affichage.
*/

.show-info button {
    background-color: #4CAF50;
    /* Vert moderne pour les boutons d'action */
    color: white;
    /* Texte blanc pour contraste et lisibilité */
    padding: 10px 20px;
    /* Padding pour un bouton confortable et facile à cliquer */
    border: none;
    /* Pas de bordure pour un look minimaliste */
    border-radius: 5px;
    /* Coins arrondis pour un design homogène */
    cursor: pointer;
    /* Curseur en pointeur pour indiquer l'interaction */
    font-size: 1em;
    transition: background-color 0.3s ease;
    /* Transition douce pour le survol */
}

.show-info button:hover {
    background-color: #45a049;
    /* Couleur légèrement plus foncée pour le survol */
}

.show-info button:active {
    background-color: #3e8e41;
    /* Couleur encore plus foncée lors du clic */
}

/* Style pour le bouton "Afficher plus" */
.show-info summary {
    font-size: 1.2em;
    color: #4CAF50;

    cursor: pointer;
    width: fit-content;
    /* Ajuste la largeur en fonction du texte */
    margin-top: 10px;
    border: none;
    /* Supprime les bordures par défaut */
    display: inline-block;
    /* Pour appliquer les styles de bouton */
    transition: background-color 0.3s ease;
    /* Transition douce pour le survol */
    outline: none;
    /* Supprime le contour bleu par défaut */
}

/* Effet de survol pour "Afficher plus" */
.show-info summary:hover {
    color: #45a049;
    /* Couleur plus foncée au survol */
}

.show-info summary:active {
    color: #3e8e41;
    /* Couleur encore plus foncée lors du clic */
}

/* Supprime les puces par défaut des éléments <details> */
.show-info details {
    list-style: none;
}


/*Ensemble de classe pour gérer les margins*/
.m-10 {
    margin: 10px;
}

.mt-10 {
    margin-top: 10px;
}

.mt-20 {
    margin-top: 20px;
}

.mt-30 {
    margin-top: 30px;
}

.mb-10 {
    margin-bottom: 10px;
}

.mb-20 {
    margin-bottom: 20px;
}

.mb-30 {
    margin-bottom: 30px;
}

.ml-10 {
    margin-left: 10px;
}

.ml-20 {
    margin-left: 20px;
}

.ml-30 {
    margin-left: 30px;
}

.mr-10 {
    margin-right: 10px;
}

.mr-20 {
    margin-right: 20px;
}

.mr-30 {
    margin-right: 30px;
}

/*Padding*/
.p-10 {
    padding: 10px;
}

.p-20 {
    padding: 20px;
}

.p-30 {
    padding: 30px;
}

.pb-10 {
    padding-bottom: 10px;
}

.pb-5 {
    padding-bottom: 5px;
}

/*Width and height class*/
.w-100 {
    width: 100%;
}

.h-100 {
    height: 100%;
}

/*Display inline-block*/
.d-inline-block {
    display: inline-block;
}

/*Bg white*/
.bg-white {
    background-color: #fff;
}

/*Shadow small like boostrap*/
.shadow-sm {
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

/*New default table more modern style*/
.table-modern {
    border-collapse: collapse;
    background-color: #fff;
    border-radius: 6px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.table-modern th {
    background-color: #f8f9fa;
    color: #333;
    font-weight: 600;
    padding: 5px;
    text-align: left;
    border-bottom: 2px solid #dee2e6;
}

.table-modern td {
    padding: 5px;
    border-bottom: 1px solid #dee2e6;
    color: #495057;
}

.table-modern tr:hover {
    background-color: #f8f9fa;
}

.table-modern a {
    color: #4CAF50 !important;
    text-decoration: none;
}

.table-modern a:hover {
    color: #327135;
}

.table-modern input[type="submit"] {
    background-color: #4CAF50;
    color: #fff;
    border: none;
    border-radius: 5px;
    padding: 7px 15px;
    cursor: pointer;
    transition: all 0.2s;
    font-size: .6rem;
    font-weight: 500;
    box-shadow: 0 2px 5px rgba(76, 175, 80, 0.2);
    text-transform: uppercase;
}

.table-modern input[type="submit"]:hover {
    background-color: #45a049;
    box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3);
}

.table-modern input[type="submit"]:active {
    background-color: #3d8b40;
    transform: translateY(1px);
}

/*Style for a link in text with dark background*/
.link-dark-bg {
    color: #4CAF50 !important;
}

.link-dark-bg:hover {
    color: #3d8b40 !important;
}

/* 
=========================
Responsive Design
=========================
Assure que la .show-info s'adapte bien sur les petits écrans.
*/

@media (max-width: 768px) {
    .show-info {
        padding: 15px;
        /* Réduit le padding sur les petits écrans */
        font-size: 0.9em;
        /* Réduit la taille de texte pour l'adapter aux mobiles */
    }

    .show-info h2 {
        font-size: 1.5em;
        /* Ajuste la taille du titre pour les petits écrans */
    }

    .show-info th,
    .show-info td {
        padding: 10px;
        /* Réduit le padding des cellules pour gagner de l'espace */
    }
}

/*
=========================
WB 2.0 — Sidebar de migration + coquille de page (?page=slug)
=========================
Sidebar verticale neuve, en flux, à gauche du contenu. Coexiste volontairement
avec le menu horizontal legacy (position:fixed) le temps de la migration
(« présentation bâtarde »). Palette alignée sur le vert #4CAF50 du site.
*/

/* Pages WB 2.0 : reset du fond dark legacy webbike.css */
body.wb-modern {
    background-color: var(--bs-body-bg) !important;
    color: var(--bs-body-color) !important;
    margin: 0 !important;
}

/* #menu est position:fixed → seul un override direct de left/top le déplace.
   Il était calé DERRIÈRE la sidebar (250 px + 8 px de gouttière), donc sa
   position dépendait de la largeur de celle-ci — d'où une seconde règle pour la
   colonne étroite, et une classe `wb-rail` posée sur <body> à seule fin de la
   déclencher. Aligné à droite (21.08.2026), il ne dépend plus de rien : la
   sidebar peut se replier, la barre ne bouge pas. La règle `.wb-rail` et la
   classe sur <body> ont disparu avec cette dépendance. */
body.wb-modern #menu {
    left: auto !important;
    right: 100px !important;
    top: 0 !important;
    margin: 0 !important;
}
@media screen and (max-width: 992px) {
    body.wb-modern #menu {
        right: 8px !important;
    }
}

.wb-shell {
    display: flex;
    align-items: flex-start;
    gap: 0;
}

.wb-main {
    flex: 1 1 auto;
    min-width: 0;
    /* Offset navbar fixe (≈32px) + 1rem de respiration Bootstrap */
    padding-top: calc(32px + 1rem);
}

/* ===== Barre de page : fil d'Ariane, titre, onglets de sous-section =====
   L'en-tête de page était masqué globalement, au motif que la sidebar indique
   déjà la page active. Elle dit OÙ l'on est ; un titre dit CE QU'ON PEUT Y
   FAIRE — et il porte les actions de la page, que trente pages sur quarante et
   une déclaraient déjà avant d'être cachées. */
.wb-pagebar {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    margin-bottom: 0.9rem;
}

/* Fil d'Ariane. Le groupe n'est pas cliquable : un groupe est un rangement,
   pas une destination — le rendre cliquable promettrait une page inexistante. */
.wb-fil {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    color: var(--bs-secondary-color, #6c757d);
    min-height: 1.1rem;
}
.wb-fil .bi {
    font-size: 0.6rem;
    opacity: 0.55;
}
.wb-fil-groupe { text-transform: uppercase; font-weight: 600; letter-spacing: 0.07em; }
.wb-fil-ici { color: var(--bs-body-color); }

/* L'en-tête déclaré par les pages : `d-flex align-items-center mb-3`, avec
   `me-auto` sur le h1 — les actions se rangent donc à droite d'elles-mêmes.
   `margin-bottom` neutralisé : l'espacement est porté par .wb-pagebar. */
.wb-page-header { margin-bottom: 0 !important; }

/* Onglets de sous-section — remplacent la 2e sidebar de 210 px. */
.wb-onglets {
    display: flex;
    gap: 0.15rem;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
    margin-top: 0.25rem;
}
.wb-onglet {
    padding: 0.4rem 0.75rem 0.5rem;
    font-size: 0.87rem;
    color: var(--bs-secondary-color, #6c757d);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;   /* le filet actif recouvre celui du conteneur */
    white-space: nowrap;
}
.wb-onglet:hover { color: var(--bs-body-color); }
.wb-onglet.actif {
    color: var(--bs-body-color);
    font-weight: 600;
    border-bottom-color: var(--bs-primary, #0d6efd);
}
.wb-onglet:focus-visible {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: -2px;
    border-radius: 4px 4px 0 0;
}

/* --- Sidebar (Bootstrap 5.3) --- */
.wb-sidebar {
    flex: 0 0 250px;
    width: 250px;
    position: sticky;
    top: 0;
    /* ⚠️ CE `z-index` N'EST PAS DÉCORATIF — il rend le volet du mode rail
       cliquable.

       `position: sticky` crée TOUJOURS un contexte d'empilement, z-index ou pas.
       Le volet, à l'intérieur, avait beau porter `z-index: 1080`, il restait
       confiné dans celui de la sidebar — lequel valait `auto`, donc 0. Or toute
       carte Bootstrap est `position: relative` : à égalité de niveau, c'est
       l'ordre du DOM qui tranche, et le contenu de la page vient APRÈS la
       sidebar. Le volet passait donc sous les cartes, visible et inutilisable
       (constaté sur ?page=compta-vente).

       1035 : au-dessus de la barre fixe Bootstrap (1030), en dessous du fond
       d'offcanvas (1040) et des modales (1055) — une modale doit continuer de
       recouvrir la sidebar. */
    z-index: 1035;
    min-height: 100vh;
    /* ⚠️ LE REPLI N'EST PAS DÉCORATIF. `background-color: var(--bs-body-bg)`
       sans repli ne vaut pas « blanc » quand la variable n'existe pas : la
       déclaration devient invalide et le fond retombe à TRANSPARENT — on lit
       alors le contenu de la page à travers le menu. C'est ce qui arrive quand
       la feuille Bootstrap du CDN ne charge pas (réseau mobile filtré, tunnel),
       et ça se voit d'abord sur téléphone. Sans Bootstrap il n'y a pas non plus
       de thème sombre : le blanc est le bon repli. */
    background-color: var(--bs-body-bg, #fff);
    border-right: 1px solid var(--bs-border-color, #dee2e6);
    overflow: hidden;
    transition: flex-basis 0.2s ease, width 0.2s ease;
}

/* En-tête sidebar : fond sombre pour le logo blanc */
.wb-brand {
    background: #12123F;
    cursor: pointer;
    text-align: left;
}

.wb-brand:focus-visible {
    outline: 2px solid #4CAF50;
    outline-offset: -2px;
}

.wb-logo {
    height: 22px;
    width: auto;
    flex-shrink: 0;
}

/* ⚠️ Le logotype complet (384×87) DÉBORDAIT de la colonne en mode rail : 4,5 rem
   de large contre ~97 px de logo. Le réduire assez pour qu'il tienne l'aurait
   rendu illisible ; on affiche donc le « w » seul, découpé du même fichier
   source. Un seul des deux est visible à la fois. */
.wb-logo-mark {
    display: none;
    height: 26px;
    width: auto;
    flex-shrink: 0;
}
/* Ceinture : même si le logotype restait affiché, il ne dépasserait plus. */
.wb-brand { overflow: hidden; }

.wb-brand-name {
    font-size: 0.9rem;
    font-weight: 700;
    color: #fff;
    line-height: 1.1;
}

.wb-brand-version {
    font-size: 0.65rem;
    color: rgba(255,255,255,0.55);
    line-height: 1;
}

/* Padding latéral de la zone de navigation (l'aside n'a plus p-3) */
.wb-sidebar .nav,
.wb-sidebar .dropdown,
.wb-sidebar hr {
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}
.wb-sidebar > hr { padding: 0; margin-left: 0.75rem; margin-right: 0.75rem; }

/* Liens de navigation (nav-pills) */
.wb-sidebar .nav-link {
    display: flex;
    align-items: center;
    white-space: nowrap;
    padding: 0.55rem 0.7rem;
    border-radius: 7px;
    color: var(--bs-body-color, #212529);
}

.wb-sidebar .nav-link .bi {
    flex: 0 0 1.5rem;
    font-size: 1.1rem;
    text-align: center;
}

.wb-sidebar .nav-link .wb-label {
    margin-left: 0.6rem;
}

.wb-sidebar a.nav-link:hover {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}

.wb-sidebar .nav-link.disabled {
    color: var(--bs-secondary-color, #6c757d);
    cursor: not-allowed;
    pointer-events: none;
}

.wb-sidebar .wb-soon {
    margin-left: auto;
    font-size: 0.6rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    background: var(--bs-secondary-bg);
    color: var(--bs-secondary-color);
    border-radius: 20px;
    padding: 0.1rem 0.5rem;
}

/* Menu profil (dropdown, bas de sidebar) */
.wb-sidebar .wb-profile {
    display: flex;
    align-items: center;
    white-space: nowrap;
    padding: 0.45rem 0.55rem;
    border-radius: 7px;
}

.wb-sidebar .wb-profile:hover,
.wb-sidebar .wb-profile.active {
    background: var(--bs-secondary-bg);
}

/* ===== Groupes de navigation =====
   Un groupe = un en-tête (icône + nom) suivi de ses entrées. En mode déplié
   l'en-tête n'est qu'une étiquette et les entrées sont listées dessous ; en mode
   rail l'icône devient le seul repère et les entrées passent dans un volet. */
.wb-group {
    list-style: none;
    position: relative;   /* ancre du volet en mode rail */
}
.wb-group + .wb-group { margin-top: 0.3rem; }

.wb-group-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--bs-secondary-color, #6c757d);
    padding: 0.3rem 0.55rem 0.15rem;
    white-space: nowrap;
}
/* Déplié : le nom du groupe suffit, l'icône ferait doublon avec celles des entrées. */
.wb-group-head .wb-group-icon,
.wb-group-head .wb-group-short { display: none; }

/* --- Groupe à UNE seule entrée ---
   ⚠️ Un titre de section au-dessus d'une seule ligne fait lire deux mots pour
   une seule destination (« AUJOURD'HUI » puis « Tableau de bord »), et ce titre
   n'est pas cliquable : il ressemble à un contrôle mort. Déplié, on le retire —
   l'entrée se suffit. En rail il reste, mais c'est alors un LIEN (cf.
   App\Sidebar::renderGroupe), donc plus rien à ouvrir. */
.wb-group-solo > .wb-group-head { display: none; }

/* Les entrées d'un groupe ne reprennent PAS le padding latéral de .wb-sidebar .nav
   (règle plus haut) : il est déjà porté par la liste englobante .wb-nav. */
.wb-sidebar .wb-group-items { padding-left: 0; padding-right: 0; }

.wb-nav-sep { border-color: var(--bs-border-color, #dee2e6); }

/* ===== Recherche globale (palette Ctrl+K) =====
   Elle a la FORME d'un champ de saisie, pas d'un bouton. C'est délibéré : un
   bouton se lit comme une commande à déclencher, un champ comme un endroit où
   écrire — et c'est bien d'écrire qu'il s'agit. La frappe y est acceptée et
   reportée dans la palette (js/wb-palette.js) ; sans ça, la forme mentirait. */
.wb-search {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.4rem 0.625rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
    cursor: text;                 /* la forme annonce l'usage */
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
    user-select: none;
}

.wb-search:hover {
    border-color: var(--bs-primary);
    color: var(--bs-body-color);
}

.wb-search:focus-visible {
    outline: none;
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
}

.wb-search .bi { flex-shrink: 0; }
.wb-search-ph { flex: 1 1 auto; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Le « Ctrl K » du champ est posé par l'aide clavier (js/wb-hints.js), pas écrit
   en dur : c'est elle qui décide de l'afficher ou non. */
.wb-search .wb-hint {
    font-size: 0.68rem;
    padding: 0.1rem 0.3rem;
    background: var(--bs-body-bg);
}

/* --- Première visite : la bulle qui présente la recherche, UNE fois ---
   Un champ, même bien dessiné, ne dit pas TOUT ce qu'il accepte. Cette bulle le
   dit une fois, puis ne revient jamais (localStorage). */
.wb-search-coach {
    position: absolute;
    z-index: 1080;
    max-width: 17rem;
    padding: 0.6rem 0.75rem;
    border-radius: 0.5rem;
    background: var(--bs-emphasis-color);
    color: var(--bs-body-bg);
    font-size: 0.8125rem;
    line-height: 1.35;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.25);
}

.wb-search-coach::before {
    content: "";
    position: absolute;
    top: -0.35rem;
    left: 1.25rem;
    width: 0.7rem;
    height: 0.7rem;
    background: inherit;
    transform: rotate(45deg);
}

.wb-search-coach button {
    margin-top: 0.4rem;
    border: 0;
    background: transparent;
    color: inherit;
    font-weight: 600;
    font-size: 0.8125rem;
    padding: 0;
    text-decoration: underline;
    cursor: pointer;
}

/* --- Exemples affichés dans la palette tant que rien n'est tapé --- */
.wb-palette-exemples {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 1px solid var(--bs-border-color);
    font-size: 0.75rem;
    color: var(--bs-secondary-color);
}

.wb-palette-exemples code {
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    padding: 0.05rem 0.3rem;
    border-radius: 0.25rem;
}

/* En mode rail, il ne reste que la loupe — centrée, sans placeholder ni kbd
   (tous deux portent `wb-label`, masqué plus bas). */
.wb-shell.wb-rail .wb-search {
    justify-content: center;
    padding-left: 0.4rem;
    padding-right: 0.4rem;
}

/* ===== Touches de clavier — une seule apparence pour toute l'application =====
   ⚠️ POURQUOI CETTE CLASSE EXISTE. Bootstrap habille `<kbd>` en INVERSE du
   corps : `background-color: var(--bs-body-color)` (donc presque noir en thème
   clair) et `color: var(--bs-body-bg)`. C'est lisible tel quel — mais y ajouter
   une classe utilitaire de couleur (`text-body-secondary`, par exemple) ne
   remplace QUE le texte : on obtient du gris sur du noir, et la touche devient
   un rectangle illisible. C'est ce qu'on voyait sur la touche « Échap » de la
   palette.
   `.wb-kbd` pose les deux couleurs ensemble, dans le sens du corps de page :
   elles ne peuvent plus se contredire. */
.wb-kbd {
    display: inline-block;
    font-size: 0.72rem;
    line-height: 1.2;
    padding: 0.15rem 0.35rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.25rem;
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
}

/* ===== Badges d'aide clavier (js/wb-hints.js) =====
   Discret par construction : c'est un indice, pas une décoration. Il doit se
   lire quand on le cherche et s'oublier quand on ne le cherche pas — d'où
   l'opacité réduite, qui remonte au survol de l'élément porteur. */
.wb-hint {
    /* Même apparence que `.wb-kbd`, en plus discret : c'est un indice, pas une
       décoration. Il doit se lire quand on le cherche et s'oublier sinon. */
    margin-left: auto;
    flex-shrink: 0;
    display: inline-block;
    font-size: 0.65rem;
    line-height: 1.1;
    padding: 0.1rem 0.28rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 0.2rem;
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    opacity: 0.72;
    transition: opacity 0.12s ease;
}

*:hover > .wb-hint,
*:focus-visible > .wb-hint { opacity: 1; }

/* En mode rail, l'entrée n'est plus qu'une icône : un badge par-dessus la
   rendrait illisible. */
.wb-shell.wb-rail .wb-hint { display: none; }

/* ===== Mode RAIL : la sidebar se réduit à une colonne d'icônes =====
   UNE icône par GROUPE, pas par route : les vingt icônes de route faisaient
   ~800 px de haut, plus que l'écran d'un poste de comptoir. Les entrées du
   groupe s'ouvrent dans un volet ancré à droite de l'icône. */
.wb-shell.wb-rail .wb-sidebar {
    flex-basis: 4.5rem;
    width: 4.5rem;
    /* Sans ça, `overflow: hidden` (état déplié, pour couper les libellés pendant
       la transition de largeur) découperait le volet au ras de la colonne. */
    overflow: visible;
}

.wb-shell.wb-rail .wb-sidebar .wb-label,
.wb-shell.wb-rail .wb-sidebar .wb-soon,
.wb-shell.wb-rail .wb-sidebar .wb-brand-name,
.wb-shell.wb-rail .wb-sidebar .wb-brand-version,
.wb-shell.wb-rail .wb-sidebar .dropdown-toggle::after {
    display: none !important;
}

.wb-shell.wb-rail .wb-sidebar .nav-link,
.wb-shell.wb-rail .wb-sidebar .wb-profile {
    justify-content: center;
    padding-left: 0.4rem;
    padding-right: 0.4rem;
}

.wb-shell.wb-rail .wb-sidebar #wb-brand-toggle,
.wb-shell.wb-rail .wb-sidebar #wb-theme-toggle {
    justify-content: center;
}

/* En rail : le logotype cède la place à la marque. */
.wb-shell.wb-rail .wb-logo { display: none; }
.wb-shell.wb-rail .wb-logo-mark { display: block; }

/* --- En-tête de groupe en mode rail : l'icône, et son nom court dessous --- */
.wb-shell.wb-rail .wb-group-head {
    flex-direction: column;
    gap: 1px;
    padding: 0.4rem 0.15rem 0.3rem;
    border-radius: 7px;
    letter-spacing: 0.02em;
}
.wb-shell.wb-rail .wb-group-head .wb-group-icon {
    display: block;
    font-size: 1.15rem;
    line-height: 1;
}
.wb-shell.wb-rail .wb-group-head .wb-group-name { display: none; }
.wb-shell.wb-rail .wb-group-head .wb-group-short {
    display: block;
    font-size: 0.575rem;
    font-weight: 600;
    text-transform: none;
}
/* Groupe contenant la page courante : repère quand le volet est fermé. */
.wb-shell.wb-rail .wb-group-active > .wb-group-head {
    background: var(--bs-secondary-bg);
    color: var(--bs-body-color);
}
.wb-shell.wb-rail .wb-group:hover > .wb-group-head { color: var(--bs-body-color); }

/* En rail, l'icône de groupe est le seul repère : le titre d'un groupe solo
   revient donc, en tant que lien direct vers son unique entrée. */
.wb-shell.wb-rail .wb-group-solo > .wb-group-head {
    display: flex;
    text-decoration: none;
}
/* …et son volet n'a plus de raison d'exister : il répéterait le lien. */
.wb-shell.wb-rail .wb-group-solo > .wb-group-items,
.wb-shell.wb-rail .wb-group-solo:hover > .wb-group-items,
.wb-shell.wb-rail .wb-group-solo:focus-within > .wb-group-items {
    opacity: 0;
    pointer-events: none;
}

/* --- Le volet --- */
.wb-shell.wb-rail .wb-group-items {
    position: absolute;
    left: 100%;
    top: 0;
    z-index: 1080;              /* au-dessus du menu horizontal legacy (z-index 999) */
    min-width: 12.5rem;
    margin-left: 0.35rem;
    padding: 0.35rem;
    /* Repli explicite : un volet qui RECOUVRE la page ne peut pas se permettre
       un fond transparent. Cf. la note du bloc `.wb-sidebar`. */
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 8px;
    box-shadow: 0 8px 26px -10px rgba(0, 0, 0, 0.4);
    /* `opacity` et NON `display:none`/`visibility:hidden` : les liens restent
       atteignables à la tabulation volet fermé, et c'est le focus qui l'ouvre
       (règle :focus-within ci-dessous). `pointer-events` évite les clics
       fantômes sur un volet invisible. */
    opacity: 0;
    pointer-events: none;
}

/* ⚠️ LE PONT. Le volet est décollé de l'icône de 0.35rem — une marge purement
   esthétique. En traversant ces quelques pixels, le pointeur n'est ni sur
   l'icône ni sur le volet : le `:hover` retombe et le volet se ferme. On
   échouait au premier tiers du trajet.

   Ce pseudo-élément recouvre le fossé, invisible et sans épaisseur visuelle. Il
   déborde aussi VERS LE HAUT ET VERS LE BAS, parce qu'on ne vise pas droit : on
   part de l'icône vers une entrée basse du volet, en diagonale. */
.wb-shell.wb-rail .wb-group-items::before {
    content: "";
    position: absolute;
    top: -0.75rem;
    bottom: -0.75rem;
    left: -0.85rem;      /* couvre la marge, et un peu au-delà */
    width: 0.85rem;
}
/* `wb-group-ouvert` est posée par js/wb-rail-menu.js : elle porte le DÉLAI DE
   GRÂCE à la fermeture et l'ÉPINGLAGE au clic, deux choses qu'un `:hover` seul
   ne sait pas faire. Le `:hover` reste en secours si le script ne charge pas —
   le menu doit fonctionner sans JavaScript, même moins confortablement. */
.wb-shell.wb-rail .wb-group:hover > .wb-group-items,
.wb-shell.wb-rail .wb-group:focus-within > .wb-group-items,
.wb-shell.wb-rail .wb-group-ouvert > .wb-group-items {
    opacity: 1;
    pointer-events: auto;
}

/* Un volet épinglé se voit : sans repère, on ne comprend pas pourquoi il reste. */
.wb-shell.wb-rail .wb-group-ouvert > .wb-group-head {
    color: var(--bs-body-color);
    background: var(--bs-tertiary-bg);
    border-radius: 6px;
}
/* Dans le volet, les libellés reviennent : ils sont masqués globalement en rail
   par la règle `.wb-label { display: none !important }` plus haut. Les deux sont
   `!important`, c'est donc la spécificité qui tranche (4 classes contre 3).
   NB : la valeur CALCULÉE sera `block`, pas `inline` — `.nav-link` est un
   conteneur flex, et tout enfant de flex est blockifié. Ce qui compte ici est
   qu'elle ne soit pas `none`. */
.wb-shell.wb-rail .wb-sidebar .wb-group-items .wb-label,
.wb-shell.wb-rail .wb-sidebar .wb-group-items .wb-soon {
    display: inline !important;
}
.wb-shell.wb-rail .wb-sidebar .wb-group-items .nav-link {
    justify-content: flex-start;
    padding-left: 0.7rem;
    padding-right: 0.7rem;
}
.wb-shell.wb-rail .wb-sidebar .wb-group-items .nav-link .bi {
    flex: 0 0 1.5rem;
    margin-right: 0.15rem;
}

.wb-shell.wb-rail .wb-sidebar .nav-link .bi {
    margin: 0;
}

/* La 2e sidebar contextuelle (.wb-subnav, 210 px) a été retirée : les
   sous-sections d'une parente subNav sont désormais une rangée d'onglets sous
   le titre de page (.wb-onglets plus bas, cf. app/PageBar.php). Avec elle
   disparaît le repli automatique en rail de la bande 768–1199 px, qui n'existait
   que pour lui rendre de la largeur. */

/* --- Page moderne (layout.php) --- */

/* --- Tableau de bord (dashboard.php) --- */
.wb-dash-intro {
    max-width: 720px;
    color: #455a64;
    line-height: 1.5;
}

/* ═══ GRILLE DU TABLEAU DE BORD ═══════════════════════════════════════════
   ⚠️ CE QUI NE MARCHAIT PAS. Les cartes portaient `aspect-ratio: 1/1` : chacune
   décidait de sa hauteur toute seule. Dès qu'une carte débordait de son carré,
   sa rangée grandissait — mais les voisines gardaient leur hauteur de carré, et
   il restait dessous un grand trou. C'était visible à l'écran.

   La hauteur ne peut pas être décidée par la carte : elle appartient à la
   RANGÉE. D'où une grille à unité de ligne fixe, où chaque carte remplit sa
   cellule (`height: 100%`) au lieu de se dimensionner elle-même.

   Et une fois l'unité de ligne posée, les formats demandés tombent tout seuls :
   une carte occupe 1 ou 2 colonnes, 1 ou 2 rangées. */
.wb-dash-cards {
    display: grid;
    /* Entre ~300 px et une part égale : 3 à 4 colonnes sur un écran de comptoir. */
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    /* L'UNITÉ DE RANGÉE. `minmax(…, auto)` et non une valeur fixe : une carte
       dont le contenu dépasse doit pouvoir pousser sa rangée — et toutes ses
       voisines suivront, puisqu'elles remplissent leur cellule. */
    grid-auto-rows: minmax(230px, auto);
    /* `dense` rebouche les trous qu'une carte 1×2 laisserait derrière elle.
       C'est précisément ce que « toutes les cartes doivent être alignées »
       demande : pas de case vide au milieu de la mosaïque. */
    grid-auto-flow: row dense;
    gap: 16px;
    margin-top: 18px;
}

.wb-card {
    display: flex;
    flex-direction: column;
    /* ⚠️ La carte REMPLIT sa cellule, elle ne se dimensionne plus elle-même :
       c'est ce qui aligne les bas de cartes d'une même rangée. */
    height: 100%;
    min-height: 0;
    /* ⚠️ Variables Bootstrap et non couleurs en dur : les cartes étaient
       #ffffff sur #e0e0e0 avec du texte #2c3e50 — donc BLANCHES en thème
       sombre, avec du texte foncé sur fond clair au milieu d'une page sombre.
       Le thème sombre existe depuis longtemps ; ces quatre valeurs étaient les
       seules à ne pas le suivre. */
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: 8px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
    padding: 18px;
}

/* ── LES FORMATS, et pourquoi les hauteurs seulement ──────────────────────
   ⚠️ UNE CARTE PLUS LARGE NE PEUT PAS PAVER. Le nombre de colonnes varie avec
   la largeur de l'écran (`auto-fill`) : dès qu'il est impair, une carte de deux
   colonnes ne trouve plus sa place en fin de rangée et se reporte plus bas, en
   laissant un TROU AU MILIEU de la mosaïque. C'est exactement ce qu'on voyait :
   la carte « Échéances dépassées » renvoyée sous un vide.

   Une carte plus HAUTE, elle, pave toujours : `grid-auto-flow: dense` comble
   les creux qu'elle laisse avec les cartes suivantes, et ce qui reste se trouve
   au BAS de la grille — où ça se lit comme une fin, pas comme un trou.

   `2x1` reste donc défini, mais AUCUN widget ne l'emploie par défaut : à
   réserver à un contenu qui exige vraiment de la largeur (un graphique), en
   sachant ce qu'il coûte. */
.wb-card--2x1 { grid-column: span 2; }
.wb-card--1x2 { grid-row: span 2; }

/* Une carte large ne doit pas déborder quand il n'y a qu'une colonne. */
@media (max-width: 1000px) {
    .wb-card--2x1 { grid-column: span 1; }
}

/* ⚠️ Le corps prend la place restante ET DÉFILE. Sans ça, une liste longue
   (« Suivi des services » et ses 231 véhicules) imposerait sa hauteur à toute
   la rangée : une carte ne doit pas dicter la mise en page des autres. */
.wb-card-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* le lien « Tout voir » reste ancré en bas de la carte */
.wb-card-link {
    margin-top: auto;
    padding-top: 10px;
}

.wb-card-icon {
    font-size: 1.6rem;
    color: var(--bs-success);
    margin-bottom: 8px;
}

.wb-card-title {
    font-size: 1.05rem;
    color: var(--bs-emphasis-color);
    margin: 0 0 8px;
}

.wb-card-text {
    font-size: 0.9rem;
    color: var(--bs-secondary-color);
    line-height: 1.45;
    margin: 0;
}

.wb-card-text code {
    background: var(--bs-tertiary-bg);
    border-radius: 4px;
    padding: 1px 5px;
    color: var(--bs-body-color);
    font-size: 0.85em;
}

/* ⛔ CE BLOC A RETOURNÉ LA COQUILLE SUR TÉLÉPHONE, ET C'ÉTAIT L'ERREUR.
   Il posait `flex-direction: column` sur `.wb-shell` et rendait la sidebar
   pleine largeur (`width: 100%`) et NON POSITIONNÉE (`position: static`). Trois
   conséquences, toutes visibles :

     1. le menu passait AVANT le contenu, sur toute la largeur : deux à trois
        écrans de navigation à faire défiler avant la première ligne utile ;
     2. `position: static` annule le `z-index` — un élément non positionné n'en
        a pas. Le `z-index: 1035` de la sidebar (cf. son bloc plus haut, et
        SidebarEmpilementTest) tombait donc avec lui, et avec le contexte
        d'empilement qu'il créait : volets de groupe et menu du profil
        repassaient sous les cartes de la page, à moitié recouverts ;
     3. la sidebar perdait sa hauteur (`min-height: 0`), donc son fond : là où
        elle ne portait plus rien, on lisait la page au travers.

   La sidebar reste donc, sur téléphone, la colonne de gauche qu'elle est
   partout ailleurs — en version ÉTROITE, forcée à l'ouverture de `.wb-shell`
   (cf. `index.php`). Les règles de largeur sont plus bas, après les profils
   d'écran par poste : elles doivent gagner contre eux. */
@media (max-width: 768px) {
    /* mobile : une seule colonne, hauteur libre — aligner n'a plus de sens
       quand il n'y a qu'une carte par rangée, et une hauteur imposée y ferait
       défiler pour rien. */
    .wb-dash-cards {
        grid-template-columns: 1fr;
        grid-auto-rows: auto;
    }

    .wb-card { height: auto; }
    .wb-card--1x2 { grid-row: span 1; }
    .wb-card-body { overflow-y: visible; }
}

/* ===== Tabulator WB 2.0 — densité et scroll ===== */
.tabulator {
    font-size: 0.8rem;
}
.tabulator .tabulator-header .tabulator-col .tabulator-col-content {
    padding: 4px 6px;
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell {
    padding: 3px 6px;
}
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input {
    font-size: 0.75rem;
    padding: 1px 4px;
    height: auto;
}

/* ===== Tabulator WB 2.0 — habillage visuel (tokens + composants réutilisables) =====
   Ce bloc s'applique à TOUS les tableaux du socle Datatable (véhicules, articles,
   commandes, location...), pas seulement à un dataset en particulier. Les sélecteurs
   restent chaînés/spécifiques pour continuer à gagner sur tabulator_bootstrap5.min.css
   et Bootstrap sans recourir à !important (cf. bloc densité ci-dessus). */
:root {
    --wb-dt-border: #dee1e4;
    --wb-dt-border-strong: #c7cbcf;
    --wb-dt-surface: #ffffff;
    --wb-dt-surface-alt: #f4f6f7;
    --wb-dt-text: #262a2d;
    --wb-dt-text-muted: #6b7176;
    --wb-dt-accent: #2a5f8f;
    --wb-dt-accent-soft: #e7f0f7;
    --wb-dt-ok: #1f8a57;
    --wb-dt-ok-soft: #e3f3ea;
    --wb-dt-warn: #b6690a;
    --wb-dt-warn-soft: #faedde;
    --wb-dt-info: #2563aa;
    --wb-dt-info-soft: #e5eefa;
    --wb-dt-alt: #7a5c99;
    --wb-dt-alt-soft: #eee7f4;
}

.tabulator.tabulator {
    border: 1px solid var(--wb-dt-border);
    border-radius: 8px;
    background: var(--wb-dt-surface);
    color: var(--wb-dt-text);
}
.tabulator .tabulator-header {
    background: var(--wb-dt-surface-alt);
    border-bottom: 1px solid var(--wb-dt-border);
}
.tabulator .tabulator-header .tabulator-col {
    background: transparent;
    /* box-shadow plutôt que border : un vrai border-right modifierait la largeur
       de la boîte si box-sizing n'est pas garanti border-box à cet endroit précis
       (Tabulator calcule et synchronise les largeurs header/corps en px inline) —
       box-shadow est purement visuel, ne participe jamais au layout. */
    box-shadow: inset -1px 0 0 var(--wb-dt-border);
}
.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title {
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 700;
    color: var(--wb-dt-text-muted);
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row {
    border-bottom: 1px solid var(--wb-dt-border);
    background: var(--wb-dt-surface);
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-row-even {
    background: var(--wb-dt-surface);
}
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover {
    background: var(--wb-dt-surface-alt);
}

/* Boutons d'action de ligne (__actions) : discrets, apparaissent au survol */
.tabulator .tabulator-row .wb-dt-actions-cell {
    /* inline-flex, pas flex : cette cellule reste un .tabulator-cell positionné
       dans le flux de la ligne (inline-block) — display:flex la rendrait "block"
       et casserait l'alignement de toutes les colonnes suivantes. */
    display: inline-flex;
    gap: 4px;
    opacity: 0.45;
    transition: opacity 0.1s ease;
}
.tabulator .tabulator-row:hover .wb-dt-actions-cell {
    opacity: 1;
}
.tabulator .tabulator-row .wb-dt-actions-cell .btn {
    width: 26px;
    height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
}

/* Pills sémantiques réutilisables — alimentées par le formatter 'badge' générique
   (config colonne badgeMap) : Status véhicule, Etape location, tout futur champ
   d'état par dataset. */
.wb-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.5;
    padding: 2px 9px;
    border-radius: 20px;
    white-space: nowrap;
}
.wb-pill.wb-pill-neutral {
    background: transparent;
    border: 1px solid var(--wb-dt-border-strong);
    color: var(--wb-dt-text-muted);
}
.wb-pill.wb-pill-muted {
    background: transparent;
    border: 1px dashed var(--wb-dt-border-strong);
    color: var(--wb-dt-text-muted);
    font-weight: 500;
}
.wb-pill.wb-pill-ok { background: var(--wb-dt-ok-soft); color: var(--wb-dt-ok); }
.wb-pill.wb-pill-warn { background: var(--wb-dt-warn-soft); color: var(--wb-dt-warn); }
.wb-pill.wb-pill-info { background: var(--wb-dt-info-soft); color: var(--wb-dt-info); }
.wb-pill.wb-pill-alt { background: var(--wb-dt-alt-soft); color: var(--wb-dt-alt); }

/* ── Barre de critères ──────────────────────────────────────────────────────
   Les critères actifs, énoncés en toutes lettres au-dessus de la grille. Elle
   existe parce qu'un critère posé sur une colonne MASQUÉE était invisible : la
   grille se restreignait sans que rien ne dise pourquoi. */
.wb-dt-criteres {
    border: 1px solid var(--wb-dt-border);
    border-radius: 8px;
    background: var(--wb-dt-surface-alt);
    padding: 8px 10px;
    margin-bottom: 8px;
}
.wb-dt-criteres-puces {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-bottom: 8px;
}
.wb-dt-criteres-ajout {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding-top: 8px;
    border-top: 1px solid var(--wb-dt-border);
}
.wb-dt-critere {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--wb-dt-surface);
    border: 1px solid var(--wb-dt-border);
    border-radius: 14px;
    padding: 3px 6px 3px 10px;
    font-size: 0.8rem;
    line-height: 1.4;
    color: var(--wb-dt-text);
}
.wb-dt-critere strong { font-weight: 600; }
.wb-dt-critere-x {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--wb-dt-text-muted);
    font-size: 0.85rem;
    line-height: 1;
    padding: 2px 4px;
    border-radius: 50%;
    cursor: pointer;
}
.wb-dt-critere-x:hover { background: var(--wb-dt-danger-soft, #f8d7da); color: #58151c; }

/* Bandeau d'onglets de portée (scopeTabs), au-dessus de la grille */
.wb-dt-scope-tabs {
    display: inline-flex;
    background: var(--wb-dt-surface-alt);
    border: 1px solid var(--wb-dt-border);
    border-radius: 8px;
    padding: 3px;
    gap: 2px;
    margin-bottom: 8px;
}
.wb-dt-scope-tabs button {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--wb-dt-text-muted);
    font-size: 0.78rem;
    font-weight: 600;
    padding: 5px 11px;
    border-radius: 6px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.wb-dt-scope-tabs button.wb-dt-scope-active {
    background: var(--wb-dt-surface);
    color: var(--wb-dt-text);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

/* Compteur inscrit dans chaque onglet (« Parc location (350) »). Rempli par
   wb-datatable.js depuis l'API des facettes : un onglet vide s'explique alors
   de lui-même, au lieu de laisser croire que l'écran est cassé. */
.wb-dt-scope-tabs .wb-dt-scope-count {
    font-weight: 500;
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
}
/* Un onglet à zéro se voit AVANT le clic — on ne fait pas partir quelqu'un en
   exploration pour lui montrer une grille vide. Estompé, jamais masqué : la
   portée existe, elle est simplement sans contenu aujourd'hui. */
.wb-dt-scope-tabs button.wb-dt-scope-vide {
    opacity: 0.5;
}
.wb-dt-scope-tabs button.wb-dt-scope-vide.wb-dt-scope-active {
    opacity: 1;
}

/* Pill de détail dynamique accolé à un .wb-pill (ex. date "Loué jusqu'au…") */
.wb-pill-detail {
    opacity: 0.78;
    font-weight: 500;
    margin-left: 1px;
}

/* ===== Dialogues : socle commun aux modales et aux tiroirs =====
   Le comportement (focus, Entrée, empilement) est dans js/wb-dialog.js ; ici,
   ce qui se voit. */

/* Pied de tiroir. `.offcanvas-footer` N'EXISTE PAS dans Bootstrap : c'est une
   classe maison que trois gabarits utilisaient sans qu'aucune règle ne la
   définisse. Les boutons d'action suivaient donc le contenu et partaient sous la
   ligne de flottaison dès que la fiche était longue — il fallait dérouler tout
   le tiroir pour trouver « Enregistrer ». */
.offcanvas-footer {
    flex: 0 0 auto;
    margin-top: auto;
    background: var(--bs-body-bg);
    position: sticky;
    bottom: 0;
    z-index: 2;
}

/* Le corps prend la place restante et défile seul, pour que l'en-tête et le pied
   restent en place. */
.offcanvas-body { flex: 1 1 auto; min-height: 0; }

/* ══ LARGEUR DES TIROIRS ═════════════════════════════════════════════════════
   L'étroit (28rem) est celui des aperçus à une colonne ; `.wb-drawer-wide`
   habille les fiches du socle Datatable, en trois colonnes (`modalLayout:
   '3col'`), où un champ `col-md-3` tombe sous 150 px dès que le tiroir est
   étroit. En dessous de 576 px de viewport, le tiroir prend tout l'écran : en
   laisser une bande de fond inutilisable sur un téléphone n'aide personne.

   ⛔ ON RÈGLE `--bs-offcanvas-width`, ET SURTOUT PAS `width`. C'est LA leçon de
   ce coin du fichier, et elle a été payée trois fois.

   Bootstrap 5.3.3 écrit `.offcanvas.offcanvas-end { width: var(--bs-offcanvas-width) }`
   — DEUX classes, donc la spécificité EXACTE de `.offcanvas.wb-drawer-wide` — et
   `index.php` le charge (l. 315) APRÈS `css/app.css` (l. 303). À spécificité
   égale, le dernier arrivé gagne : **toute déclaration `width` écrite ici est
   morte**, silencieusement. La variable, elle, est posée par Bootstrap sur
   `.offcanvas` SEUL (une classe) : la surcharger depuis deux classes gagne par
   spécificité, quel que soit l'ordre de chargement.

   ⛔ ET C'EST EXACTEMENT CE QUI S'EST PASSÉ. `webbike.css:697` portait
   `.wb-drawer-wide { width: 720px }` ; cette feuille étant chargée (l. 404)
   APRÈS Bootstrap, c'était la SEULE qui pouvait gagner. En la retirant le
   15.08.2026 « au profit de la définition unique d'app.css », on a fait retomber
   tous les tiroirs à la valeur par défaut de Bootstrap — **400 px** — pendant
   que le commentaire d'ici annonçait 45rem. Trois valeurs successives ont ainsi
   été écrites, lues et commentées sans jamais s'appliquer : 38rem, 45rem, 90rem.
   Signalé par le mandant le 18.08.2026, capture à l'appui : « le drawer est
   toujours trop étroit, le fix ne fonctionne pas ». Mesuré au navigateur avant
   correction : 400 px à 1366, 1512 ET 1920 px de viewport.

   ⚠️ Tenu par `tests/Unit/TiroirLargeurTest.php`, qui refuse un `width` sur un
   sélecteur d'offcanvas ET vérifie que l'ordre de chargement reste celui qui
   justifie la règle.

   ⚠️ Le palier intermédiaire n'est pas décoratif : 90rem (1440 px) couvrirait la
   quasi-totalité d'un écran de portable, et un tiroir qui masque la liste d'où
   il sort cesse d'être un tiroir. */
.offcanvas.offcanvas-end,
.offcanvas.offcanvas-start { --bs-offcanvas-width: min(28rem, 100vw); }
.offcanvas.wb-drawer-wide  { --bs-offcanvas-width: min(90rem, 96vw); }
@media (max-width: 1599.98px) {
    .offcanvas.wb-drawer-wide { --bs-offcanvas-width: min(72rem, 96vw); }
}
@media (max-width: 575.98px) {
    .offcanvas.offcanvas-end,
    .offcanvas.offcanvas-start,
    .offcanvas.wb-drawer-wide { --bs-offcanvas-width: 100vw; }
}

/* Modale haute : le corps défile, l'en-tête et le pied restent visibles. Sans
   ça, les boutons d'un long formulaire sortent de l'écran. */
.modal-dialog { max-height: calc(100vh - 3.5rem); }
.modal-content { max-height: inherit; }
.modal-body    { overflow-y: auto; }

/* Le focus doit se VOIR dans un dialogue : on y arrive au clavier, souvent sans
   avoir touché la souris. L'anneau par défaut de Bootstrap disparaît sur les
   fonds colorés des en-têtes. */
.modal :focus-visible,
.offcanvas :focus-visible {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .modal.fade .modal-dialog,
    .offcanvas { transition: none !important; }
}

/* ----- Présentation des champs, commune à tous les dialogues -----
   Les 14 dialogues à formulaire habillaient leurs champs chacun à sa façon :
   étiquettes tantôt `fw-medium`, tantôt nues, espacements de 2 à 4 crans. */
.modal .form-label,
.offcanvas .form-label {
    font-size: 0.8rem;
    font-weight: 600;
    margin-bottom: 0.2rem;
    color: var(--bs-secondary-color, #6c757d);
}

/* Champ obligatoire : marqué une fois, au même endroit, partout. Le titre porte
   le mot en clair — un astérisque seul n'est compris que de qui connaît déjà la
   convention. */
.modal .form-label.wb-requis::after,
.offcanvas .form-label.wb-requis::after {
    content: "*";
    color: var(--bs-danger, #dc3545);
    margin-left: 0.2rem;
    font-weight: 700;
}

/* Sections d'un formulaire long : le tiroir location en compte cinq. */
.modal fieldset,
.offcanvas fieldset { margin-bottom: 1.1rem; border: 0; }
.wb-legend {
    font-size: 0.82rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    color: var(--bs-primary, #0d6efd);
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
    padding-bottom: 0.25rem;
    margin-bottom: 0.7rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    float: none;   /* <legend> flotte par défaut dans certains UA */
}

/* Aide sous un champ : ton et taille identiques partout. */
.modal .form-text,
.offcanvas .form-text { font-size: 0.75rem; }

/* Pied de dialogue : le DESTRUCTIF est repoussé à gauche par `me-auto`, loin du
   bouton principal, pour qu'un clic qui rate ne détruise rien. L'ordre de
   lecture — et donc de tabulation — devient : destructif · annuler · principal.
   Il variait d'un dialogue à l'autre, y compris l'inverse. */
.modal-footer .wb-destructif,
.offcanvas-footer .wb-destructif { margin-right: auto; }

/* Sur écran étroit, les boutons passent en colonne pleine largeur plutôt que de
   se tasser à trois par ligne. */
@media (max-width: 400px) {
    .modal-footer, .offcanvas-footer { flex-direction: column-reverse; align-items: stretch; }
    .modal-footer .wb-destructif,
    .offcanvas-footer .wb-destructif { margin-right: 0; margin-top: 0.25rem; }
}

/* ----- Titre de dialogue : une icône, toujours au même endroit -----
   Le tiroir livraisons ouvre sur « 🚚 Modifier la livraison » ; six autres
   ouvrent sur un titre nu (« Détail », « Aperçu », « Actions », « Revue de la
   paire »). Sur un poste où trois tiroirs s'enchaînent, l'icône est ce qui dit
   de quoi on parle AVANT d'avoir lu. */
.modal-title > i.bi,
.offcanvas-title > i.bi {
    margin-right: 0.5rem;
    color: var(--bs-primary, #0d6efd);
}

/* ----- Liste de suggestions, commune aux dialogues -----
   Promue du tiroir livraisons (`css/livraison_calendrier.css`, préfixe `lc-`),
   où elle était enfermée dans une feuille servie par une seule page. Le tiroir
   location a sa propre variante en `.list-group` ; elle reste, on ne réécrit pas
   deux composants le même jour. */
.wb-ac-wrapper { position: relative; }

.wb-ac-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-top: none;
    border-radius: 0 0 0.375rem 0.375rem;
    max-height: 220px;
    overflow-y: auto;
    /* Au-dessus du tiroir (Bootstrap : 1045) sans passer devant une modale. */
    z-index: 1046;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
    display: none;
}
.wb-ac-results.wb-ac-visible { display: block; }

.wb-ac-item {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 0.85rem;
    border-bottom: 1px solid var(--bs-border-color-translucent, #f0f0f0);
}
.wb-ac-item:last-child { border-bottom: none; }
.wb-ac-item:hover,
.wb-ac-item.wb-ac-actif { background: var(--bs-primary-bg-subtle, #cfe2ff); }

.wb-ac-item-main { font-weight: 600; color: var(--bs-body-color, #212529); }
.wb-ac-item-sub  { font-size: 0.75rem; color: var(--bs-secondary-color, #6c757d); margin-top: 1px; }

.wb-ac-vide,
.wb-ac-charge { padding: 10px 12px; font-size: 0.8rem; color: var(--bs-secondary-color, #6c757d); }
.wb-ac-charge { text-align: center; }

/* ----- Puce de sélection -----
   Ce qui est CHOISI cesse d'être du texte dans un champ et devient un objet
   qu'on voit et qu'on retire. Promue de `.lc-selected-tag`. */
.wb-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: var(--bs-success-bg-subtle, #d1e7dd);
    color: var(--bs-success-text-emphasis, #0a3622);
    padding: 5px 12px;
    border-radius: 20px;
    font-size: 0.82rem;
    font-weight: 500;
    margin-top: 6px;
}
.wb-chip-remove {
    cursor: pointer;
    font-size: 1.1rem;
    line-height: 1;
    opacity: 0.7;
    background: none;
    border: 0;
    padding: 0;
    color: inherit;
}
.wb-chip-remove:hover { opacity: 1; }

/* ⚠️ UNE ABSENCE NE SE DIT PAS EN VERT. La puce de base est celle d'un fait
   acquis ; « aucune relation commerciale » (écran Qualité du fichier clients)
   est un manque, et le peindre de la même couleur qu'une facture encaissée le
   ferait lire comme un acquis de plus. */
.wb-chip-neutre {
    background: var(--bs-secondary-bg-subtle, #e2e3e5);
    color: var(--bs-secondary-color, #6c757d);
    font-weight: 400;
    font-style: italic;
}

/* Drawer/offcanvas de détail : sections repliables (ex. section "Location") */
.wb-drawer-wide .offcanvas-header {
    border-bottom: 1px solid var(--wb-dt-border);
}
.wb-dt-section {
    border: 1px solid var(--wb-dt-border);
    border-radius: 8px;
    margin-bottom: 12px;
    overflow: hidden;
}
.wb-dt-section .wb-dt-section-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    background: var(--wb-dt-surface-alt);
    font-weight: 700;
    font-size: 0.78rem;
    cursor: pointer;
    user-select: none;
}
.wb-dt-section .wb-dt-section-head .wb-dt-section-chev {
    margin-left: auto;
    transition: transform 0.15s ease;
    color: var(--wb-dt-text-muted);
}
.wb-dt-section.wb-dt-section-open .wb-dt-section-head .wb-dt-section-chev {
    transform: rotate(180deg);
}
.wb-dt-section .wb-dt-section-body {
    padding: 12px;
    display: none;
}
.wb-dt-section.wb-dt-section-open .wb-dt-section-body {
    display: block;
}

/* ===== Calendriers : le repère temporel, commun aux trois vues =====
   Comportement dans js/wb-calendrier-passe.js ; ici, ce qui se voit.

   ⛔ CE QUI A ÉTÉ DEMANDÉ, ET POURQUOI CE N'EST PAS CE QUI EST FAIT.
   La demande (15.08.2026) était de GRISER les jours passés. Mesuré sur la vue
   Mois des livraisons au 15.08.2026 : 19 cellules sur 42 sont passées (45 %), et
   les six livraisons affichées — 28.07, 3, 4, 5, 6.08 — sont TOUTES dans le
   passé. La règle telle que formulée aurait donc grisé la totalité de ce qui
   porte du contenu : un signal qui couvre la moitié de la surface n'est plus un
   signal, et une livraison passée n'est pas une donnée morte — c'est la trace
   qu'on vient consulter.

   ⚠️ LE VRAI DÉFAUT ÉTAIT AILLEURS : « aujourd'hui » valait
   `rgba(76,175,80,0.06)`, un vert à 6 % d'opacité — invisible. Sur les
   calendriers atelier et collaborateurs, il n'y avait rien du tout, ils
   héritaient du défaut du bundle.

   D'où trois niveaux, du plus effacé au plus marqué :
     hors-mois (numéro à .3, réglé par le bundle) · passé (numéro à .45)
     · aujourd'hui (pastille pleine + bordure d'accent).
   Le passé est atténué SUR LE NUMÉRO, pas sous les pastilles : c'est exactement
   ce que le bundle fait déjà pour les jours hors-mois, et ça ne coûte rien au
   contraste des événements. Le grisé franc reste à une variable près si le
   besoin se confirme à l'usage. */

.wb-cal-passe { background-color: rgba(0, 0, 0, 0.018); }
.wb-cal-passe .ec-day-head time { opacity: 0.45; }

/* Aujourd'hui : la pastille pleine est ce qui se voit d'un coup d'œil, la teinte
   de fond seule ne suffisait pas. */
.ec-day.ec-today {
    background-color: var(--bs-primary-bg-subtle, #cfe2ff);
    box-shadow: inset 0 2px 0 0 var(--bs-primary, #0d6efd);
}
.ec-day.ec-today .ec-day-head time,
.ec-col-head .ec-today .ec-day-head time {
    display: inline-block;
    min-width: 1.7em;
    padding: 0.1em 0.45em;
    border-radius: 999px;
    background: var(--bs-primary, #0d6efd);
    color: #fff;
    font-weight: 700;
    text-align: center;
}

/* ⚠️ AUJOURD'HUI N'EST JAMAIS DU PASSÉ. La classe est posée en JS sur les
   cellules antérieures ; si les deux se rencontraient (bascule de minuit, fuseau),
   c'est « aujourd'hui » qui doit gagner. */
.ec-day.ec-today.wb-cal-passe { background-color: var(--bs-primary-bg-subtle, #cfe2ff); }
.ec-day.ec-today.wb-cal-passe .ec-day-head time { opacity: 1; }

/* ===== Profils d'écran par poste ===== */

/* Grand écran (iMac, 4K externe ≥ 24") — maximiser l'espace */
html.wb-screen-grande .wb-sidebar {
    flex-basis: 280px;
    width: 280px;
}
html.wb-screen-grande .wb-main {
    font-size: 0.95rem;
}

/* Petit écran (MacBook Air 13", écran 15") — compacter */
html.wb-screen-petite .wb-sidebar {
    flex-basis: 220px;
    width: 220px;
}
html.wb-screen-petite .wb-main {
    padding: 0.5rem;
    font-size: 0.875rem;
}

/* Moyenne (MacBook Pro 14/16", 22-24") : comportement par défaut, aucun override */

/* ===== TÉLÉPHONE : la colonne étroite, d'office =====
   Sous 768 px, une sidebar dépliée de 250 px prend les deux tiers de l'écran :
   il ne reste pas de quoi lire une fiche. La colonne étroite n'y est donc pas
   une préférence de poste mais la mise en page par défaut — `index.php` pose
   `.wb-rail` dès l'ouverture de `.wb-shell`, avant que quoi que ce soit soit
   peint, et le réglage mémorisé du poste ne l'emporte pas dessus.

   ⚠️ CE BLOC EST ICI, ET PAS AVEC LES AUTRES RÈGLES MOBILES PLUS HAUT, parce
   qu'il doit gagner contre les profils d'écran ci-dessus : un téléphone
   rattaché à un poste « grand écran » se retrouvait sinon avec 280 px de menu.
   `html.wb-screen-grande .wb-sidebar` pèse deux classes et un élément ; les
   sélecteurs de ce bloc partent tous de `.wb-shell` et en pèsent trois — la
   spécificité passe avant l'ordre, et c'est elle qui tranche. */
@media (max-width: 768px) {
    /* ── Sans JavaScript ───────────────────────────────────────────────────
       `.wb-rail` est posée par un script : s'il n'a pas tourné, la sidebar
       reprendrait 250 px devant le contenu. La colonne reste donc étroite —
       les libellés sont rognés (la sidebar est `overflow: hidden`), mais les
       icônes restent visibles et cliquables. Dégradé, pas cassé.
       `.wb-decide` est posée par ce même script : elle dit « quelqu'un a
       décidé », ce qui permet de distinguer ce cas-ci du dépliage DEMANDÉ. */
    .wb-shell:not(.wb-decide) .wb-sidebar {
        flex: 0 0 4.5rem;
        width: 4.5rem;
    }

    /* ── Déplié à la demande : le menu RECOUVRE, il ne pousse pas ──────────
       La colonne étroite est le défaut, pas une prison : on peut vouloir lire
       les libellés en clair une fois. Mais déplier en poussant laisserait
       140 px de fiche sur un écran de 390 — autant dire rien. Le menu passe
       donc par-dessus, comme un tiroir ; le clic suivant sur le logo le
       referme, et le chargement suivant repart de la colonne étroite. */
    .wb-shell.wb-decide:not(.wb-rail) .wb-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        /* Jamais tout l'écran : la bande de contenu qui dépasse à droite est ce
           qui montre qu'on est par-dessus une page, et où appuyer pour revenir. */
        width: min(15.625rem, 85vw);
        overflow-y: auto;
        box-shadow: 0 0 2rem rgba(0, 0, 0, 0.35);
    }

    /* ⚠️ Le volet s'ouvre à DROITE du rail : sans borne, ses 12,5 rem minimum
       sortaient de l'écran sur un téléphone étroit — la moitié des libellés
       hors champ, et rien pour aller les chercher. Et il se déploie vers le
       bas depuis son groupe : plafonné et rendu défilant, il ne peut plus
       passer sous le bord de l'écran. */
    .wb-shell.wb-rail .wb-group-items {
        max-width: calc(100vw - 5.5rem);
        min-width: 0;
        max-height: 70vh;
        overflow-y: auto;
    }
}

/* Logo du garage en tête du tableau de bord — borné en hauteur pour qu'un
   fichier mal exporté ne pousse pas la page. */
.wb-dash-logo,
.wb-dash-mark {
    height: 40px;
    width: auto;
    object-fit: contain;
    flex-shrink: 0;
}
.wb-dash-logo { max-width: 260px; }
.wb-dash-mark { max-width: 120px; display: none; }

/* ⚠️ Sous 900 px, le logotype prendrait toute la largeur de l'en-tête et
   pousserait le nom et le bouton hors de vue. La marque compacte le remplace :
   même signe, place tenue. */
@media (max-width: 900px) {
    .wb-dash-logo { display: none; }
    .wb-dash-mark { display: block; }
}

/* Message éphémère du lecteur de codes (js/wb-scan.js).
   ⚠️ Il apparaît quand un code est LU mais qu'aucun écran ne lui correspond
   encore. Sans lui, un scan valide ne produirait rien du tout, et l'opérateur
   conclurait que la douchette est en panne. */
.wb-scan-msg {
    position: fixed;
    z-index: 1060;                 /* au-dessus des modales : il les commente */
    left: 50%;
    bottom: 1.5rem;
    transform: translateX(-50%);
    max-width: 32rem;
    padding: 0.55rem 0.9rem;
    border-radius: 0.5rem;
    background: var(--bs-emphasis-color);
    color: var(--bs-body-bg);
    font-size: 0.875rem;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.25);
}

/* ── Statistiques : la palette des années ─────────────────────────────────────
   ⚠️ UNE TEINTE PAR ANNÉE, PAS QUATRE NUANCES DE BLEU. La première version
   dégradait un bleu du foncé au clair : élégant sur maquette, illisible en
   usage — trois traits pâles qu'on ne distingue qu'en pointant la légende.
   Signalé par le mandant le 09.08.2026.

   Les quatre teintes sont celles d'OKABE-ITO, la palette qualitative de
   référence pour rester distinguable sous les trois formes courantes de
   daltonisme, dont la deutéranopie (≈ un homme sur douze).

   ⚠️ DÉCLARÉES SUR `:root` ET NON SUR LA PAGE : la vignette du tableau de bord
   les emploie aussi, et elle vit dans une autre page. Une palette définie deux
   fois finit par diverger.                                                   */
:root {
  --wb-stats-c1: #0072B2;  /* bleu   — année en cours */
  --wb-stats-c2: #E69F00;  /* orange — N-1 */
  --wb-stats-c3: #009E73;  /* vert   — N-2 */
  --wb-stats-c4: #CC79A7;  /* mauve  — N-3 */
}
/* Sur fond sombre, les mêmes valeurs tombent sous le seuil de contraste. */
[data-bs-theme="dark"] {
  --wb-stats-c1: #56B4E9;
  --wb-stats-c2: #F0B840;
  --wb-stats-c3: #2FBF95;
  --wb-stats-c4: #E39BC3;
}

/* Vignette du widget d'accueil : rendue côté serveur, sans JavaScript. */
.wb-stats-vignette { width: 100%; height: 48px; display: block; margin: .25rem 0; }

/* ── Panneau « Documents » : ce qui s'accroche à une facture ────────────────
   ⛔ L'INDENTATION EST UNE AFFIRMATION, PAS UNE DÉCORATION : elle dit « ceci
   se rattache à la ligne du dessus ». Le JS ne la pose que lorsque la facture
   (ou un maillon déjà rattaché) est réellement au-dessus — sur un document
   isolé, elle désignerait le dossier d'un autre numéro.

   ⚠️ DEUX NIVEAUX, ET LA COULEUR EST LA DIFFÉRENCE. `.wb-ged-attache` est le
   rattachement nu : un bulletin de versement accompagne sa facture, il ne la
   réclame pas. `.wb-ged-relance` reprend cette base et n'ajoute QUE la couleur
   d'alerte — une relance est une créance impayée, elle doit se repérer en
   survolant la colonne, sans lire. Peindre les deux pareil ferait lire un
   impayé là où il n'y en a pas.                                              */
.wb-ged-attache,
.wb-ged-relance {
    margin-left: 1.75rem;
    position: relative;
    border-left: 3px solid var(--wb-dt-border-strong) !important;
    /* ⚠️ RESSERRER L'ÉCART, sinon le bloc ne se lit pas comme un bloc : la liste
       espace toutes ses lignes de la même façon (`gap-2`), et une relance se
       retrouvait aussi loin de sa facture que d'un dossier étranger. */
    margin-top: -0.25rem;
}
/* Le crochet qui rattache visuellement le bloc à la facture au-dessus.
   `aria-hidden` implicite : c'est un pseudo-élément, aucun lecteur d'écran ne
   le rencontre — le libellé « Rappel (2e sommation) » porte déjà le sens. */
.wb-ged-relance { border-left-color: var(--wb-dt-warn) !important; }
.wb-ged-relance::before { background: var(--wb-dt-warn); }

.wb-ged-attache::before,
.wb-ged-relance::before {
    content: "";
    position: absolute;
    left: -1rem;
    top: 1.1rem;
    width: 1rem;
    height: 3px;
    background: var(--wb-dt-border-strong);
    opacity: .55;
}

/* ── Comparaison de factures (?page=facture-comparaison) ──────────────────────
   ⚠️ UNE HAUTEUR EXPLICITE EST OBLIGATOIRE. Un <iframe> sans hauteur retombe sur
   150 px : les deux visionneuses PDF s'afficheraient en bandeaux inutilisables,
   et l'écran raterait exactement ce qu'il existe pour montrer. `vh` plutôt que
   `%` — le parent n'a pas de hauteur définie, un pourcentage n'aurait rien à
   quoi se rapporter. */
.wb-fcmp-vue {
  display: block;
  width: 100%;
  height: calc(100vh - 19rem);
  min-height: 26rem;
  border: 0;
  border-top: 1px solid var(--bs-border-color, #dee2e6);
  background: var(--bs-tertiary-bg, #f8f9fa);
}
