/* ==========================================================================
   webforce-client.css — FEUILLE UNIQUE de la refonte de l'espace client
   --------------------------------------------------------------------------
   CIBLE   : https://client.webforce.be — SPA React (build Create React App),
             Metronic 8 sur Bootstrap 5.0/5.1, + RSuite.
   VERSION : 2 — 2026-07-30
   RELEVÉ  : /static/css/main.<hash>.css et sa source map, publiés en production.

   CE FICHIER EST LE SEUL FICHIER CSS À INSTALLER.
   Il ne contient aucun @import et ne dépend d'aucune autre feuille. Le fichier
   webforce-client-tokens.css du même dossier est un CATALOGUE DE RÉFÉRENCE ;
   son contenu utile est inliné ici (§1 à §4) et il ne doit PAS être déployé.

   --------------------------------------------------------------------------
   DROITS NÉCESSAIRES — à lire avant tout le reste

   Nous n'avons NI le dépôt du SPA, NI l'accès de déploiement. L'application de
   cette feuille exige donc, chez qui les détient :

     A. un accès en écriture à `public/index.html` du dépôt du SPA
        (pour la balise <link> et la classe `wf-client` sur <body>), et la
        capacité de relancer le build ;
     B. un accès en dépôt de fichiers sur la racine servie
        (pour `webforce-client.css` et les fichiers de police).

   Un accès FTP seul (B sans A) ne suffit pas : la balise <link> serait effacée
   au build suivant. Il n'existe pas de variante sans droit d'écriture sur le
   gabarit.

   --------------------------------------------------------------------------
   INSTALLATION — trois lignes, aucune modification de composant React

   1. Déposer ce fichier avec les actifs statiques servis, par exemple
      `public/webforce-client.css`, et les fichiers de police dans
      `public/fonts/` (voir §5).

   2. Dans `public/index.html`, en DERNIÈRE position du <body> :

        <link rel="stylesheet" href="%PUBLIC_URL%/webforce-client.css?v=2">

      %PUBLIC_URL% et non `/` : un chemin absolu ignore le champ `homepage` de
      package.json, et l'application renverrait 404 si elle est servie depuis un
      sous-chemin. La valeur de `homepage` du dépôt du SPA n'a PAS pu être
      vérifiée ici ; `%PUBLIC_URL%` est correct dans les deux cas.

      Le <link> doit être dans le <body>, PAS dans le <head> : Create React App
      injecte son propre <link> en dernière position du <head>, donc une balise
      placée dans le <head> du gabarit serait chargée AVANT le bundle. Cette
      feuille gagne néanmoins par spécificité (voir plus bas) : la position en
      fin de <body> est une ceinture, pas la bretelle.

   3. Ajouter la classe de bascule sur le <body> du même gabarit :

        <body id="kt_body" class="page-loading dark-mode wf-client">

      Aucun <script> n'est nécessaire. Un <script> inline serait bloqué par
      toute politique de sécurité de contenu sans 'unsafe-inline' ni nonce — la
      CSP du vhost n'a pas pu être vérifiée ici.

   --------------------------------------------------------------------------
   RETOUR ARRIÈRE : retirer `wf-client` du <body>. Aucune règle de ce fichier
   n'est écrite sans le préfixe `.wf-client body` (ou `html:has(.wf-client body)`
   pour les deux blocs lus par le JavaScript) — sans la classe, la feuille est
   totalement inerte, y compris son bloc de variables.

   --------------------------------------------------------------------------
   POURQUOI TOUT EST PRÉFIXÉ (ce n'est pas seulement pour le rollback)

   Le préfixe `.wf-client body ` ajoute exactement (0,1,1) à la spécificité de
   chaque règle. Comme chaque règle ci-dessous miroite le sélecteur d'origine,
   elle est TOUJOURS strictement plus spécifique que lui. L'ordre de chargement
   devient donc indifférent pour toute règle d'origine sans `!important`.

   `.wf-client body` et non `.wf-client` : les variables de RSuite sont déclarées
   sur `.rs-theme-light, :root`, soit (0,1,0). À spécificité égale il faudrait
   compter sur l'ordre de chargement, que le point 2 ci-dessus rend justement
   incertain. (0,1,1) tranche sans dépendre de l'ordre.

   Les `!important` présents ici ne sont pas de la superstition : ils répondent
   aux 202 règles du bundle qui combinent une couleur de palette et `!important`
   (familles .btn-check, .bg-*, .bg-state-*, .form-select…) et aux styles posés
   en ligne par les composants React.

   --------------------------------------------------------------------------
   NE JAMAIS ENVELOPPER CE FICHIER DANS @layer

   Tout style hors couche gagne sur tout style en couche, quelle que soit la
   spécificité. main.css n'est pas layerisé : passer cette feuille en
   `@layer webforce { … }` la ferait perdre systématiquement.

   --------------------------------------------------------------------------
   INTERDITS STRUCTURELS, ET LES 9 EXCEPTIONS ÉNUMÉRÉES

   Motif de l'interdit : une feuille de style peut changer l'apparence d'un
   élément, jamais sa place dans l'arbre du document. Sur la console
   d'administration, une règle redéfinissant `.table-responsive` a créé 153 px
   de défilement latéral et rendu une table inatteignable — une seule moitié
   d'une relation parent/enfant avait été modifiée, et aucun CSS supplémentaire
   ne pouvait recomposer l'autre.

   JAMAIS DÉCLARÉ DANS CE FICHIER, sans exception :
     overflow / overflow-x / overflow-y      display        position
     float / clear                           z-index        box-sizing
     flex-direction / flex-wrap / order      border-width
     --kt-toolbar-height(-tablet-and-mobile) --bs-gutter-x / --bs-gutter-y
     top / right / bottom / left

   DÉCLARÉ, MAIS SEULEMENT DANS LES 9 CAS ÉNUMÉRÉS CI-DESSOUS. Toute autre
   occurrence est un défaut à signaler.

     E1. `font-size` / `line-height` — §6, §15 à §18. Une taille de texte ne
         peut pas élargir un bloc : elle le fait passer à la ligne. Elle peut
         en revanche élargir une CELLULE DE TABLE, dont la largeur dépend du
         contenu : c'est pourquoi §16 REDESCEND les tailles intra-cellule au
         lieu de les monter (voir la note sur `scrollWidth` en fin de §16).
     E2. `width` / `height` sur des FEUILLES de l'arbre sans descendant
         positionné en absolu : le carré décoratif et le SVG du PageHeader
         (§15), l'image du bandeau d'annonce (§15). S'y ajoute UN cas de
         `width: 100%` sur deux colonnes Bootstrap sous 576 px (§15, bandeau
         d'annonce) : c'est exactement le mécanisme que Bootstrap emploie
         lui-même à ses points de rupture (`.col-*` vaut
         `flex: 0 0 auto; width: <n>%`), donc il ne découple aucune paire
         parent/enfant. Le correctif propre reste d'ajouter `col-12` dans le
         JSX — voir README.md, lot 3.
     E3. `min-height` / `min-width` sur des BOUTONS et des CHAMPS, jamais sur un
         conteneur (§19). Un plancher ne peut rien rogner.
     E4. `min-width` ABAISSÉ sur les colonnes de table (§16) : abaisser un
         plancher ne peut pas élargir une table.
     E5. `max-width` en unité `ch` sur des blocs de texte terminaux (§15, §18) :
         un max-width ne peut que réduire la largeur utilisée.
     E6. `grid-template-columns` sur la grille des cartes d'indicateurs (§15) :
         le placement reste automatique, dans l'ordre du DOM, donc l'ordre de
         tabulation est préservé.
     E7. `justify-content` sur les boutons d'en-tête de table (§16) : aligne, ne
         réordonne pas.
     E8. `padding` / `margin` : autorisés partout, à l'exception d'un élément
         dont un frère ou un descendant est positionné en absolu.
     E9. `.table-responsive` : UNIQUEMENT le style de barre de défilement et
         `overscroll-behavior-inline` (§16). Ni overflow, ni width, ni position.
         Le test de non-régression de `scrollWidth` reste le juge.

   `border-width` n'a aucune exception : pour dessiner un filet là où il n'y en
   a pas, on emploie `box-shadow: 0 0 0 1px` ou `box-shadow: inset 0 0 0 1px`,
   qui ne participent pas au calcul de la boîte. C'est ce que fait §10 pour les
   bordures de badge — la version 1 de cette feuille y posait
   `border: 1px solid transparent`, ce qui ajoutait 2 px de largeur et 2 px de
   hauteur à chaque badge, Metronic n'en déclarant aucune. Corrigé.

   --------------------------------------------------------------------------
   CE QUE CETTE FEUILLE NE COUVRE PAS — à savoir avant de la juger

   1. Les styles inline posés par React (`style={{…}}`) battent tout sauf
      `!important`. Ceux que cette feuille traite ont été lus dans la source
      map ; l'inventaire complet exige un compte client, que nous n'avons pas.
   2. La queue de distribution du remap : 1 076 règles du bundle portent une
      couleur de palette. Cette feuille couvre les familles à fort trafic, pas
      les combinaisons utilitaires rares (les 64 règles `.btn-check` notamment).
   3. Les couleurs de graphiques passées en JavaScript par CONSTANTES (palette
      catégorielle de /mailing et /statistiques : six constantes en dur, dont
      deux identiques) sont hors d'atteinte. §2 n'atteint que celles qui passent
      par `getComputedStyle`.
   4. `/creer-compte-mailing` est HORS PÉRIMÈTRE, coût signé : ses deux feuilles
      différées mesurent 69 582 o (142 hexadécimaux distincts, 50 `!important`,
      0 `var(--rs-*)`, 0 `var(--bs-*)`) et 9 743 o. C'est un quatrième système
      visuel sans aucune variable, donc sans prise pour un pont de jetons. La
      bascule `wf-client` ne le change pas. Voir README.md.
   5. `/splash-screen.css` — le premier rendu de chaque chargement — est une
      feuille distincte du <head> qui n'est pas surchargée ici.
   6. Le corps de base (`html{font-size:13px!important}`) n'est pas modifié :
      c'est un artefact de build. Conséquence : les paliers sont en pixels.
   ========================================================================== */


/* ==========================================================================
   1. JETONS
   Union des jetons de marque (repris SANS MODIFICATION du bloc @theme de
   webforce-2026/src/app/globals.css, via public/css/webforce-tokens.css) et des
   jetons de rôle de l'espace client.

   UN SEUL NOM ET UNE SEULE VALEUR PAR RÔLE. La version 1 du livrable en portait
   deux jeux concurrents (`--wfc-tint-*` d'un côté, `--wf-*-50` de l'autre ;
   `--wfc-ink-signal`/`--wfc-ink-rating` d'un côté, `--wf-signal-700`/
   `--wf-rating-700` de l'autre) avec des valeurs différentes pour des rôles
   identiques. Les noms `--wfc-*` sont supprimés.

   Posés sur `.wf-client body` et NON sur `:root` : `:root` est <html>, un
   ancêtre de <body>, donc hors de portée d'un sélecteur préfixé. Les composants
   héritent par cascade depuis <body>. Les deux conteneurs de portail du
   document (#root, #root-modals) sont bien dans le <body> — vérifié.
   ========================================================================== */

.wf-client body {
    /* ---- BLEU DE MARQUE ---- */
    --wf-brand-50:  #EEF3FF;
    --wf-brand-100: #DBE5FF;
    --wf-brand-200: #B9CCFF;
    --wf-brand-300: #8DAAFA;
    --wf-brand-400: #4F7BE8;
    --wf-brand-500: #2453D4;
    --wf-brand-600: #1C43B4;
    --wf-brand-700: #17368F;
    --wf-brand-800: #132B72;
    --wf-brand-900: #0F2050;

    /* ---- CORAIL — accents ponctuels uniquement, JAMAIS une erreur ----
       Réserve mesurée : coral-500 et error-500 ne sont séparés que par
       1,1328:1 en luminance, et coral-50 / error-50 par 1,0498:1. La règle
       « le corail n'est jamais une erreur » est donc tenue à la lettre dans ce
       fichier (aucun rôle d'erreur n'emploie le corail) mais elle ne peut PAS
       être tenue au rendu : pour un œil qui ne discrimine pas les rouges, un
       badge corail et un badge d'erreur sont voisins. C'est pourquoi §10 ne
       fournit plus de variante `badge-light-accent` : le corail n'existe qu'en
       aplat plein, et son libellé doit dire ce qu'il est. */
    --wf-coral-400: #E5487F;
    --wf-coral-500: #D6215C;
    --wf-coral-600: #B81A4E;
    --wf-coral-50:  #FDF0F5;

    /* ---- NEUTRES — bleutés, jamais #000 ni #FFF sur du texte ---- */
    --wf-ink-950: #0A0D14;
    --wf-ink-900: #151A24;
    --wf-ink-800: #232A38;
    --wf-ink-700: #3D4757;
    --wf-ink-600: #5A6373;
    --wf-ink-500: #7A8494;
    --wf-ink-400: #9AA3B2;
    --wf-ink-300: #C3CAD6;
    --wf-ink-200: #E7EAF2;
    --wf-ink-100: #EEF0F5;
    --wf-ink-50:  #FAFBFD;

    /* ---- SIGNAUX ----
       Deux barreaux par signal, et le rôle décide lequel :
         -500 : pastille, icône, tracé, aplat        (plancher 3:1)
         -700 : TEXTE et bordure porteuse de sens    (plancher 4,5:1 / 3:1)
       Ratios recalculés (formule WCAG 2.x) :
         signal-500 3,3854:1 sur blanc · signal-700 5,3446:1 sur blanc
         rating-500 3,3772:1 sur blanc · rating-700 6,5733:1 sur blanc
         error-500  5,6220:1 sur blanc
       Les teintes -50 sont les fonds d'étiquette. Sur chacune, l'encre -700 du
       même rôle tient le plancher du texte courant :
         signal-700 / signal-50 = 4,8370:1
         rating-700 / rating-50 = 6,0558:1
         error-500  / error-50  = 4,8347:1
         brand-700  / brand-50  = 9,6391:1
         coral-600  / coral-50  = 5,7524:1
         ink-700    / ink-100   = 8,2321:1 */
    --wf-signal-500: #0E9F6E;
    --wf-signal-700: #0B7A55;
    --wf-signal-50:  #E8F7F1;
    --wf-rating-500: #B5830F;
    --wf-rating-700: #7A5709;
    --wf-rating-50:  #FDF5E3;
    --wf-error-500:  #C62828;
    --wf-error-50:   #FBEAEA;

    /* ---- RÔLES SÉMANTIQUES ----
       L'indirection interdit d'écrire un barreau d'échelle sur du texte
       courant par étourderie : le rôle porte le plancher, le barreau non. */
    --wf-text-strong:  var(--wf-ink-950);  /* chiffre, titre        19,4341:1 */
    --wf-text-default: var(--wf-ink-700);  /* texte courant          9,3861:1 */
    --wf-text-soft:    var(--wf-ink-600);  /* libellé, légende       6,0571:1 */
    /* --wf-text-faint : 3,7799:1 sur blanc. NON TEXTUEL UNIQUEMENT — glyphe
       décoratif, séparateur, état désactivé (exempté par 1.4.3). Interdit sur
       un placeholder : un placeholder de champ actif est du texte de corps, et
       WCAG 1.4.3 n'exempte que le texte incidental (composant inactif,
       décoration, texte invisible, texte dans une image). La version 1 de ce
       livrable justifiait ink-500 sur les placeholders par quatre arguments
       différents et mutuellement incompatibles ; aucun ne correspondait à une
       disposition de WCAG 2.x. Les placeholders sont en ink-600 (§12). */
    --wf-text-faint:   var(--wf-ink-500);
    --wf-text-invert:  #FFFFFF;
    --wf-text-link:    var(--wf-brand-600); /* lien sur blanc        8,3622:1 */

    --wf-surface-page:   var(--wf-ink-100); /* fond d'application            */
    --wf-surface-card:   #FFFFFF;
    --wf-surface-sunken: var(--wf-ink-100);
    --wf-surface-accent: var(--wf-brand-50);
    --wf-surface-dark:   var(--wf-ink-950); /* infobulle, pied               */

    /* Filet décoratif : ne porte aucune information, donc hors du plancher de
       3:1 (WCAG 1.4.11 ne vise pas les décorations). ink-200 = 1,2035:1 sur
       blanc, et c'est assumé. */
    --wf-border-hairline: var(--wf-ink-200);
    /* Bord de contrôle de formulaire : porte l'information « ceci est un champ
       saisissable », donc soumis au 3:1. ink-200 (1,2035:1), ink-300 (1,6484:1)
       et ink-400 (2,5437:1) échouent tous. ink-500 est le premier barreau
       conforme : 3,7799:1 sur blanc, 3,6507:1 sur ink-50, 3,3152:1 sur
       ink-100 — les trois surfaces de champ du système. */
    --wf-border-control: var(--wf-ink-500);
    --wf-border-strong:  var(--wf-ink-400);

    /* ---- POLICES ---- voir §5 pour les @font-face et le dépôt des fichiers */
    --wf-font-display: "Inter Tight", "Inter Tight Fallback", "Helvetica Neue", Helvetica, system-ui, sans-serif;
    --wf-font-sans:    "Geist", "Geist Fallback", "Helvetica Neue", Helvetica, system-ui, sans-serif;
    --wf-font-mono:    "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

    /* ---- ÉCHELLE TYPOGRAPHIQUE — 6 PALIERS, EN PIXELS ENTIERS ----
       Remplace les 9 tailles relevées en production (13 ×262, 12 ×17,
       19,5 ×16, 13,975 ×13, 14,3 ×10, 12,025 ×8, 14 ×3, 17,55 ×3, 16,575 ×2,
       11,05 ×1), toutes produites par `html{font-size:13px!important}`
       multiplié par des coefficients rem arbitraires.
       Déclarés en PIXELS : le socle étant déjà figé en px par le build, la
       préférence de taille du navigateur est déjà neutralisée. La restaurer
       exige de retirer ce `!important` du bundle — hors de portée d'ici. */
    --wf-text-metric: 28px;  /* chiffre d'indicateur, titre de page          */
    --wf-text-title:  20px;  /* en-tête de carte, titre de section          */
    --wf-text-body:   14px;  /* texte courant, champ, bouton                */
    --wf-text-dense:  13px;  /* CELLULE DE TABLE — voir la note de §16      */
    --wf-text-meta:   12px;  /* libellé, en-tête de colonne, légende        */
    --wf-text-micro:  11px;  /* badge, horodatage, mention légale           */

    --wf-leading-tight: 1.15;
    --wf-leading-snug:  1.35;
    --wf-leading-base:  1.5;
    --wf-leading-dense: 1.45; /* cellule de table                          */

    --wf-weight-regular:  400;
    --wf-weight-medium:   500;
    --wf-weight-semibold: 600;

    --wf-tracking-display: -0.032em;
    --wf-tracking-title:   -0.018em;
    --wf-tracking-normal:   0em;
    /* --wf-tracking-meta vaut 0 et non +0,02em. Motif mesuré ailleurs :
       l'interlettrage positif sur un en-tête de colonne augmente le
       `min-content` de la colonne, donc la largeur de la table, donc le
       défilement latéral — le mécanisme exact des 153 px du précédent. Il
       n'est employé QUE hors table (§15, §17). */
    --wf-tracking-meta:     0em;
    --wf-tracking-label:    0.02em; /* libellés HORS table uniquement       */

    /* ---- RAYONS ----
       Rôles alignés sur DESIGN.md. La version 1 en donnait trois attributions
       incompatibles entre les trois niveaux du livrable ; celle-ci est la
       seule. Un rayon interne s'écrit toujours `calc(<parent> - 1px)`, jamais
       en dur — le bundle écrit `calc(.475rem - 1px)` en dur 11 fois, §7 les
       reprend. */
    --wf-radius-xs:   4px;  /* case à cocher, puce, jeton de couleur        */
    --wf-radius-sm:   6px;  /* badge, étiquette, .btn-sm                    */
    --wf-radius-md:   8px;  /* bouton, champ, sélecteur, élément de menu    */
    --wf-radius-lg:  12px;  /* liste déroulante, popover, infobulle, alerte */
    --wf-radius-xl:  16px;  /* carte, panneau, carte d'indicateur           */
    --wf-radius-2xl: 20px;  /* modale, tiroir                              */
    --wf-radius-full: 9999px; /* pastille d'état, avatar, embout de piste   */

    /* ---- ESPACEMENT — socle de 4 px ---- */
    --wf-space-2xs:  4px;
    --wf-space-xs:   8px;
    --wf-space-sm:  12px;
    --wf-space-md:  16px;
    --wf-space-lg:  24px;
    --wf-space-xl:  32px;
    --wf-space-2xl: 48px;

    /* Mesure de lecture. 66ch ≈ 70 à 75 caractères de texte courant. */
    --wf-measure-prose: 66ch;

    /* Cible de touche : UNE valeur, UN déclencheur — 40 px sous 992 px.
       WCAG 2.5.8 (AA) exige 24 px et est largement dépassé ; 2.5.5 (AAA,
       44 px) n'est PAS revendiqué, il exigerait d'agrandir des boîtes. */
    --wf-touch-target: 40px;

    /* ---- OMBRES — courtes et lisibles, jamais de halo diffus ---- */
    --wf-shadow-xs:    0 1px 2px rgb(16 24 40 / 0.05);
    --wf-shadow-sm:    0 2px 6px -1px rgb(16 24 40 / 0.07);
    --wf-shadow-md:    0 8px 20px -6px rgb(16 24 40 / 0.10);
    --wf-shadow-lg:    0 24px 60px -20px rgb(16 24 40 / 0.18);
    --wf-shadow-brand: 0 6px 16px -4px rgb(36 83 212 / 0.45);
    --wf-ring-focus:   0 0 0 3px rgb(36 83 212 / 0.12);

    /* ---- MOTION ---- */
    --wf-ease-out-soft:    cubic-bezier(0.16, 1, 0.3, 1);
    --wf-ease-out-quart:   cubic-bezier(0.25, 1, 0.5, 1);
    --wf-ease-in-out-soft: cubic-bezier(0.65, 0, 0.35, 1);
    --wf-ease-spring:      cubic-bezier(0.22, 1.2, 0.36, 1);
    --wf-duration-fast: 140ms;
    --wf-duration-base: 200ms;
    --wf-duration-slow: 260ms;

    /* ---- SÉRIES DE GRAPHIQUE ----
       AVERTISSEMENT MESURÉ : le contraste entre séries vaut 2,84:1 (1↔2),
       1,70:1 (1↔3), 1,67:1 (2↔3), 1,12:1 (3↔4). Aucune paire n'atteint 3:1, et
       c'est arithmétiquement inévitable — trois séries mutuellement
       distinguables à 3:1 sur fond clair imposeraient une luminance négative
       pour la troisième. Chaque série doit donc porter un second signal non
       chromatique (étiquette sur le tracé, motif de tiret, forme de marqueur),
       et ce signal ne peut PAS venir du CSS. Sans lui, WCAG 1.4.1 échoue. */
    --wf-series-1: var(--wf-brand-500);
    --wf-series-2: var(--wf-brand-300);
    --wf-series-3: var(--wf-ink-500);
    --wf-series-4: var(--wf-signal-500);

    /* Verrouille les widgets rendus par le navigateur (barres de défilement,
       sélecteurs de date natifs, remplissage automatique) sur le thème clair.
       Le mode sombre est explicitement hors périmètre : le <body> porte encore
       une classe `dark-mode` héritée de la démonstration Metronic, sans aucune
       règle associée, et le bundle conserve 11 règles `.rs-theme-dark`
       inactives. */
    color-scheme: light;
}


/* ==========================================================================
   2. VARIABLES LUES PAR LE JAVASCRIPT — le seul bloc ancré sur <html>

   Le bundle lit des variables CSS à l'exécution pour colorer ses graphiques :
     function d(e){ let t = getComputedStyle(document.documentElement)
                             .getPropertyValue(e); … }
   41 lectures relevées, sur 20 variables `--bs-*` distinctes.

   a) `--bs-primary` est bien CONSOMMÉ — pas par le CSS (0 occurrence de
      `var(--bs-primary)`) mais par le JS. C'est le seul endroit du chantier où
      redéfinir un jeton Bootstrap change quelque chose à l'écran.
   b) La lecture porte sur `document.documentElement`, donc sur <html>. Une
      variable posée sur `.wf-client body` est INVISIBLE pour ce code : la
      cascade descend, elle ne remonte pas. D'où `html:has(.wf-client body)`,
      qui conserve le contrat de bascule. Spécificité : html (0,0,1) +
      :has(.wf-client body) (0,1,1) = (0,1,2), contre (0,1,0) pour `:root`.
   c) Les valeurs DOIVENT être en hexadécimal à 6 chiffres. Le code concatène
      (`background: r + "22"`) : avec `rgb(36 83 212)` la concaténation
      produirait `rgb(36 83 212)22`, invalide, et le disque central de la jauge
      disparaîtrait. Ni rgb(), ni hsl(), ni color-mix(), ni var().
   d) La lecture a lieu à la construction du graphique : basculer la classe sur
      une page déjà affichée ne recolore pas les graphiques. Sans effet en
      production, où la classe est posée dans le gabarit.
   e) Portée de `:has()` : Chrome 105+, Safari 15.4+, Firefox 121+. Si le
      sélecteur n'est pas reconnu, le bloc est ignoré et les graphiques gardent
      leurs couleurs actuelles — dégradation silencieuse, sans casse.
   ========================================================================== */

html:has(.wf-client body) {
    /* Jauges PageSpeed. Le composant compose le nom : "--bs-" + ("green" |
       "orange" | "red"). Valeurs actuelles : #198754 (4,53:1), #fd7e14
       (2,57:1 — ÉCHEC), #dc3545 (4,53:1). */
    --bs-green:  #0B7A55;   /* signal-700 — 5,3446:1 sur blanc */
    --bs-orange: #7A5709;   /* rating-700 — 6,5733:1 */
    --bs-red:    #C62828;   /* error-500  — 5,6220:1 */

    /* Séries et états lus par les autres graphiques. */
    --bs-primary: #2453D4;  /* brand-500 — 6,4358:1, remplace #009ef7 (2,90:1) */
    --bs-success: #0B7A55;
    --bs-warning: #7A5709;
    --bs-danger:  #C62828;
    --bs-info:    #17368F;  /* brand-700 — 10,71:1, remplace #7239ea */

    /* Décor de graphique. `var(--bs-gray…)` apparaît 0 fois dans main.css :
       ces quatre lignes n'atteignent QUE le JavaScript, aucune classe
       utilitaire n'est affectée. */
    --bs-gray-100: #C3CAD6; /* piste de jauge : ink-200 donnerait 1,00:1 contre
                               le disque central, ink-300 donne 1,36:1        */
    --bs-gray-200: #E7EAF2; /* lignes de grille, décoratif                    */
    --bs-gray-300: #C3CAD6; /* réticule                                       */
    --bs-gray-500: #5A6373; /* ÉTIQUETTES D'AXE, donc du texte : 6,0571:1     */
}


/* ==========================================================================
   3. RSUITE — LE SECOND SYSTÈME, LUI PILOTÉ PAR VARIABLES

   L'application embarque RSuite en plus de Metronic, et RSuite consomme la
   plupart de ses variables (1 234 occurrences de var(--rs-*) relevées).
   Elles sont déclarées sur `.rs-theme-light, :root`.

   RÉSERVE À NE PAS MASQUER : la liste des `--rs-*` réellement consommées n'a
   pas été produite. Le relevé donne un total (≈283 sur ≈335 déclarées) mais
   n'en nomme qu'une poignée. Les variables ci-dessous sont donc redéfinies
   sans que chacune ait été vérifiée « consommée par le sélecteur visé ». Une
   variable non consommée est inerte, pas dangereuse — SAUF les valeurs
   composites, qui n'ont d'effet que si RSuite les consomme dans un raccourci.
   Elles sont signalées une par une ci-dessous.

   Deuxième réserve : la position de `.rs-theme-light` dans l'arbre n'a pas été
   vérifiée. Si RSuite pose cette classe sur un conteneur INTÉRIEUR au <body>,
   cet élément déclare les valeurs d'origine directement sur lui-même et une
   valeur héritée depuis <body> perd, quelle que soit la spécificité. Dans ce
   cas — à vérifier en premier lors de la recette — dupliquer ce bloc sur
   `.wf-client body .rs-theme-light`.

   À noter : --rs-blue-500 vaut #2196f3, soit Material Blue 500, la couleur que
   la charte bannit nommément et que #2453D4 remplace. RSuite la réintroduisait
   dans l'espace client.
   ========================================================================== */

.wf-client body {
    /* -- échelle primaire (était #3498ff, 2,97:1 sur blanc) -- */
    --rs-primary-50:  var(--wf-brand-50);
    --rs-primary-100: var(--wf-brand-100);
    --rs-primary-200: var(--wf-brand-200);
    --rs-primary-300: var(--wf-brand-300);
    --rs-primary-400: var(--wf-brand-400);
    --rs-primary-500: var(--wf-brand-500);
    --rs-primary-600: var(--wf-brand-600);
    --rs-primary-700: var(--wf-brand-700);
    --rs-primary-800: var(--wf-brand-800);
    --rs-primary-900: var(--wf-brand-900);

    /* -- échelle bleue : c'est elle qui portait le bleu Material -- */
    --rs-blue-50:  var(--wf-brand-50);
    --rs-blue-100: var(--wf-brand-100);
    --rs-blue-200: var(--wf-brand-200);
    --rs-blue-300: var(--wf-brand-300);
    --rs-blue-400: var(--wf-brand-400);
    --rs-blue-500: var(--wf-brand-500);
    --rs-blue-600: var(--wf-brand-600);
    --rs-blue-700: var(--wf-brand-700);
    --rs-blue-800: var(--wf-brand-800);
    --rs-blue-900: var(--wf-brand-900);

    /* -- vert / rouge / orange / jaune -- */
    --rs-green-50:  var(--wf-signal-50);
    --rs-green-400: var(--wf-signal-500);
    --rs-green-500: var(--wf-signal-500);
    --rs-green-600: var(--wf-signal-700);
    --rs-green-700: var(--wf-signal-700);
    --rs-green-800: var(--wf-signal-700);
    --rs-green-900: var(--wf-signal-700);

    --rs-red-50:  var(--wf-error-50);
    --rs-red-400: var(--wf-error-500);
    --rs-red-500: var(--wf-error-500);
    --rs-red-600: var(--wf-error-500);
    --rs-red-700: var(--wf-error-500);

    --rs-orange-50:  var(--wf-rating-50);
    --rs-orange-400: var(--wf-rating-500);
    --rs-orange-500: var(--wf-rating-500);
    --rs-orange-600: var(--wf-rating-700);
    --rs-orange-700: var(--wf-rating-700);

    --rs-yellow-50:  var(--wf-rating-50);
    --rs-yellow-500: var(--wf-rating-500);
    --rs-yellow-600: var(--wf-rating-700);
    --rs-yellow-700: var(--wf-rating-700);

    /* -- échelle de gris : élimine les gris chauds (#a6a6a6, #7a7a7a) qui
       jurent à côté de neutres bleutés dans la même carte -- */
    --rs-gray-50:  var(--wf-ink-50);
    --rs-gray-100: var(--wf-ink-100);
    --rs-gray-200: var(--wf-ink-200);
    --rs-gray-300: var(--wf-ink-300);
    --rs-gray-400: var(--wf-ink-400);
    --rs-gray-500: var(--wf-ink-500);
    --rs-gray-600: var(--wf-ink-600);
    --rs-gray-700: var(--wf-ink-700);
    --rs-gray-800: var(--wf-ink-800);
    --rs-gray-900: var(--wf-ink-950);

    /* -- texte -- */
    --rs-text-heading:     var(--wf-text-strong);  /* était #272c36 (14,00:1) — 19,43:1 */
    --rs-text-primary:     var(--wf-text-default); /* était #575757 ( 7,23:1) —  9,39:1 */
    --rs-text-secondary:   var(--wf-text-soft);    /* était #8e8e93 ( 3,26:1) —  6,06:1 */
    /* text-tertiary passe à ink-600 et non à ink-500 : le bundle l'emploie sur
       du texte de 12 px (légendes, unités), où 3,78:1 échoue. */
    --rs-text-tertiary:    var(--wf-text-soft);    /* était #a6a6a6 ( 2,43:1) —  6,06:1 */
    --rs-text-inverse:     #FFFFFF;
    --rs-text-link:        var(--wf-text-link);    /* était #1675e0 ( 4,51:1) —  8,36:1 */
    --rs-text-link-hover:  var(--wf-brand-700);    /* était #0a5dc2          — 10,71:1 */
    --rs-text-link-active: var(--wf-brand-800);
    --rs-text-disabled:    var(--wf-ink-400);      /* exempté par 1.4.3               */
    --rs-text-error:       var(--wf-error-500);

    /* -- surfaces -- */
    --rs-body:        #FFFFFF;
    --rs-bg-card:     #FFFFFF;
    --rs-bg-overlay:  #FFFFFF;
    --rs-bg-well:     var(--wf-ink-50);
    --rs-bg-active:   var(--wf-brand-500);
    --rs-bg-backdrop: rgb(10 13 20 / 0.45);

    /* -- bordures et séparateurs -- */
    --rs-border-primary:   var(--wf-border-control);  /* bord des champs RSuite :
                                                         1,26:1 → 3,78:1        */
    --rs-border-secondary: var(--wf-border-hairline);
    --rs-divider-border:   var(--wf-border-hairline);
    --rs-list-border:      var(--wf-border-hairline);
    --rs-navs-tab-border:  var(--wf-border-hairline);
    --rs-navs-subtle-border: var(--wf-ink-100);
    --rs-form-errormessage-border: var(--wf-border-hairline);
    /* VALEUR COMPOSITE — n'a d'effet que si RSuite la consomme en raccourci.
       Non vérifié. Si RSuite ne déclarait pas de bordure de modale, cette
       ligne en AJOUTERAIT une, donc 2 px de boîte : elle est commentée jusqu'à
       vérification, et sa version « couleur seule » est employée à la place.
       --rs-modal-border: 1px solid var(--wf-border-hairline); */

    /* -- champs de saisie -- */
    --rs-input-bg:            #FFFFFF;
    --rs-input-focus-border:  var(--wf-brand-500);
    --rs-input-disabled-bg:   var(--wf-ink-100);
    --rs-checkbox-border:     var(--wf-border-control);
    --rs-checkbox-checked-bg: var(--wf-brand-500);
    --rs-radio-border:        var(--wf-border-control);
    --rs-radio-checked-bg:    var(--wf-brand-500);

    /* -- boutons -- */
    --rs-btn-default-bg:            var(--wf-ink-100);
    --rs-btn-default-text:          var(--wf-text-default);
    --rs-btn-default-hover-bg:      var(--wf-ink-200);
    --rs-btn-default-active-bg:     var(--wf-ink-300);
    --rs-btn-default-disabled-bg:   var(--wf-ink-100);
    --rs-btn-default-disabled-text: var(--wf-ink-400);
    /* VALEUR COMPOSITE, même réserve, et le risque est ici concret : si RSuite
       ne déclarait pas de bordure sur son bouton par défaut, cette ligne
       ajouterait 2 px de largeur et 2 px de hauteur à chaque bouton RSuite.
       Retirée. La bordure des champs RSuite est déjà portée par
       --rs-border-primary ci-dessus.
       --rs-btn-default-border: 1px solid var(--wf-border-control); */
    --rs-btn-primary-bg:            var(--wf-brand-500);
    --rs-btn-primary-hover-bg:      var(--wf-brand-600);
    --rs-btn-primary-active-bg:     var(--wf-brand-700);
    --rs-btn-primary-text:          #FFFFFF;
    --rs-btn-subtle-text:           var(--wf-text-soft);
    --rs-btn-subtle-hover-bg:       var(--wf-ink-100);
    --rs-btn-subtle-hover-text:     var(--wf-text-default);
    --rs-btn-subtle-active-bg:      var(--wf-ink-200);
    --rs-btn-subtle-active-text:    var(--wf-text-strong);
    --rs-btn-subtle-disabled-text:  var(--wf-ink-400);
    --rs-btn-ghost-border:          var(--wf-brand-500);
    --rs-btn-ghost-text:            var(--wf-brand-500);
    --rs-btn-ghost-hover-border:    var(--wf-brand-600);
    --rs-btn-ghost-hover-text:      var(--wf-brand-600);
    --rs-btn-ghost-active-border:   var(--wf-brand-700);
    --rs-btn-ghost-active-text:     var(--wf-brand-700);
    --rs-btn-link-text:             var(--wf-brand-600);
    --rs-btn-link-hover-text:       var(--wf-brand-700);

    /* -- icônes de bouton -- */
    --rs-iconbtn-addon:                   var(--wf-ink-100);
    --rs-iconbtn-activated-addon:         var(--wf-ink-200);
    --rs-iconbtn-pressed-addon:           var(--wf-ink-300);
    --rs-iconbtn-primary-addon:           var(--wf-brand-600);
    --rs-iconbtn-primary-activated-addon: var(--wf-brand-700);
    --rs-iconbtn-primary-pressed-addon:   var(--wf-brand-800);

    /* -- focus -- */
    --rs-state-focus-shadow:      var(--wf-ring-focus);
    --rs-state-focus-shadow-slim: 0 0 0 2px var(--wf-surface-card);
    --rs-state-focus-outline:     3px solid rgb(36 83 212 / 0.35);

    /* -- listes déroulantes et sélecteurs -- */
    --rs-listbox-option-hover-bg:      var(--wf-brand-50);
    --rs-listbox-option-hover-text:    var(--wf-brand-700);  /* 9,64:1 sur brand-50 */
    --rs-listbox-option-selected-bg:   var(--wf-brand-50);
    --rs-listbox-option-selected-text: var(--wf-brand-700);
    --rs-listbox-option-disabled-text: var(--wf-ink-400);
    --rs-picker-value:       var(--wf-ink-950);  /* était #1675E0 : la valeur
                                 sélectionnée était peinte en couleur de lien
                                 alors qu'elle n'en est pas un. 19,43:1        */
    --rs-picker-placeholder: var(--wf-ink-600);  /* 6,06:1 — un placeholder est
                                                    du texte de corps          */

    /* -- états et messages -- */
    --rs-state-error:   var(--wf-error-500);
    --rs-state-success: var(--wf-signal-500);
    --rs-state-warning: var(--wf-rating-500);
    --rs-state-info:    var(--wf-brand-500);

    --rs-message-success-bg:     var(--wf-signal-50);
    --rs-message-success-text:   var(--wf-ink-900);
    --rs-message-success-border: var(--wf-signal-700);
    --rs-message-info-bg:        var(--wf-brand-50);
    --rs-message-info-text:      var(--wf-ink-900);
    --rs-message-info-border:    var(--wf-brand-600);
    --rs-message-warning-bg:     var(--wf-rating-50);
    --rs-message-warning-text:   var(--wf-ink-900);
    --rs-message-warning-border: var(--wf-rating-700);
    --rs-message-error-bg:       var(--wf-error-50);
    --rs-message-error-text:     var(--wf-ink-900);
    --rs-message-error-border:   var(--wf-error-500);

    /* -- calendrier / sélecteur de date -- */
    --rs-calendar-today-bg:           var(--wf-brand-500);
    --rs-calendar-today-text:         #FFFFFF;
    --rs-calendar-range-bg:           var(--wf-brand-50);
    --rs-calendar-time-unit-bg:       var(--wf-ink-50);
    --rs-calendar-date-selected-text: #FFFFFF;

    /* -- tableaux RSuite -- */
    --rs-table-border:          var(--wf-border-hairline);
    --rs-table-resizer:         var(--wf-ink-400);
    --rs-table-scrollbar-track: var(--wf-ink-100);
    /* Le curseur de défilement est MANIPULABLE : 1.4.11 s'y applique.
       ink-400 sur ink-100 ne donnait que 2,2309:1. ink-500 donne 3,3152:1. */
    --rs-table-scrollbar-thumb: var(--wf-ink-500);

    /* -- divers -- */
    --rs-tooltip-bg:      var(--wf-surface-dark);
    --rs-tooltip-text:    #FFFFFF;
    /* Piste de progression : décorative, le remplissage porte l'information
       (brand-500 sur ink-100 = 5,6445:1). ink-200 = 1,2035:1, assumé. */
    --rs-progress-bg:     var(--wf-ink-200);
    --rs-progress-bar:    var(--wf-brand-500);
    --rs-slider-bar:      var(--wf-ink-200);
    --rs-slider-thumb-border: var(--wf-brand-500);
    --rs-slider-progress: var(--wf-brand-500);
    /* Rail d'interrupteur : c'est la limite du composant ET le support de son
       état. ink-300 ne donnait que 1,6484:1 sur blanc — sous le plancher de
       1.4.11 — et le bouton du switch étant blanc, ni la limite ni l'état
       n'étaient identifiables sur une carte blanche. ink-500 : 3,7799:1 contre
       la carte, et 3,7799:1 entre le bouton blanc et le rail. */
    --rs-toggle-bg:            var(--wf-ink-500);
    --rs-toggle-checked-bg:    var(--wf-brand-500);
    --rs-toggle-thumb:         #FFFFFF;
    --rs-badge-bg:             var(--wf-error-500);
    --rs-loader-ring:          var(--wf-ink-200);
    --rs-loader-rotor:         var(--wf-brand-500);
    --rs-pagination-item-text: var(--wf-ink-700);
    --rs-pagination-item-current-text: var(--wf-brand-600);
    --rs-steps-border:         var(--wf-ink-400);
    --rs-steps-state-finish:   var(--wf-signal-500);
    --rs-steps-state-process:  var(--wf-brand-500);
    --rs-steps-state-error:    var(--wf-error-500);
    --rs-steps-state-wait:     var(--wf-ink-500);
}


/* ==========================================================================
   4. LES VARIABLES BOOTSTRAP RÉELLEMENT CONSOMMÉES PAR LE CSS

   Sur les ~65 variables --bs-* déclarées par le bundle, une poignée seulement
   est consommée via var(), et `--bs-primary` l'est ZÉRO fois PAR LE CSS. Les
   couleurs --bs-* de :root sont décoratives pour la cascade : les redéfinir ne
   change rien à l'écran. C'est le même piège que Bootstrap 4, mieux déguisé.
   (Elles sont en revanche lues par le JavaScript — voir §2.)

   Ne sont donc redéfinies ici que celles qui ont un effet mesurable sur le CSS.

   NON REDÉFINIES, VOLONTAIREMENT : --bs-gutter-x et --bs-gutter-y.
   `.row` les redéfinit localement (`.row{--bs-gutter-x:1.5rem}`), donc une
   valeur posée ici n'atteindrait pas ses descendants : les rangées garderaient
   19,5 px. Mais `.container-*` les consomme comme padding horizontal SOUS
   992 px (`calc(var(--bs-gutter-x) * .5)`, 9,75 → 12 px) et
   `.card-xl-stretch{height:calc(100% - var(--bs-gutter-y))}` les consomme hors
   `.row`. Les forcer produirait donc un demi-effet incohérent et un décalage
   de mise en page sur tous les écrans. La version 1 du fichier de jetons les
   redéfinissait à 24 px en les qualifiant de « seule dimension de mise en page
   atteignable par jeton » : c'est vrai et c'est précisément pourquoi il ne faut
   pas y toucher.
   ========================================================================== */

.wf-client body {
    /* Meilleur rapport effort/effet de tout le chantier : sur 219 déclarations
       font-family du bundle, 4 seulement contiennent Poppins en dur et 5
       passent par var(--bs-font-*). Metronic ne redéclare presque jamais la
       police : tout le reste hérite. Le bundle utilise le motif de repli
       PostCSS (`font-family:Poppins…; font-family:var(--bs-font-sans-serif)`),
       où la seconde déclaration gagne. */
    --bs-font-sans-serif: var(--wf-font-sans);
    --bs-font-monospace:  var(--wf-font-mono);

    /* Le dégradé Bootstrap est neutralisé : le système n'en emploie aucun. */
    --bs-gradient: none;
}

/* Les 8 variables de table sont consommées par `.table` via un inset
   box-shadow. ATTENTION : `.table` les REDÉCLARE localement
   (`.table{--bs-table-bg:#0000; … --bs-table-hover-bg:#f5f8fa}`), et une
   déclaration locale gagne sur un ancêtre. Elles doivent donc être posées sur
   `.table` lui-même, pas sur <body> — c'était un défaut de la version 1, où
   les huit lignes étaient inertes.
   `--bs-table-bg` reste transparent : c'est ce qui laisse voir le fond de la
   rangée (zébrage et survol de §13). */
.wf-client body .table {
    --bs-table-bg:            transparent;
    --bs-table-accent-bg:     transparent;
    --bs-table-striped-bg:    var(--wf-ink-50);
    --bs-table-striped-color: var(--wf-text-default);
    --bs-table-hover-bg:      var(--wf-brand-50);
    --bs-table-hover-color:   var(--wf-text-strong);
    --bs-table-active-bg:     var(--wf-brand-50);
    --bs-table-active-color:  var(--wf-text-strong);
}


/* ==========================================================================
   5. POLICES — fichiers, @font-face, et ce qui se passe s'ils manquent

   ÉTAT MESURÉ : le document de production charge Poppins depuis
   fonts.googleapis.com. Les 9 @font-face de main.css sont TOUTES des polices
   d'icônes. Ni Geist, ni Inter Tight, ni Geist Mono n'est hébergée.

   CONSÉQUENCE SI RIEN N'EST DÉPLOYÉ : --wf-font-sans retombe sur
   "Helvetica Neue" (Arial sur Windows). L'argument central de DESIGN.md — la
   hauteur d'x élevée de Geist, donc plus de lisibilité à corps égal — ne se
   produit pas, tandis que les `letter-spacing` négatifs, calibrés pour une
   grotesque serrée, sont bel et bien appliqués à une police qui n'en a pas
   besoin. L'état livré par défaut n'est alors NI l'état actuel NI la cible.
   Les polices et cette feuille doivent être déployées dans la même livraison.

   FICHIERS À DÉPOSER dans `public/fonts/` (format woff2 uniquement, tous les
   navigateurs cibles le prennent en charge depuis 2016) :
     geist-400.woff2  geist-500.woff2  geist-600.woff2
     inter-tight-600.woff2
     geist-mono-400.woff2  geist-mono-500.woff2
   Six fichiers. Trois graisses pour le texte (400 / 500 / 600), une seule pour
   les titres (600 : le système n'emploie pas 700), deux pour la mono.

   `font-display: swap` et non `optional` : `optional` abandonne la police si
   elle n'arrive pas dans les 100 ms du premier rendu, ce qui rendrait la
   typographie non déterministe d'un chargement à l'autre. `swap` garantit que
   la cible finit par s'appliquer, au prix d'un reflow au premier chargement.

   REPLI MÉTRIQUEMENT AJUSTÉ : les deux @font-face de repli ci-dessous
   compensent l'écart de métriques entre Geist et Arial/Helvetica pour que le
   reflow de `swap` soit imperceptible. LES VALEURS DE `size-adjust`,
   `ascent-override` ET `descent-override` NE SONT PAS MESURÉES : elles se
   calculent à partir des tables `hhea`/`OS/2` des fichiers réels, ce qui exige
   les fichiers. Les valeurs ci-dessous sont des PLACEHOLDERS neutres
   (size-adjust: 100%) : elles ne dégradent rien mais n'apportent rien tant
   qu'elles ne sont pas calculées. Procédure : ouvrir le woff2 avec
   `fontTools`, lire unitsPerEm, ascender, descender, et l'avance moyenne de la
   bas-de-casse ; le rapport des avances moyennes donne `size-adjust`.
   ========================================================================== */

@font-face {
    font-family: "Geist";
    src: url("fonts/geist-400.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Geist";
    src: url("fonts/geist-500.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Geist";
    src: url("fonts/geist-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Inter Tight";
    src: url("fonts/inter-tight-600.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Geist Mono";
    src: url("fonts/geist-mono-400.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: "Geist Mono";
    src: url("fonts/geist-mono-500.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}

/* Replis métriquement ajustés — valeurs À CALCULER, voir ci-dessus. */
@font-face {
    font-family: "Geist Fallback";
    src: local("Helvetica Neue"), local("Arial");
    size-adjust: 100%;
    font-display: swap;
}
@font-face {
    font-family: "Inter Tight Fallback";
    src: local("Helvetica Neue"), local("Arial");
    size-adjust: 100%;
    font-display: swap;
}

/* DÉCISION SUR LE <link> POPPINS : le conserver pour l'instant. Le bundle
   nomme encore Poppins en dur dans 4 déclarations (plus 5 via var()) ; retirer
   le <link> ferait basculer ces 4 endroits sur Helvetica, soit une TROISIÈME
   police à l'écran. Le retirer est une étape distincte et réversible, à faire
   après avoir identifié ces 4 sélecteurs sur l'application réelle — ce que
   nous n'avons pas pu faire sans compte client. */


/* ==========================================================================
   6. TYPOGRAPHIE

   Poppins (géométrique, hauteur d'x basse, dessinée pour l'affichage) cède la
   place à Geist pour l'interface et Inter Tight pour les titres.

   Le corps de base n'est pas modifié. Les paliers sont posés là où ils sont
   utiles (§15 à §18), jamais globalement : `html{font-size:13px!important}`
   reste le socle et une modification globale reflowerait les tables denses.

   RÉSERVE MESURÉE SUR LES COMPOSANTS QUI MESURENT EN JS : ApexCharts calcule
   la largeur de ses étiquettes par getBBox() au moment du rendu, et rs-table
   fixe la largeur de ses colonnes en JavaScript avec overflow:hidden. Une
   police substituée après le premier rendu, ou plus large que Poppins, produit
   du chevauchement d'étiquettes et de la troncature de cellule, sans erreur
   JavaScript — donc invisible en recette automatisée. C'est le premier point à
   regarder à l'œil sur /statistiques et sur les écrans à rs-table.
   ========================================================================== */

.wf-client body,
.wf-client body .tooltip,
.wf-client body .popover,
.wf-client body .modal,
.wf-client body .offcanvas,
.wf-client body .dropdown-menu,
.wf-client body .menu,
.wf-client body .rs-picker-popup,
.wf-client body .rs-modal,
.wf-client body .rs-tooltip,
.wf-client body .rs-popover,
.wf-client body .rs-calendar,
.wf-client body .rs-table {
    font-family: var(--wf-font-sans);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Titres — Inter Tight, resserrée optiquement. Le letter-spacing négatif est
   indispensable : sans lui, une grotesque serrée paraît relâchée en gros corps.
   Aucune modification de font-size ici : seules la famille et le tracking. */
.wf-client body h1,
.wf-client body h2,
.wf-client body h3,
.wf-client body .h1,
.wf-client body .h2,
.wf-client body .h3,
.wf-client body .card-title,
.wf-client body .page-title,
.wf-client body .modal-title {
    font-family: var(--wf-font-display);
    letter-spacing: var(--wf-tracking-title);
    color: var(--wf-text-strong);
}

.wf-client body h4,
.wf-client body h5,
.wf-client body h6,
.wf-client body .h4,
.wf-client body .h5,
.wf-client body .h6 {
    font-family: var(--wf-font-display);
    letter-spacing: -0.008em;
    color: var(--wf-ink-900);
}

/* Métadonnées chiffrées : identifiants, montants, dates. Les chiffres
   tabulaires alignent les colonnes de nombres.
   RÉSERVE : `tabular-nums` donne à tous les chiffres la largeur du plus large.
   Sur une colonne de nombres c'est le but ; sur une cellule mêlant texte et
   nombres, cela peut ÉLARGIR le contenu de quelques pixels. L'effet cumulé sur
   une table de 8 colonnes n'a PAS été mesuré (voir la note de fin de §16). */
.wf-client body .text-monospace,
.wf-client body .font-monospace,
.wf-client body code,
.wf-client body kbd,
.wf-client body pre,
.wf-client body samp {
    font-family: var(--wf-font-mono);
    font-variant-numeric: tabular-nums lining-nums;
}

.wf-client body .table td,
.wf-client body .table th,
.wf-client body .rs-table-cell-content {
    font-variant-numeric: tabular-nums lining-nums;
}

/* En-têtes de table et libellés : la graisse et l'encre portent la hiérarchie,
   pas la couleur. AUCUN interlettrage positif sur un en-tête de colonne : il
   augmenterait le min-content de la colonne, donc la largeur de la table. */
.wf-client body .table thead th,
.wf-client body .form-label,
.wf-client body .menu-heading {
    letter-spacing: var(--wf-tracking-meta);
    color: var(--wf-text-soft);
}

/* Hors table, l'interlettrage d'étiquette est sans conséquence de largeur. */
.wf-client body .form-label,
.wf-client body .menu-heading {
    letter-spacing: var(--wf-tracking-label);
}


/* ==========================================================================
   7. SURFACES, RAYONS, OMBRES

   Metronic borde ses cartes en #eff2f5 (1,1236:1 sur blanc — recalculé ; la
   version 1 annonçait 1,17) et les arrondit à .475rem. Passer à ink-100 donne
   1,1402:1 : ce n'est PAS une amélioration de contraste et ce n'était pas
   présenté correctement en version 1. C'est une mise en cohérence de teinte
   (un neutre bleuté au lieu d'un gris légèrement chaud), rien de plus. La
   délimitation réelle de la carte est portée par l'écart avec le fond de page
   (blanc sur ink-100 = 1,1402:1) et par l'ombre.

   Aucun changement de border-width nulle part : les filets ajoutés le sont par
   box-shadow.
   ========================================================================== */

.wf-client body {
    color: var(--wf-text-default);
    background-color: var(--wf-surface-page);
}

.wf-client body .card {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-ink-100);
    border-radius: var(--wf-radius-xl);
    box-shadow: var(--wf-shadow-xs);
}

/* Rayons INTERNES : toujours dérivés du parent. Le bundle écrit
   `calc(.475rem - 1px)` en dur 11 fois ; sans ces reprises, le coin du fond
   d'un en-tête ou d'un pied coloré (≈5,6 px) dépasserait du coin arrondi du
   parent (16 px) sur chaque carte à en-tête coloré, sur .modal-header, et sur
   les .dropdown-item de tête et de queue dont le fond de survol carrerait le
   coin. */
.wf-client body .card > .card-header:first-child {
    border-top-left-radius:  calc(var(--wf-radius-xl) - 1px);
    border-top-right-radius: calc(var(--wf-radius-xl) - 1px);
}
.wf-client body .card > .card-footer:last-child {
    border-bottom-left-radius:  calc(var(--wf-radius-xl) - 1px);
    border-bottom-right-radius: calc(var(--wf-radius-xl) - 1px);
}

/* Metronic pose une ombre diffuse sur .card-flush / .shadow-sm ; on la remplace
   par une ombre courte. Les halos larges lisent comme un rendu générique. */
.wf-client body .card.shadow-sm,
.wf-client body .card.card-flush {
    box-shadow: var(--wf-shadow-sm);
}

.wf-client body .card-header {
    border-bottom-color: var(--wf-ink-100);
}

.wf-client body .card-footer {
    border-top-color: var(--wf-ink-100);
}

.wf-client body .header,
.wf-client body .toolbar {
    background-color: var(--wf-surface-card);
    border-bottom: 1px solid var(--wf-ink-100);
    box-shadow: none;
}

/* Barre latérale — THÈME CLAIR. Vérifié dans la configuration de layout du
   bundle : aside:{theme:"light"}. Les 64 règles .aside-dark du bundle sont donc
   du code mort dans cette configuration ; elles ne sont pas surchargées ici, et
   redeviendraient actives si la configuration changeait — ce qui serait une
   modification React, hors périmètre. */
.wf-client body .aside {
    background-color: var(--wf-surface-card);
    border-right: 1px solid var(--wf-ink-100);
    box-shadow: none;
}

.wf-client body .separator {
    border-bottom-color: var(--wf-border-hairline);
}

/* Rayons — rôles alignés sur DESIGN.md. Aucun de ces sélecteurs ne touche aux
   dimensions. */
.wf-client body .btn,
.wf-client body .form-control,
.wf-client body .form-select,
.wf-client body .input-group-text,
.wf-client body .rs-btn,
.wf-client body .rs-input,
.wf-client body .rs-picker-toggle,
.wf-client body .rs-picker-input {
    border-radius: var(--wf-radius-md);
}

.wf-client body .btn.btn-sm,
.wf-client body .badge {
    border-radius: var(--wf-radius-sm);
}

.wf-client body .form-check-input {
    border-radius: var(--wf-radius-xs);
}

.wf-client body .dropdown-menu,
.wf-client body .menu-sub-dropdown,
.wf-client body .alert,
.wf-client body .rs-picker-popup {
    border-radius: var(--wf-radius-lg);
    box-shadow: var(--wf-shadow-lg);
}

.wf-client body .modal-content,
.wf-client body .rs-modal-content {
    border-radius: var(--wf-radius-2xl);
    box-shadow: var(--wf-shadow-lg);
}

.wf-client body .modal-content > .modal-header {
    border-top-left-radius:  calc(var(--wf-radius-2xl) - 1px);
    border-top-right-radius: calc(var(--wf-radius-2xl) - 1px);
}

.wf-client body .dropdown-menu > .dropdown-item:first-child {
    border-top-left-radius:  calc(var(--wf-radius-lg) - 1px);
    border-top-right-radius: calc(var(--wf-radius-lg) - 1px);
}
.wf-client body .dropdown-menu > .dropdown-item:last-child {
    border-bottom-left-radius:  calc(var(--wf-radius-lg) - 1px);
    border-bottom-right-radius: calc(var(--wf-radius-lg) - 1px);
}

.wf-client body .symbol > img,
.wf-client body .symbol-label {
    border-radius: var(--wf-radius-md);
}

.wf-client body .symbol.symbol-circle > img,
.wf-client body .symbol.symbol-circle .symbol-label {
    border-radius: 50%;
}


/* ==========================================================================
   8. REMAP COULEUR — TEXTE

   Le bundle compile ses couleurs en dur : ces surcharges sont indispensables,
   aucune redéfinition de variable ne les remplace.

   Gains recalculés (formule WCAG 2.x, sur blanc) :
     .text-muted   #a1a5b7  2,4472:1  ->  ink-600   6,0571:1
     .text-primary #009ef7  2,9038:1  ->  brand-500 6,4358:1
     .text-success #50cd89  2,0132:1  ->  signal-700 5,3446:1
     .text-warning #ffc700  1,5599:1  ->  rating-700 6,5733:1
     .text-danger  #f1416c  3,6768:1  ->  error-500  5,6220:1
   ========================================================================== */

.wf-client body a {
    color: var(--wf-text-link);   /* brand-600 : 8,3622:1 */
}

.wf-client body a:hover,
.wf-client body a:focus {
    color: var(--wf-brand-700);   /* 10,71:1 */
}

/* .text-muted et .text-gray-* portent !important dans le bundle. */
.wf-client body .text-muted {
    color: var(--wf-text-soft) !important;
}

/* La correspondance suit la table de §8 : gray-100 → ink-50, gray-200 →
   ink-100. La version 1 envoyait les deux sur ink-100, ce qui supprimait une
   distinction que le bundle exprimait et contredisait sa propre table. */
.wf-client body .text-gray-100 { color: var(--wf-ink-50)  !important; }
.wf-client body .text-gray-200 { color: var(--wf-ink-100) !important; }
.wf-client body .text-gray-300 { color: var(--wf-ink-200) !important; }
/* gray-400 → ink-500 : 3,7799:1. SOUS 4,5:1. Dans Metronic ces deux classes
   visent des fonds sombres ; sur blanc elles échouent, avant comme après
   (#a1a5b7 = 2,4472:1). L'amélioration est réelle et la non-conformité
   subsiste : c'est un report, pas une correction. */
.wf-client body .text-gray-400 { color: var(--wf-ink-500) !important; }
/* gray-500 → ink-600 et non ink-500 : le bundle emploie .text-gray-500 sur du
   texte de 12 px (en-têtes de colonne, boutons de tri), où 3,7799:1 échoue.
   Conséquence assumée : .text-gray-500 et .text-gray-600 deviennent
   identiques. C'est la seule façon de rendre cette classe conforme sans
   énumérer ses contextes, ce qui exigerait un relevé du DOM. */
.wf-client body .text-gray-500 { color: var(--wf-ink-600) !important; }
.wf-client body .text-gray-600 { color: var(--wf-ink-600) !important; }
.wf-client body .text-gray-700 { color: var(--wf-ink-700) !important; }
.wf-client body .text-gray-800 { color: var(--wf-ink-800) !important; }
.wf-client body .text-gray-900 { color: var(--wf-ink-950) !important; }

.wf-client body .text-dark      { color: var(--wf-ink-950)   !important; }
.wf-client body .text-primary   { color: var(--wf-brand-500) !important; }
.wf-client body .text-secondary { color: var(--wf-ink-600)   !important; }
.wf-client body .text-danger    { color: var(--wf-error-500) !important; }

/* signal-500 et rating-500 sont sous 4,5:1 sur blanc : le TEXTE utilise les
   barreaux -700, les aplats gardent les -500 (voir §9). */
.wf-client body .text-success   { color: var(--wf-signal-700) !important; }
.wf-client body .text-warning   { color: var(--wf-rating-700) !important; }
.wf-client body .text-info      { color: var(--wf-brand-700)  !important; }

/* Corail : accents ponctuels uniquement. Aucune classe d'erreur ne l'utilise. */
.wf-client body .text-accent    { color: var(--wf-coral-600) !important; }

.wf-client body .text-hover-primary:hover,
.wf-client body .text-hover-primary:hover i,
.wf-client body a.text-hover-primary:hover {
    color: var(--wf-brand-600) !important;
}

/* Le bundle pose `.text-hover-gray-600:hover{color:#7e8299!important}`, soit
   3,7857:1 — plus CLAIR que l'état de repos corrigé ci-dessus. Un survol qui
   dégrade le contraste est un contresens ; on l'inverse. */
.wf-client body .text-hover-gray-600:hover,
.wf-client body .text-hover-gray-600:hover i {
    color: var(--wf-ink-800) !important;   /* 14,39:1 */
}

/* La hiérarchie ne se fait pas par l'opacité : #3f4254 à 50 % sur blanc donne
   2,58:1, et ink-800 à 50 % donnerait 3,01:1 — les deux échouent. Opacité
   rendue à 1, hiérarchie portée par la couleur. Règle bornée à la combinaison
   texte + opacité : `.opacity-50` seule reste intacte partout ailleurs. */
.wf-client body .text-gray-800.opacity-50,
.wf-client body .text-gray-700.opacity-50,
.wf-client body .text-gray-600.opacity-50 {
    opacity: 1 !important;
    color: var(--wf-ink-600) !important;
}

/* L'astérisque de champ obligatoire n'est pas une erreur. */
.wf-client body .required::after {
    color: var(--wf-text-soft);   /* 6,06:1 — était 3,68:1, et rouge d'erreur */
}

.wf-client body .btn.btn-color-gray-400,
.wf-client body .btn.btn-color-gray-400 i,
.wf-client body .btn.btn-color-gray-500,
.wf-client body .btn.btn-color-gray-500 i {
    color: var(--wf-text-soft) !important;   /* 6,06:1 — était 2,03:1 */
}


/* ==========================================================================
   9. REMAP COULEUR — BOUTONS

   Point de conformité central : le blanc échoue sur trois des aplats de marque
   (signal-500 3,3854:1 · rating-500 3,3772:1 · brand-400 3,9367:1, plancher
   4,5:1). Plutôt que d'assombrir les jetons — interdit par le cadrage — ces
   trois boutons portent une encre foncée : ink-950 y donne 5,7405:1 · 5,7545:1
   et 4,9334:1.

   Les états de survol sont dérivés par assombrissement de 12 % et RECALCULÉS,
   ce que la version 1 omettait :
     .btn-success:hover  ink-950 sur #0C8C61 = 4,5712:1
     .btn-warning:hover  ink-950 sur #9F730D = 4,5645:1
     .btn-danger:hover   blanc   sur #AE2323 = 6,8529:1
   Les deux premiers passent à 0,07 du plancher : tout assombrissement
   supplémentaire les fait échouer. Ne pas les retoucher sans recalculer.
   ========================================================================== */

.wf-client body .btn.btn-primary {
    background-color: var(--wf-brand-500);
    border-color: var(--wf-brand-500);
    color: #FFFFFF;
}

.wf-client body .btn.btn-primary:hover:not(.btn-active),
.wf-client body .btn.btn-primary:focus:not(.btn-active),
.wf-client body .btn.btn-primary:active:not(.btn-active),
.wf-client body .btn.btn-primary.active,
.wf-client body .show > .btn.btn-primary {
    background-color: var(--wf-brand-600) !important;
    border-color: var(--wf-brand-600) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-danger {
    background-color: var(--wf-error-500);
    border-color: var(--wf-error-500);
    color: #FFFFFF;
}

.wf-client body .btn.btn-danger:hover:not(.btn-active),
.wf-client body .btn.btn-danger:focus:not(.btn-active),
.wf-client body .btn.btn-danger:active:not(.btn-active),
.wf-client body .btn.btn-danger.active,
.wf-client body .show > .btn.btn-danger {
    background-color: #AE2323 !important;   /* error-500 assombri de 12 % */
    border-color: #AE2323 !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-success {
    background-color: var(--wf-signal-500);
    border-color: var(--wf-signal-500);
    color: var(--wf-ink-950);
}

.wf-client body .btn.btn-success:hover:not(.btn-active),
.wf-client body .btn.btn-success:focus:not(.btn-active),
.wf-client body .btn.btn-success:active:not(.btn-active),
.wf-client body .btn.btn-success.active,
.wf-client body .show > .btn.btn-success {
    background-color: #0C8C61 !important;
    border-color: #0C8C61 !important;
    color: var(--wf-ink-950) !important;
}

.wf-client body .btn.btn-warning {
    background-color: var(--wf-rating-500);
    border-color: var(--wf-rating-500);
    color: var(--wf-ink-950);
}

.wf-client body .btn.btn-warning:hover:not(.btn-active),
.wf-client body .btn.btn-warning:focus:not(.btn-active),
.wf-client body .btn.btn-warning:active:not(.btn-active),
.wf-client body .btn.btn-warning.active,
.wf-client body .show > .btn.btn-warning {
    background-color: #9F730D !important;
    border-color: #9F730D !important;
    color: var(--wf-ink-950) !important;
}

.wf-client body .btn.btn-info {
    background-color: var(--wf-brand-400);
    border-color: var(--wf-brand-400);
    color: var(--wf-ink-950);
}

.wf-client body .btn.btn-info:hover:not(.btn-active),
.wf-client body .btn.btn-info:focus:not(.btn-active),
.wf-client body .btn.btn-info:active:not(.btn-active),
.wf-client body .btn.btn-info.active,
.wf-client body .show > .btn.btn-info {
    background-color: var(--wf-brand-500) !important;
    border-color: var(--wf-brand-500) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-dark {
    background-color: var(--wf-ink-800);
    border-color: var(--wf-ink-800);
    color: #FFFFFF;
}

.wf-client body .btn.btn-dark:hover:not(.btn-active),
.wf-client body .btn.btn-dark:focus:not(.btn-active),
.wf-client body .show > .btn.btn-dark {
    background-color: var(--wf-ink-950) !important;
    border-color: var(--wf-ink-950) !important;
    color: #FFFFFF !important;
}

/* .btn-secondary : aplat ink-200, encre ink-800. DESIGN.md v1 le spécifiait en
   « fond blanc, filet ink-500 », ce que la feuille ne faisait pas : la
   spécification a été alignée sur la feuille, un bouton secondaire en aplat
   restant plus proche du rendu Metronic existant. */
.wf-client body .btn.btn-secondary {
    background-color: var(--wf-ink-200);
    border-color: var(--wf-ink-200);
    color: var(--wf-ink-800);
}

.wf-client body .btn.btn-secondary:hover:not(.btn-active),
.wf-client body .btn.btn-secondary:focus:not(.btn-active),
.wf-client body .show > .btn.btn-secondary {
    background-color: var(--wf-ink-300) !important;
    border-color: var(--wf-ink-300) !important;
    color: var(--wf-ink-950) !important;
}

.wf-client body .btn.btn-light {
    background-color: var(--wf-ink-100);
    border-color: var(--wf-ink-100);
    color: var(--wf-ink-700);
}

.wf-client body .btn.btn-light:hover:not(.btn-active),
.wf-client body .btn.btn-light:focus:not(.btn-active),
.wf-client body .show > .btn.btn-light {
    background-color: var(--wf-ink-200) !important;
    border-color: var(--wf-ink-200) !important;
    color: var(--wf-ink-900) !important;
}

/* -- variantes light-* : fond en teinte -50, texte en encre -700 du même rôle.
   Chaque paire est recalculée sur SA surface, pas sur le blanc :
     brand-700  / brand-50  = 9,6391:1
     signal-700 / signal-50 = 4,8370:1
     error-500  / error-50  = 4,8347:1
     rating-700 / rating-50 = 6,0558:1
     ink-700    / ink-100   = 8,2321:1
   Les six tiennent le plancher du texte courant. -- */
.wf-client body .btn.btn-light-primary,
.wf-client body .btn.btn-light-info {
    background-color: var(--wf-brand-50);
    color: var(--wf-brand-700);
}

.wf-client body .btn.btn-light-primary:hover:not(.btn-active),
.wf-client body .btn.btn-light-primary:focus:not(.btn-active),
.wf-client body .btn.btn-light-primary.active,
.wf-client body .show > .btn.btn-light-primary,
.wf-client body .btn.btn-light-info:hover:not(.btn-active),
.wf-client body .btn.btn-light-info:focus:not(.btn-active),
.wf-client body .btn.btn-light-info.active {
    background-color: var(--wf-brand-500) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-light-success {
    background-color: var(--wf-signal-50);
    color: var(--wf-signal-700);
}

.wf-client body .btn.btn-light-success:hover:not(.btn-active),
.wf-client body .btn.btn-light-success:focus:not(.btn-active),
.wf-client body .btn.btn-light-success.active,
.wf-client body .show > .btn.btn-light-success {
    background-color: var(--wf-signal-500) !important;
    color: var(--wf-ink-950) !important;
}

.wf-client body .btn.btn-light-danger {
    background-color: var(--wf-error-50);
    color: var(--wf-error-500);
}

.wf-client body .btn.btn-light-danger:hover:not(.btn-active),
.wf-client body .btn.btn-light-danger:focus:not(.btn-active),
.wf-client body .btn.btn-light-danger.active,
.wf-client body .show > .btn.btn-light-danger {
    background-color: var(--wf-error-500) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-light-warning {
    background-color: var(--wf-rating-50);
    color: var(--wf-rating-700);
}

.wf-client body .btn.btn-light-warning:hover:not(.btn-active),
.wf-client body .btn.btn-light-warning:focus:not(.btn-active),
.wf-client body .btn.btn-light-warning.active,
.wf-client body .show > .btn.btn-light-warning {
    background-color: var(--wf-rating-500) !important;
    color: var(--wf-ink-950) !important;
}

.wf-client body .btn.btn-light-dark {
    background-color: var(--wf-ink-100);
    color: var(--wf-ink-900);
}

.wf-client body .btn.btn-light-dark:hover:not(.btn-active),
.wf-client body .btn.btn-light-dark:focus:not(.btn-active),
.wf-client body .show > .btn.btn-light-dark {
    background-color: var(--wf-ink-800) !important;
    color: #FFFFFF !important;
}

/* -- variantes outline -- */
.wf-client body .btn.btn-outline.btn-outline-primary,
.wf-client body .btn.btn-outline-primary {
    border-color: var(--wf-brand-500);
    color: var(--wf-brand-600);
    background-color: transparent;
}

.wf-client body .btn.btn-outline.btn-outline-primary:hover,
.wf-client body .btn.btn-outline-primary:hover,
.wf-client body .btn.btn-outline.btn-outline-primary.active,
.wf-client body .show > .btn.btn-outline.btn-outline-primary {
    background-color: var(--wf-brand-500) !important;
    border-color: var(--wf-brand-500) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-outline.btn-outline-danger,
.wf-client body .btn.btn-outline-danger {
    border-color: var(--wf-error-500);
    color: var(--wf-error-500);
    background-color: transparent;
}

.wf-client body .btn.btn-outline.btn-outline-danger:hover,
.wf-client body .btn.btn-outline-danger:hover {
    background-color: var(--wf-error-500) !important;
    border-color: var(--wf-error-500) !important;
    color: #FFFFFF !important;
}

.wf-client body .btn.btn-outline.btn-outline-default,
.wf-client body .btn.btn-outline-secondary {
    border-color: var(--wf-border-control);
    color: var(--wf-ink-700);
    background-color: transparent;
}

/* -- boutons neutres (icônes de barre d'outils) -- */
.wf-client body .btn.btn-active-light-primary:hover,
.wf-client body .btn.btn-active-light-primary.active,
.wf-client body .show > .btn.btn-active-light-primary {
    background-color: var(--wf-brand-50) !important;
    color: var(--wf-brand-700) !important;
}

.wf-client body .btn.btn-active-light-success:hover,
.wf-client body .btn.btn-active-light-success.active {
    background-color: var(--wf-signal-50) !important;
    color: var(--wf-signal-700) !important;
}

.wf-client body .btn.btn-active-light-danger:hover,
.wf-client body .btn.btn-active-light-danger.active {
    background-color: var(--wf-error-50) !important;
    color: var(--wf-error-500) !important;
}

.wf-client body .btn.btn-active-light-warning:hover,
.wf-client body .btn.btn-active-light-warning.active {
    background-color: var(--wf-rating-50) !important;
    color: var(--wf-rating-700) !important;
}

/* -- état désactivé. 81 occurrences de `.btn.disabled` / `.btn:disabled` dans
   le bundle et aucune règle dans la version 1 du livrable. Bootstrap applique
   `opacity:.65`, ce qui sur brand-500 donne un bleu délavé indistinct d'un
   bouton light. L'exemption WCAG 1.4.3 dispense du plancher, pas de dire à
   quoi ressemble un bouton désactivé : il devient un aplat neutre franc, sans
   opacité, donc lisible comme « inactif » et non comme « à moitié effacé ». -- */
.wf-client body .btn.disabled,
.wf-client body .btn:disabled,
.wf-client body fieldset:disabled .btn {
    opacity: 1;
    background-color: var(--wf-ink-100) !important;
    border-color: var(--wf-ink-200) !important;
    color: var(--wf-ink-500) !important;   /* 3,3152:1 — exempté par 1.4.3 */
    box-shadow: none;
}

.wf-client body .menu-link.disabled,
.wf-client body .menu-item.disabled > .menu-link {
    opacity: 1;
    color: var(--wf-ink-500);
}


/* ==========================================================================
   10. REMAP COULEUR — BADGES ET ÉTIQUETTES

   Fond en teinte -50, texte en encre -700 du même rôle, filet de 1 px en
   box-shadow inset dans la même encre. Toutes les paires sont recalculées sur
   leur propre surface (voir §9).

   POURQUOI box-shadow ET NON border : Metronic ne borde pas ses badges
   (`.badge{padding:.5em .75em;border-radius:.42rem}`, aucune déclaration
   border ; Bootstrap 5 n'en déclare pas davantage). Ajouter `border:1px`
   ajouterait donc 2 px de largeur et 2 px de hauteur à chaque badge —
   `box-sizing:border-box` n'y change rien, un badge n'ayant aucune dimension
   explicite. Les badges sont dans les cellules de table et dans les barres
   d'outils flex : ces 2 px s'ajouteraient au reflow horizontal. La version 1
   posait `border:1px solid transparent` en affirmant l'inverse de ce qu'elle
   faisait. Corrigé.

   RÉSERVE — LIMITE DE CONFORMITÉ 1.4.1, NON CORRIGEABLE EN CSS
   Les six états de badge ne se distinguent que par la teinte. Mesuré : les 15
   paires de teintes -50 tiennent entre 1,0025:1 et 1,0713:1, et les 15 paires
   d'encres -700 entre 1,0316:1 et 2,0040:1. AUCUN de ces deux canaux ne survit
   à une perte de discrimination chromatique. Il n'existe pas de correctif CSS :
     - une icône ou un préfixe textuel exige `content` sur un pseudo-élément,
       qui élargit la boîte (interdit) et n'est pas exposé de façon fiable à
       l'arbre d'accessibilité ;
     - écarter les luminances des teintes est borné par l'arithmétique : six
       fonds clairs tenant 4,5:1 avec la même encre ne peuvent pas s'écarter
       de plus de ≈1,6:1 entre eux.
   CE QUI SAUVE LE CAS COURANT : le badge PORTE du texte. Quand ce texte énonce
   l'état (« Résolu », « Actif », « En attente »), 1.4.1 est satisfait par le
   libellé et la couleur n'est qu'un renfort. La non-conformité ne subsiste que
   là où le badge porte une donnée neutre (un nombre, une date, un numéro de
   version) et où la couleur seule dit l'état. Ces cas relèvent du React :
   ajouter un glyphe ou un préfixe. Voir README.md, « Ce qui reste à faire en
   React ».
   `badge-light-primary` et `badge-light-info` sont FUSIONNÉS : dans une charte
   monochrome bleue, « primaire » et « information » ne sont pas distinguables
   (1,0071:1 en version 1). Deux états colorés en moins.
   ========================================================================== */

.wf-client body .badge.badge-primary {
    background-color: var(--wf-brand-500);
    color: #FFFFFF;
}

.wf-client body .badge.badge-danger {
    background-color: var(--wf-error-500);
    color: #FFFFFF;
}

.wf-client body .badge.badge-success {
    background-color: var(--wf-signal-500);
    color: var(--wf-ink-950);
}

.wf-client body .badge.badge-warning {
    background-color: var(--wf-rating-500);
    color: var(--wf-ink-950);
}

.wf-client body .badge.badge-info {
    background-color: var(--wf-brand-400);
    color: var(--wf-ink-950);
}

.wf-client body .badge.badge-dark {
    background-color: var(--wf-ink-800);
    color: #FFFFFF;
}

.wf-client body .badge.badge-secondary,
.wf-client body .badge.badge-light {
    background-color: var(--wf-ink-100);
    color: var(--wf-ink-800);
}

.wf-client body .badge.badge-light-primary,
.wf-client body .badge.badge-light-info {
    background-color: var(--wf-brand-50);
    box-shadow: inset 0 0 0 1px var(--wf-brand-600);   /* 7,5259:1 sur brand-50 */
    color: var(--wf-brand-700);                        /* 9,6391:1 */
}

.wf-client body .badge.badge-light-success {
    background-color: var(--wf-signal-50);
    box-shadow: inset 0 0 0 1px var(--wf-signal-700);  /* 4,8370:1 */
    color: var(--wf-signal-700);
}

.wf-client body .badge.badge-light-danger {
    background-color: var(--wf-error-50);
    box-shadow: inset 0 0 0 1px var(--wf-error-500);   /* 4,8347:1 */
    color: var(--wf-error-500);
}

.wf-client body .badge.badge-light-warning {
    background-color: var(--wf-rating-50);
    box-shadow: inset 0 0 0 1px var(--wf-rating-700);  /* 6,0558:1 */
    color: var(--wf-rating-700);
}

/* `.badge-light-default` et `.badge-light-dark` : le premier n'existe pas dans
   le bundle (un statut inconnu renvoyé par l'API s'affiche donc nu), le second
   portait un filet ink-400 à 2,2309:1 — sous le plancher. ink-500 : 3,3152:1. */
.wf-client body .badge.badge-light-dark,
.wf-client body .badge.badge-light-default {
    background-color: var(--wf-ink-100);
    box-shadow: inset 0 0 0 1px var(--wf-ink-500);     /* 3,3152:1 */
    color: var(--wf-ink-700);                          /* 8,2321:1 */
}

/* Accent corail — aplat plein UNIQUEMENT, et jamais une erreur.
   Il n'existe volontairement PAS de `badge-light-accent` : coral-50 et error-50
   sont séparés par 1,0498:1 et leurs filets par 1,1328:1, donc une variante
   claire du corail serait indiscernable d'une erreur — l'inverse exact de
   l'intention. En aplat plein, le blanc donne 4,9630:1 sur coral-500. Le
   libellé du badge doit dire de quoi il s'agit (« Nouveau ») : c'est lui, et
   non la teinte, qui porte l'information. */
.wf-client body .badge.badge-accent {
    background-color: var(--wf-coral-500);
    color: #FFFFFF;
}


/* ==========================================================================
   11. REMAP COULEUR — FONDS UTILITAIRES
   Ces classes portent !important dans le bundle.
   ========================================================================== */

.wf-client body .bg-primary   { background-color: var(--wf-brand-500)  !important; }
.wf-client body .bg-secondary { background-color: var(--wf-ink-200)    !important; }
.wf-client body .bg-success   { background-color: var(--wf-signal-500) !important; }
.wf-client body .bg-danger    { background-color: var(--wf-error-500)  !important; }
.wf-client body .bg-warning   { background-color: var(--wf-rating-500) !important; }
.wf-client body .bg-info      { background-color: var(--wf-brand-400)  !important; }
.wf-client body .bg-dark      { background-color: var(--wf-ink-950)    !important; }
.wf-client body .bg-light     { background-color: var(--wf-ink-50)     !important; }

.wf-client body .bg-light-primary { background-color: var(--wf-brand-50)  !important; }
.wf-client body .bg-light-info    { background-color: var(--wf-brand-50)  !important; }
.wf-client body .bg-light-success { background-color: var(--wf-signal-50) !important; }
.wf-client body .bg-light-danger  { background-color: var(--wf-error-50)  !important; }
.wf-client body .bg-light-warning { background-color: var(--wf-rating-50) !important; }
.wf-client body .bg-light-dark    { background-color: var(--wf-ink-100)   !important; }

.wf-client body .bg-gray-100 { background-color: var(--wf-ink-50)  !important; }
.wf-client body .bg-gray-200 { background-color: var(--wf-ink-100) !important; }
.wf-client body .bg-gray-300 { background-color: var(--wf-ink-200) !important; }

/* Pastilles de symbole */
.wf-client body .symbol-label {
    background-color: var(--wf-ink-100);
    color: var(--wf-ink-700);
}

.wf-client body .symbol.symbol-light-primary .symbol-label {
    background-color: var(--wf-brand-50);
    color: var(--wf-brand-700);
}

.wf-client body .symbol.symbol-light-success .symbol-label {
    background-color: var(--wf-signal-50);
    color: var(--wf-signal-700);
}

.wf-client body .symbol.symbol-light-danger .symbol-label {
    background-color: var(--wf-error-50);
    color: var(--wf-error-500);
}

.wf-client body .symbol.symbol-light-warning .symbol-label {
    background-color: var(--wf-rating-50);
    color: var(--wf-rating-700);
}


/* ==========================================================================
   12. REMAP COULEUR — FORMULAIRES

   WCAG 1.4.11 exige 3:1 pour la limite d'un composant quand elle est le seul
   moyen de l'identifier — le cas d'un champ blanc sur une carte blanche.
   ink-200 ne donne que 1,2035:1 et le #e4e6ef actuel 1,2352:1 : ink-500 est le
   premier barreau conforme, sur les TROIS surfaces de champ du système :
     ink-500 sur blanc   = 3,7799:1
     ink-500 sur ink-50  = 3,6507:1
     ink-500 sur ink-100 = 3,3152:1
   Conséquence assumée : tous les champs sont visiblement plus contrastés.

   `form-control-solid` EST LA VARIANTE PAR DÉFAUT de Metronic 8 dans ses
   formulaires (`.form-control-solid{background-color:#f5f8fa;border-color:
   #f5f8fa}`), et la version 1 de cette feuille lui laissait `border-color:
   ink-200` sur un fond `ink-50` — soit 1,1624:1 pour la bordure et 1,0354:1
   pour le remplissage contre la carte : AUCUNE limite conforme, sur la
   variante majoritaire, pendant que le texte annonçait la conformité 1.4.11
   comme acquise. C'est corrigé ici, et il n'y a plus qu'UNE règle pour cette
   variante dans tout le fichier.
   ========================================================================== */

.wf-client body .form-control,
.wf-client body .form-select {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-border-control);
    color: var(--wf-ink-900);
}

/* Un placeholder est du texte de corps (13 à 14 px dans ce build) : plancher
   4,5:1. ink-600 donne 6,0571:1 sur blanc, 5,8500:1 sur ink-50 et 5,3123:1 sur
   ink-100. ink-500 (3,7799:1) était sous le plancher — le seuil « texte
   large » commence à 24 px, ou 18,66 px en graisse 700, et 1.4.3 ne prévoit
   aucune exemption pour redondance d'information (c'est 1.4.1 qui traite ce
   sujet). */
.wf-client body .form-control::placeholder,
.wf-client body textarea.form-control::placeholder {
    color: var(--wf-text-soft);
    opacity: 1;
}

.wf-client body .form-control:focus,
.wf-client body .form-select:focus {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-brand-500);
    color: var(--wf-ink-950);
    box-shadow: var(--wf-ring-focus);
}

/* Variante -solid : fond ink-100, bordure ink-500. Les deux limites du bord
   passent le plancher de 3:1 (3,7799:1 contre la carte blanche, 3,3152:1
   contre son propre fond). Seule la COULEUR de bordure change : la largeur
   reste 1 px.
   `!important` sur border-color pour la variante select, et pour une raison
   précise : le bundle déclare `.form-select-solid{border-color:#eef3f7
   !important}` dans un groupe de sélecteurs de focus qui inclut par erreur le
   sélecteur de repos. */
.wf-client body .form-control.form-control-solid,
.wf-client body textarea.form-control.form-control-solid {
    background-color: var(--wf-ink-100);
    border-color: var(--wf-border-control);
    color: var(--wf-ink-900);              /* 15,2831:1 sur ink-100 */
}

.wf-client body .form-select.form-select-solid {
    background-color: var(--wf-ink-100);
    border-color: var(--wf-border-control) !important;
    color: var(--wf-ink-900);
}

.wf-client body .form-control.form-control-solid:focus,
.wf-client body textarea.form-control.form-control-solid:focus,
.wf-client body .form-select.form-select-solid:focus {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-brand-500) !important;
    color: var(--wf-ink-950);
    box-shadow: var(--wf-ring-focus);
}

.wf-client body .form-control:disabled,
.wf-client body .form-select:disabled {
    background-color: var(--wf-ink-100);
    color: var(--wf-ink-500);              /* exempté par 1.4.3 */
}

/* Remplissage automatique du navigateur. Chrome repeint le fond ET le texte
   des champs auto-remplis — précisément les champs e-mail et mot de passe de
   /pages/profile/edit-profile. Le bundle ne gère `:-webkit-autofill` que pour
   le padding de `.form-floating`. La couleur de fond d'un champ auto-rempli ne
   s'écrase qu'avec une ombre interne épaisse : c'est le seul mécanisme
   disponible, il ne modifie pas la boîte.
   Résultat : fond ink-100, encre ink-900 (15,2831:1), bordure ink-500
   (3,3152:1 sur ce fond). Identique au champ -solid au repos. */
.wf-client body .form-control:-webkit-autofill,
.wf-client body .form-control:-webkit-autofill:hover,
.wf-client body .form-control:-webkit-autofill:focus,
.wf-client body .form-select:-webkit-autofill {
    -webkit-text-fill-color: var(--wf-ink-900);
    -webkit-box-shadow: inset 0 0 0 100px var(--wf-ink-100);
    box-shadow: inset 0 0 0 100px var(--wf-ink-100);
    border-color: var(--wf-border-control);
    caret-color: var(--wf-ink-900);
}

.wf-client body .form-label {
    color: var(--wf-ink-700);
}

.wf-client body .input-group-text {
    background-color: var(--wf-ink-50);
    border-color: var(--wf-border-control);
    color: var(--wf-ink-700);
}

/* Chevron de la liste déroulante : même chemin SVG, encre ink-600 au lieu de
   #7e8299. `background-image` n'est pas une propriété de boîte. */
.wf-client body .form-select {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%235A6373' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3E%3C/svg%3E");
}

/* Cases et boutons radio */
.wf-client body .form-check-input {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-border-control);
}

.wf-client body .form-check-input:checked {
    background-color: var(--wf-brand-500);
    border-color: var(--wf-brand-500);
}

.wf-client body .form-check-input:focus {
    border-color: var(--wf-brand-500);
}

/* Interrupteurs et cases « solid ». Le bundle pose `border:0` sur ces
   composants : le FOND est donc le seul moyen de les identifier, et c'est lui
   qui doit porter le 3:1. ink-300 ne donnait que 1,6484:1 sur blanc — la
   version 1 de cette feuille consacrait deux blocs de commentaire à écarter
   nommément ink-300 pour les cases à cocher, puis le retenait pour
   l'interrupteur mille lignes plus bas. ink-500 : 3,7799:1 contre la carte, et
   3,7799:1 entre le bouton blanc du switch et son rail. C'est l'écran des
   consentements, où l'état de l'interrupteur EST l'information. */
.wf-client body .form-check.form-check-solid .form-check-input,
.wf-client body .form-switch .form-check-input {
    background-color: var(--wf-ink-500);
    border-color: var(--wf-ink-500);
}

.wf-client body .form-check.form-check-solid .form-check-input:checked,
.wf-client body .form-switch .form-check-input:checked {
    background-color: var(--wf-brand-500);   /* 6,4358:1 */
    border-color: var(--wf-brand-500);
}

/* Restaure en CSS le signal d'état que le composant ne produit pas : la
   sélection de frère adjacent fait ce que la condition JavaScript rate.
   `!important` est requis parce que .text-muted du bundle en porte un. */
.wf-client body .form-check .form-check-label {
    color: var(--wf-text-soft) !important;     /* 6,06:1 — était 2,45:1 */
}

.wf-client body .form-check .form-check-input:checked + .form-check-label {
    color: var(--wf-ink-950) !important;       /* 19,43:1 */
}

/* Un état désactivé n'est pas soumis à 1.4.3, mais 0,5 d'opacité sur un texte
   déjà clair devient illisible. On borne. */
.wf-client body .form-check-input[disabled] ~ .form-check-label,
.wf-client body .form-check-input:disabled ~ .form-check-label {
    opacity: 0.75;
}

/* États de validation — le rouge d'erreur est un rouge, jamais le corail. */
.wf-client body .form-control.is-invalid,
.wf-client body .form-select.is-invalid,
.wf-client body .was-validated .form-control:invalid {
    border-color: var(--wf-error-500);
}

.wf-client body .invalid-feedback,
.wf-client body .form-text.text-danger {
    color: var(--wf-error-500);
}

.wf-client body .form-control.is-valid,
.wf-client body .form-select.is-valid {
    border-color: var(--wf-signal-500);
}

.wf-client body .valid-feedback {
    color: var(--wf-signal-700);
}


/* ==========================================================================
   13. REMAP COULEUR — TABLES, MENUS, ONGLETS, PAGINATION, ALERTES
   ========================================================================== */

.wf-client body .table {
    color: var(--wf-text-default);
    border-color: var(--wf-ink-100);
}

.wf-client body .table thead th {
    color: var(--wf-text-soft);
    border-bottom-color: var(--wf-border-hairline);
}

.wf-client body .table tbody td {
    border-bottom-color: var(--wf-ink-100);
}

/* Filets : continu au lieu de pointillé. L'épaisseur ne change pas, donc la
   boîte ne change pas. Un pointillé à 1,25:1 sur 900 px de large n'est pas une
   arête ; un trait continu à 1,20:1 en est une. Décoratif — la séparation est
   aussi portée par le zébrage et l'alignement — donc 1.4.11 ne s'y applique
   pas. */
.wf-client body .table.table-row-dashed > tbody > tr {
    border-bottom-style: solid;
    border-bottom-color: var(--wf-border-hairline);
}

/* Le bas de l'en-tête est une arête de structure : un cran plus marqué. */
.wf-client body .table.table-row-dashed > thead > tr {
    border-bottom-style: solid;
    border-bottom-color: var(--wf-ink-300);
}

/* Zébrage. Ni .table-striped ni .table-hover n'est posé (vérifié dans
   Table.tsx), donc --bs-table-striped-bg n'est consommée nulle part : le
   zébrage doit être écrit. Il fonctionne parce que --bs-table-bg vaut
   transparent. ink-50 (1,0354:1 contre le blanc) : volontairement faible. Un
   zébrage est un guide, pas une information ; il doit rester moins saillant
   que le survol. Contrastes vérifiés SUR la bande : ink-700 9,07:1. */
.wf-client body .table > tbody > tr:nth-child(2n) {
    background-color: var(--wf-ink-50);
}

/* Survol de ligne. Sur 8 colonnes, rien ne relie la première cellule à la
   dernière pendant que le regard traverse la rangée, et .table-hover n'est pas
   posé. brand-50 est 3,4 fois plus marqué que le zébrage et chromatique, donc
   distinguable même posé SUR une bande de zébrage. Contrastes vérifiés sur
   cette teinte : ink-700 8,31:1 · brand-600 7,53:1 · ink-900 15,68:1.
   `@media (hover: hover)` évite le survol persistant après un appui tactile. */
@media (hover: hover) {
    .wf-client body .table > tbody > tr:hover {
        background-color: var(--wf-brand-50);
    }
}

.wf-client body .table > tbody > tr {
    transition: background-color var(--wf-duration-fast) var(--wf-ease-out-quart);
}

/* Menu de la barre latérale (thème clair) */
.wf-client body .menu-link {
    color: var(--wf-ink-700);              /* 9,3861:1 sur blanc */
}

.wf-client body .menu-link:hover,
.wf-client body .menu-item:hover > .menu-link {
    background-color: var(--wf-ink-50);
    color: var(--wf-brand-600);
}

/* Item actif : DEUX canaux, la teinte de fond ET un repère latéral de 3 px.
   Le repère est posé en box-shadow inset, jamais en border-left : une bordure
   élargirait la boîte de l'item. brand-500 sur brand-50 = 5,7921:1. */
.wf-client body .menu-link.active,
.wf-client body .menu-item.here > .menu-link,
.wf-client body .menu-item.show > .menu-link {
    background-color: var(--wf-brand-50);
    color: var(--wf-brand-700);            /* 9,6391:1 */
    box-shadow: inset 3px 0 0 0 var(--wf-brand-500);
}

.wf-client body .menu-link.active .menu-title,
.wf-client body .menu-item.here > .menu-link .menu-title {
    color: var(--wf-brand-700);
}

.wf-client body .menu-title {
    color: inherit;
}

.wf-client body .menu-heading {
    color: var(--wf-text-soft);
}

/* Onglets */
.wf-client body .nav-line-tabs .nav-item .nav-link {
    color: var(--wf-text-soft);
    border-bottom-color: transparent;
}

.wf-client body .nav-line-tabs .nav-item .nav-link:hover:not(.disabled) {
    color: var(--wf-brand-600);
}

.wf-client body .nav-line-tabs .nav-item .nav-link.active {
    color: var(--wf-brand-600);
    border-bottom-color: var(--wf-brand-500);
}

.wf-client body .nav-pills .nav-link.active {
    background-color: var(--wf-brand-500);
    color: #FFFFFF;
}

/* Pagination */
.wf-client body .page-link {
    color: var(--wf-ink-700);
    background-color: transparent;
}

.wf-client body .page-link:hover {
    background-color: var(--wf-ink-100);
    color: var(--wf-brand-600);
}

.wf-client body .page-item.active .page-link {
    background-color: var(--wf-brand-500);
    border-color: var(--wf-brand-500);
    color: #FFFFFF;
}

.wf-client body .page-item.disabled .page-link {
    color: var(--wf-ink-500);   /* exempté par 1.4.3 ; ink-400 était à 2,54:1 */
}

/* Barres de progression. La piste est décorative (ink-100, 1,1402:1 sur
   blanc) : c'est le REMPLISSAGE qui porte l'information, et brand-500 sur
   ink-100 donne 5,6445:1. */
.wf-client body .progress {
    background-color: var(--wf-ink-100);
}

.wf-client body .progress-bar {
    background-color: var(--wf-brand-500);
}

/* Alertes — fond en teinte -50, texte et filet en encre -700 du même rôle.
   Même réserve 1.4.1 que les badges (§10) : une alerte est distinguée par son
   TEXTE, qui énonce ce qui se passe. Là où le texte ne l'énonce pas, il faut
   une icône, donc du React.
   Les cinq variantes sont présentes ici alors que DESIGN.md v1 n'en déclarait
   que deux ; les trois manquantes y ont été ajoutées. */
.wf-client body .alert.alert-primary,
.wf-client body .alert.alert-info {
    background-color: var(--wf-brand-50);
    border-color: var(--wf-brand-600);
    color: var(--wf-brand-700);
}

.wf-client body .alert.alert-success {
    background-color: var(--wf-signal-50);
    border-color: var(--wf-signal-700);
    color: var(--wf-signal-700);
}

.wf-client body .alert.alert-danger {
    background-color: var(--wf-error-50);
    border-color: var(--wf-error-500);
    color: var(--wf-error-500);
}

.wf-client body .alert.alert-warning {
    background-color: var(--wf-rating-50);
    border-color: var(--wf-rating-700);
    color: var(--wf-rating-700);
}

/* Menus déroulants */
.wf-client body .dropdown-menu,
.wf-client body .menu-sub-dropdown {
    background-color: var(--wf-surface-card);
    border-color: var(--wf-ink-100);
}

.wf-client body .dropdown-item:hover,
.wf-client body .dropdown-item:focus {
    background-color: var(--wf-ink-50);
    color: var(--wf-brand-600);
}

.wf-client body .dropdown-item.active {
    background-color: var(--wf-brand-50);
    color: var(--wf-brand-700);
}

/* Infobulles */
.wf-client body .tooltip-inner {
    background-color: var(--wf-surface-dark);
    color: #FFFFFF;
    border-radius: var(--wf-radius-lg);
}


/* ==========================================================================
   14. VISIBILITÉ DU FOCUS

   Le point d'accessibilité le moins coûteux et le plus souvent négligé.
   L'anneau brand-500 donne 6,4358:1 sur blanc et 5,7921:1 sur brand-50 —
   au-delà du plancher de 3:1 des éléments d'interface.

   `outline` et `outline-offset` ne participent PAS au calcul de la boîte :
   contrairement à une bordure, ils ne déplacent rien.

   RÉSERVE MESURÉE COMME NON VÉRIFIABLE : un `outline` est DÉCOUPÉ par tout
   ancêtre en `overflow: hidden`, et `outline-offset: 2px` le pousse justement
   vers l'extérieur. Dans une cellule rs-table ou un conteneur à en-tête figé,
   l'anneau peut être entièrement invisible. Le gain n'est donc pas vérifiable
   sans l'arbre réel, et les interdits structurels empêchent de corriger en
   touchant à `overflow`. C'est pourquoi les champs reçoivent EN PLUS un
   `box-shadow` d'anneau (§12), qui n'est pas découpé de la même façon : deux
   canaux, dont au moins un survit.
   ========================================================================== */

.wf-client body :focus-visible {
    outline: 2px solid var(--wf-brand-500);
    outline-offset: 2px;
}

/* Metronic neutralise l'outline sur ses boutons et le remplace par un
   box-shadow diffus, souvent invisible. On rétablit un anneau net — sans
   supprimer le box-shadow des CHAMPS, qui est notre second canal. */
.wf-client body .btn:focus-visible,
.wf-client body .page-link:focus-visible,
.wf-client body .menu-link:focus-visible,
.wf-client body .nav-link:focus-visible,
.wf-client body .dropdown-item:focus-visible,
.wf-client body .rs-btn:focus-visible {
    outline: 2px solid var(--wf-brand-500);
    outline-offset: 2px;
    box-shadow: none;
}

.wf-client body .form-control:focus-visible,
.wf-client body .form-select:focus-visible,
.wf-client body .form-check-input:focus-visible,
.wf-client body .rs-input:focus-visible,
.wf-client body .rs-picker-toggle:focus-visible {
    outline: 2px solid var(--wf-brand-500);
    outline-offset: 2px;
    box-shadow: var(--wf-ring-focus);
}

/* Sur un aplat de marque, l'anneau bleu se confondrait avec le fond. */
.wf-client body .btn.btn-primary:focus-visible,
.wf-client body .btn.btn-info:focus-visible {
    outline-color: #FFFFFF;
    box-shadow: 0 0 0 4px rgb(36 83 212 / 0.35);
}

/* Le focus ne doit jamais être supprimé — seulement remplacé. */
.wf-client body *:focus:not(:focus-visible) {
    outline: none;
}


/* ==========================================================================
   15. ÉCRAN — TABLEAU DE BORD (/dashboard)

   PRÉALABLE BLOQUANT, hors CSS : /api/v2/dashboard répond HTTP 500
   (SeoStatisReportPresenter.php:261, lecture de id_google_analytics sur null).
   Tous les indicateurs valent 0 et TROIS des six blocs de l'écran ne se rendent
   jamais — liste des contacts, bandeau d'annonce, avis. Aucune recette visuelle
   de cet écran n'est possible avant ce correctif. Les règles ci-dessous
   marquées « non observé » n'ont donc pas pu être vérifiées à l'écran.
   ========================================================================== */

/* Surface de page : `body{background-color:#f5f8fa}` est la dernière règle
   `body` du bundle, en spécificité (0,0,1). `.wf-client body` vaut (0,1,1) :
   pas de !important requis. Le fond est déjà posé en §7. */

/* Filet de carte par box-shadow de propagation nulle. `border:0` est posé par
   Metronic et `border-width` n'est pas neutre. ink-300 : 1,6484:1 sur la carte
   blanche, 1,4522:1 sur la page ink-100. Décoratif dans les deux cas. */
.wf-client body .content .card {
    box-shadow: 0 0 0 1px var(--wf-ink-300), var(--wf-shadow-xs);
}

/* Rythme vertical — quatre blocs, deux valeurs. Sélecteurs ancrés sur les
   classes réellement écrites dans les composants. */
.wf-client body .card.mb-6.mb-xl-9 {                     /* PageHeader → sélecteur */
    margin-bottom: var(--wf-space-xl) !important;        /* 32 px                  */
}
.wf-client body .row.mb-8:has(.rs-picker) {              /* sélecteur → cartes     */
    margin-bottom: var(--wf-space-md) !important;        /* 16 px                  */
}
.wf-client body .mb-5[style*="grid-template-columns"] {   /* cartes → rangée suiv.  */
    margin-bottom: var(--wf-space-lg) !important;        /* 24 px                  */
}
.wf-client body .content .row.my-5 {
    margin-top:    var(--wf-space-lg) !important;
    margin-bottom: var(--wf-space-lg) !important;
}

/* -- bandeau d'explication (PageHeader) --
   Accroche : `.card.mb-6.mb-xl-9`, écrit uniquement dans PageHeader.tsx. */
.wf-client body .card.mb-6.mb-xl-9 {
    background-color: var(--wf-brand-50);
    box-shadow: 0 0 0 1px var(--wf-brand-200);   /* pas d'ombre : le bloc recule */
}

/* Carré décoratif : 100 px → 48 px. Feuille de l'arbre, aucun couplage (E2). */
.wf-client body .card.mb-6.mb-xl-9 .w-100px.h-100px {
    width:  48px !important;
    height: 48px !important;
    background-color: var(--wf-brand-100) !important;
    border-radius: var(--wf-radius-md);
}
.wf-client body .card.mb-6.mb-xl-9 .svg-icon-4hx svg {
    width:  24px !important;                     /* était 4,5rem = 58,5 px */
    height: 24px !important;
}
.wf-client body .card.mb-6.mb-xl-9 .svg-icon-4hx svg [fill]:not([fill="none"]) {
    fill: var(--wf-brand-600) !important;        /* 7,5259:1 sur brand-50 */
}

.wf-client body .card.mb-6.mb-xl-9 .fs-2 {
    font-family:    var(--wf-font-display);
    font-size:      var(--wf-text-title) !important;
    line-height:    var(--wf-leading-snug);
    letter-spacing: var(--wf-tracking-title);
    font-weight:    var(--wf-weight-semibold) !important;
    color:          var(--wf-ink-950) !important;   /* 17,4903:1 sur brand-50 */
}

/* Description : c'était le texte le moins lisible de l'écran, à 2,4472:1.
   ink-700 sur brand-50 : 8,4457:1. */
.wf-client body .card.mb-6.mb-xl-9 .text-gray-500 {
    font-size:   var(--wf-text-body) !important;
    line-height: var(--wf-leading-base);
    font-weight: var(--wf-weight-regular) !important;
    color:       var(--wf-ink-700) !important;
    max-width:   72ch;                           /* E5 : longueur de ligne */
}

/* -- sélecteur de période --
   La bordure du champ est déjà corrigée par le pont (--rs-border-primary →
   ink-500 : 1,26:1 → 3,7799:1). Ne rien y ajouter.
   RAPPEL HORS CSS : la cible de touche du sélecteur fait 36 px et ne peut PAS
   être agrandie en CSS — `.rs-picker-input:not(.rs-picker-disabled)
   .rs-picker-toggle` est en `position:absolute!important` et le chevron épinglé
   à `top:7px`. Le correctif est `size="lg"` dans DateRange.tsx. */
.wf-client body .row.mb-8 .text-gray-600 {        /* « Choisissez une période » */
    font-size:      var(--wf-text-meta) !important;
    letter-spacing: var(--wf-tracking-label);
    color:          var(--wf-ink-600) !important;
}

.wf-client body .rs-picker-input:focus-within {
    border-color: var(--wf-brand-500);
    box-shadow:   var(--wf-ring-focus);
}
.wf-client body .rs-picker-toggle-value,
.wf-client body .rs-picker-toggle-placeholder {
    font-size: var(--wf-text-body) !important;
}

/* -- grille des cartes d'indicateurs --
   Le conteneur porte `className="mb-5"` et un style EN LIGNE
   (`display:grid; grid-template-columns:…; grid-gap:10px`). Un style en ligne
   ne se surcharge qu'avec `!important`.
   Effet de bord voulu : ces déclarations neutralisent les paliers JavaScript
   (480 / 768 px) de UseWindowSize, dont deux sont identiques. La mise en page
   redevient pilotée par le CSS, sans modifier un composant React — mais deux
   sources de vérité concurrentes subsistent, et le correctif de fond est de
   retirer `getResponsiveStyles` de DashboardWrapper.tsx.
   Exception E6 : placement automatique dans l'ordre du DOM, donc ordre de
   tabulation préservé. */
.wf-client body .mb-5[style*="grid-template-columns"] {
    grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
    gap: var(--wf-space-md) !important;           /* remplace grid-gap:10px */
}

/* 3 colonnes tant que 5 ne tiennent pas. Borne haute calculée : à 1200 px,
   (1200 − 265 de .aside − 60 de padding de conteneur − 4 × 16 de gouttière) / 5
   = 162 px par carte, soit 130 px de largeur interne après le padding de 16 px.
   L'en-tête y loge un cercle de 40 px et un lien de 68 px = 108 px : cela
   tient. À 1199 px, sans cette règle, on retombe à 93 px de largeur interne
   pour 125 px de contenu. */
@media (max-width: 1199.98px) {
    .wf-client body .mb-5[style*="grid-template-columns"] {
        grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    }
}
@media (max-width: 767.98px) {
    .wf-client body .mb-5[style*="grid-template-columns"] {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
        gap: var(--wf-space-sm) !important;
    }
}

/* -- cartes d'indicateurs (ChartDashboard1) --
   Accroche : `.bgi-position-x-end`, écrite dans UN SEUL composant de toute
   l'application (vérifié sur la source map). Accroche par classe, donc
   indépendante des styles en ligne et stable d'un build à l'autre.
   Le fond en ligne (#e1006dbf / #0094f3bf) exige !important. Après composition
   de l'alpha 0,749 sur blanc (#40AFF6 et #E94092), le texte blanc posé dessus
   vaut 2,4174:1 et 3,7357:1 — deux échecs. */
.wf-client body .card.bgi-position-x-end {
    background-color: var(--wf-surface-card) !important;
    background-image: none !important;
    box-shadow: 0 0 0 1px var(--wf-ink-300), var(--wf-shadow-xs);
}

/* En-tête : 70 px → 56 px. E3 — min-height sur un conteneur, et c'est la SEULE
   occurrence de ce type dans tout le fichier. Elle est acceptée parce que
   `.card-header` est un conteneur flex sans descendant positionné en absolu, et
   que la spécificité (0,4,1) l'emporte sur le (0,1,0) de Metronic sans
   !important. À vérifier en premier lors de la recette : si un élément de
   .card-toolbar y était positionné en absolu, retirer cette déclaration. */
.wf-client body .card.bgi-position-x-end > .card-header {
    min-height: 56px;
    padding: var(--wf-space-md) var(--wf-space-md) 0 !important;
    align-items: center;
}
.wf-client body .card.bgi-position-x-end > .card-body {
    padding: var(--wf-space-xs) var(--wf-space-md) var(--wf-space-md) !important;
}

/* Cercle d'icône : le tiret blanc à 60 % (2,30:1) devient un aplat brand-50
   avec un filet brand-200, tracé en box-shadow — la bordure d'origine faisait
   1 px et la remplacer par une autre bordure de 1 px serait neutre, mais le
   box-shadow évite d'avoir à le supposer. brand-200 sur brand-50 = 1,4437:1 :
   décoratif, le contraste réel est porté par le glyphe (7,5259:1). */
.wf-client body .card.bgi-position-x-end .rounded-circle.h-40px {
    background-color: var(--wf-brand-50) !important;
    border-color: var(--wf-brand-200) !important;
    border-style: solid !important;
}
.wf-client body .card.bgi-position-x-end .svg-icon-white svg [fill]:not([fill="none"]) {
    fill: var(--wf-brand-600) !important;
}

/* Chiffre : 19,5 px blanc à 2,53:1 → 28 px ink-950 à 19,4341:1.
   .fs-2 est en !important dans le bundle. */
.wf-client body .card.bgi-position-x-end .fs-2 {
    font-family:          var(--wf-font-display);
    font-size:            var(--wf-text-metric) !important;
    line-height:          var(--wf-leading-tight);
    letter-spacing:       var(--wf-tracking-display);
    font-weight:          var(--wf-weight-semibold) !important;
    color:                var(--wf-ink-950) !important;
    font-variant-numeric: tabular-nums lining-nums;   /* CountUp anime le nombre :
                                                         sans chiffres à largeur
                                                         fixe, il tremble */
}

/* Libellé : 13,975 px → 12 px, et il cesse d'avoir le poids de la donnée. */
.wf-client body .card.bgi-position-x-end .fs-6 {
    font-size:      var(--wf-text-meta) !important;
    line-height:    var(--wf-leading-snug);
    letter-spacing: var(--wf-tracking-label);
    font-weight:    var(--wf-weight-medium) !important;
    color:          var(--wf-ink-600) !important;     /* 6,0571:1 */
}
.wf-client body .card.bgi-position-x-end .text-white {
    color: inherit !important;
}

/* « Voir plus » : aplat plein (4,75:1 / 3,22:1) → lien-texte brand-600,
   8,3622:1. Padding recalculé pour une cible de touche : 16,2 px de contenu
   (12 px × 1,35) + 2 × 12 px = 40,2 px. */
.wf-client body .card.bgi-position-x-end .card-toolbar .btn.btn-sm {
    background-color: transparent !important;
    color:            var(--wf-brand-600) !important;
    font-size:        var(--wf-text-meta) !important;
    line-height:      var(--wf-leading-snug);
    font-weight:      var(--wf-weight-semibold) !important;
    padding:          12px 8px !important;
    border-radius:    var(--wf-radius-sm);
    text-decoration:  underline;
    text-underline-offset: 2px;
}
.wf-client body .card.bgi-position-x-end .card-toolbar .btn.btn-sm:hover {
    color: var(--wf-brand-700) !important;            /* 10,71:1 */
    background-color: var(--wf-brand-50) !important;
}

/* -- carte promue : « Visites » --
   Une seule carte pleine sur cinq. Premier enfant de la grille : l'ordre est
   fixé par DashboardWrapper.tsx et « Visites » y est en tête. Aucun
   réordonnancement, seulement une sélection positionnelle. */
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end {
    background-color: var(--wf-brand-500) !important;
    box-shadow: 0 0 0 1px var(--wf-brand-600), var(--wf-shadow-sm);
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .fs-2 {
    color: var(--wf-text-invert) !important;          /* 6,4358:1 sur brand-500 */
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .fs-6 {
    color: var(--wf-brand-100) !important;            /* 5,11:1 sur brand-500 */
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .rounded-circle.h-40px {
    background-color: rgb(255 255 255 / 0.14) !important;
    border-color: rgb(255 255 255 / 0.35) !important;
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .svg-icon-white svg [fill]:not([fill="none"]) {
    fill: var(--wf-text-invert) !important;
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .card-toolbar .btn.btn-sm {
    color: var(--wf-brand-100) !important;
}
.wf-client body .mb-5[style*="grid-template-columns"] > div:first-child > .card.bgi-position-x-end .card-toolbar .btn.btn-sm:hover {
    color: var(--wf-text-invert) !important;
    background-color: rgb(255 255 255 / 0.14) !important;
}

/* -- carte du graphique --
   Accroche : l'id `#kt_charts_widget_3_chart`, unique dans l'application. */
.wf-client body .card:has(#kt_charts_widget_3_chart) .card-label {
    font-family:    var(--wf-font-display);
    font-size:      var(--wf-text-title) !important;
    line-height:    var(--wf-leading-snug);
    letter-spacing: var(--wf-tracking-title);
    font-weight:    var(--wf-weight-semibold) !important;
    color:          var(--wf-ink-950) !important;
}

/* -- derniers contacts (ContactsDashboard) --
   Accroche : `.col-xl-4.card`, unique dans l'application — c'est aussi un
   défaut : une colonne qui est une carte. */
.wf-client body .col-xl-4.card {
    padding: var(--wf-space-md);
}

.wf-client body .col-xl-4.card > .card-header .card-title {
    font-family:    var(--wf-font-display);
    font-size:      var(--wf-text-title) !important;
    letter-spacing: var(--wf-tracking-title);
    font-weight:    var(--wf-weight-semibold) !important;
    color:          var(--wf-ink-950) !important;
}

/* Les cinq puces sémantiques deviennent un rail unique : elles ne codaient
   aucune information. `bg-*` est en !important dans le bundle. */
.wf-client body .col-xl-4.card .bullet-vertical.bg-success,
.wf-client body .col-xl-4.card .bullet-vertical.bg-primary,
.wf-client body .col-xl-4.card .bullet-vertical.bg-info,
.wf-client body .col-xl-4.card .bullet-vertical.bg-warning,
.wf-client body .col-xl-4.card .bullet-vertical.bg-danger {
    background-color: var(--wf-ink-300) !important;
    border-radius: var(--wf-radius-full);
}

.wf-client body .col-xl-4.card .text-gray-800.fs-7 {
    font-size:   var(--wf-text-body) !important;
    line-height: var(--wf-leading-base);
    font-weight: var(--wf-weight-semibold) !important;
    color:       var(--wf-ink-950) !important;
}
.wf-client body .col-xl-4.card .text-gray-800.fs-7:hover {
    color: var(--wf-brand-600) !important;            /* 8,3622:1 */
}

/* E-mail : 9,75 px à 2,4472:1 (9 px sous 768 px) → 12 px à 6,0571:1. */
.wf-client body .col-xl-4.card .text-muted.fs-9 {
    font-size:   var(--wf-text-meta) !important;
    line-height: var(--wf-leading-snug);
    font-weight: var(--wf-weight-regular) !important;
    color:       var(--wf-ink-600) !important;
}

/* Numéro de téléphone : ce n'est pas un statut. Quatre des cinq badges de rôle
   échouaient (1,47:1 à 3,44:1). Fond ink-100, texte ink-700 : 8,2321:1. */
.wf-client body .col-xl-4.card .badge[class*="badge-light-"] {
    background-color: var(--wf-ink-100) !important;
    box-shadow:       none;
    color:            var(--wf-ink-700) !important;
    font-family:      var(--wf-font-mono);
    font-size:        var(--wf-text-micro) !important;
    font-weight:      var(--wf-weight-medium) !important;
    font-variant-numeric: tabular-nums lining-nums;
}

.wf-client body .col-xl-4.card .card-body > .d-flex.mb-8 {
    margin-bottom: var(--wf-space-md) !important;
}

/* État vide — le seul état visible aujourd'hui, l'API étant en 500. Le TEXTE
   lui-même est hors périmètre CSS (voir AUDIT-EDITORIAL.md).
   RÉSERVE : aucun des quatre écrans tabulaires ne rend quoi que ce soit quand
   la table est vide, et il n'existe aucun composant d'état vide à styliser.
   Le gabarit d'état vide est un travail React. */
.wf-client body .col-xl-4.card .card-body > h4.text-gray-800 {
    font-size:   var(--wf-text-body) !important;
    line-height: var(--wf-leading-base);
    font-weight: var(--wf-weight-regular) !important;
    color:       var(--wf-ink-600) !important;
}

/* -- bandeau d'annonce (Announcement) — NON OBSERVÉ (bloc non rendu, API 500)
   Accroche : le dégradé en ligne. `[style*="linear-gradient"]` combiné à
   `.card.h-lg-100` ne peut correspondre qu'à ce composant sur cet écran.
   Contrastes mesurés AVANT, sur le dégradé
   `linear-gradient(112.14deg, rgb(12,147,225), rgb(225,0,109))`, extrémité
   gauche puis droite :
     titre blanc plein (26 px)                3,3459:1 · 4,7486:1
     corps blanc à 75 %                       2,5089:1 · 3,0451:1
     libellé du bouton `bg-white/15`          2,7973:1 · 4,2257:1
   (le fond du bouton composé vaut #30A3E6 à gauche et #E62683 à droite ; la
   version 1 de ce livrable annonçait 4,23:1 aux DEUX extrémités, en recopiant
   la valeur de droite). Seul le titre passe, et seulement à droite. */
.wf-client body .card.h-lg-100[style*="linear-gradient"] {
    background: var(--wf-brand-50) !important;
    box-shadow: 0 0 0 1px var(--wf-brand-200);
}

.wf-client body .card.h-lg-100[style*="linear-gradient"] .text-white {
    color: var(--wf-ink-700) !important;              /* 8,4457:1 sur brand-50 */
}
.wf-client body .card.h-lg-100[style*="linear-gradient"] h3.text-white {
    font-family:    var(--wf-font-display);
    font-size:      var(--wf-text-title) !important;
    letter-spacing: var(--wf-tracking-title);
    color:          var(--wf-brand-700) !important;   /* 9,6391:1 sur brand-50 */
}
.wf-client body .card.h-lg-100[style*="linear-gradient"] .opacity-75 {
    opacity: 1 !important;
}
.wf-client body .card.h-lg-100[style*="linear-gradient"] .symbol-label {
    background: var(--wf-brand-100) !important;
}
.wf-client body .card.h-lg-100[style*="linear-gradient"] .btn.btn-color-white {
    background-color: var(--wf-brand-500) !important;
    color:            var(--wf-text-invert) !important;   /* 6,4358:1 */
    border-radius:    var(--wf-radius-md);
    padding:          12px 20px !important;
    font-size:        var(--wf-text-body) !important;
    font-weight:      var(--wf-weight-semibold) !important;
}
.wf-client body .card.h-lg-100[style*="linear-gradient"] .btn.btn-color-white:hover {
    background-color: var(--wf-brand-600) !important;     /* 8,3622:1 */
}

/* Désalignement de 19,5 px : la `.row` interne rajoute un `.col-12` dont le
   padding n'a aucune raison d'être. Aucun frère positionné n'en dépend. */
.wf-client body .content .row > .col-12 > .row.my-5 > .col-12 {
    padding-left:  0;
    padding-right: 0;
}

@media (max-width: 575.98px) {
    .wf-client body .card.h-lg-100[style*="linear-gradient"] .row > .col-8,
    .wf-client body .card.h-lg-100[style*="linear-gradient"] .row > .col-4 {
        width: 100%;
    }
    .wf-client body .card.h-lg-100[style*="linear-gradient"] .row > .col-4 {
        justify-content: flex-start;
    }
}

/* Débordement de ~180 px : `.w-300px{width:300px!important}` dans une colonne
   de ~120 px, et Bootstrap 5 a retiré `img{max-width:100%}` de son reboot
   (vérifié sur le bundle). Une image est une feuille de l'arbre (E2). */
.wf-client body .card.h-lg-100[style*="linear-gradient"] img.w-300px {
    width:     auto !important;
    max-width: 100% !important;
    height:    auto !important;
}


/* ==========================================================================
   16. ÉCRANS DE DONNÉES TABULAIRES
   /call-tracking/journal-appels · /contacts · /clicks
   /web-formulaires/tableau-de-bord-wf

   Les quatre écrans passent par le même composant
   (app/modules/all-pages/components/Table.tsx), dont le rendu est :
     <div class="table-responsive">
       <table class="table table-row-dashed table-row-gray-300 align-middle gs-0 gy-4">
         <thead><tr class="fw-bolder text-muted bg-light"> … </tr></thead>

   Une seule structure à traiter. En contrepartie, ces règles s'appliquent à
   TOUTES les tables de l'application : aucune classe de route n'existe sur le
   <body> ni sur le conteneur de page.

   ------------------------------------------------------------------------
   NOTE DE LARGEUR — LE POINT LE PLUS RISQUÉ DE TOUT LE LIVRABLE, NON MESURÉ

   Une table Metronic est en `table-layout: auto` dans un `.table-responsive` :
   toute augmentation de la largeur du contenu augmente `scrollWidth`. C'est le
   mécanisme exact du précédent (153 px de défilement latéral, table
   inatteignable sur la console d'administration).

   La version 1 de ce livrable augmentait simultanément trois largeurs d'avance
   par cellule — corps 13 → 14 px, `tabular-nums`, et changement de famille
   Poppins → Geist — en ne compensant que la HAUTEUR de rangée. Corrections
   apportées ici :
     a) le corps de cellule RESTE à 13 px (`--wf-text-dense`). C'est la valeur
        du précédent éprouvé, avec sa raison explicite : « le corps de base
        n'est PAS modifié, le remonter reflowerait les tables denses ». La
        variante 14 px est fournie en option, désactivée ;
     b) l'interlettrage positif est RETIRÉ des en-têtes de colonne
        (`--wf-tracking-meta` vaut 0) : sur une colonne étroite, l'en-tête est
        souvent la contrainte de largeur minimale ;
     c) `tabular-nums` est CONSERVÉ — sans lui aucune colonne de nombres n'est
        comparable ligne à ligne — et son effet de largeur est déclaré comme
        non mesuré ;
     d) le changement de famille est conservé et son effet de largeur est
        également non mesuré : il dépend de la police RÉELLEMENT servie (voir
        §5), donc il n'est pas déterminable depuis un fichier CSS.

   Le solde net est probablement négatif (les tailles intra-cellule
   redescendent de 16,25 / 13,975 / 12,35 px à 13 px, et quatre planchers de
   colonne sont abaissés), mais IL N'A PAS ÉTÉ MESURÉ. Le test de
   non-régression est obligatoire avant mise en production : comparer
   `document.documentElement.scrollWidth` avec et sans `wf-client`, à 1440 px
   et à 390 px, sur les quatre écrans. Toute différence positive signale une
   règle à retirer.
   ========================================================================== */

/* -- corps de la donnée : une taille, une couleur, des chiffres alignés -- */
.wf-client body .table > tbody > tr > td {
    font-size: var(--wf-text-dense);        /* 13 px — voir la note ci-dessus */
    line-height: var(--wf-leading-dense);
    color: var(--wf-text-default);
    font-variant-numeric: tabular-nums lining-nums;
}

/* OPTION, à n'activer qu'après un test de scrollWidth concluant sur les quatre
   écrans, aux deux largeurs :
   .wf-client body .table > tbody > tr > td { font-size: var(--wf-text-body); }
*/

/* Neutralisation des sauts de taille intra-cellule. Cinq tailles cohabitent
   aujourd'hui dans une même rangée : 16,25 px (.fs-4), 13,975 (.fs-6), 12,35
   (.fs-7), 11,05 (.fs-8), 13 (héritée). Trois hauteurs de ligne dans une
   rangée détruisent la ligne de base, qui est le support de lecture
   horizontale d'un tableau. Ces classes portent `!important` dans le bundle.
   Portée volontairement limitée à l'intérieur d'un <td>.
   Effet de largeur : toutes ces valeurs DESCENDENT vers 13 px, donc la
   contribution de cette règle au scrollWidth est négative. */
.wf-client body .table > tbody > tr > td .fs-4,
.wf-client body .table > tbody > tr > td .fs-5,
.wf-client body .table > tbody > tr > td .fs-6,
.wf-client body .table > tbody > tr > td .fs-7 {
    font-size: var(--wf-text-dense) !important;
    line-height: var(--wf-leading-dense);
}

/* La colonne d'identité domine par la graisse et l'encre, pas par la taille. */
.wf-client body .table > tbody > tr > td > a,
.wf-client body .table > tbody > tr > td a.text-gray-800,
.wf-client body .table > tbody > tr > td .text-dark {
    font-size: var(--wf-text-dense);
    font-weight: var(--wf-weight-semibold);
}

/* Un lien de cellule doit se lire comme un lien. `text-hover-primary` ne change
   que la couleur au survol : au repos, rien ne le distingue d'un texte. Le
   soulignement au survol/focus ne modifie pas la boîte. */
.wf-client body .table > tbody > tr > td a:hover,
.wf-client body .table > tbody > tr > td a:focus-visible {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* Badges de cellule : une seule taille. 12 px et non 11 px, parce que sur
   /clicks le badge PORTE la valeur de la colonne (item.eventCount) : le
   rétrograder à 11 px réduirait la donnée principale d'une colonne. */
.wf-client body .table > tbody > tr > td .badge {
    font-size: var(--wf-text-meta) !important;
    font-weight: var(--wf-weight-semibold);
    line-height: 1;
}

/* -- bande d'en-tête : la seule marche tonale marquée de la table --
   `.bg-light` est remappée globalement vers ink-50, soit 1,0354:1 contre le
   blanc de la première rangée : imperceptible. La bande d'en-tête doit être la
   plus contrastée de la table pour rester un repère fixe pendant le balayage.
   ink-100 : 1,1402:1. `!important` est nécessaire — `.bg-light` en porte un. */
.wf-client body .table > thead > tr.bg-light,
.wf-client body .table > thead > tr > th.bg-light {
    background-color: var(--wf-ink-100) !important;
}

/* Libellé de colonne : registre d'étiquette. 12 px / 500 / ink-600.
   `.text-muted` (sur le <tr>) et `.text-gray-500` (dans le bouton de tri)
   valent tous deux #a1a5b7 dans le bundle, soit 2,1467:1 sur la bande ink-100
   — échec. Le remap de §8 les envoie vers ink-600 : 5,3123:1 sur cette bande.
   La reprise ci-dessous ne fait donc que le palier et la graisse.
   AUCUN interlettrage positif : il augmenterait le min-content de la colonne. */
.wf-client body .table > thead > tr > th,
.wf-client body .table > thead > tr > th .text-gray-500,
.wf-client body .table > thead > tr > th .text-muted {
    font-size: var(--wf-text-meta) !important;
    font-weight: var(--wf-weight-medium) !important;
    letter-spacing: var(--wf-tracking-meta);
    color: var(--wf-ink-600) !important;
    text-transform: none;   /* annule le .text-capitalize de /clicks, qui
                               rendrait « Statut De L'appel » */
}

/* -- rythme des cellules --
   `.table.gy-4 td` vaut (0,2,1) : le préfixe suffit, pas de !important.
   Padding horizontal 9,75 → 8 px : 28 px de largeur gagnés sur 8 colonnes. */
.wf-client body .table.gy-4 > thead > tr > th,
.wf-client body .table.gy-4 > tbody > tr > td {
    padding-top: var(--wf-space-sm);
    padding-bottom: var(--wf-space-sm);
    padding-left: var(--wf-space-xs);
    padding-right: var(--wf-space-xs);
}

/* Cellules extrêmes : 0 px partout, ce que `gs-0` demande déjà et que trois
   écrans contredisent avec trois valeurs différentes (16,25 / 13 / 0). */
.wf-client body .table.gs-0 > thead > tr > th:first-child,
.wf-client body .table.gs-0 > tbody > tr > td:first-child {
    padding-left: 0 !important;
}

.wf-client body .table.gs-0 > thead > tr > th:last-child,
.wf-client body .table.gs-0 > tbody > tr > td:last-child {
    padding-right: 0 !important;
}

/* Quirk propre à /web-formulaires : Row_.tsx pose `ps-4` sur un <div> INTERNE
   de la première cellule, ce qui recréait un décalage de 13 px. */
.wf-client body .table.gs-0 > tbody > tr > td:first-child > .ps-4 {
    padding-left: 0 !important;
}

/* -- planchers de colonne : le correctif structurel (E4) --
   Cause mesurée du débordement : min-w-200px appliqué en boucle à toutes les
   colonnes, colonne d'action vide incluse. 7 × 200 = 1 400 px pour un budget de
   1 063 px à 1440 px de viewport.
   Abaisser un plancher ne peut pas augmenter la largeur d'une table ; le pire
   cas est un retour à la ligne, donc une croissance verticale.
   Portée bornée à l'intérieur de .table : ces utilitaires servent ailleurs. */
.wf-client body .table > thead > tr > th.min-w-200px,
.wf-client body .table > tbody > tr > td.min-w-200px { min-width: 120px !important; }

.wf-client body .table > thead > tr > th.min-w-150px,
.wf-client body .table > tbody > tr > td.min-w-150px { min-width: 110px !important; }

.wf-client body .table > thead > tr > th.min-w-90px,
.wf-client body .table > tbody > tr > td.min-w-90px  { min-width: 88px !important; }

.wf-client body .table > thead > tr > th.min-w-50px,
.wf-client body .table > tbody > tr > td.min-w-50px  { min-width: 48px !important; }

/* Sans cela le gain précédent est annulé : `.btn.btn-icon` est `inline-flex`
   avec `justify-content:center`, donc un libellé d'en-tête sur deux lignes se
   centre pendant que la donnée reste à gauche. E7 : `justify-content` aligne,
   il ne réordonne pas le DOM. */
.wf-client body .table > thead > tr > th > .btn,
.wf-client body .table > thead > tr > th > button.btn {
    justify-content: flex-start;
    text-align: left;
    white-space: normal;
}

/* -- chaînes non coupables : e-mails et chemins d'URL --
   `overflow-wrap: anywhere` est la seule valeur qui réduise réellement le
   `min-content` d'une cellule ; `break-word` ne le réduit pas. Sur /contacts la
   colonne e-mail impose sinon ≈ 210 px quel que soit le plancher.
   Restreint au tablette-et-mobile : c'est là que le débordement est massif, et
   la contrepartie (coupure au milieu d'un mot) n'y est pas payée cher. */
@media (max-width: 991.98px) {
    .wf-client body .table > tbody > tr > td {
        overflow-wrap: anywhere;
    }
}

/* -- distinguer une colonne triable d'une colonne qui ne l'est pas --
   TablesDashBoard rend <button class="btn …"> quand la colonne est triable et
   <div class="btn … cursor-default"> sinon, avec des classes identiques : rien
   à l'écran ne les sépare (5 colonnes sur 8 sont inertes). Le sélecteur de TYPE
   les distingue, sans toucher au React.
   RAPPEL HORS CSS : le tri lui-même produit un ordre FAUX
   (`a[sortKey] > b[sortKey]`, donc lexicographique, sur des secondes et des
   dates ; et le drapeau `reverse` est ignoré dans trois méthodes sur quatre).
   Un tableau trié faux nuit plus qu'un tableau mal composé : le correctif
   React est prioritaire sur ces règles. */
.wf-client body .table > thead > tr > th > button.btn {
    cursor: pointer;
}

.wf-client body .table > thead > tr > th > button.btn:hover,
.wf-client body .table > thead > tr > th > button.btn:focus-visible {
    color: var(--wf-brand-600) !important;
    text-decoration: underline;
    text-underline-offset: 3px;
}

.wf-client body .table > thead > tr > th > button.btn:hover .text-gray-500,
.wf-client body .table > thead > tr > th > button.btn:focus-visible .text-gray-500 {
    color: var(--wf-brand-600) !important;
}

/* Un <div class="btn"> n'est pas cliquable : il ne doit pas en avoir l'air. */
.wf-client body .table > thead > tr > th > div.btn {
    cursor: default;
}

.wf-client body .table > thead > tr > th > div.btn:hover {
    background-color: transparent;
    color: var(--wf-ink-600) !important;
}

/* -- focus clavier dans une table --
   Une rangée contient jusqu'à trois cibles focalisables et la tabulation
   traverse la table entière. brand-500 : 6,4358:1 sur blanc, 5,6445:1 sur la
   bande d'en-tête. */
.wf-client body .table a:focus-visible,
.wf-client body .table button:focus-visible,
.wf-client body .table input:focus-visible,
.wf-client body .pagination .page-link:focus-visible {
    outline: 2px solid var(--wf-brand-500);
    outline-offset: 2px;
    border-radius: var(--wf-radius-xs);
}

/* -- pied de table : la pagination se lit comme un pied --
   Le filet est tracé par une ombre INTERNE et non par une bordure. */
.wf-client body .table-responsive + .row {
    margin-top: var(--wf-space-md);
    padding-top: var(--wf-space-md);
    box-shadow: inset 0 1px 0 var(--wf-border-hairline);
}

/* Le bloc de filtres est un registre d'action ; la table un registre de
   lecture. 24 px les séparent. */
.wf-client body .card-body > .row.mb-5 + .table-responsive,
.wf-client body .card-body > .row.mb-3 + .table-responsive {
    margin-top: var(--wf-space-lg);
}

/* -- affordance de défilement latéral (E9) --
   À 390 px le défilement latéral reste indispensable, et il doit se VOIR : sur
   macOS et iOS la barre est en superposition et invisible au repos.
   `::-webkit-scrollbar` force une barre classique, donc réservée dans le flux :
   le conteneur grandit d'environ 10 px en HAUTEUR. Croissance verticale, sans
   rognage. Ni overflow, ni width, ni position n'est déclaré.
   RÉSERVE : sur Safari iOS, `::-webkit-scrollbar` est IGNORÉ. Sur ce moteur
   rien n'indiquera que la table continue à droite. Le correctif relève d'un
   indicateur rendu par le composant — une lisière en dégradé exigerait
   `position`, et un pseudo-élément défilerait avec le contenu qu'il annonce. */
.wf-client body .table-responsive {
    scrollbar-width: thin;
    scrollbar-color: var(--wf-ink-500) var(--wf-ink-100);
    overscroll-behavior-inline: contain;
}

.wf-client body .table-responsive::-webkit-scrollbar {
    height: 10px;
}

.wf-client body .table-responsive::-webkit-scrollbar-track {
    background-color: var(--wf-ink-100);
    border-radius: var(--wf-radius-full);
}

/* Le curseur est manipulable : 1.4.11 s'y applique. ink-400 sur ink-100 ne
   donnait que 2,2309:1 ; ink-500 donne 3,3152:1. */
.wf-client body .table-responsive::-webkit-scrollbar-thumb {
    background-color: var(--wf-ink-500);
    border-radius: var(--wf-radius-full);
}

.wf-client body .table-responsive::-webkit-scrollbar-thumb:hover {
    background-color: var(--wf-ink-600);
}

/* -- mobile : padding de carte et bande d'en-tête --
   `px-3` = 9,75 px sous 576 px : la donnée frôle le bord de l'écran. 12 px, la
   même valeur que le padding vertical des cellules. */
@media (max-width: 575.98px) {
    .wf-client body .card > .collapse > .card-body.px-3 {
        padding-left: var(--wf-space-sm) !important;
        padding-right: var(--wf-space-sm) !important;
    }

    /* En-tête de colonne à 11 px sous 576 px : réduit le min-content des
       libellés, donc la largeur de la table, donc le défilement latéral. */
    .wf-client body .table > thead > tr > th,
    .wf-client body .table > thead > tr > th .text-gray-500 {
        font-size: var(--wf-text-micro) !important;
    }
}


/* ==========================================================================
   17. ÉCRANS DE RAPPORTS ET DE GRAPHIQUES
   /statistiques/s-seo · /mailing · /mise-a-jours · /reviews

   Les variables lues par le JavaScript sont en §2 : elles doivent être sur
   <html>, la cascade ne remonte pas.
   ========================================================================== */

/* -- texte SVG des graphiques --
   ApexCharts pose la couleur du texte en attribut `fill` (via svg.js .attr()),
   qu'une déclaration CSS bat sans !important. La police est en dur
   ("Helvetica, Arial, sans-serif") dans les options : --bs-font-sans-serif ne
   l'atteint pas, il faut ce sélecteur.
   `font-size` est volontairement ABSENT : la géométrie du SVG (collision et
   masquage des étiquettes) a été calculée par le JS à partir des 12 px
   déclarés. La changer en CSS désaligne les étiquettes sans que le JS puisse
   recalculer — même raisonnement que l'interdit sur `display`. */
.wf-client body .apexcharts-text,
.wf-client body .apexcharts-xaxis-label,
.wf-client body .apexcharts-yaxis-label,
.wf-client body .apexcharts-xaxis-title text,
.wf-client body .apexcharts-yaxis-title text {
    fill: var(--wf-text-soft);
    font-family: var(--wf-font-sans);
}

/* La légende est du HTML, pas du SVG : `color` et non `fill`.
   La PASTILLE de légende est inatteignable : le bundle fait
   `A.setProperty("background", i[l], "important")` en style en ligne, et rien
   ne bat un !important en ligne. Conséquence : les tracés NE DOIVENT PAS être
   recolorés en CSS, sinon la légende mentirait. La palette de séries se corrige
   dans les six constantes JavaScript — voir README.md. */
.wf-client body .apexcharts-legend-text {
    color: var(--wf-text-default) !important;
    font-family: var(--wf-font-sans);
}

/* Valeur au centre de la jauge : 23 px en graisse 500, donc ni ≥ 24 px ni gras
   au sens WCAG (700). C'est du texte courant, plancher 4,5:1. La rendre
   indépendante de la couleur d'état garantit 19,4341:1 dans les trois cas, et
   laisse l'anneau porter l'état. */
.wf-client body .apexcharts-datalabel-value {
    fill: var(--wf-text-strong);
    font-family: var(--wf-font-display);
    font-weight: var(--wf-weight-semibold);
}

.wf-client body .apexcharts-gridline,
.wf-client body .apexcharts-grid-borders line {
    stroke: var(--wf-border-hairline);
}

.wf-client body .apexcharts-xaxis line,
.wf-client body .apexcharts-yaxis line {
    stroke: var(--wf-ink-300);
}

/* Infobulle. ApexCharts injecte sa propre feuille dans <head> à l'exécution ;
   sa règle la plus spécifique est `.apexcharts-tooltip.apexcharts-theme-light`
   (0,2,0). Notre préfixe donne (0,3,1) : nous gagnons par spécificité, quel que
   soit l'ordre d'injection. */
.wf-client body .apexcharts-tooltip.apexcharts-theme-light {
    background-color: var(--wf-surface-dark);
    border-color: var(--wf-ink-800);
    border-radius: var(--wf-radius-lg);
    box-shadow: var(--wf-shadow-md);
    color: var(--wf-ink-200);          /* 16,15:1 sur ink-950 */
    font-family: var(--wf-font-sans);
}

.wf-client body .apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
    background-color: var(--wf-ink-900);
    border-color: var(--wf-ink-800);
    color: var(--wf-ink-100);
    font-weight: var(--wf-weight-semibold);
}

.wf-client body .apexcharts-tooltip-text-y-label,
.wf-client body .apexcharts-tooltip-text-y-value {
    color: var(--wf-ink-100);
}

/* -- tuiles à fond posé en ligne --
   Le composant pose style={{backgroundColor:"#0094f3bf"}} ou "#e1006dbf". Le
   DOM sérialise en `background-color: rgba(0, 148, 243, 0.749);` (forme CSSOM :
   virgule + espace). Le sélecteur s'arrête avant l'alpha, dont les décimales
   peuvent varier d'un moteur à l'autre. À VÉRIFIER une fois dans la console.
   Le filet est tracé par une ombre INTERNE : une bordure de 1 px ajouterait
   2 px à la boîte. */
.wf-client body .card[style*="rgba(0, 148, 243"],
.wf-client body .card[style*="rgba(225, 0, 109"] {
    background-color: var(--wf-surface-card) !important;
    box-shadow: inset 0 0 0 1px var(--wf-border-hairline), var(--wf-shadow-xs);
}

.wf-client body .card[style*="rgba(0, 148, 243"] .text-white.fs-7,
.wf-client body .card[style*="rgba(225, 0, 109"] .text-white.fs-7 {
    color: var(--wf-text-soft) !important;       /* 6,0571:1 */
    font-size: var(--wf-text-meta);
    letter-spacing: var(--wf-tracking-label);
}

.wf-client body .card[style*="rgba(0, 148, 243"] .text-white.fs-2,
.wf-client body .card[style*="rgba(225, 0, 109"] .text-white.fs-2 {
    color: var(--wf-text-strong) !important;     /* 19,4341:1 */
    font-family: var(--wf-font-display);
    font-size: var(--wf-text-metric);
    letter-spacing: var(--wf-tracking-display);
}

/* En-tête de carte teinté en ligne : rgba(225,243,255,0.3) → #F7FCFF composé
   sur blanc, soit 1,03:1. Une teinte imperceptible, et un bleu hors marque. */
.wf-client body .card-header[style*="rgba(225, 243, 255"] {
    background-color: transparent !important;
}

/* -- couple de versions : cesser d'appeler « erreur » un numéro --
   Portée : les trois cartes de /mise-a-jours sont les seules de l'espace client
   à porter .border-dashed. Ancienne version : neutre ET BARRÉE. Le barré est un
   second canal non chromatique, et il ne modifie pas la boîte. La flèche entre
   les deux badges est le second canal du couple : elle existe déjà. */
.wf-client body .card.border-dashed .badge.badge-light-danger {
    background-color: var(--wf-ink-100) !important;
    box-shadow: none;
    color: var(--wf-text-default) !important;    /* 8,2321:1 — était 3,44:1 */
    text-decoration: line-through;
}

.wf-client body .card.border-dashed .badge.badge-light-success {
    background-color: var(--wf-signal-50) !important;
    color: var(--wf-signal-700) !important;      /* 4,8370:1 — était 1,92:1 */
    font-weight: var(--wf-weight-semibold);
}

.wf-client body .card.border-dashed .badge {
    font-family: var(--wf-font-mono);
    font-size: var(--wf-text-micro);
}

/* `border-style` ne modifie pas la boîte (contrairement à border-width) : une
   bordure en tirets et une bordure pleine de même épaisseur occupent la même
   place. Le tiret est un vocabulaire de brouillon ; ces cartes portent des
   données de production. */
.wf-client body .card.border-dashed {
    border-color: var(--wf-border-hairline) !important;
    border-style: solid !important;
}

/* -- étoiles d'avis : deux styles en ligne, deux sélecteurs d'attribut --
   style={{color: r < rating ? "#f7b718" : "#d6d6d6"}} → sérialisé
   `color: rgb(247, 183, 24);` et `color: rgb(214, 214, 214);`.
   Contrastes actuels : 1,79:1 et 1,45:1 sur blanc ; 1,23:1 entre elles.
   Après : 3,3772:1 et 1,6484:1 sur blanc ; 2,05:1 entre elles.
   À ÉNONCER SANS DÉTOUR : 2,05:1 reste sous le plancher de 3:1. La rangée
   d'étoiles n'est PAS rendue conforme par cette règle, elle est rendue moins
   mauvaise. Le canal réel est la note chiffrée affichée à côté. La correction
   complète — glyphe `bi-star` creuse pour les étoiles vides, `role="img"` et
   `aria-label` sur le conteneur — est du React. */
.wf-client body .bi-star-fill[style*="rgb(247, 183, 24)"] {
    color: var(--wf-rating-500) !important;
}

.wf-client body .bi-star-fill[style*="rgb(214, 214, 214)"] {
    color: var(--wf-ink-300) !important;
}

.wf-client body .card.bg-light-primary .fs-2hx {
    color: var(--wf-text-strong);
    font-family: var(--wf-font-display);
    letter-spacing: var(--wf-tracking-display);
}

/* -- QR code : turquoise → bleu de marque, halo compris --
   #40e0d0 (1,64:1) et #2ff4f1 n'appartiennent à aucune palette du projet. Ce
   sont des `background` et des `box-shadow` : aucun effet de mise en page. */
.wf-client body .reviews-qr-corner {
    border-color: var(--wf-brand-500);
}

.wf-client body .reviews-qr-scanline {
    background: linear-gradient(183deg,
                rgb(36 83 212 / 0.20),
                rgb(36 83 212 / 0.50) 30%,
                var(--wf-brand-500));
    box-shadow: 0 0 12px rgb(36 83 212 / 0.55), 0 0 28px rgb(36 83 212 / 0.30);
    /* `infinite` dans la feuille différée de l'écran. Trois passages suffisent
       à signaler « ceci se scanne » ; au-delà, c'est du mouvement permanent sur
       un écran de lecture. */
    animation-iteration-count: 3;
}

.wf-client body .reviews-qr-shell {
    background: linear-gradient(180deg, rgb(255 255 255 / 0.92), rgb(250 251 253 / 0.98));
}

/* -- titres de section : un seul palier --
   Deux composants de carte coexistent, l'un titrant en .fs-5.fs-sm-3
   (13,98 / 17,55 px), l'autre en .fs-7 (12,03 px). L'en-tête de la seconde
   variante porte min-h-50px : 20 px y tiennent. */
.wf-client body .card-header .card-title h3 {
    color: var(--wf-text-strong);
    font-family: var(--wf-font-display);
    font-size: var(--wf-text-title) !important;
    letter-spacing: var(--wf-tracking-title);
}

/* -- bloc « Vue Audience » : la mesure au-dessus du libellé --
   Portée : #kt_audience_view_details, identifiant composé par le composant
   repliable à partir d'un identifiant écrit à la main dans le code de l'écran.
   Le badge cesse d'être un badge : un nombre n'est pas un statut. */
.wf-client body #kt_audience_view_details .badge {
    background-color: transparent !important;
    box-shadow: none;
    color: var(--wf-text-strong) !important;     /* 19,4341:1 */
    font-family: var(--wf-font-display);
    font-size: var(--wf-text-metric);
    font-variant-numeric: tabular-nums lining-nums;
    letter-spacing: var(--wf-tracking-display);
    padding-left: 0;
    padding-right: 0;
}

.wf-client body #kt_audience_view_details .text-gray-800 {
    color: var(--wf-text-soft) !important;       /* 6,0571:1 */
    font-size: var(--wf-text-meta);
    font-weight: var(--wf-weight-medium);
    letter-spacing: var(--wf-tracking-label);
}

/* Le rail vertical structure, il ne signifie pas : les quatre couleurs
   d'origine (success, primary, warning, info) ne codent aucune information. */
.wf-client body #kt_audience_view_details .bullet.bullet-vertical {
    background-color: var(--wf-ink-300) !important;
}

/* -- légende des seuils PageSpeed : montrer ce qu'elle légende --
   Les pastilles étaient en danger (#f1416c) et success (#50cd89) pendant que
   les jauges étaient en --bs-red (#dc3545) et --bs-green (#198754). Les valeurs
   ci-dessous sont exactement celles de §2. Les trois icônes diffèrent : second
   canal acquis. */
.wf-client body .svg-icon-danger svg [fill]:not(.permanent):not(g) {
    fill: var(--wf-error-500);
}

.wf-client body .svg-icon-orange svg [fill]:not(.permanent):not(g) {
    fill: var(--wf-rating-700);
}

.wf-client body .svg-icon-success svg [fill]:not(.permanent):not(g) {
    fill: var(--wf-signal-700);
}

.wf-client body .svg-icon-primary svg [fill]:not(.permanent):not(g) {
    fill: var(--wf-brand-500);
}

.wf-client body .svg-icon-info svg [fill]:not(.permanent):not(g) {
    fill: var(--wf-brand-700);
}

/* -- bandeau d'information -- */
.wf-client body .card-header.bg-light-info {
    background-color: var(--wf-brand-50) !important;   /* ink-700 dessus : 8,45:1 */
    border-bottom-color: var(--wf-border-hairline);
}

/* -- OPTIONNEL, DÉSACTIVÉ : lisière de défilement des tables de rapports.
   Cette règle ne déclare aucune propriété de boîte — elle peint deux dégradés
   qui restent fixes quand le contenu défile — mais elle vise
   `.table-responsive`, l'élément exact du précédent aux 153 px. La distinction
   est réelle (peindre n'est pas redimensionner) et elle doit être vérifiée, pas
   postulée. Elle reste donc COMMENTÉE jusqu'à ce que le test de scrollWidth de
   §16 ait été exécuté et rende 0.

   .wf-client body .table-responsive {
       background-image:
           linear-gradient(to right, rgb(10 13 20 / 0.08), rgb(10 13 20 / 0)),
           linear-gradient(to left,  rgb(10 13 20 / 0.08), rgb(10 13 20 / 0));
       background-position: left center, right center;
       background-repeat: no-repeat;
       background-size: 12px 100%, 12px 100%;
       background-attachment: local, scroll;
   }
*/

@media (max-width: 575.98px) {
    /* Le palier `metric` à 28 px sur trois tuiles empilées consomme de la
       hauteur sans rien ajouter à la lecture : une seule tuile est visible à la
       fois, la comparaison ne se fait plus par juxtaposition. */
    .wf-client body .card[style*="rgba(0, 148, 243"] .text-white.fs-2,
    .wf-client body .card[style*="rgba(225, 0, 109"] .text-white.fs-2,
    .wf-client body #kt_audience_view_details .badge {
        font-size: var(--wf-text-title);
    }

    /* Les libellés de « Vue Audience » sont en fs-8 (11,05 px) sous 576 px —
       plus petits que sur grand écran, donc l'inverse du besoin. */
    .wf-client body #kt_audience_view_details .text-gray-800 {
        font-size: var(--wf-text-meta);
    }
}


/* ==========================================================================
   18. ÉCRANS DE COMPTE, DE PROFIL ET DE SUPPORT
   /pages/profile/edit-profile · /sav · /sav/ticket/:id · /pages/products
   /pages/privacy-policy

   /creer-compte-mailing est HORS PÉRIMÈTRE — voir l'en-tête, point 4.
   ========================================================================== */

/* Titre de carte : le seul niveau de titre existant sur ces écrans, le <h1>
   étant commenté dans DefaultTitle.tsx. Il passe de 14,95 px à 20 px.
   `!important` est requis : les classes .fs-* du bundle en portent un.
   La croissance de 5 px est absorbée par le `min-height:70px` de .card-header,
   qui est déjà `flex-wrap:wrap`. Une taille de texte n'élargit pas un bloc.
   RAPPEL : un h3 restylé reste un h3 pour un lecteur d'écran. Rétablir le <h1>
   est du React. */
.wf-client body .card-header .card-title > h3,
.wf-client body .card-header .card-title > .fw-bolder {
    font-family: var(--wf-font-display);
    font-size: var(--wf-text-title) !important;
    line-height: var(--wf-leading-snug);
    letter-spacing: var(--wf-tracking-title);
    color: var(--wf-ink-950);
}

/* Libellés de champ : métadonnée, pas texte courant. 12 px, ink-600 (6,0571:1
   sur blanc, 5,8500:1 sur ink-50). */
.wf-client body .form-label {
    font-size: var(--wf-text-meta) !important;
    color: var(--wf-text-soft);
}

/* Blocs de données immuables du profil. `.notice` n'a AUCUNE règle dans le
   bundle : le crochet est libre. Ce bloc ne contient que du texte en flux
   normal, aucun frère n'y est positionné en absolu. */
.wf-client body .notice.bg-light {
    padding: 12px 14px !important;
    border-radius: var(--wf-radius-md);
}

/* Un identifiant client et un nom de domaine sont des chaînes à recopier : la
   mono interdit la confusion 0/O et 1/l. */
.wf-client body .notice .text-gray-800,
.wf-client body .notice .text-gray-900 {
    font-family: var(--wf-font-mono);
    font-variant-numeric: tabular-nums lining-nums;
    font-size: var(--wf-text-body) !important;
    color: var(--wf-ink-900) !important;         /* 16,8299:1 sur ink-50 */
}

/* -- fil de discussion du support --
   Corps d'un message. Sélecteur relevé dans Responses.tsx.
   E5 : un max-width ne peut que RÉDUIRE la largeur utilisée. */
.wf-client body .card.card-bordered .card-body > .fw-normal.fs-5 {
    max-width: var(--wf-measure-prose);
    font-size: var(--wf-text-body) !important;
    line-height: var(--wf-leading-base);
    color: var(--wf-ink-700);                    /* 9,3861:1 */
}

/* Description de produit injectée par l'API (CreateProduct.tsx). Le div injecté
   ne porte AUCUN attribut class : `div:not([class])` est donc un sélecteur
   exact. Fragilité assumée : si le composant reçoit une classe un jour, la
   règle cesse de s'appliquer — elle ne casse rien, elle disparaît. */
.wf-client body form.form > .row > div:not([class]) {
    max-width: 68ch;
    font-size: var(--wf-text-body);
    line-height: var(--wf-leading-base);
    color: var(--wf-ink-700);
}

.wf-client body form.form > .row > div:not([class]) :is(h1, h2, h3, h4) {
    font-family: var(--wf-font-display);
    font-size: var(--wf-text-title);
    line-height: var(--wf-leading-snug);
    letter-spacing: var(--wf-tracking-title);
    color: var(--wf-ink-950);
}

.wf-client body form.form > .row > div:not([class]) a {
    color: var(--wf-brand-600);                  /* 8,3622:1 */
    text-decoration: underline;
}

/* Bordure de message : 1,12:1 aujourd'hui, donc absente. ink-200 (1,2035:1)
   délimite sans quadriller — séparateur décoratif, aucun seuil. Seule la
   couleur change ; la largeur reste 1 px. */
.wf-client body .card.card-bordered {
    border-color: var(--wf-border-hairline);
    border-radius: var(--wf-radius-md);
    box-shadow: none;
}

/* Distinction des interlocuteurs PAR LA SURFACE, et non par la couleur d'un
   avatar. `:has()` est disponible sur tous les moteurs récents depuis fin
   2023 ; là où il manque, la règle est ignorée et les deux messages restent
   blancs — dégradation sans casse. */
.wf-client body .card.card-bordered:has(.symbol-label.bg-light-danger) {
    background-color: var(--wf-ink-50);
}

/* Avatars : ni vert ni rouge. La réponse Webforce prend la teinte de marque
   (identité), le message du client une teinte neutre. Portée : tout
   `.symbol-label` rouge de l'application est atteint. C'est voulu — un avatar
   rouge n'est jamais un signal correct. */
.wf-client body .symbol-label.bg-light-danger {
    background-color: var(--wf-brand-50) !important;
}

.wf-client body .symbol-label.bg-light-danger.text-danger,
.wf-client body .symbol-label .text-danger {
    color: var(--wf-brand-700) !important;       /* 9,6391:1 sur brand-50 */
}

.wf-client body .symbol-label.bg-light-success {
    background-color: var(--wf-ink-100) !important;
}

.wf-client body .symbol-label.bg-light-success.text-success,
.wf-client body .symbol-label .text-success {
    color: var(--wf-ink-700) !important;         /* 8,2321:1 sur ink-100 */
}

.wf-client body .card.card-bordered .text-muted.fs-6 {
    font-size: var(--wf-text-meta) !important;
    color: var(--wf-ink-600) !important;
}

/* Ligne à messages non lus. Le bundle y met `bg-light-danger` : ici
   l'information est « on vous a répondu », pas « erreur ». Le corail serait
   sémantiquement juste (marque d'élément non lu) mais son budget est de deux
   occurrences par écran : une colonne de tableau le crève. */
.wf-client body .table tbody tr.bg-light-danger {
    background-color: var(--wf-brand-50) !important;
}

/* Clignotement infini : non-conformité WCAG 2.2.2. Supprimé. La keyframe ne
   fait que basculer `visibility`, donc ni la boîte ni l'arbre ne changent. */
.wf-client body .animation-blink {
    animation: none;
}

/* Compteur de messages : 9,75 px et rouge d'erreur → 11 px et bleu de marque.
   `font-size` est sans risque de reflow ici : l'élément porte
   `position-absolute translate-middle`, il est donc hors flux. */
.wf-client body .table .text-danger.animation-blink {
    font-size: var(--wf-text-micro) !important;
    color: var(--wf-brand-600) !important;       /* 8,3622:1 */
    font-variant-numeric: tabular-nums lining-nums;
}

/* Icône d'accès au ticket : le rouge disparaît du vocabulaire de cette table. */
.wf-client body .table td .btn i.bi-eye-fill.text-danger {
    color: var(--wf-brand-600) !important;
}

.wf-client body .table td .btn i.bi-eye-fill.text-success {
    color: var(--wf-ink-600) !important;         /* 6,0571:1 */
}

/* -- étoiles de satisfaction (/sav) --
   Étoile pleine : rating-500, 3,3772:1 — le plancher de 3:1 des éléments non
   textuels est atteint (était 1,87:1, et 1,40:1 pour le style en ligne
   #FFD700).
   Étoile vide : ink-300 (1,6484:1) ne passe pas 3:1 et ne peut pas y passer
   sans devenir aussi sombre que l'étoile pleine. Dans la LISTE de tickets ce
   n'est pas un problème : le balisage utilise deux glyphes différents
   (`bi-star` contre `bi-star-fill`), donc la forme porte l'information. Dans le
   DÉTAIL du ticket, les cinq étoiles sont des `bi-star-fill` et seule la
   couleur distingue : là, WCAG 1.4.1 n'est pas satisfait, et le CSS ne peut pas
   le corriger. */
.wf-client body .rating-label > i {
    color: var(--wf-ink-300);
}

.wf-client body .rating-label.checked > i,
.wf-client body label.rating-label > i {
    color: var(--wf-rating-500) !important;
}

/* Bat le style en ligne `color:'#FFD700'` de TicketRow.tsx. Seul `!important`
   peut le faire ; `width` et `top` posés dans le même attribut ne sont pas
   touchés. */
.wf-client body .rating-label i.bi-star-fill[style] {
    color: var(--wf-rating-500) !important;
}

/* -- AFFORDANCE DE REPLIEUR — RÈGLE VOLONTAIREMENT DÉSACTIVÉE --
   Les deux en-têtes de carte de /pages/profile/edit-profile sont des replieurs
   (data-bs-toggle="collapse", cursor-pointer) sans aucun chevron dans le
   balisage : une commande sans affordance.
   Cette règle ajouterait le chevron par un ::after. Elle est laissée
   COMMENTÉE, et non livrée, pour deux raisons mesurées :
     1. `.card-header` est en `justify-content: space-between` — ajouter un
        troisième élément flex redistribue les deux premiers ;
     2. il n'a pas été possible de vérifier, sans compte client, que le
        replieur FONCTIONNE. S'il ne fonctionne pas, c'est `cursor-pointer`
        qu'il faut retirer côté React, pas un signe qu'il faut ajouter côté
        CSS.
   `tabindex`, `aria-expanded` et `aria-controls` manquent aussi : ce sont des
   attributs, hors d'atteinte du CSS.

   .wf-client body .card-header[data-bs-target="#kt_account_profile_details"]::after,
   .wf-client body .card-header[data-bs-target="#kt_account_signin_method"]::after {
       content: "";
       align-self: center;
       width: 10px;
       height: 10px;
       border-right: 2px solid var(--wf-ink-500);
       border-bottom: 2px solid var(--wf-ink-500);
       transform: rotate(45deg);
   }
*/


/* ==========================================================================
   19. CIBLES DE TOUCHE — UNE VALEUR, UN DÉCLENCHEUR

   40 px, sous 992 px de largeur. C'est la seule règle de cible de touche du
   fichier ; la version 1 du livrable en portait cinq combinaisons différentes
   (40 px sur `(pointer: coarse)`, 40 px sous 576 px, 44 px sous 768 px, 44 px
   sous 992 px, et un jeton à 44 px), si bien que sur une tablette de 900 px
   trois écrans sur quatre conservaient des cibles sous le seuil.

   Critère visé : WCAG 2.5.8 (AA, 24 px), largement dépassé. 2.5.5 (AAA, 44 px)
   n'est PAS revendiqué : il n'est pas atteignable sans agrandir des boîtes.

   Déclencheur = LARGEUR et non `(pointer: coarse)`. Deux raisons : un iPad au
   clavier rapporte `pointer: fine` tout en ayant besoin de cibles
   confortables, et une requête sur le pointeur est invérifiable depuis un
   navigateur de bureau, donc non testable. La largeur, elle, se mesure.

   E3 : `min-height`/`min-width` uniquement sur des boutons, des champs et des
   liens de pagination — des feuilles de l'arbre. Un plancher ne peut rien
   rogner ; il ne peut que faire grandir vers le bas, dans le flux normal.

   NON MESURÉ : les cibles de touche de l'espace client sous 992 px. Le chiffre
   de 15 cibles sous 32 px et le hamburger à 24 × 24 px viennent du relevé de la
   console d'administration. Plausible ici, pas établi.
   ========================================================================== */

@media (max-width: 991.98px) {
    .wf-client body .btn.btn-sm,
    .wf-client body .btn.btn-icon.btn-sm,
    .wf-client body .form-control.form-control-sm,
    .wf-client body .form-select.form-select-sm,
    .wf-client body .pagination .page-item .page-link {
        min-height: var(--wf-touch-target);
        min-width: var(--wf-touch-target);
    }

    /* Un champ de saisie n'a pas de largeur minimale à imposer : il occupe
       déjà sa colonne, et un min-width l'empêcherait de se réduire. */
    .wf-client body .form-control.form-control-sm,
    .wf-client body .form-select.form-select-sm {
        min-width: 0;
        font-size: var(--wf-text-body);
    }

    /* Interrupteurs de consentement : le rail fait 20 × 30 px, imposé par
       `.h-20px`/`.w-30px` en `!important`. On n'y touche pas — on agrandit la
       cible réelle, qui est le LIBELLÉ (il porte `htmlFor`, donc il commute
       l'interrupteur). 12 + 12 + ≈ 17 px de ligne ≈ 41 px. */
    .wf-client body .form-check .form-check-label {
        padding-top: 12px !important;
        padding-bottom: 12px !important;
    }

    /* Le lien du nom de contact est en flux inline dans un `.flex-grow-1` : lui
       donner 40 px de hauteur sans toucher à `display` ni à `position` se fait
       par la hauteur de ligne, qui reflowe le bloc vers le bas sans le
       déplacer dans l'arbre. */
    .wf-client body .col-xl-4.card .text-gray-800.fs-7 {
        line-height: var(--wf-touch-target);
        padding-right: var(--wf-space-xs);
    }

    /* Le titre de carte reste à 20 px : c'est la seule ancre de hiérarchie de
       la page, et 20 px sur 390 px de large tient sur deux lignes au pire. On
       ne le réduit pas « pour faire mobile ». */
}


/* ==========================================================================
   20. MOTION

   On ne reprend de la charte que les courbes : ses animations pilotées au
   défilement sont une grammaire de site vitrine, sans objet dans un outil
   consulté plusieurs heures par jour.

   `transition` ne modifie ni la boîte ni l'arbre : sans danger. Aucune règle
   n'utilise `transition: all` — c'est un anti-patron banni par la charte, et
   une source de reflow imprévisible.

   CE BLOC DOIT RESTER L'AVANT-DERNIER DU FICHIER (avant l'impression) : la
   garde `prefers-reduced-motion` ne protège que ce qui la précède ou ce qui a
   une spécificité inférieure. Toute règle ajoutant une `transition` ou une
   `animation` doit être placée AVANT.
   ========================================================================== */

.wf-client body .btn,
.wf-client body .badge,
.wf-client body .menu-link,
.wf-client body .nav-link,
.wf-client body .page-link,
.wf-client body .dropdown-item,
.wf-client body .form-control,
.wf-client body .form-select,
.wf-client body .rs-btn {
    transition:
        background-color var(--wf-duration-fast) var(--wf-ease-out-quart),
        border-color     var(--wf-duration-fast) var(--wf-ease-out-quart),
        color            var(--wf-duration-fast) var(--wf-ease-out-quart),
        box-shadow       var(--wf-duration-base) var(--wf-ease-out-soft);
}

.wf-client body .card {
    transition: box-shadow var(--wf-duration-base) var(--wf-ease-out-soft);
}

/* Respect de la préférence système.
   LES INDICATEURS DE CHARGEMENT SONT EXCLUS. La version 1 de cette feuille
   appliquait `animation-duration: 0.01ms !important` et
   `animation-iteration-count: 1 !important` à `.wf-client body *`, ce qui
   atteignait aussi `.spinner-border`, `.spinner-grow` (vérifiés dans le
   bundle, tous deux en `animation … infinite`) et le rotor RSuite. Pour un
   utilisateur ayant la préférence activée, un chargement en cours devenait
   indistinguable d'une interface figée. Une implémentation correcte de ce
   motif exclut les indicateurs de progression : leur rotation est une
   information d'état, pas une décoration. */
@media (prefers-reduced-motion: reduce) {
    .wf-client body *,
    .wf-client body *::before,
    .wf-client body *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
    }

    /* RÉTABLISSEMENT EXPLICITE DES INDICATEURS DE CHARGEMENT.
       Écrit en règle séparée, plus spécifique et placée après, plutôt qu'en
       `:not()` sur le sélecteur universel : une liste de `:not()` contenant un
       sélecteur composé n'est pas également prise en charge partout, et un
       échec de parsing y invaliderait la garde entière.
       Durées reprises du bundle : Bootstrap déclare `.75s linear infinite` pour
       .spinner-border et .spinner-grow, RSuite `.6s infinite linear` pour son
       rotor. */
    .wf-client body .spinner-border,
    .wf-client body .spinner-grow,
    .wf-client body .rs-loader-spin::before,
    .wf-client body .rs-loader-spin::after {
        animation-duration: 0.75s !important;
        animation-iteration-count: infinite !important;
    }

    /* Les quatre coins du QR terminent leur animation en `forwards` : les
       neutraliser les laisse à 0 × 0, donc invisibles. C'est le comportement
       voulu — ils sont décoratifs. */
    .wf-client body .reviews-qr-scanline,
    .wf-client body .reviews-qr-corner {
        animation: none;
    }
}


/* ==========================================================================
   21. IMPRESSION — DERNIER BLOC DU FICHIER
   Les couleurs de marque coûtent de l'encre et ne servent à rien sur papier.
   Aucune propriété de mise en page n'est touchée.
   ========================================================================== */

@media print {
    .wf-client body {
        background-color: #FFFFFF;
    }

    .wf-client body .card {
        box-shadow: none;
        border-color: var(--wf-ink-300);
    }

    .wf-client body .content .card,
    .wf-client body .card.bgi-position-x-end {
        box-shadow: 0 0 0 1px var(--wf-ink-300);
    }

    .wf-client body a {
        color: var(--wf-ink-900);
    }

    .wf-client body .badge {
        box-shadow: inset 0 0 0 1px var(--wf-ink-500);
        color: var(--wf-ink-900);
    }
}

/* ==========================================================================
   FIN — webforce-client.css

   AVANT MISE EN PRODUCTION, dans cet ordre :
     1. Déployer les six fichiers de police (§5). Sans eux, la refonte
        typographique n'a pas lieu et les réglages optiques sont appliqués à la
        mauvaise police.
     2. Exécuter le contrôle de non-régression de mise en page : capturer chaque
        écran à 1440 px et 390 px, avec et sans `wf-client`, et comparer
        `document.documentElement.scrollWidth`. Toute différence POSITIVE
        signale une règle qui a élargi le document. Les quatre écrans tabulaires
        sont les plus exposés (§16).
     3. Vérifier la position de `.rs-theme-light` dans l'arbre (§3). Si elle est
        posée sur un conteneur intérieur au <body>, dupliquer le bloc §3 sur
        `.wf-client body .rs-theme-light`.
     4. Vérifier à l'œil les graphiques ApexCharts et les rs-table : ces deux
        composants mesurent leur texte en JavaScript, et un changement de police
        peut produire du chevauchement ou de la troncature sans erreur console.
   ========================================================================== */


/* ==========================================================================
   webforce-client-menu.css — refonte du menu latéral de l'espace client
   --------------------------------------------------------------------------
   ADDENDUM à webforce-client.css. À fusionner dans ce fichier avant livraison :
   la consigne d'installation est « une seule feuille ». Séparé ici pour être
   relisible et testable isolément.

   POURQUOI CET ADDENDUM EXISTE — trois défauts mesurés sur la production que
   la couche principale n'a pas traités :

   1. L'item ACTIF reste au bleu Metronic #009EF7 sur son fond #EEF3FF, soit
      **2,61:1**. C'est un échec du plancher 4,5:1, sur l'élément qui indique
      à l'utilisateur où il se trouve. C'est le défaut le plus grave du menu.
   2. Les icônes sont incohérentes PAR CONSTRUCTION : sur 10 items, 9 sont des
      SVG à tracé (`fill: none`) et 1 est un SVG plein (`fill: rgb(0,0,0)`).
      Elles ne viennent pas du même jeu. C'est la cause de l'impression de
      bric-à-brac, et aucune couleur de texte n'y changera quoi que ce soit.
   3. Hauteur d'item **39 px**, sous le minimum praticable au doigt, et
      `font-weight: 400` pour TOUS les items — actif compris. Aucune
      différenciation par la graisse.

   Tous les contrastes ci-dessous sont calculés (WCAG 2.x), pas estimés.
   ========================================================================== */


/* --------------------------------------------------------------------------
   1. LA COLONNE
   Le blanc est conservé : le passer en sombre déséquilibrerait la page, dont
   le fond est déjà clair, et obligerait à retraiter le logo. On borde plutôt.
   -------------------------------------------------------------------------- */

.wf-client .aside {
    background-color: #FFFFFF;
    /* Un filet, pas une ombre : à 265 px de large, une ombre portée crée une
       bande grise sur toute la hauteur de l'écran. */
    border-right: 1px solid var(--wf-ink-200, #E7EAF2);
    box-shadow: none;
}

/* Respiration sous le logo, pour que le premier item ne colle pas à la marque. */
.wf-client .aside .aside-logo,
.wf-client .aside > a:first-child {
    padding-bottom: 20px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--wf-ink-100, #EEF0F5);
}


/* --------------------------------------------------------------------------
   2. LES ITEMS AU REPOS
   -------------------------------------------------------------------------- */

.wf-client .aside .menu-item > .menu-link,
.wf-client .aside a.menu-link,
.wf-client .aside nav a {
    /* 44 px : le minimum praticable au doigt (WCAG 2.5.5). On y arrive par le
       padding et non par une hauteur fixe, pour qu'un libellé qui passe sur
       deux lignes s'étende au lieu d'être rogné. */
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
    padding-left: 16px;
    padding-right: 12px;
    margin-bottom: 2px;

    border-radius: var(--wf-radius-md, 8px);

    /* ink-700 sur blanc = 9,39:1. */
    color: var(--wf-ink-700, #3D4757) !important;
    font-size: var(--wf-text-body, 14px);
    font-weight: 500;
    letter-spacing: -0.006em;

    /* Jamais `all` : sur une colonne toujours affichée, animer des propriétés
       de mise en page coûte un recalcul à chaque survol. */
    transition-property: background-color, color;
    transition-duration: 140ms;
    transition-timing-function: cubic-bezier(0.25, 1, 0.5, 1);

    /* Réserve la place du repère d'état actif, pour que rien ne se décale
       quand on change de page. */
    box-shadow: inset 3px 0 0 0 transparent;
}


/* --------------------------------------------------------------------------
   3. LES ICONES — le cœur du problème
   On ne peut pas remplacer un jeu d'icônes depuis du CSS. On peut en revanche
   forcer sa COHERENCE : ramener tracé et remplissage à `currentColor`, de
   sorte que les 10 icônes prennent la couleur de leur libellé, quelle que
   soit leur nature d'origine.
   -------------------------------------------------------------------------- */

.wf-client .aside a svg {
    width: 20px;
    height: 20px;
    flex: 0 0 20px;
}

/* Icônes à tracé (9 items sur 10). */
.wf-client .aside a svg [stroke]:not([stroke="none"]),
.wf-client .aside a svg[stroke]:not([stroke="none"]) {
    stroke: currentColor;
}

/* Icône pleine (« Journal des appels », seule de son espèce). Le
   :not([fill="none"]) est indispensable : sans lui, on remplirait les tracés
   des 9 autres et elles deviendraient des taches. */
.wf-client .aside a svg [fill]:not([fill="none"]),
.wf-client .aside a svg[fill]:not([fill="none"]) {
    fill: currentColor;
}

/* Couleur de l'icône au repos : ink-500, 3,78:1 sur blanc. Une icône est un
   élément non textuel, son plancher est 3:1 — et la garder plus claire que le
   libellé maintient la hiérarchie sans descendre sous la conformité. */
.wf-client .aside .menu-item:not(.active):not(.here) > .menu-link svg,
.wf-client .aside a:not(.active) svg {
    color: var(--wf-ink-500, #7A8494);
    opacity: 1;   /* on annule tout affaiblissement par opacité : il ferait
                     chuter le contraste sans que ce soit visible dans le code */
}


/* --------------------------------------------------------------------------
   4. SURVOL
   Un cran sous l'état actif, pour que les deux restent distinguables.
   -------------------------------------------------------------------------- */

.wf-client .aside .menu-item:not(.active) > .menu-link:hover,
.wf-client .aside a:not(.active):hover {
    background-color: var(--wf-ink-50, #FAFBFD);
    color: var(--wf-ink-900, #151A24) !important;
}

.wf-client .aside .menu-item:not(.active) > .menu-link:hover svg,
.wf-client .aside a:not(.active):hover svg {
    color: var(--wf-ink-700, #3D4757);
}


/* --------------------------------------------------------------------------
   5. ETAT ACTIF — la correction du défaut 2,61:1
   Trois marqueurs redondants : fond, graisse, repère latéral. La redondance
   est voulue — WCAG 1.4.1 interdit de porter une information par la seule
   couleur, et un menu doit rester lisible en niveaux de gris.
   -------------------------------------------------------------------------- */

/* La marque de l'état actif est `active` sur le <a class="menu-link">, PAS sur
   le `.menu-item` parent — vérifié dans le DOM de production.

   La spécificité est volontairement haute. Deux règles concurrentes existent :
   Metronic pose `.menu-state-title-primary .menu-item .menu-link.active`
   (5 classes) et la couche principale de la refonte pose le FOND en brand-50
   tout en laissant la COULEUR au bleu Metronic #009EF7 — un oubli, mesuré à
   2,61:1. Une règle en (0,3,1) perdait même avec `!important`. On réplique
   donc la forme complète du sélecteur Metronic, préfixée par la bascule. */
.wf-client body .aside .menu .menu-item > .menu-link.active,
.wf-client body .aside .menu .menu-item > .menu-link.here,
.wf-client body .aside .menu .menu-item > .menu-link[aria-current="page"] {
    background-color: #EEF3FF !important;   /* brand-50 */
    color: #17368F !important;              /* brand-700 — 9,64:1 sur brand-50 */
    font-weight: 600 !important;
    box-shadow: inset 3px 0 0 0 #2453D4 !important;   /* brand-500 */
}

.wf-client body .aside .menu .menu-item > .menu-link.active svg,
.wf-client body .aside .menu .menu-item > .menu-link.here svg,
.wf-client body .aside .menu .menu-item > .menu-link[aria-current="page"] svg {
    color: #2453D4 !important;              /* brand-500 — 5,79:1 sur brand-50 */
}

/* Les valeurs sont écrites en hexadécimal et non en var() : aucun jeton
   --wf-* n'est déclaré sur cette page (vérifié — la couche principale nomme
   ses variables autrement), donc un var() n'apporterait qu'une indirection
   trompeuse. À réunifier lors de la fusion des deux feuilles. */


/* --------------------------------------------------------------------------
   6. FOCUS CLAVIER
   Le menu est le premier ensemble tabulable de la page. Sans contour visible,
   la navigation au clavier y est impossible à suivre.
   -------------------------------------------------------------------------- */

.wf-client .aside a:focus-visible {
    outline: 2px solid var(--wf-brand-500, #2453D4);
    outline-offset: -2px;   /* vers l'intérieur : à 265 px de large, un offset
                               positif serait rogné par le bord de la colonne */
    border-radius: var(--wf-radius-md, 8px);
}


/* --------------------------------------------------------------------------
   7. MOBILE
   -------------------------------------------------------------------------- */

@media (max-width: 991.98px) {
    /* Au doigt, on remonte à 48 px : la colonne devient la cible principale. */
    .wf-client .aside .menu-item > .menu-link,
    .wf-client .aside a.menu-link,
    .wf-client .aside nav a {
        min-height: 48px;
        padding-top: 12px;
        padding-bottom: 12px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .wf-client .aside .menu-item > .menu-link,
    .wf-client .aside a.menu-link,
    .wf-client .aside nav a {
        transition-duration: 0.01ms;
    }
}


/* ==========================================================================
   §N — STYLES EN LIGNE POSES PAR REACT
   --------------------------------------------------------------------------
   Releve sur /rapport le 2026-07-30 : 45 elements portent un attribut style=,
   dont 13 avec un background-color en dur. Un style en ligne bat toute regle
   de feuille — SAUF une regle `!important`. C'est le seul cas ou `!important`
   est la solution correcte et non un contournement, d'ou son usage ici.

   Contrastes mesures avant correction :
     #FA459C  2,86:1 (cartes) et 3,28:1 (bouton)   -> ECHEC
     #55B5F7  2,25:1 (bouton « Votre compte »)     -> ECHEC
     #CDECFF  7,62:1   #8BCEFB  5,51:1   #A6DAFF  6,30:1  -> conformes mais
     hors palette : remappes pour la coherence, pas pour l'accessibilite.

   Les selecteurs ciblent la VALEUR de l'attribut style. C'est fragile par
   nature : si le composant React change sa teinte, la regle ne matchera plus.
   Ces couleurs doivent a terme sortir du JSX. Liste tenue a jour ici.
   ========================================================================== */

/* --- Le rose #FA459C ---------------------------------------------------- */

/* Cartes : surface claire de marque, texte sombre. On ne remplace pas une
   couleur vive par une autre couleur vive — la hierarchie se fait par la
   surface, comme sur le tableau de bord. */
.wf-client [style*="rgb(250, 69, 156)"]:not(.btn):not(a) {
    background-color: #EEF3FF !important;   /* brand-50 */
    border: 1px solid #DBE5FF !important;   /* brand-100 */
}

.wf-client [style*="rgb(250, 69, 156)"]:not(.btn):not(a) * {
    color: #0A0D14 !important;              /* ink-950 — 18,1:1 sur brand-50 */
}

/* Boutons : eux doivent rester saillants, donc fond plein de marque et texte
   blanc. brand-500 sous blanc donne 6,44:1, contre 3,28:1 avant. */
.wf-client a.btn[style*="rgb(250, 69, 156)"],
.wf-client button[style*="rgb(250, 69, 156)"],
.wf-client .btn[style*="rgb(250, 69, 156)"] {
    background-color: #2453D4 !important;   /* brand-500 */
    color: #FFFFFF !important;
    border-color: #2453D4 !important;
}

/* --- Le bleu #55B5F7, uniquement porte par un bouton -------------------- */
.wf-client [style*="rgb(85, 181, 247)"] {
    background-color: #2453D4 !important;   /* 2,25:1 -> 6,44:1 */
    color: #FFFFFF !important;
    border-color: #2453D4 !important;
}

.wf-client a.btn[style*="rgb(85, 181, 247)"] * ,
.wf-client .btn[style*="rgb(85, 181, 247)"] * {
    color: #FFFFFF !important;
}

/* --- Les bleus pales : remappes vers la marque -------------------------- */
/* Deja conformes en contraste. Le remap ne sert que la coherence de marque,
   et il ameliore les ratios au passage puisque les cibles sont plus claires. */
.wf-client [style*="rgb(205, 236, 255)"] {
    background-color: #EEF3FF !important;   /* #CDECFF -> brand-50 */
}

.wf-client [style*="rgb(139, 206, 251)"] {
    background-color: #DBE5FF !important;   /* #8BCEFB -> brand-100 */
}

.wf-client [style*="rgb(166, 218, 255)"] {
    background-color: #EEF3FF !important;   /* #A6DAFF -> brand-50 */
}

/* Les intitules en capitales de ces cartes de rapport sont en ink-600 par
   defaut, soit 5,3:1 sur brand-50 : conforme, on n'y touche pas. */

/* --- Texte a l'interieur des boites remappees ---------------------------
   Correction d'un defaut que le remap de fond a lui-meme cree : plusieurs de
   ces boites avaient un fond soutenu et un texte blanc en ligne. En eclaircis-
   sant le fond vers brand-50 / brand-100, le texte blanc est tombe a 1,26:1 —
   illisible. Mesure faite apres coup, sur la production.

   On force donc la couleur du texte par CONTENEUR, et non en ciblant la valeur
   de l'attribut style du texte : le format exact de cet attribut (rgb(), #hex,
   nom) est inconnu et varie d'un composant a l'autre. Cibler le conteneur
   couvre les trois cas d'un coup — le rose #FA459C (2,95:1), le bleu #1096F3
   (2,83:1) et le blanc (1,26:1).

   Les boutons sont exclus : ils gardent leur fond plein de marque et donc leur
   texte blanc, qui y est a 6,44:1. */
.wf-client [style*="rgb(205, 236, 255)"] *:not(.btn):not(.btn *),
.wf-client [style*="rgb(139, 206, 251)"] *:not(.btn):not(.btn *),
.wf-client [style*="rgb(166, 218, 255)"] *:not(.btn):not(.btn *),
.wf-client [style*="rgb(238, 243, 255)"] *:not(.btn):not(.btn *) {
    color: #0A0D14 !important;   /* ink-950 — 17,5:1 sur brand-50 */
}

/* Les valeurs chiffrees restent les elements dominants : la hierarchie se fait
   par la taille, jamais par la teinte. */
.wf-client [style*="rgb(205, 236, 255)"] .fs-2x,
.wf-client [style*="rgb(139, 206, 251)"] .fs-2x,
.wf-client [style*="rgb(166, 218, 255)"] .fs-2x {
    font-weight: 600 !important;
}

/* --- Correctifs de la passe precedente ----------------------------------
   Trois defauts mesures apres coup, dont deux introduits par mes propres
   regles. Consignes ici plutot que corriges en silence.

   1. Les regles « conteneur * » ne touchaient QUE les descendants. Or plusieurs
      de ces boites portent le fond ET la couleur de texte sur le MEME element
      (<div style="background:X; color:#FA459C">appels</div>). Le rose restait
      donc a 2,95:1. Il faut inclure l'element lui-meme.

   2. Les conteneurs remappes vers brand-500 (fond SOMBRE) recevaient un texte
      ink-950 par heritage de la regle « fond clair » : 3,02:1. Sur fond sombre
      le texte doit etre blanc. Cette regle vient donc APRES celle du fond
      clair, pour gagner a specificite egale.

   3. L'item de menu actif « Rapport » restait au bleu Metronic #009EF7
      (2,61:1) : ma regle exigeait un enfant DIRECT (`>`), ce qui echoue quand
      la structure du menu differe d'un ecran a l'autre. Variante sans `>`.
   -------------------------------------------------------------------------- */

/* 1 — l'element porteur du fond clair, pas seulement ses descendants */
.wf-client [style*="rgb(250, 69, 156)"]:not(.btn):not(a),
.wf-client [style*="rgb(205, 236, 255)"]:not(.btn):not(a),
.wf-client [style*="rgb(139, 206, 251)"]:not(.btn):not(a),
.wf-client [style*="rgb(166, 218, 255)"]:not(.btn):not(a) {
    color: #0A0D14 !important;
}

/* 2 — fonds sombres : texte blanc. Placee apres la regle du fond clair. */
.wf-client [style*="rgb(85, 181, 247)"],
.wf-client [style*="rgb(85, 181, 247)"] *,
.wf-client a.btn[style*="rgb(250, 69, 156)"],
.wf-client a.btn[style*="rgb(250, 69, 156)"] *,
.wf-client .btn[style*="rgb(250, 69, 156)"],
.wf-client .btn[style*="rgb(250, 69, 156)"] * {
    color: #FFFFFF !important;   /* 6,44:1 sur brand-500 */
}

/* 3 — item de menu actif, variante sans enfant direct */
.wf-client .aside .menu-link.active,
.wf-client .aside a.active {
    background-color: #EEF3FF !important;
    color: #17368F !important;   /* brand-700 — 9,64:1 */
    font-weight: 600 !important;
    box-shadow: inset 3px 0 0 0 #2453D4 !important;
}

.wf-client .aside .menu-link.active svg,
.wf-client .aside a.active svg {
    color: #2453D4 !important;
}

/* --- Trois cibles precisees par inspection du DOM -----------------------
   Les regles precedentes visaient les mauvais elements. Ascendances relevees
   sur la production plutot que supposees.

   1. LE LIBELLE DU MENU ACTIF. Le texte est dans un `span.menu-title` ENFANT
      du <a class="menu-link active">, et Metronic lui pose sa propre couleur.
      Colorer le <a> ne change donc rien au libelle : il restait au bleu
      #009EF7, soit 2,61:1 sur brand-50.
      A noter : mes mesures precedentes annonçaient 9,64:1 parce qu'elles
      lisaient la couleur du <a> et non celle du span. Le libelle n'avait
      jamais ete corrige.

   2. Le bouton « Telecharger mon rapport » : son libelle est en ink-500 sur
      ink-100, soit 3,32:1. Sous le plancher de 4,5:1 applicable au texte.

   3. `text-gray-100` est une classe Metronic prevue pour les FONDS SOMBRES.
      Le remap de couleur l'a assombrie vers ink-950, et elle se retrouve a
      3,02:1 sur brand-500. Ces utilitaires clairs doivent rester clairs.
   -------------------------------------------------------------------------- */

/* 1 — le libelle, l'icone et la puce de l'item actif */
.wf-client .aside .menu-link.active .menu-title,
.wf-client .aside .menu-link.active span,
.wf-client .aside a.active .menu-title {
    color: #17368F !important;   /* brand-700 — 9,64:1 sur brand-50 */
    font-weight: 600 !important;
}

/* Les items INACTIFS : leur libelle est aussi dans un span, meme probleme. */
.wf-client .aside .menu-link:not(.active) .menu-title {
    color: #3D4757 !important;   /* ink-700 — 9,39:1 sur blanc */
}

.wf-client .aside .menu-link:not(.active):hover .menu-title {
    color: #151A24 !important;   /* ink-900 */
}

/* 2 — libelles de boutons clairs : ink-700 au lieu d'ink-500 */
.wf-client .btn.btn-flex span,
.wf-client .btn.btn-light span,
.wf-client .btn.btn-sm span:not([class*="text-white"]) {
    color: #3D4757 !important;   /* 9,39:1 sur blanc, 8,0:1 sur ink-100 */
}

/* 3 — les utilitaires de gris CLAIR restent clairs : ils sont faits pour les
   fonds sombres, les assombrir inverse leur raison d'etre. */
.wf-client .text-gray-100,
.wf-client .text-gray-200,
.wf-client .text-gray-100 *,
.wf-client .text-gray-200 * {
    color: #FFFFFF !important;   /* 6,44:1 sur brand-500 */
}

.wf-client .text-gray-300 {
    color: #DBE5FF !important;   /* brand-100 — 5,11:1 sur brand-500 */
}

/* --- Rattrapage de specificite ------------------------------------------
   La couche generee a ete reecrite en `.wf-client body ...` (3 classes) lors du
   deplacement de la bascule sur <html>. Mes regles en `.wf-client .classe`
   (2 classes) perdaient donc, malgre `!important` des deux cotes — a priorite
   egale, c'est la specificite qui tranche.

   On repasse les regles concernees sous la meme forme. Elles sont plus bas
   dans le fichier, donc a specificite egale elles gagnent.
   -------------------------------------------------------------------------- */
.wf-client body .text-gray-100,
.wf-client body .text-gray-200,
.wf-client body .text-gray-100 *,
.wf-client body .text-gray-200 * {
    color: #FFFFFF !important;
}

.wf-client body .text-gray-300 {
    color: #DBE5FF !important;
}

.wf-client body .aside .menu-link.active .menu-title,
.wf-client body .aside a.active .menu-title {
    color: #17368F !important;
    font-weight: 600 !important;
}

.wf-client body .aside .menu-link:not(.active) .menu-title {
    color: #3D4757 !important;
}

.wf-client body .btn.btn-flex span,
.wf-client body .btn.btn-light span {
    color: #3D4757 !important;
}

/* ==========================================================================
   §N+1 — LA PALETTE METRONIC 8, QUI AVAIT ETE MANQUEE
   --------------------------------------------------------------------------
   Diagnostic pose apres l'audit des 11 ecrans : la couche de remap a ete
   construite sur la palette Metronic 7 (#3699FF, #1BC5BD, #F64E60, #FFA800,
   #8950FC). Or l'espace client tourne sur Metronic 8, dont les jetons sont
   DIFFERENTS : #009EF7, #50CD89, #F1416C, #FFC700, #7239EA. Aucun n'etait
   remappe — d'ou le bleu residuel sur /statistiques/s-seo (2,90:1) et sur le
   libelle du menu actif (2,61:1).

   Contrastes sur blanc, calcules :
     #009EF7 -> 2,90:1  ECHEC      remplace par brand-500  6,44:1
     #50CD89 -> 1,95:1  ECHEC      remplace par signal-700 5,34:1
     #FFC700 -> 1,63:1  ECHEC      remplace par rating-700 6,57:1
     #F1416C -> 3,66:1  ECHEC      remplace par error-500  5,62:1
     #7239EA -> 6,26:1  conforme   remplace par brand-600 pour la coherence

   Les FONDS gardent la teinte -500 (plancher 3:1, non textuel) ; seuls les
   TEXTES passent aux variantes sombres. Meme regle que sur la console d'admin.
   ========================================================================== */

/* --- Textes ------------------------------------------------------------- */
html.wf-client body .text-primary,
html.wf-client body a.text-primary,
html.wf-client body .text-hover-primary:hover {
    color: #2453D4 !important;
}

html.wf-client body .text-success { color: #0B7A55 !important; }
html.wf-client body .text-warning { color: #7A5709 !important; }
html.wf-client body .text-danger  { color: #C62828 !important; }
html.wf-client body .text-info    { color: #1C43B4 !important; }

/* --- Fonds pleins : la teinte -500 reste, le texte passe en blanc -------- */
html.wf-client body .bg-primary { background-color: #2453D4 !important; }
html.wf-client body .bg-success { background-color: #0E9F6E !important; }
html.wf-client body .bg-warning { background-color: #B5830F !important; }
html.wf-client body .bg-danger  { background-color: #C62828 !important; }
html.wf-client body .bg-info    { background-color: #4F7BE8 !important; }

html.wf-client body .bg-primary, html.wf-client body .bg-primary *,
html.wf-client body .bg-success, html.wf-client body .bg-success *,
html.wf-client body .bg-danger,  html.wf-client body .bg-danger * {
    color: #FFFFFF !important;
}

/* --- Fonds « light » : surface claire, texte sombre --------------------- */
html.wf-client body .bg-light-primary { background-color: #EEF3FF !important; }
html.wf-client body .bg-light-success { background-color: #E6F6EF !important; }
html.wf-client body .bg-light-warning { background-color: #FDF5E3 !important; }
html.wf-client body .bg-light-danger  { background-color: #FBEAEA !important; }
html.wf-client body .bg-light-info    { background-color: #EEF3FF !important; }

/* --- Petits textes gris : ink-500 est sous le plancher a petite taille --- */
/* ink-500 donne 3,78:1 : conforme pour un bord, PAS pour du texte. */
html.wf-client body .text-gray-500,
html.wf-client body .text-muted,
html.wf-client body .fs-7.text-gray-500,
html.wf-client body .btn .text-gray-500 {
    color: #5A6373 !important;   /* ink-600 — 5,32:1 sur blanc */
}

/* --- Les gris CLAIRS restent clairs (fonds sombres) --------------------- */
/* Specificite volontairement elevee : la couche generee les cible en
   `.wf-client body ...`, il faut passer au-dessus. */
html.wf-client body .card-body .text-gray-100,
html.wf-client body .card-body .text-gray-100.fw-bold,
html.wf-client body .text-gray-100.fw-bold,
html.wf-client body .text-gray-100 * {
    color: #FFFFFF !important;
}

/* --- Badges blancs sur fond trop clair ---------------------------------- */
/* Releve sur /web-formulaires : blanc sur #B7C6CF = 1,75:1. */
html.wf-client body .badge[style*="rgb(183, 198, 207)"],
html.wf-client body [style*="rgb(183, 198, 207)"] {
    background-color: #5A6373 !important;   /* blanc dessus = 5,32:1 */
    color: #FFFFFF !important;
}

/* ==========================================================================
   §N+2 — /rapport EN MOBILE : 59 px de debordement
   --------------------------------------------------------------------------
   Seule page des 11 auditees a deborder a 390 px. Chaine de coupables mesuree :
     - une rangee a 508 px contenant « Telecharger mon rapport »
     - div.col-8.d-flex.justify-content-end a 339 px : un col-8 SANS point de
       rupture, qui reste donc a 8/12 meme sur telephone
     - div.container.card.card-custom a 490 px : le corps du rapport lui-meme

   Le corps du rapport est concu pour l'impression et le PDF. On ne le reflow
   donc PAS : on le rend scrollable horizontalement. C'est la leçon de la
   console d'administration — forcer un reflow sur une mise en page a largeur
   fixe rend le contenu inatteignable, alors qu'un conteneur scrollable le
   garde accessible. Le defaut devient cosmetique au lieu d'etre bloquant.

   Les colonnes sans point de rupture, elles, se reflowent : c'est leur bug.
   ========================================================================== */

@media (max-width: 991.98px) {

    /* Le corps du rapport : scrollable, jamais amputé. */
    html.wf-client body .container.card.card-custom {
        max-width: 100% !important;
        overflow-x: auto !important;
        -webkit-overflow-scrolling: touch;
    }

    /* Les cols sans point de rupture repassent en pleine largeur. */
    html.wf-client body .col-8.d-flex.justify-content-end {
        flex: 0 0 100% !important;
        max-width: 100% !important;
    }

    html.wf-client body .col-8.d-flex.justify-content-end {
        justify-content: flex-start !important;
        margin-top: 12px;
    }

    /* Les rangees a largeur fixe autorisent le retour a la ligne. */
    html.wf-client body .d-flex.flex-wrap.flex-sm-nowrap {
        flex-wrap: wrap !important;
    }

    /* Le selecteur de periode ne doit pas pousser la page. */
    html.wf-client body .rs-picker,
    html.wf-client body .rs-picker-toggle {
        max-width: 100% !important;
    }
}
