/* FontAwesome Free 7.3.0 auto-hébergé (CSS + webfonts dans assets/), plus de
   dépendance au kit CDN kit.fontawesome.com (cf. audit best-practices Lighthouse
   + durcissement CSP). L'@import est résolu par AssetMapper (CssAssetUrlCompiler)
   qui réécrit à la fois le chemin de cet import et les url() internes du fichier
   importé (assets/styles/fontawesome.css -> ../webfonts/fa-*.woff2). */
@import "fontawesome-t_1P82Q.css";

/* Police Inter auto-hébergée (variable, latin) - RGPD + sans render-blocking */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("../fonts/inter-Gz7AlRl.woff2") format('woff2');
}

/* ---------- Replis à métriques ajustées pour Inter.

   Mesuré en ligne le 2026-08-16 sur la production : le swap d'Inter déplaçait
   le contenu de /faq (CLS 0,051) et /documentation (0,046), et Lighthouse en
   attribuait la plus grosse part - 0,033 et 0,028 - à « Web font loaded ».
   Le `preload` de base.html.twig ne suffit pas et ne pouvait pas suffire : il
   avance l'arrivée du fichier (~2,2 s sous throttling mobile), il ne réconcilie
   pas les métriques d'Inter avec celles du repli système. Tant qu'elles
   diffèrent, la bascule repeint le texte à une autre taille et tout ce qui suit
   descend.

   Chaque repli reçoit donc les overrides qui lui donnent l'encombrement exact
   d'Inter. Le texte occupe la même place avant et après le swap, et le
   décalage disparaît sans rien changer au rendu final.

   VALEURS CALCULÉES, JAMAIS SAISIES À LA MAIN : `@capsizecss/unpack` lu sur
   `assets/fonts/inter.woff2` (unitsPerEm 2048, ascent 1984, descent -494,
   lineGap 0, xWidthAvg 978 à la graisse 400), puis `createFontStack` de
   `@capsizecss/core`. **À recalculer si le fichier de police change** - un
   sous-ensemble regénéré suffit à déplacer `xWidthAvg`, donc `size-adjust`.
   `testInterFallbackMetricsMatchTheShippedFont` tient l'empreinte du fichier
   et casse pour le signaler.

   DEUX FACES PAR REPLI, ET C'EST LE POINT LE PLUS FACILE À PERDRE. Un repli
   sans `font-weight` ne porte qu'une graisse 400 ; tout ce que le premier rendu
   affiche en 600 (`.btn`), 700 (les titres) ou 800 (`.mkt-brand`) tombait donc
   dessus et recevait un GRAS SYNTHÉTIQUE. Or `size-adjust` est calculé sur le
   Regular : il ne compense pas la largeur ajoutée par cette synthèse, et le
   texte gras restait 3 % trop étroit (4,2 % en 800) face au vrai Inter. Le
   titre pouvait donc encore se recasser au swap - exactement le décalage que
   tout ce bloc existe pour supprimer. Chaque repli déclare donc une face
   `100 500` calée sur Inter 400 et une face `600 900` calée sur Inter 700,
   pointant la VRAIE graisse installée (`local('Segoe UI Bold')`...).

   Le poids 700 comme cible de la plage 600-900 : c'est celui des titres, de
   loin le plus présent. Le 600 et le 800 gardent un écart résiduel d'environ
   1 %, contre 2 à 4,2 % avant.

   `system-ui` et `-apple-system` restent en fin de pile et non en tête :
   San Francisco n'est pas atteignable par `local()`, donc on ne peut pas lui
   ajuster ses métriques. macOS et iOS prennent le repli « Helvetica Neue »
   ajusté, présent chez eux d'origine. Lui seul n'a PAS de face `600 900` :
   Helvetica Neue est propriétaire Apple, `@capsizecss/metrics` n'en publie que
   le Regular, et on ne peut pas mesurer son Bold sur un poste Windows. Inventer
   ses métriques serait pire que la synthèse qu'on retire ailleurs.

   Les métriques verticales d'Inter (ascent, descent, lineGap) ne bougent pas
   le long de l'axe `wght` : seul `xWidthAvg` change d'une graisse à l'autre.
   D'où des `ascent-override` / `descent-override` qui ne diffèrent entre les
   deux faces que par le `size-adjust` qui les divise.

   MAINTENANCE : ce bloc et la pile `--font` sont dupliqués dans
   `templates/_partials/_critical_public.css.twig`, qui est celui qui compte
   pour le premier rendu des pages publiques. Les deux se modifient
   ensemble. ---------- */
@font-face {
    font-family: "Inter Fallback Segoe UI";
    src: local('Segoe UI'), local('SegoeUI');
    font-weight: 100 500;
    ascent-override: 89.9412%;
    descent-override: 22.3946%;
    size-adjust: 107.7093%;
}
@font-face {
    font-family: "Inter Fallback Segoe UI";
    src: local('Segoe UI Bold'), local('SegoeUI-Bold');
    font-weight: 600 900;
    ascent-override: 93.5113%;
    descent-override: 23.2836%;
    size-adjust: 103.5971%;
}
@font-face {
    font-family: "Inter Fallback Roboto";
    src: local('Roboto Regular'), local('Roboto-Regular');
    font-weight: 100 500;
    ascent-override: 90.2384%;
    descent-override: 22.4686%;
    size-adjust: 107.3546%;
}
@font-face {
    font-family: "Inter Fallback Roboto";
    src: local('Roboto Bold'), local('Roboto-Bold');
    font-weight: 600 900;
    ascent-override: 88.9943%;
    descent-override: 22.1589%;
    size-adjust: 108.8553%;
}
@font-face {
    font-family: "Inter Fallback Helvetica Neue";
    src: local('Helvetica Neue'), local('HelveticaNeue');
    ascent-override: 91.2883%;
    descent-override: 22.7301%;
    line-gap-override: 0%;
    size-adjust: 106.1198%;
}
@font-face {
    font-family: "Inter Fallback Arial";
    src: local('Arial'), local('ArialMT');
    font-weight: 100 500;
    ascent-override: 90.4365%;
    descent-override: 22.518%;
    line-gap-override: 0%;
    size-adjust: 107.1194%;
}
@font-face {
    font-family: "Inter Fallback Arial";
    src: local('Arial Bold'), local('Arial-BoldMT');
    font-weight: 600 900;
    ascent-override: 94.4723%;
    descent-override: 23.5229%;
    line-gap-override: 0%;
    size-adjust: 102.5432%;
}

/* ============================================================
   fx.diamontis.fr - Design system
   Inspiré de la charte Diamontis (intranet) : indigo #3937a3,
   cartes, sidebar, page-header, badges. Autonome (sans Bootstrap).
   ============================================================ */

:root {
    --primary: #3937a3;
    --primary-rgb: 57, 55, 163;
    --primary-600: #322f93;
    --primary-700: #2a2880;
    --primary-50: #eeeefb;
    --accent: #15b8a6;

    --bg: #f5f6fa;
    --surface: #ffffff;
    --surface-2: #f8f9fc;
    --border: #e6e8ef;
    --text: #1c1f2b;
    --muted: #525a68;

    --success: #16a34a;
    --warning: #d97706;
    --danger: #dc2626;
    /* Teinte de survol de `.btn-danger`, sur le modèle de `--primary-600`. */
    --danger-700: #b91c1c;
    --info: #2563eb;

    --radius: 10px;
    --radius-lg: 16px;
    --shadow-sm: 0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .04);
    --shadow: 0 4px 16px rgba(16, 24, 40, .08);
    --shadow-lg: 0 18px 48px rgba(16, 24, 40, .14);
    --sidebar-w: 256px;

    /* Hauteur commune des contrôles alignés sur une même ligne (cf. .filter-bar).
       Vaut la hauteur naturelle d'un `.form-control` : padding .6rem en haut et
       en bas, texte à .95rem, plus les deux bordures. */
    --control-height: 2.6rem;

    /* Les quatre « Inter Fallback » précèdent les familles système : ce sont
       les mêmes polices, avec les métriques d'Inter. Cf. le bloc @font-face
       plus haut. */
    --font: "Inter", "Inter Fallback Segoe UI", "Inter Fallback Roboto", "Inter Fallback Helvetica Neue", "Inter Fallback Arial", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    color: var(--text);
    background: var(--bg);
    font-size: 15px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { opacity: .85; }

/* Un lien AU MILIEU D'UN TEXTE ne peut pas se signaler par la seule couleur.
   Entre le violet de marque (#3937a3) et le gris du texte courant, le contraste
   n'est que de 1,35:1 quand WCAG 1.4.1 en exige 3:1 : un daltonien ne voit
   simplement pas qu'il y a un lien. Le soulignement est le second signal,
   indépendant de la couleur.
   Ciblé sur les liens SANS CLASSE, c'est-à-dire ceux du corps de texte : les
   boutons, les cartes cliquables et la navigation portent tous une classe et
   gardent leur apparence. Constat Lighthouse `link-in-text-block` du 2026-08-04,
   relevé sur l'accueil ET les mentions légales - corrigé pour la famille
   entière, pas pour les trois liens cités. */
p > a:not([class]) { text-decoration: underline; text-underline-offset: .15em; }

h1, h2, h3, h4 { line-height: 1.2; margin: 0 0 .5rem; font-weight: 700; letter-spacing: -.01em; }

/* `hidden` doit CACHER, quoi qu'en dise la classe portee par l'element.
   Le `display: none` de l'attribut vient de la feuille du NAVIGATEUR : la
   moindre regle d'auteur posant un `display` (`.empty-state { display: flex }`,
   `.modal-overlay { display: flex }`) gagne et rend l'element visible alors que
   le HTML le declare cache. Le bug s'etait deja paye trois fois, corrige a
   chaque fois au cas par cas - jusqu'a l'ecran /admin/stockage, qui affichait
   « Aucun resultat » AU-DESSUS de la liste de fichiers.
   `!important` est ici a sa place : c'est une regle de derniere instance sur un
   attribut d'etat, pas une surcharge de style. */
[hidden] { display: none !important; }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .55rem 1rem; border-radius: var(--radius);
    border: 1px solid transparent; font-weight: 600; font-size: .9rem;
    cursor: pointer; transition: all .15s ease; text-decoration: none; white-space: nowrap;
    /* `<a class="btn">` et `<button class="btn">` doivent rendre la MÊME boîte.
       Un `<button>` n'herite ni de la police ni de l'interlignage du document -
       il prend ceux du navigateur -, la ou un `<a>` prend ceux du body. Sur la
       fiche client, « Modifier » (un lien) etait donc visiblement plus grand
       que « Envoyer un lien vers le portail » et « Cloturer » (des boutons),
       alors que les trois portent la meme classe. Corrige ici plutot qu'a
       l'endroit du symptome : le defaut existe partout ou les deux se cotoient
       dans une meme barre. */
    font-family: inherit; line-height: 1.25;
}
.btn:hover { text-decoration: none; transform: translateY(-1px); }
.btn-primary { background: var(--primary); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: var(--primary-600); color: #fff; }
.btn-outline { background: var(--surface); color: var(--primary); border-color: var(--border); }
.btn-outline:hover { border-color: var(--primary); background: var(--primary-50); }
.btn-ghost { background: transparent; color: var(--muted); }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); }
.btn-lg { padding: .8rem 1.5rem; font-size: 1rem; }
.btn-block { width: 100%; justify-content: center; }
.btn-sm { padding: .35rem .7rem; font-size: .8rem; border-radius: 8px; }
/* MANQUAIT DEPUIS TOUJOURS, et vingt gabarits l'écrivaient : suppression d'une
   entreprise, effacement de compte RGPD, désactivation de la double
   authentification, révocation d'une clé d'API, purge en maintenance. Sans
   déclaration, ces boutons ne recevaient que `.btn`, dont la bordure est
   `1px solid transparent` et qui ne pose aucun fond : un geste irréversible
   rendait comme du texte nu, moins visible qu'un lien ordinaire.
   Trouvé le 2026-09-12 pendant la repasse mobile, en cherchant tout autre
   chose. `FrontendConventionsTest` interdit désormais une classe `.btn-*`
   employée sans être définie. */
.btn-danger { background: var(--danger); color: #fff; box-shadow: var(--shadow-sm); }
.btn-danger:hover { background: var(--danger-700); color: #fff; }
.btn-danger-soft { background: #fdecec; color: var(--danger); border-color: #f8cccc; }
.btn-danger-soft:hover { background: var(--danger); color: #fff; }
/* Action lourde de conséquences mais RÉVERSIBLE (clôturer un client, archiver) :
   neutre au repos - elle n'a pas à crier en permanence au milieu d'actions
   ordinaires - et rouge au survol, c'est-à-dire au moment précis où le geste va
   être fait. À distinguer de `.btn-danger-soft`, qui signale d'emblée une
   destruction. */
.btn-ghost-danger { background: transparent; color: var(--muted); }
.btn-ghost-danger:hover { background: #fdecec; color: var(--danger); }
/* États inactif et occupé, déclarés UNE fois pour toutes les variantes (audit
   du 2026-09-13, UI-03). Sans eux, un bouton désactivé par `submit-once`,
   l'éditeur de lignes ou l'import gardait fond plein, curseur main et
   décalage au survol : « Valider » sur un brouillon vide paraissait cliquable
   et rien ne répondait. Placés après les variantes pour l'emporter sur leurs
   `:hover`, sans `!important`. */
.btn:active { transform: none; }
.btn:disabled, .btn[aria-disabled="true"],
.btn:disabled:hover, .btn[aria-disabled="true"]:hover {
    opacity: .55; cursor: not-allowed; transform: none; box-shadow: none;
}
.btn[aria-busy="true"] { cursor: progress; opacity: .8; transform: none; }

/* ---------- Forms ---------- */
.form-group { margin-bottom: 1.1rem; }
.form-label { display: block; font-weight: 600; font-size: .85rem; margin-bottom: .4rem; }
.form-control, .form-select {
    width: 100%; padding: .6rem .8rem; font-size: .95rem; font-family: inherit;
    color: var(--text); background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius); transition: border-color .15s, box-shadow .15s;
}
.form-control:focus, .form-select:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .18);
}
.form-hint { font-size: .78rem; color: var(--muted); margin-top: .35rem; }

/* Désignation multiligne : un champ d'UNE ligne au repos, qui grandit avec
   son contenu (contrôleur `autosize`). `overflow: hidden` évite la barre de
   défilement fantôme pendant le calcul ; la poignée reste offerte, pour qui
   veut plus de place avant d'écrire. `line-height` fixé : sans lui, la hauteur
   d'une ligne dépend de la police de repli et la première mesure est fausse. */
textarea.textarea-autosize {
    min-height: 2.6rem; line-height: 1.4; resize: vertical; overflow: hidden;
}

/* Champ compact, à hauteur de `.btn-sm` : pour les formulaires POSÉS DANS une
   cellule de tableau, où un champ pleine taille désaligne la ligne. Écrit le
   jour où /admin/fonctions réclamait un `.input-sm` qui n'a jamais existé. */
.form-control-sm { padding: .35rem .6rem; font-size: .82rem; border-radius: 8px; }

/* Barre de filtre : des champs et un bouton d'application sur une même ligne
   (période d'un export, d'un rapport, d'une campagne).

   Le bouton doit prendre la HAUTEUR DU CHAMP et non la sienne. `.btn` est
   calibré pour une barre d'ACTIONS - padding plus serré, police plus petite -
   et posé à côté d'un `.form-control` il paraît systématiquement plus court.
   `align-items: flex-end` alignait bien les bas, ce qui reportait tout l'écart
   en haut : deux contrôles côte à côte de hauteurs différentes.

   Une hauteur commune plutôt qu'un accord de paddings : les deux éléments n'ont
   ni la même police ni le même `line-height`, et faire coïncider leurs paddings
   ne tient que jusqu'à la prochaine retouche de l'un des deux. `box-sizing:
   border-box` est global, la hauteur est donc directement comparable. */
.filter-bar { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; }
.filter-bar .form-control,
.filter-bar .form-select,
.filter-bar .btn { height: var(--control-height); }

/* Utilitaire d'accessibilité : masqué visuellement mais lu par les lecteurs
   d'écran (contrairement à display:none). Sert notamment à la région
   aria-live du bouton de révélation du mot de passe (D5). */
.visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- Champ mot de passe avec bouton de révélation (oeil) - D5 ---------- */
.password-field { position: relative; }
.password-field .form-control { padding-right: 2.75rem; }
.password-toggle-btn {
    position: absolute; top: 50%; right: .35rem; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: center;
    width: 2.1rem; height: 2.1rem; padding: 0;
    background: transparent; border: none; border-radius: var(--radius);
    color: var(--muted); cursor: pointer; transition: color .15s, background-color .15s;
}
.password-toggle-btn:hover { color: var(--text); background: var(--surface-2); }
.password-toggle-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; color: var(--text); }

/* Champs date/heure natifs : look cohérent avec .form-control même sans la classe,
   et indicateur de calendrier aux couleurs de la charte. */
.app-content :where(
    input[type="date"], input[type="datetime-local"],
    input[type="month"], input[type="time"], input[type="week"]
) {
    width: 100%; padding: .6rem .8rem; font-size: .95rem; font-family: inherit;
    color: var(--text); background: var(--surface);
    border: 1px solid var(--border); border-radius: var(--radius);
    transition: border-color .15s, box-shadow .15s;
}
.app-content input[type="date"]:focus,
.app-content input[type="datetime-local"]:focus,
.app-content input[type="month"]:focus,
.app-content input[type="time"]:focus,
.app-content input[type="week"]:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .18);
}
.app-content input[type="date"]::-webkit-calendar-picker-indicator,
.app-content input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.app-content input[type="month"]::-webkit-calendar-picker-indicator,
.app-content input[type="time"]::-webkit-calendar-picker-indicator,
.app-content input[type="week"]::-webkit-calendar-picker-indicator {
    cursor: pointer; opacity: .55; transition: opacity .15s;
    /* Teinte l'icône calendrier en --primary (#3937a3). */
    filter: invert(20%) sepia(64%) saturate(2200%) hue-rotate(232deg) brightness(85%) contrast(95%);
}
.app-content input[type="date"]::-webkit-calendar-picker-indicator:hover,
.app-content input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover,
.app-content input[type="month"]::-webkit-calendar-picker-indicator:hover,
.app-content input[type="time"]::-webkit-calendar-picker-indicator:hover,
.app-content input[type="week"]::-webkit-calendar-picker-indicator:hover { opacity: 1; }

/* ---------- Zone de dépôt de fichiers (import, GED) ---------- */
.dropzone {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .6rem; padding: 2.5rem 1.5rem; text-align: center; cursor: pointer;
    border: 2px dashed var(--border); border-radius: var(--radius-lg);
    background: var(--surface-2); transition: border-color .15s, background .15s;
}
.dropzone:hover { border-color: var(--primary); }
.dropzone.is-dragover { border-color: var(--primary); background: var(--primary-50); }
.dropzone .dz-icon { font-size: 2rem; color: var(--primary); }
.dropzone .dz-title { font-weight: 600; }
.dropzone .dz-hint { font-size: .85rem; color: var(--muted); }
.dropzone.dz-compact { padding: 1.6rem 1.25rem; }

/* Champs de fichier : bouton de sélection stylé (au lieu de l'apparence native).
   Les dropzones cachent leur input via style="display:none" inline (non affecté). */
input[type="file"] { cursor: pointer; max-width: 100%; }
input[type="file"]::file-selector-button {
    margin-right: .8rem; padding: .45rem .9rem; border: 0; border-radius: 7px;
    background: var(--primary); color: #fff; font-weight: 600; font-size: .85rem;
    cursor: pointer; transition: background .15s;
}
input[type="file"]::file-selector-button:hover { background: var(--primary-600); }

/* ---------- Éditeur de lignes de facture (émission compacte) ---------- */
.invoice-lines thead th { padding: .5rem .55rem; }
.invoice-lines tbody td { padding: .35rem .5rem; }
.invoice-lines .form-control, .invoice-lines .form-select { padding: .42rem .55rem; font-size: .88rem; }
.invoice-lines .line-total { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Bouton « ajouter une ligne » : ligne d'ajout pleine largeur (style add-row). */
.line-add {
    display: flex; align-items: center; justify-content: center; gap: .5rem; width: 100%;
    padding: .6rem; margin: .6rem 0 0; border: 1px dashed var(--border); border-radius: var(--radius);
    background: var(--surface-2); color: var(--muted); font-weight: 600; font-size: .88rem;
    cursor: pointer; transition: border-color .15s, color .15s, background .15s;
}
.line-add:hover { border-color: var(--primary); color: var(--primary); background: var(--primary-50); }

.emit-foot { display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; flex-wrap: wrap; margin: 1.1rem 0; }
.emit-totals { min-width: 240px; margin-left: auto; }
.emit-totals > div { display: flex; justify-content: space-between; gap: 2rem; padding: .2rem 0; }
.emit-total-ttc { font-size: 1.1rem; border-top: 1px solid var(--border); margin-top: .3rem; padding-top: .4rem; }

/* Gestionnaire d'étiquettes GED (modal CRUD couleur) */
/* Spécificité .modal.modal-* : .modal (défini plus bas) fixe max-width:440px et
   écraserait .modal-lg/.modal-xl à spécificité égale - on la relève ici. */
.modal.modal-lg { max-width: 560px; }
.modal.modal-xl { max-width: min(1080px, 94vw); width: 100%; }
.tag-manager { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; max-height: 52vh; overflow-y: auto; }
.tag-row { display: flex; align-items: center; gap: .5rem; }
.tag-row form { margin: 0; }
.tag-row .tag-edit { display: flex; align-items: center; gap: .5rem; flex: 1; }
.tag-color {
    width: 2.4rem; height: 2.4rem; flex-shrink: 0; padding: 2px; cursor: pointer;
    border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
}
.tag-add { border-top: 1px solid var(--border); padding-top: .85rem; margin-top: .35rem; }
.tag-empty { color: var(--muted); font-size: .88rem; padding: .5rem 0; }

/* Barre d'outils GED (recherche + filtres) */
.ged-toolbar { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
.ged-search { position: relative; flex: 1; min-width: 220px; }
.ged-search i { position: absolute; left: .8rem; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: .85rem; }
.ged-search input { padding-left: 2.1rem; }

/* ---------- Cards ---------- */
.card {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
}
.card-pad { padding: 1.25rem 1.5rem; }
.card-header { padding: 1rem 1.5rem; border-bottom: 1px solid var(--border); font-weight: 600; }
/* En-tête à deux éléments : le titre à gauche, son complément à droite.
   `.card-header` est un bloc, donc le `margin-left:auto` que plusieurs
   gabarits posaient sur leur second enfant ne poussait rien. */
.card-header-split { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.card-hover { transition: box-shadow .2s, transform .2s; }
.card-hover:hover { box-shadow: var(--shadow); transform: translateY(-2px); }

/* Stat cards */
/* `min(<base>, 100%)` sur TOUTES les grilles intrinsèques du fichier, et ce
   n'est pas un détail : sans lui, une piste de 210 px dans un conteneur de
   180 px déborde, ce qui est exactement le défaut qu'`auto-fit` est censé
   éviter. Le cas se produit dans une colonne étroite ou une carte, jamais sur
   un écran vide - donc jamais en revue de code. Doctrine de docs/52. */
/* La piste vaut le quart de la grille, jamais moins de 155 px : quatre tuiles
   par rangée sur un grand contenu comme avant, mais DEUX sur un téléphone, là
   où le plancher de 210 px n'en laissait tenir qu'une - six tuiles empilées,
   540 px avant le premier contenu (relevé C, G1). */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(max(155px, 25% - .75rem), 100%), 1fr)); gap: 1rem; }
/* Une tuile de 160 px ne porte pas l'icône ET un montant de 1,6 rem :
   « 12 345,67 € » débordait, et la page défilait de 47 px à 375 et de 23 px à
   768 - mesuré APRÈS le passage à deux colonnes, qui avait donc d'abord
   aggravé l'écran. C'est la TUILE qui mesure sa place, pas la grille : à 768,
   quatre tuiles de 168 px manquent autant de place que deux à 375. L'icône est
   décorative, elle part ; la marge suit la largeur de la piste. */
.stats-grid > .stat-card { container-type: inline-size; padding: clamp(.75rem, 8%, 1.25rem); }
@container (max-width: 15rem) {
    .stat-card > .stat-figure { display: none; }
    .stat-card .stat-value { font-size: 1.2rem; }
    .stat-card .stat-title { margin-top: .4rem; }
}
.stat-card { display: flex; align-items: center; gap: 1rem; padding: 1.25rem; }
.stat-figure {
    width: 3rem; height: 3rem; flex-shrink: 0; border-radius: var(--radius);
    display: flex; align-items: center; justify-content: center;
    background: var(--primary-50); color: var(--primary); font-size: 1.25rem;
}
.stat-value { font-size: 1.6rem; font-weight: 800; line-height: 1; }
.stat-title { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-top: 10px; }
.stat-subvalue { font-size: .78rem; color: var(--muted); margin-top: .15rem; }

/* ---------- Admin : statistiques produit (barres CSS, sans librairie JS) ---------- */
.section-title { font-size: 1rem; font-weight: 700; margin: 1.75rem 0 .9rem; color: var(--text); }
.bar-list { display: flex; flex-direction: column; gap: .8rem; }
/* L'intitulé prend 8 rem OU 30 % de la ligne, le plus petit des deux : dans
   une carte de 302 px, les 8 rem fixes ne laissaient que 94 px à la jauge,
   qui ne disait plus rien sur un écran qu'on ouvre pour une alerte disque
   (relevé D, /admin/systeme). */
.bar-row { display: grid; grid-template-columns: min(8rem, 30%) minmax(0, 1fr) 3.5rem; align-items: center; gap: .75rem; }
.bar-row .bar-label { font-size: .85rem; font-weight: 600; color: var(--text); }
.bar-row .bar-track { background: var(--surface-2); border-radius: 999px; height: .6rem; overflow: hidden; }
.bar-row .bar-fill { display: block; height: 100%; background: var(--primary); border-radius: 999px; transition: width .3s ease; }
.bar-row .bar-count { text-align: right; font-size: .82rem; font-variant-numeric: tabular-nums; color: var(--muted); }
/* Distributions stats (par plan / périodicité) : libellé 20 %, barre 60 %, valeur 20 %.
   `padding-right` sur la valeur : sans lui le texte « 12 (34,5 %) » colle au bord
   droit de la carte (.card-pad n'a pas de marge interne supplémentaire). */
/* En `fr` et non en pourcentages : 20 + 60 + 20 faisaient 100 % PLUS les deux
   gouttières, soit 24 px de débordement à toute largeur. La valeur garde sa
   largeur de contenu, « 128 (42,3 %) » ne se coupe pas. */
.bar-list--wide .bar-row { grid-template-columns: minmax(0, 1fr) minmax(0, 3fr) minmax(max-content, 1fr); }
.bar-list--wide .bar-row .bar-count { padding-right: .75rem; white-space: nowrap; }

/* Occupation de l'offre (/app/abonnement) : la valeur (« 12,3 Go / 75 Go ») doit
   tenir sur UNE seule ligne -> colonne de droite dimensionnée sur son contenu.
   Modificateur dédié plutôt qu'un élargissement de `.bar-row` : `.bar-count`
   sert aussi à la GED, au tableau de bord et à /admin/systeme, où la valeur est
   un pourcentage court que la colonne de 3,5 rem cadre très bien. */
.bar-row--usage { grid-template-columns: min(8rem, 30%) minmax(0, 1fr) minmax(3.5rem, max-content); }
.bar-row--usage .bar-count { white-space: nowrap; }
@media (max-width: 640px) {
    .bar-row--usage { grid-template-columns: 1fr; gap: .35rem; }
    .bar-row--usage .bar-count { text-align: left; }
}
.dynamics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr)); gap: 1rem; }
.dynamics-card { display: flex; flex-direction: column; gap: .35rem; padding: 1.1rem 1.25rem; }
.dynamics-card .dynamics-value { font-size: 1.4rem; font-weight: 800; line-height: 1; }
.dynamics-card .dynamics-label { font-size: .82rem; color: var(--muted); display: flex; align-items: center; gap: .4rem; }

/* ---------- Tables ---------- */
.table-container { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow-x: auto; -webkit-overflow-scrolling: touch; box-shadow: var(--shadow-sm); }
/* Tableau posé DANS une carte : la carte porte déjà bordure et ombre (audit du
   2026-09-13, UI-04). Le motif s'écrivait en ligne dans 55 gabarits, hors de
   portée de toute correction de token ou de media query. */
.table-container--flush { border: 0; box-shadow: none; }
table.table { width: 100%; border-collapse: collapse; }
.table thead th {
    text-align: left; padding: .75rem 1rem; background: var(--surface-2);
    font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
    border-bottom: 1px solid var(--border);
}
.table tbody td { padding: .8rem 1rem; border-bottom: 1px solid var(--border); }
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background .12s; }
.table tbody tr:hover { background: var(--surface-2); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
/* Historique système : données centrées dans les colonnes. */
.sys-history .table th, .sys-history .table td { text-align: center; }
/* Ligne d'état vide d'un tableau (cellule unique en `colspan`) : message centré
   au milieu du tableau plutôt que collé à la première colonne. Classe portée par
   la cellule, donc valable pour `.table` comme pour `.data-table`. */
.table-empty-cell { text-align: center; padding: 2rem 1rem; }

/* Page « Tâches planifiées » (/admin/supervision/taches) : rappel discret de la
   clé technique, historique compact et indice de retard sous le badge d'état. */
.cron-key { display: block; font-size: .72rem; font-family: var(--font-mono, monospace); }
.cron-late-hint { font-size: .75rem; margin-top: .25rem; }
.cron-runs { list-style: none; margin: 0; padding: 0; font-size: .78rem; color: var(--muted); }
.cron-runs li { white-space: nowrap; }
.cron-row--down td:first-child { box-shadow: inset 3px 0 0 var(--danger); }
.cron-row--degraded td:first-child { box-shadow: inset 3px 0 0 var(--warning); }

/* Modale « Tâches planifiées » de /admin/supervision : la vingtaine de lignes
   que la carte ne porte plus. Hauteur bornée et défilement interne - sans quoi
   la modale dépasse l'écran et ses boutons deviennent inatteignables. */
.cron-modal-list { list-style: none; margin: .85rem 0 0; padding: 0; max-height: 55vh; overflow-y: auto; }
.cron-modal-item { padding: .5rem .6rem; border-bottom: 1px solid var(--border); }
.cron-modal-item:last-child { border-bottom: 0; }
.cron-modal-head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.cron-modal-label { font-size: .85rem; font-weight: 600; }
.cron-modal-value { display: block; margin-top: .15rem; font-size: .78rem; font-variant-numeric: tabular-nums; }
.cron-modal-item .badge { flex-shrink: 0; }
.cron-modal-item--degraded { background: #fef5e7; box-shadow: inset 3px 0 0 var(--warning); }
.cron-modal-item--down { background: #fdecec; box-shadow: inset 3px 0 0 var(--danger); }

/* Identifiants (SIREN, SIRET, adresse d'annuaire, clé technique) en chasse
   fixe. Ils se comparent chiffre à chiffre - c'est le seul endroit où la police
   proportionnelle gêne vraiment la lecture. Nommé `id-mono` et non
   `supplier-mono` : la même règle sert au répertoire fournisseurs, à la facture
   reçue et au diagnostic d'entreprise, et un utilitaire ne se redéclare pas. */
.id-mono { font-family: var(--font-mono, monospace); font-size: .85em; letter-spacing: .01em; }

/* Diagnostic d'entreprise (admin) : formulaire à deux champs qui passe en
   colonne sous 720 px, et mise en forme du verdict. Le `caveat` porte la seule
   phrase que cet écran doit toujours afficher - l'annuaire ne dit rien de la
   capacité à ÉMETTRE - d'où une règle à lui, pour qu'on ne la retire pas en
   croyant retirer du décor. */
.diagnostic-form { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.diagnostic-form-field { flex: 1 1 18rem; min-width: 0; }
.diagnostic-form-field label { display: block; font-weight: 600; font-size: .85rem; margin-bottom: .35rem; }
.diagnostic-form-actions { flex: 0 0 auto; align-self: flex-start; padding-top: 1.65rem; }
.diagnostic-hint { font-size: .78rem; margin: .35rem 0 0; }
.diagnostic-verdict { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0 0 .75rem; }
.diagnostic-caveat { font-size: .82rem; margin: .75rem 0 0; overflow-wrap: anywhere; }

@media (max-width: 720px) {
    .diagnostic-form-actions { padding-top: 0; width: 100%; }
    .diagnostic-form-actions .btn { width: 100%; justify-content: center; }
}

/* Recherche globale des factures : un champ texte qui prend la place restante
   et trois filtres étroits, tous repliés en colonne sur mobile. Les filtres ont
   une base fixe plutôt qu'un pourcentage : ce sont des listes déroulantes, leur
   largeur utile est celle de leur libellé le plus long, pas une fraction de
   l'écran. */
.search-form { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; }
.search-form-text { flex: 3 1 20rem; min-width: 0; }
.search-form-filter { flex: 1 1 9rem; min-width: 0; }
.search-form label { display: block; font-weight: 600; font-size: .85rem; margin-bottom: .35rem; }
.search-form-actions { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding-top: 1.65rem; }
.search-hint { font-size: .78rem; margin: .75rem 0 0; }

@media (max-width: 720px) {
    .search-form-actions { padding-top: 0; width: 100%; }
    .search-form-actions .btn { flex: 1 1 auto; justify-content: center; }
}

/* Vue source d'une facture (admin) : la source à gauche, la confrontation à
   droite. Deux colonnes tant que l'écran le permet, empilées ensuite - la
   confrontation reste lisible seule, la source non, donc c'est elle qui garde
   la place quand il faut choisir. */
.source-layout { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 1rem; align-items: start; }
.source-note { font-size: .78rem; margin: 0 0 .75rem; }
.source-path { font-size: .75rem; margin-top: .2rem; overflow-wrap: anywhere; }

/* `overflow: auto` et non `scroll` : une barre permanente sur un document court
   ferait croire qu'il continue. Le bloc défile sur LUI-MÊME, jamais la page -
   un XML de facture a des lignes de plusieurs centaines de caractères. */
.source-code {
    max-height: 70vh;
    overflow: auto;
    margin: 0;
    padding: .85rem 1rem;
    background: #fbfbfd;
    border: 1px solid var(--border, #e3e3ee);
    border-radius: 8px;
    font-family: var(--font-mono, monospace);
    font-size: .78rem;
    line-height: 1.55;
    tab-size: 2;
}
.source-code code { white-space: pre; }

/* Jetons de coloration. Les teintes sont choisies pour rester distinctes une
   fois imprimées en niveaux de gris, et pour tenir le contraste AA sur le fond
   clair du bloc : c'est du texte, pas de la décoration. */
.tok-key     { color: #1f6f4a; }
.tok-str     { color: #9a3412; }
.tok-num     { color: #1d4ed8; }
.tok-lit     { color: #7c3aed; font-weight: 600; }
.tok-punct   { color: #6b7280; }
.tok-name    { color: var(--primary, #3937a3); font-weight: 600; }
.tok-attr    { color: #1f6f4a; }
.tok-comment { color: #6b7280; font-style: italic; }

@media (max-width: 1100px) {
    .source-layout { grid-template-columns: minmax(0, 1fr); }
}

/* Onglet « Scellé » : les verdicts en tête, puis le détail. Les empreintes sont
   des chaînes de 64 caractères sans espace - `anywhere` et non `break-word`,
   seul le premier réduit la largeur min-content, donc la largeur minimale de la
   piste de grille. Sans lui, la page défile horizontalement sur mobile. */
.seal-verdicts { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .75rem; }
.seal-heading { font-size: 1rem; margin: 1.5rem 0 .5rem; }
.seal-hash { overflow-wrap: anywhere; }



/* ---------- Badges ---------- */
.badge {
    display: inline-flex; align-items: center; gap: .3rem; padding: .2rem .6rem;
    border-radius: 999px; font-size: .73rem; font-weight: 600; line-height: 1.4;
    background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
}
.badge-success { background: #eafaf0; color: #15803d; border-color: #c9f0d8; }
.badge-warning { background: #fef5e7; color: var(--warning); border-color: #fbe3bd; }
.badge-danger  { background: #fdecec; color: var(--danger);  border-color: #f8cccc; }
.badge-info    { background: #eaf1fe; color: var(--info);    border-color: #cfe0fc; }
.badge-primary { background: var(--primary-50); color: var(--primary); border-color: #d9d8f5; }
.badge-neutral { background: var(--surface-2); color: var(--muted); border-color: var(--border); }

/* Badge dont l'information est LOCALE, non portée à la connaissance de la
   Plateforme Agréée : « Soldée » posée dans fx sans déclaration fr:211/fr:212.
   La bordure en pointillé se combine à la couleur (`badge-success badge-local`)
   plutôt que de la remplacer - c'est bien un solde, pas un avertissement.
   `2px` et non `1px` : à 1px, un pointillé de cette longueur d'arc se lit comme
   un trait plein sur un écran non HiDPI. La distinction ne tient JAMAIS à elle
   seule, l'infobulle du badge dit laquelle des deux situations on regarde
   (WCAG 1.4.1 : pas d'information portée par le seul aspect visuel). */
.badge-local { border-style: dashed; border-width: 2px; padding: calc(.2rem - 1px) calc(.6rem - 1px); }

/* Badge ACTIONNABLE : le badge lui-même est le bouton (relancer une
   consultation d'annuaire depuis la liste des clients, sans ouvrir la fiche).
   `<button>` et non `<a>` : c'est un POST qui écrit.
   La petite flèche circulaire signale l'action - un badge cliquable qui
   ressemble en tout point à un badge inerte n'invite personne à cliquer. */
/* `min-height` explicite : la hauteur naturelle du badge vaut 23,3 px
   (line-height 1.4 x .73rem + .2rem x 2 + 2 px de bordure), sous les 24 px
   du critère WCAG 2.5.8 - et l'exception « cible en ligne dans une phrase »
   ne vaut pas dans une cellule de tableau. */
.badge-action { cursor: pointer; font-family: inherit; min-height: 24px; transition: filter .15s, box-shadow .15s; }
.badge-action:hover { filter: brightness(.97); box-shadow: var(--shadow-sm); }
.badge-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.badge-action .badge-action-icon { font-size: .68rem; opacity: .75; }

/* ---------- Liste déroulante avec recherche ----------
   Champ de filtre posé AU-DESSUS d'un `<select>` natif (contrôleur Stimulus
   searchable-select), pour les listes qui grossissent avec l'usage : carnet
   clients, catalogue produits. Le select reste natif - donc utilisable sans
   JavaScript, et servi par le sélecteur du système sur mobile.
   `.searchable-select-idle` : le contrôleur masque le champ quand la liste est
   assez courte pour se parcourir à l'œil. */
.searchable-select { display: flex; flex-direction: column; gap: .4rem; }
.searchable-select-field { position: relative; display: flex; align-items: center; }
.searchable-select-field > i {
    position: absolute; left: .75rem; color: var(--muted); font-size: .82rem; pointer-events: none;
}
/* 6,5 rem et non 4,5 : « 128 résultat(s) » fait ~87 px à .72rem, il mordait
   sur la zone de saisie dès qu'on tapait un nom un peu long. */
.searchable-select-field .form-control { padding-left: 2.1rem; padding-right: 6.5rem; }
.searchable-select-count {
    position: absolute; right: .75rem; font-size: .72rem; pointer-events: none; white-space: nowrap;
}
.searchable-select-idle .searchable-select-field { display: none; }

/* ---------- Timeline (cycle de vie facture) ---------- */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline-item { position: relative; padding: 0 0 1.4rem 1.85rem; }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
    content: ''; position: absolute; left: .5rem; top: 1.35rem; bottom: -.15rem;
    width: 2px; background: var(--border);
}
.timeline-item:last-child::before { display: none; }
/* Ligne de tableau cliquable (liste des factures). Le curseur est le seul
   indice qu'un clic mène quelque part : sans lui, la ligne réagit sans avoir
   annoncé qu'elle le ferait. Le survol reste celui de `.table`, qui existe. */
.row-clickable { cursor: pointer; }

/* Jalon LOCAL (règlement) : même frise, trait pointillé. Un suivi de trésorerie
   interne n'a pas la valeur d'un acte transmis à la Plateforme Agréée, et les
   aligner à l'identique laisserait croire le contraire. */
.timeline-item--local::before {
    background: repeating-linear-gradient(var(--border) 0 3px, transparent 3px 6px);
}
.timeline-dot {
    position: absolute; left: 0; top: .15rem; width: 1.1rem; height: 1.1rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: .52rem; color: #fff; box-shadow: 0 0 0 3px var(--surface);
}
.timeline-label { font-weight: 600; font-size: .9rem; line-height: 1.35; }
.timeline-meta { color: var(--muted); font-size: .78rem; margin-top: .15rem; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.timeline-meta .by-you { color: var(--primary); font-weight: 600; }

/* ---------- Alerts / flash ---------- */
.alert { padding: .8rem 1rem; border-radius: var(--radius); margin-bottom: 1rem; border: 1px solid var(--border); font-size: .9rem; background: var(--surface); }
.alert-success { background: #eafaf0; color: #14622f; border-color: #c9f0d8; }
.alert-warning { background: #fef5e7; color: #7a4a06; border-color: #fbe3bd; }
.alert-error, .alert-danger { background: #fdecec; color: #8a1c1c; border-color: #f8cccc; }
.alert-info { background: #eaf1fe; color: #14367a; border-color: #cfe0fc; }

/* ---------- Bannière d'annonce globale (sous la navbar) ---------- */
.announcement {
    display: flex; align-items: flex-start; gap: .7rem;
    padding: .8rem 1rem; margin-bottom: 1rem;
    border: 1px solid var(--border); border-left-width: 4px;
    border-radius: var(--radius); background: var(--surface); font-size: .9rem;
}
.announcement-icon { margin-top: .15rem; flex: 0 0 auto; }
.announcement-body { flex: 1 1 auto; min-width: 0; }
.announcement-body p { margin: 0; }
.announcement-body p + p { margin-top: .4rem; }
.announcement-body ul { margin: .3rem 0 0; padding-left: 1.2rem; }
.announcement-body a { text-decoration: underline; }
.announcement-body code {
    font-size: .82em; background: rgba(0, 0, 0, .06);
    padding: .05rem .3rem; border-radius: 4px;
}
.announcement-close {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    color: inherit; opacity: .55; padding: .1rem .3rem; font-size: 1rem; line-height: 1;
}
.announcement-close:hover { opacity: 1; }
.announcement--info    { background: #eaf1fe; color: #14367a; border-color: #cfe0fc; border-left-color: var(--info); }
.announcement--warning { background: #fef5e7; color: #7a4a06; border-color: #fbe3bd; border-left-color: var(--warning); }
.announcement--success { background: #eafaf0; color: #14622f; border-color: #c9f0d8; border-left-color: var(--success); }
.announcement--danger  { background: #fdecec; color: #8a1c1c; border-color: #f8cccc; border-left-color: var(--danger); }

/* ============================================================
   App layout (sidebar)
   ============================================================ */
.app-layout { display: flex; min-height: 100vh; }

.app-sidebar {
    width: var(--sidebar-w); flex-shrink: 0; background: #1b1c39; color: #c9cae2;
    display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
}
.app-brand { display: flex; align-items: center; gap: .65rem; padding: 1.15rem 1.25rem; color: #fff; font-weight: 800; font-size: 1.05rem; }
.app-brand .logo { width: 30px; height: 30px; border-radius: 8px; background: var(--primary); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800; }
/* La marque est un LIEN vers le tableau de bord : il faut que ça se voie au
   survol, sinon on a un lien invisible qu'on ne clique que par hasard. Le
   contour de focus est explicite parce que la barre latérale est sombre : le
   contour par défaut du navigateur s'y perd. */
.app-brand:hover { opacity: 1; }
.app-brand:hover .logo { filter: brightness(1.15); }
.app-brand:focus-visible { outline: 2px solid #fff; outline-offset: -2px; border-radius: var(--radius); }
.app-nav { padding: .5rem .75rem; display: flex; flex-direction: column; gap: .15rem; flex: 1; overflow-y: auto; }
.app-nav .nav-label { font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: #6f719c; padding: 1rem .75rem .35rem; }
/* Barre de défilement de la nav thématisée (sidebar sombre) au lieu de la native. */
.app-nav { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .18) transparent; }
.app-nav::-webkit-scrollbar { width: 8px; }
.app-nav::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .18); border-radius: 8px; }
.app-nav::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .3); }
.app-nav::-webkit-scrollbar-track { background: transparent; }
.app-nav a {
    display: flex; align-items: center; gap: .7rem; padding: .4rem .5rem; border-radius: var(--radius);
    color: #c9cae2; font-weight: 500; font-size: .92rem; text-decoration: none; transition: background .12s, color .12s;
}
.app-nav a i { width: 1.1rem; text-align: center; opacity: .85; }
.app-nav a:hover { background: rgba(255, 255, 255, .06); color: #fff; }
.app-nav a.active { background: var(--primary); color: #fff; }
/* Entrée hors groupe (le tableau de bord, et lui seul). Le filet la détache du
   premier intitulé de catégorie : sans lui, elle se lit comme un élément que
   l'on aurait oublié de classer plutôt que comme un point de retour. */
.app-nav a.nav-standalone { margin-bottom: .35rem; padding-bottom: .5rem; border-bottom: 1px solid rgba(255, 255, 255, .08); border-radius: var(--radius) var(--radius) 0 0; }
.app-nav-bottom { padding: .5rem .75rem; display: flex; flex-direction: column; gap: .15rem; }
.app-nav-bottom a {
    display: flex; align-items: center; gap: .7rem; padding: .6rem .75rem; border-radius: var(--radius);
    color: #c9cae2; font-weight: 500; font-size: .92rem; text-decoration: none; transition: background .12s, color .12s;
}
.app-nav-bottom a i { width: 1.1rem; text-align: center; opacity: .85; }
.app-nav-bottom a:hover { background: rgba(255, 255, 255, .06); color: #fff; }
.app-nav-bottom a.active { background: var(--primary); color: #fff; }

/* ---------- Paramètres : sous-sidebar dédiée ---------- */
.settings-shell { display: flex; gap: 1.5rem; align-items: flex-start; }
.settings-subnav {
    flex-shrink: 0; width: 288px; position: sticky; top: calc(62px + 1.5rem);
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: .6rem; box-shadow: var(--shadow-sm);
}
.settings-subnav-title { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 700; padding: .6rem .7rem .5rem; }
.settings-subnav a {
    display: flex; align-items: center; gap: .65rem; padding: .6rem .7rem; border-radius: var(--radius);
    color: var(--text); font-weight: 500; font-size: .9rem; text-decoration: none; transition: background .12s, color .12s;
}
.settings-subnav a i { width: 1.1rem; text-align: center; color: var(--muted); }
.settings-subnav a:hover { background: var(--surface-2); }
.settings-subnav a.active { background: var(--primary-50); color: var(--primary); }
.settings-subnav a.active i { color: var(--primary); }
.settings-content { flex: 1; min-width: 0; }
/* Cards des Paramètres : toujours pleine largeur de la zone de contenu (pas de blanc). */
.settings-content .card { width: 100%; }
/* Sous-nav Paramètres : bouton d'action (ajout entreprise) + séparateur avant suppression. */
.settings-subnav-action { padding: .5rem .35rem 0; }
.settings-subnav-action .btn { width: 100%; justify-content: flex-start; }
.settings-subnav-sep { border: 0; border-top: 1px solid var(--border); margin: .7rem .35rem .4rem; }
/* Badge « À raccorder » cliquable dans la liste des entreprises. */
.company-connect-badge { text-decoration: none; cursor: pointer; }
.company-connect-badge:hover { filter: brightness(.96); text-decoration: none; }

@media (max-width: 1024px) {
    .settings-shell { flex-direction: column; align-items: stretch; }
    .settings-subnav { width: 100%; position: static; }
    .settings-content { width: 100%; }
}

/* ---------- Grilles réutilisables ----------
   Elles remplacent les `style="grid-template-columns:..."` posés en attribut :
   un attribut `style` l'emporte sur toute media query, une colonne figée le
   restait donc jusqu'à 320 px de large (un code postal dans 48 px).

   Deux invariants à ne pas perdre :
   - chaque colonne s'écrit `minmax(0, 1fr)` et jamais `1fr` nu : la largeur
     minimale d'une piste de grille vaut `auto`, donc un contenu long (IBAN,
     nom de fichier) élargit la colonne au lieu de se contracter ;
   - tout se replie en une seule colonne sur mobile.

   L'écart se règle par `--grid-gap`, via les modificateurs `.grid-gap-sm`
   (.75rem) / `.grid-gap-md` (1rem) / `.grid-gap-lg` (1.5rem). L'alignement
   vertical est `start` par défaut ; `.grid-stretch` rétablit des colonnes de
   même hauteur (cartes côte à côte). */

/* Utilitaire nu : les colonnes viennent d'ailleurs (motifs `auto-fit`). */
.grid { display: grid; }

.grid-2,
.grid-3,
.grid-ratio-2-1,
.grid-ratio-1-2,
.grid-ratio-1-2-1 {
    display: grid;
    gap: var(--grid-gap, 1rem);
    align-items: start;
}

/* Colonnes égales. */
.grid-2 { --grid-gap: 1.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* INTRINSÈQUE, et c'est ce qui remplace quatre surcharges de point de rupture
   (860, 560, et la rustine 861-1090 de la « falaise » plus bas). `auto-fit`
   pose autant de colonnes que la place en autorise - trois au large, deux à
   l'étroit, une sur un téléphone - sans qu'aucune règle n'ait à nommer une
   largeur d'écran. Et `min(16rem, 100%)` est la moitié qui compte : sans lui,
   une piste de 16 rem dans un conteneur de 14 rem déborde, ce qui est
   exactement le défaut qu'on cherchait à corriger. La grille se replie donc
   aussi bien dans une colonne étroite d'un grand écran que sur un mobile. */
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr)); }

/* Colonnes inégales : colonne principale à gauche (2-1), à droite (1-2) ou au
   centre (1-2-1, le triplet code postal / ville / pays). */
.grid-ratio-2-1   { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.grid-ratio-1-2   { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.grid-ratio-1-2-1 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(0, 1fr); }

/* Modificateurs, déclarés après les grilles pour l'emporter à spécificité égale. */
.grid-gap-sm { --grid-gap: .75rem; }
.grid-gap-md { --grid-gap: 1rem; }
.grid-gap-lg { --grid-gap: 1.5rem; }
.grid-stretch { align-items: stretch; }

/* Colonnes en MAÇONNERIE : les cartes se remplissent de haut en bas puis
   passent à la colonne suivante, sans laisser le trou qu'une grille creuse
   quand deux cartes voisines n'ont pas la même hauteur.
   `columns` et non `grid` : une grille aligne des LIGNES, ce qui est exactement
   ce qu'on ne veut pas ici - une fiche courte à côté d'une longue étire la
   ligne à la hauteur de la plus grande. C'est le défaut que cet utilitaire
   corrige sur la fiche client de l'administration.
   `break-inside: avoid` est indispensable : sans lui une carte se coupe en deux
   au changement de colonne, et le rendu devient illisible.
   Contrepartie assumée : l'ORDRE de lecture devient vertical par colonne, plus
   « gauche puis droite ». Acceptable sur des fiches indépendantes les unes des
   autres, à éviter partout où la suite des cartes raconte quelque chose. */
.masonry-2 { column-count: 2; column-gap: 1.5rem; }
.masonry-2 > * { break-inside: avoid; margin-bottom: 1.5rem; }

@media (max-width: 720px) { .masonry-2 { column-count: 1; } }

@media (max-width: 720px) {
    .grid-2,
    .grid-ratio-2-1,
    .grid-ratio-1-2,
    .grid-ratio-1-2-1 { grid-template-columns: minmax(0, 1fr); }
}

/* En-têtes de page : les actions passent à la ligne sur mobile plutôt que déborder. */
@media (max-width: 640px) {
    .page-header { flex-wrap: wrap; }
    .page-header .page-actions { margin-left: 0; width: 100%; }
    .seats-badge { margin-left: 0; }
}

/* Sélecteur d'entreprise (topbar).
   - Variante simple (mono-entreprise) : nom long tronqué pour ne pas écraser le reste.
   - Variante menu (<details>) : NE PAS tronquer ni masquer le dépassement, sinon le
     panneau déroulant (position:absolute) est clippé par overflow:hidden et le menu
     « ne s'ouvre pas ». Le bouton prend la largeur de son contenu (+ marge) en desktop. */
.app-topbar .company-switch:not(.company-switch-menu) {
    min-width: 0; max-width: 50%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.company-switch-menu { max-width: none; overflow: visible; }
.company-switch-menu summary { min-width: 0; white-space: nowrap; }
/* Sur écran étroit, on retronque le nom dans le bouton pour préserver la topbar. */
@media (max-width: 640px) {
    .company-switch-menu summary { max-width: 55vw; }
    .company-switch-menu summary > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---------- Wizard d'onboarding (pleine largeur) ---------- */
.wizard-head { margin-bottom: 1.75rem; }
.wizard-head .page-title { font-size: 1.6rem; margin: 0; }
.wizard-head .page-subtitle { color: var(--muted); margin: .25rem 0 1.5rem; }

.wizard-lang { display: flex; justify-content: flex-end; margin-bottom: .75rem; }
.wizard-steps { display: flex; list-style: none; padding: 0; margin: 0; }
.wizard-step { flex: 1; display: flex; flex-direction: column; align-items: center; gap: .45rem; position: relative; text-align: center; }
.wizard-step::before {
    content: ''; position: absolute; top: 1rem; left: -50%; width: 100%; height: 2px; background: var(--border); z-index: 0;
}
.wizard-step:first-child::before { display: none; }
.wizard-step.done::before, .wizard-step.current::before { background: var(--primary); }
.wizard-step-dot {
    position: relative; z-index: 1; width: 2rem; height: 2rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: .85rem;
    background: var(--surface-2); color: var(--muted); border: 2px solid var(--border);
}
.wizard-step.current .wizard-step-dot { background: var(--primary); color: #fff; border-color: var(--primary); }
.wizard-step.done .wizard-step-dot { background: #15b8a6; color: #fff; border-color: #15b8a6; }
.wizard-step-label { font-size: .82rem; color: var(--muted); }
.wizard-step.current .wizard-step-label { color: var(--text); font-weight: 600; }
@media (max-width: 560px) { .wizard-steps { gap: .25rem; } .wizard-step-label { font-size: .72rem; } }

/* Ruban d'étapes du parcours d'import assisté (/app/factures/importer) : centré
   dans la page, avec une respiration au-dessus de l'étape courante.
   Le `padding-top` est porté par le conteneur et non par le seul `.current` :
   `.wizard-step::before` (le trait de liaison) est positionné à `top:1rem` dans
   le repère du <li>, décaler une seule étape désaligne le trait des pastilles
   voisines. Modificateur dédié parce que `.wizard-steps` sert aussi à
   l'onboarding et à l'inscription, dont la mise en page ne bouge pas. */
.wizard-steps--import { max-width: 640px; margin: 0 auto 1.75rem; padding-top: .85rem; }
.wizard-steps--import .wizard-step { justify-content: center; text-align: center; }

.wizard-body { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 1.5rem; align-items: start; }
.wizard-aside .card { background: var(--surface); }
.wizard-aside h3 { font-size: 1rem; margin: 0 0 .5rem; }
.wizard-aside p { color: var(--muted); font-size: .88rem; margin: 0 0 .75rem; }
.wizard-aside ul { margin: 0; padding-left: 1.1rem; color: var(--muted); font-size: .88rem; line-height: 1.7; }
@media (max-width: 980px) {
    .wizard-body { grid-template-columns: 1fr; }
}

/* Flash violet d'un champ recopié depuis une suggestion annuaire (onboarding) :
   clignote 2 fois pour matérialiser la recopie SIREN / raison sociale. */
@keyframes field-flash-violet {
    0%, 100% { background-color: var(--surface); box-shadow: 0 0 0 0 rgba(var(--primary-rgb), 0); }
    25%, 75% { background-color: var(--primary-50); box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .35); }
}
.field-flash { animation: field-flash-violet .8s ease-in-out 2; }
@media (prefers-reduced-motion: reduce) { .field-flash { animation: none; } }

/* Retours de la recherche annuaire (onboarding). La consultation recharge la
   page : sans témoin ni message d'absence de résultat, l'écran reste identique
   et l'utilisateur ne sait pas si la recherche a tourné. */
.directory-status {
    display: flex; align-items: center; gap: .5rem;
    margin: 0 0 1rem; font-size: .88rem; color: var(--muted);
}
/* `hidden` doit l'emporter sur le `display:flex` ci-dessus. */

.directory-count {
    display: flex; align-items: center; gap: .5rem;
    margin: 0; padding: .6rem .75rem .2rem;
    font-size: .82rem; font-weight: 600; color: var(--muted);
}

.directory-empty { background: var(--surface-2); padding: .9rem 1rem; }
.directory-empty-title {
    display: flex; align-items: center; gap: .5rem;
    margin: 0 0 .25rem; font-size: .92rem; font-weight: 600;
}
.directory-empty .form-hint { margin: 0; }

.app-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-topbar {
    height: 62px; background: var(--surface); border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between; padding: 0 1.5rem; position: sticky; top: 0; z-index: 20;
}
.app-topbar .company-switch { font-weight: 600; display: flex; align-items: center; gap: .5rem; }

/* Sélecteur d'entreprise active (comptes multi-entreprises) */
.company-switch-menu { position: relative; display: block; }
.company-switch-menu summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; gap: .5rem;
    padding: .4rem .7rem; border-radius: 8px; border: 1px solid transparent; user-select: none;
}
.company-switch-menu summary::-webkit-details-marker { display: none; }
.company-switch-menu summary:hover { background: var(--bg); border-color: var(--border); }
.company-switch-menu[open] summary { background: var(--bg); border-color: var(--border); }
.company-switch-caret { font-size: .65rem; color: var(--muted); transition: transform .15s; }
.company-switch-menu[open] .company-switch-caret { transform: rotate(180deg); }
.company-switch-list {
    position: absolute; top: calc(100% + 6px); left: 0; min-width: 280px; z-index: 50;
    background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12); padding: .4rem; display: flex; flex-direction: column;
}
.company-switch-label {
    font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
    padding: .35rem .6rem .25rem; font-weight: 600;
}
.company-switch-item {
    display: flex; align-items: center; gap: .6rem; padding: .5rem .6rem; border-radius: 7px;
    color: inherit; text-decoration: none; font-weight: 500; font-size: .92rem;
}
.company-switch-item small { display: block; color: var(--muted); font-weight: 400; font-size: .75rem; }
.company-switch-item:hover { background: var(--bg); }
.company-switch-item.current { background: var(--bg); cursor: default; }
.company-switch-item.current > i { color: #15b8a6; }
/* Bascule d'entreprise en POST (SEC-07) : le formulaire s'efface, le bouton
   reprend la place et l'allure du lien qu'il remplace. */
.company-switch-form { display: contents; }
button.company-switch-item,
button.status-company-link,
button.link-button { background: none; border: 0; padding-inline: 0; font-family: inherit; color: inherit; text-align: left; cursor: pointer; }
button.company-switch-item { width: 100%; padding-inline: .6rem; }
button.link-button { padding-block: 0; font-size: inherit; }
button.link-button:hover strong { color: var(--primary); }
button.badge { border: 0; font-family: inherit; cursor: pointer; }
.company-switch-add { border-top: 1px solid var(--border); border-radius: 0 0 7px 7px; margin-top: .25rem; color: var(--muted); font-size: .85rem; }
.app-topbar .user { display: flex; align-items: center; gap: .75rem; color: var(--muted); font-size: .9rem; }
.app-topbar .user-link { display: inline-flex; align-items: center; gap: .5rem; color: inherit; text-decoration: none; padding: .25rem .5rem; border-radius: var(--radius, .5rem); transition: background .15s, color .15s; }
.app-topbar .user-link:hover { color: var(--primary); background: var(--primary-50, rgba(57,55,163,.08)); }

/* Actions en icône seule de la barre du haut (aide, installation de
   l'application) : une cible de 36 px - en dessous, elle devient impraticable
   au doigt (WCAG 2.5.8 demande 24 px au minimum, et le pouce ne vise pas au
   pixel). Le bouton d'installation partage ce gabarit pour ne pas introduire
   une seconde taille de cible dans la même barre. */
.app-topbar .topbar-help,
.app-topbar .topbar-install {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; border-radius: var(--radius, .5rem);
    color: var(--muted); text-decoration: none; transition: background .15s, color .15s;
}
/* Le bouton d'installation est un <button> : il faut neutraliser le rendu
   natif que le <a> de l'aide n'a pas. */
.app-topbar .topbar-install { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
.app-topbar .topbar-help:hover,
.app-topbar .topbar-help:focus-visible,
.app-topbar .topbar-help.active,
.app-topbar .topbar-install:hover,
.app-topbar .topbar-install:focus-visible { color: var(--primary); background: var(--primary-50, rgba(57,55,163,.08)); }

/* ---------- Clochette de notifications (topbar) ----------
   Même gabarit de cible que l'aide et l'installation - 36 px -, pour ne pas
   introduire une troisième taille dans la même barre. Le `<details>` doit
   rester `overflow: visible`, sinon le volet en `position: absolute` est clippé
   et le menu « ne s'ouvre pas » : la même leçon que le sélecteur d'entreprise,
   payée une fois, ne se repaie pas. */
.notif-bell { position: relative; overflow: visible; }
.notif-bell-summary {
    display: inline-flex; align-items: center; justify-content: center; position: relative;
    width: 36px; height: 36px; border-radius: var(--radius, .5rem);
    color: var(--muted); cursor: pointer; list-style: none; transition: background .15s, color .15s;
}
/* Le chevron natif du <summary> n'a pas de sens sur une icône seule. */
.notif-bell-summary::-webkit-details-marker { display: none; }
.notif-bell-summary::marker { content: ''; }
.notif-bell-summary:hover,
.notif-bell-summary:focus-visible,
.notif-bell[open] .notif-bell-summary { color: var(--primary); background: var(--primary-50, rgba(57,55,163,.08)); }
/* Volet ouvert : l'infobulle n'a plus rien a apprendre a personne, et elle
   recouvrait « Tout marquer comme lu » - le bouton le plus haut du panneau,
   donc celui qu'elle masquait. Elle ne sert qu'a nommer une icone fermee. */
.notif-bell[open] .notif-bell-summary::before,
.notif-bell[open] .notif-bell-summary::after { display: none; }

/* La pastille. `is-empty` la retire du flux plutôt que de la peindre en
   transparent : un rond vide au coin d'une cloche se lit comme un défaut
   d'affichage. */
.notif-badge {
    position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 4px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--danger, #dc2626); color: #fff;
    border-radius: 999px; font-size: .65rem; font-weight: 700; line-height: 1;
}
.notif-badge.is-empty { display: none; }

.notif-panel {
    position: absolute; top: calc(100% + 6px); right: 0; width: 360px; max-width: calc(100vw - 1.5rem); z-index: 50;
    background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12); overflow: hidden;
}
.notif-panel-head {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    padding: .6rem .75rem; border-bottom: 1px solid var(--border);
}
.notif-panel-title { font-weight: 600; font-size: .9rem; color: var(--text); }
.notif-readall-form { margin: 0; }
.notif-readall {
    background: none; border: 0; padding: 0; cursor: pointer;
    font: inherit; font-size: .8rem; color: var(--primary); text-decoration: underline;
}
.notif-stale {
    margin: 0; padding: .6rem .75rem; font-size: .82rem;
    background: color-mix(in srgb, var(--warning) 12%, var(--surface)); color: var(--warning);
}

.notif-list { list-style: none; margin: 0; padding: 0; max-height: 22rem; overflow-y: auto; }
.notif-list-page { max-height: none; overflow: visible; }
.notif-item { border-bottom: 1px solid var(--border); }
.notif-item:last-child { border-bottom: 0; }
.notif-item form { margin: 0; }
/* La ligne entière est le bouton : viser un lien de trois mots au pouce est le
   défaut que le patron `row-link` a déjà corrigé ailleurs. */
.notif-item-btn {
    display: flex; align-items: flex-start; gap: .6rem; width: 100%; text-align: left;
    background: none; border: 0; padding: .7rem .75rem; cursor: pointer; font: inherit; color: inherit;
}
.notif-item-btn:hover,
.notif-item-btn:focus-visible { background: var(--surface-2); }
/* Le point de non-lu occupe sa place même quand la ligne est lue : sans quoi
   tout le texte se décale de six pixels d'une ligne à l'autre. */
.notif-item-dot {
    flex: 0 0 auto; width: 8px; height: 8px; margin-top: .4rem; border-radius: 50%; background: transparent;
}
.notif-item.is-unread .notif-item-dot { background: var(--primary); }
.notif-item.is-unread .notif-item-title { font-weight: 600; }
.notif-item-body { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.notif-item-title { font-size: .88rem; color: var(--text); }
.notif-item-desc { font-size: .82rem; color: var(--muted); overflow: hidden; text-overflow: ellipsis; }
/* Une notification sans contrepartie ni numero - une alerte de compte - n'a pas
   de ligne de detail. L'element reste dans le DOM pour que le sondage retrouve
   sa cible ; c'est ici qu'il disparait, sans laisser l'espacement du flex. */
.notif-item-desc:empty { display: none; }
.notif-item-time { font-size: .75rem; color: var(--muted); }
.notif-empty { padding: 1.5rem .75rem; text-align: center; font-size: .85rem; color: var(--muted); }
.notif-panel-foot {
    display: block; padding: .6rem .75rem; text-align: center;
    border-top: 1px solid var(--border); font-size: .82rem; color: var(--primary); text-decoration: none;
}
.notif-panel-foot:hover { background: var(--surface-2); }

/* Adresses publiées à l'annuaire, sur la fiche client. Une par ligne : elles se
   ressemblent a un caractere pres (`0225:853322915_85332291500010`), et les
   mettre en colonne est ce qui rend la difference lisible. */
.addressing-list { list-style: none; margin: .25rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .35rem; }
.addressing-row {
    display: flex; align-items: center; justify-content: space-between; gap: .75rem;
    padding: .4rem .6rem; border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface);
}
.addressing-row.is-pinned { border-color: var(--primary); background: var(--primary-50); }
.addressing-id { min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.addressing-id code { font-size: .8rem; word-break: break-all; }
.addressing-row form { margin: 0; flex: 0 0 auto; }
@media (max-width: 560px) {
    .addressing-row { flex-direction: column; align-items: stretch; }
}

/* Filtres de la page complète : deux états, deux liens, aucune dépendance JS. */
.notif-filters { display: flex; gap: .5rem; margin-bottom: 1rem; }
.notif-filter {
    padding: .35rem .75rem; border-radius: 999px; border: 1px solid var(--border);
    font-size: .85rem; color: var(--muted); text-decoration: none;
}
.notif-filter.is-active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* Sous 640 px, le volet part du bord droit de l'écran plutôt que de la cloche :
   ancré sur l'icône, il déborderait à droite de la fenêtre. */
@media (max-width: 640px) {
    .notif-panel { position: fixed; top: 3.5rem; right: .75rem; left: .75rem; width: auto; max-width: none; }
}

/* Infobulle en CSS pur : `title` seul attend une seconde et se rend au style du
   système, et un composant JS ne se justifie pas pour un libellé fixe. Le nom
   accessible reste porté par `aria-label` - l'infobulle est un doublon visuel,
   jamais la seule source de l'information.

   Elle apparaît aussi au clavier (`:focus-visible`), sans quoi elle ne dirait
   rien à qui n'utilise pas de souris, et `pointer-events: none` l'empêche de
   s'interposer entre le curseur et sa propre cible. */
.tip { position: relative; }
.tip::after {
    content: attr(data-tip);
    position: absolute; top: calc(100% + .4rem); left: 50%; transform: translateX(-50%);
    padding: .3rem .55rem; border-radius: .4rem;
    background: var(--text, #1f2937); color: #fff;
    font-size: .74rem; font-weight: 600; line-height: 1.3; white-space: normal;
    /* Bornée à la fenêtre : une infobulle longue sur une icône proche du bord
       sortait de l'écran entre 641 et 1024 px, où elle reste affichée. Au-delà
       de la borne, le texte passe à la ligne au lieu de déborder. */
    max-width: min(18rem, calc(100vw - 1.5rem)); width: max-content;
    opacity: 0; visibility: hidden; transition: opacity .12s ease;
    pointer-events: none; z-index: 60;
}
.tip:hover::after,
.tip:focus-visible::after { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .tip::after { transition: none; } }
/* Pleine largeur assumée : les écrans de ce produit sont des tableaux de
   factures et des grilles de suivi, qui gagnent à chaque pixel. Le plafond de
   1560 px qui valait pour du texte d'aide laissait 700 px de vide de part et
   d'autre sur un 2560 px, tableau tronqué au milieu. Les gouttières passent à
   2.5rem pour que le contenu ne colle pas aux bords. */
.app-content { padding: 1.5rem 2.5rem; flex: 1; display: flex; flex-direction: column; width: 100%; max-width: 100%; }

/* --- Débordement horizontal des listes clé/valeur ---------------------
   Onze gabarits posent `display:grid; grid-template-columns: max-content 1fr`
   en style inline (fiches client, admin, PDP, revue d'import). `max-content` ne
   se contracte pas, et le minimum d'une piste `1fr` est le MIN-CONTENT de son
   contenu : une adresse électronique, un IBAN ou un identifiant d'annuaire est
   un mot insécable, dont le min-content vaut la largeur entière.

   Mesuré sur la fiche client à 320 px : 135 px d'intitulé + 16 de gouttière
   + 200 de valeur = 351 px pour 264 px disponibles. Rien ne clippe en amont
   (`.card` et `.app-content` n'ont pas d'`overflow`, et il n'y a pas
   d'`overflow-x: hidden` sur `body`) : c'est TOUTE LA PAGE qui part en
   défilement horizontal.

   `anywhere` et non `break-word` : seul le premier modifie la taille
   min-content, donc la largeur minimale de la piste qui porte la valeur. */
.app-content dd,
.portal-main dd { min-width: 0; overflow-wrap: anywhere; }

/* Un bouton contraint en largeur ne peut pas rester `nowrap` : sa largeur
   minimale reste celle du libellé complet, il déborde donc de toute colonne
   plus étroite. Cas mesuré : la colonne latérale de la fiche facture offre
   128 px utiles à 861 px de fenêtre, pour un libellé qui en réclame 181. */
.btn-block,
.app-content [style*="width:100%"].btn,
.app-content [style*="width: 100%"].btn { white-space: normal; text-align: center; }

/* Longueur de ligne des textes d'accompagnement.

   `.form-hint` en est SORTI (2026-09-04) : la ligne de confort s'y lisait comme
   une colonne orpheline sous les blocs pleine largeur - tableaux, grilles de
   tuiles, cartes de paramètres - et laissait à droite un vide que rien ne
   justifiait. C'est le cas majoritaire, pas l'exception : le modificateur
   `.form-hint--wide` qui traitait ces cas un par un n'a donc plus lieu d'être. */
.page-subtitle, .modal-message { max-width: 78ch; }

/* ---------- Sélecteur de langue (interrupteur "pilule" FR/EN) ----------
   Composant unique : templates/_partials/_locale_switcher.html.twig,
   piloté par assets/controllers/locale_switch_controller.js. */
.lang-toggle { display: inline-flex; margin: 0; flex-shrink: 0; }
.lang-toggle-track {
    position: relative; display: inline-flex; align-items: center;
    width: 60px; height: 30px; padding: 3px; border-radius: 999px;
    background: var(--surface-2); border: 1px solid var(--border);
    cursor: pointer; user-select: none; transition: border-color .15s;
}
.lang-toggle-track:hover { border-color: var(--primary); }
.lang-toggle-track:focus-visible {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .22);
}
.lang-toggle-option {
    position: relative; z-index: 1; flex: 1; display: flex; align-items: center; justify-content: center;
    height: 100%; opacity: .45; transition: opacity .2s ease;
}
.lang-toggle-option svg { width: 18px; height: 12px; border-radius: 2px; display: block; box-shadow: 0 0 0 1px rgba(16, 24, 40, .08); }
.lang-toggle-track:not(.is-en) .lang-toggle-option-fr,
.lang-toggle-track.is-en .lang-toggle-option-en { opacity: 1; }
.lang-toggle-thumb {
    position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%;
    background: var(--surface); box-shadow: var(--shadow-sm);
    transition: transform .2s ease; pointer-events: none;
}
.lang-toggle-track.is-en .lang-toggle-thumb { transform: translateX(30px); }
/* Sur la vitrine (fond clair, header flouté) : garder un contraste net. */
.mkt-header .lang-toggle-track { background: #fff; }

/* Footer applicatif : barre fine collée en bas de la zone principale (sticky).

   Trois zones en grille `1fr auto 1fr` plutôt qu'un `space-between` : la
   version reste alors VRAIMENT au centre de la barre, et non au centre de
   l'espace laissé par ses deux voisins - qui n'ont pas la même largeur et n'en
   auront jamais, les crédits étant quatre fois plus longs que l'état du
   réseau. */
.app-footer {
    flex-shrink: 0; padding: .5rem 1.5rem;
    border-top: 1px solid var(--border); background: var(--surface);
    color: var(--muted); font-size: .78rem;
    position: sticky; bottom: 0; z-index: 10;
    display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .75rem;
}
.app-footer-credits { justify-self: start; }
/* Pied de page à mention unique (portail client) : la grille en trois colonnes
   n'a rien à répartir, la mention se centre sur toute la largeur. */
.app-footer--single { display: flex; justify-content: center; text-align: center; }
.app-footer .app-status { justify-self: end; }
.app-version { justify-self: center; color: var(--muted); opacity: .7; font-variant-numeric: tabular-nums; letter-spacing: .02em; user-select: all; }
.app-footer i.fa-heart { color: #e0245e; margin: 0 .1rem; }
.app-footer a { color: var(--muted); font-weight: 600; }
.app-footer a:hover { color: var(--primary); }

.page-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem; }

/* Profil : cartes en grille 2 x 2, repliées en 1 colonne sur mobile/tablette étroite. */
.profile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 1.5rem; align-items: start; }
@media (max-width: 720px) { .profile-grid { grid-template-columns: 1fr; } }

/* Badge sièges (page Équipe) : sièges consommés / inclus dans l'offre. */
.seats-badge {
    margin-left: auto; display: flex; flex-direction: column; align-items: flex-end;
    padding: .5rem .85rem; border: 1px solid var(--border); border-radius: 10px;
    background: var(--surface); line-height: 1.2; white-space: nowrap;
}
.seats-badge-main { display: inline-flex; align-items: center; gap: .35rem; font-size: .95rem; }
.seats-badge-main i { color: var(--primary); }
.seats-badge small { color: var(--muted); font-size: .7rem; text-transform: uppercase; letter-spacing: .03em; margin-top: .1rem; }
.seats-badge-full { border-color: #fbe3bd; background: #fef5e7; }
.seats-badge-full .seats-badge-main, .seats-badge-full .seats-badge-main i { color: #7a4a06; }
.page-header .page-title { font-size: 1.5rem; margin: 0; }
.page-header .page-subtitle { color: var(--muted); font-size: .9rem; margin: .15rem 0 0; }
/* `align-items: center` n'est pas cosmétique : sans lui la valeur par défaut est
   `stretch`, et une barre d'actions mélange toujours deux natures d'enfants - le
   lien `<a class="btn">` posé directement, et le bouton emballé dans un `<form>`
   (CSRF oblige). Le premier s'étire sur toute la hauteur de la ligne, le second
   non : son `<form>` s'étire à sa place et le bouton garde sa hauteur propre. Le
   même `.btn` paraissait donc PLUS GROS selon qu'il portait un POST ou non. */
/* `flex-wrap` n'est pas un confort : `.btn` porte `white-space: nowrap`, donc sa
   largeur minimale est celle de son libellé entier. Le repli en colonne
   n'existait qu'en dessous de 640 px, si bien qu'entre 641 px et ~860 px une
   barre à trois boutons pleins - toutes les pages de détail - faisait défiler la
   page HORIZONTALEMENT. Un écran s'en était déjà sorti par un `flex-wrap` posé
   en style inline : le symptôme était connu, la cause non. */
.page-header .page-actions { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }

/* Empty state */
.empty-state { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 3.5rem 1rem; }
.empty-state .icon { width: 4rem; height: 4rem; border-radius: 50%; background: var(--primary-50); color: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 1.5rem; margin-bottom: 1rem; }
.empty-state h3 { margin: 0 0 .35rem; }
.empty-state p { color: var(--muted); max-width: 26rem; margin: 0 0 1.25rem; }

/* ---------- Auth ---------- */
.auth-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 2rem; background: radial-gradient(1200px 600px at 100% -10%, rgba(var(--primary-rgb), .12), transparent), var(--bg); }
.auth-card { width: 100%; max-width: 400px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); padding: 2rem; }
.auth-card .brand { display: flex; align-items: center; gap: .6rem; font-weight: 800; font-size: 1.15rem; margin-bottom: 1.5rem; }
.auth-card .brand.brand-stacked { flex-direction: column; justify-content: center; gap: .5rem; text-align: center; }
.auth-card .brand .logo { width: 34px; height: 34px; border-radius: 9px; background: var(--primary); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; }

/* Onglets de la page de connexion unifiée (Espace fournisseur / Espace client). */
.login-tabs-nav { display: flex; gap: .35rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: .3rem; margin-bottom: 1.25rem; }
.login-tab { flex: 1; border: 0; background: transparent; color: var(--muted, #6b7280); font: inherit; font-size: .9rem; font-weight: 600; padding: .55rem .5rem; border-radius: calc(var(--radius) - 3px); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: .4rem; transition: background .15s ease, color .15s ease, box-shadow .15s ease; }
.login-tab:hover { color: var(--text, #111827); }
.login-tab.is-active { background: var(--surface); color: var(--primary); box-shadow: 0 1px 2px rgba(0, 0, 0, .08); }
.login-tab { white-space: nowrap; }
/* Carte de connexion élargie : les libellés d'onglets tiennent sur une ligne. */
.auth-card--wide { max-width: 480px; }
/* Inscription : le parcours affiche un fil de 5 étapes en tête, à l'étroit dans
   une carte d'authentification classique. On l'élargit nettement sur grand
   écran seulement - en dessous, la largeur reste pilotée par `auth-card`. */
.auth-card--wizard { max-width: 900px; }
.auth-card--wizard .brand { justify-content: center; margin-bottom: 2.25rem; }
/* Transition fluide au changement d'onglet (le panneau affiché apparaît en fondu). */
.login-panel:not([hidden]) { animation: login-panel-in .18s ease both; }
@keyframes login-panel-in {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
    .login-panel:not([hidden]) { animation: none; }
}

/* ============================================================
   Parcours d'authentification - durcissement mobile (2026-07-26)
   Aucune media query ne ciblait `.auth-*` / `.login-*` : tout le parcours
   était servi avec ses valeurs de bureau. Corrigés ici, dans l'ordre :
   débordement horizontal des onglets, zoom iOS au focus, cibles tactiles,
   hauteur de fenêtre, densité verticale sur téléphone.
   ============================================================ */

/* Onglets : autoriser le rétrécissement sous la largeur min-content.
   `white-space: nowrap` (l. 670) combiné au `min-width: auto` implicite d'un
   élément flex figeait la barre à ~307 px, alors que la carte n'en offre que
   190 sur un écran de 320 px -> la barre sortait de la carte, puis du
   document, et toute la page partait en défilement horizontal. */
.login-tab { white-space: normal; min-width: 0; min-height: 44px; }
.login-tab > i { flex: 0 0 auto; }

/* État actif : blanc sur `--surface-2` donne 1,03:1, indiscernable en plein
   jour. Le soulignement porte l'information sans dépendre de la couleur -
   or c'est ici la décision structurante de la page (fournisseur vs client). */
.login-tab.is-active { box-shadow: 0 1px 2px rgba(0, 0, 0, .08), inset 0 -2px 0 var(--primary); }

/* Ni `.btn` ni `.login-tab` n'avaient de focus visible. */
.btn:focus-visible,
.login-tab:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Sur écran tactile, `:hover` reste collé après un appui : le bouton gardait
   son décalage de 1 px et sa teinte jusqu'à l'appui suivant. */
@media (hover: none) {
    .btn:hover { transform: none; }
}

/* Barre d'URL iOS : `100vh` mesure la fenêtre SANS elle, la carte est donc
   poussée sous la ligne de flottaison puis « rebondit ». */
.auth-wrap { min-height: 100svh; }

/* Typographie des écrans d'authentification : elle était posée en
   `style="font-size:..."` dans sept gabarits, qu'aucune media query ne peut
   surcharger (spécificité d'un attribut de style). */
.auth-title { font-size: clamp(1.15rem, 4.6vw, 1.3rem); margin: 0 0 .35rem; }
.auth-subtitle { font-size: .9rem; margin: 0 0 1rem; }
.auth-hint { font-size: .88rem; margin: 0 0 1rem; }
.auth-footnote { font-size: .85rem; }

/* Libellé de champ et lien de service sur la même ligne (« Mot de passe » /
   « Mot de passe oublié ? ») : ils passent l'un sous l'autre si la place
   manque, plutôt que de se chevaucher. */
.form-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem; flex-wrap: wrap; }
.form-label-row .form-label { margin-bottom: 0; }

/* Séparateur « ou » : les deux panneaux le stylaient différemment (gouttière
   .75rem contre .5rem, marges 1,1rem contre 1,5rem), d'où un saut vertical du
   bouton de clé d'accès au changement d'onglet. `margin: 0` neutralise en
   prime la marge par défaut du navigateur sur `<hr>`, qui gonflait un trait
   d'1 px à 17 px. */
.auth-sep { display: flex; align-items: center; gap: .75rem; margin: 1.1rem 0; }
.auth-sep hr { flex: 1; margin: 0; border: none; border-top: 1px solid var(--border); }
.auth-sep span { font-size: .8rem; white-space: nowrap; }

/* Zone d'erreur remplie en JavaScript (clé d'accès) : masquée tant qu'elle est
   vide, sans dépendre du seul `style="display:none"` du gabarit. */
.auth-async-error { margin-bottom: .75rem; }
.auth-async-error:empty { display: none; }

@media (max-width: 480px) {
    /* 2rem de `.auth-wrap` + 2rem de `.auth-card` de chaque côté, soit 64 px :
       un cinquième de la largeur d'un écran de 320 px passait en marges. */
    .auth-wrap { padding: 1rem .75rem; align-items: flex-start; }
    .auth-card { padding: 1.25rem 1rem; border-radius: var(--radius); box-shadow: var(--shadow); }
    .auth-card .brand { margin-bottom: 1rem; }
    /* Marque en ligne plutôt qu'empilée : ~40 px regagnés au-dessus de la
       ligne de flottaison, où se trouve le bouton de connexion. */
    .auth-card .brand.brand-stacked { flex-direction: row; gap: .5rem; }
    .login-tabs-nav { margin-bottom: 1rem; }
    .login-tab { font-size: .85rem; padding: .5rem .4rem; gap: .3rem; }
    .form-group { margin-bottom: .9rem; }
}

/* Les champs passent à 16 px (zoom automatique iOS) et les boutons à leur
   taille tactile dans le bloc « Adaptation mobile » en fin de fichier, qui
   couvre toute l'application. Ne reste ici que le réglage propre aux écrans
   d'authentification : le bouton oeil grandit avec le champ, il faut donc
   relever le retrait du texte, sans quoi le mot de passe saisi passerait
   dessous. */
@media (pointer: coarse) {
    .password-field .form-control { padding-right: 3.1rem; }
}

/* Écrans d'erreur (404/403/500...) - shell app ou page autonome. */
.error-panel { text-align: center; padding: 1.5rem 0; }
.error-code { font-size: clamp(3.5rem, 12vw, 6rem); font-weight: 800; line-height: 1; color: var(--primary); letter-spacing: -.02em; }
.error-title { font-size: 1.4rem; margin: .5rem 0 .35rem; }
.error-text { max-width: 46ch; margin: 0 auto 1.25rem; }
.error-incident { font-size: .85rem; margin: 0 0 1.25rem; }
.error-incident code { background: var(--surface-2); padding: .1rem .45rem; border-radius: 6px; font-weight: 600; }
.error-actions { display: flex; gap: .6rem; justify-content: center; flex-wrap: wrap; }

/* Ligne d'entreprise active (tableau de bord). */
.table tr.row-selected td { background: var(--surface-2); }
.table tr.row-selected td:first-child { box-shadow: inset 3px 0 0 var(--primary); }

/* Onglets pur CSS de la page Statut (radios frères, CSP-safe). */
.status-tab-input { position: absolute; opacity: 0; pointer-events: none; }
.status-tabs-nav { display: flex; gap: .35rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: .3rem; margin-bottom: 1.25rem; }
.status-tabs-nav label { flex: 1; text-align: center; padding: .55rem; border-radius: calc(var(--radius) - 3px); cursor: pointer; font-weight: 600; font-size: .9rem; color: var(--muted); display: inline-flex; align-items: center; justify-content: center; gap: .4rem; }
#tab-account:checked ~ .status-tabs-nav label[for="tab-account"],
#tab-system:checked ~ .status-tabs-nav label[for="tab-system"] { background: var(--surface); color: var(--primary); box-shadow: 0 1px 2px rgba(0, 0, 0, .08); }
.status-tab-panel { display: none; }
#tab-account:checked ~ .status-panel-account { display: block; }
#tab-system:checked ~ .status-panel-system { display: block; }

/* ---------- Utilities ---------- */
.container { max-width: 1140px; margin: 0 auto; padding: 0 1.25rem; }
/* Conteneur large pour le site vitrine (marketing) : pleine largeur adaptative. */
.container-wide { width: 100%; max-width: 1440px; margin: 0 auto; padding: 0 clamp(1.25rem, 5vw, 4rem); box-sizing: border-box; }
/* Conteneur des pages de CONTENU long (pages piliers) : une mesure de ligne
   lisible. Les trois gabarits `public/pillar_*.html.twig` l'employaient depuis
   leur livraison alors qu'il n'était défini NULLE PART - mesuré le 13/08 sur
   la production : paragraphes à 1 425 px de large, collés au bord gauche. */
.container-narrow { width: 100%; max-width: 52rem; margin: 0 auto; padding: 0 1.25rem; box-sizing: border-box; }
/* Chapô d'une page de contenu. Même histoire : `.lead` n'existait que sous
   `.hero`, donc le chapô des pages piliers se rendait en paragraphe ordinaire. */
.lead { font-size: 1.12rem; color: var(--muted); }
/* Liste de LIENS (« pour aller plus loin ») : un lien de corps de texte fait
   19 px de haut, sous les 24 px exigés pour une cible tactile - Lighthouse le
   relevait sur `/facturation-electronique-2026` (accessibilité 95). Le
   remplissage vertical porte la cible à ~29 px sans toucher au rythme du texte.
   À réserver aux listes dont CHAQUE élément est un lien. */
.link-list li { margin-bottom: .55rem; }
.link-list a { display: inline-block; padding: .3rem 0; }
.muted { color: var(--muted); }
.text-center { text-align: center; }
/* `.text-right` manquait ici alors que SEIZE templates l'utilisaient déjà :
   autant de colonnes de montants et de cellules d'actions restées alignées à
   gauche, sans que personne relie le symptôme à une classe absente. La poser
   corrige ces seize endroits d'un coup - c'est voulu. Sa place est ICI, à côté
   de sa jumelle : la déclarer ailleurs aurait créé le doublon que
   `ResponsiveTablePatternTest` interdit précisément. */
.text-right { text-align: right; }
.flex { display: flex; }
.items-center { align-items: center; }
.between { justify-content: space-between; }
/* Sans `wrap`, une rangee de boutons deborde en largeur des l'ecran etroit :
   `.btn` porte `white-space: nowrap`, sa largeur minimale est celle de son
   libelle entier. C'est ce qui faisait defiler toute la page horizontalement. */
.wrap { flex-wrap: wrap; }
.gap-1 { gap: .25rem; } .gap-2 { gap: .5rem; } .gap-3 { gap: 1rem; } .gap-4 { gap: 1.5rem; }
.mt-1 { margin-top: .5rem; } .mt-2 { margin-top: 1rem; } .mt-3 { margin-top: 1.5rem; } .mt-4 { margin-top: 2.5rem; }
.mb-0 { margin-bottom: 0; } .mb-2 { margin-bottom: 1rem; } .mb-3 { margin-bottom: 1.5rem; }

/* Navigation mobile : la sidebar devient un tiroir off-canvas piloté par #nav-toggle. */
.nav-burger, .nav-overlay { display: none; }
@media (max-width: 1024px) {
    .nav-burger {
        display: inline-flex; align-items: center; justify-content: center;
        width: 40px; height: 40px; flex-shrink: 0; margin-right: .5rem;
        border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
        color: var(--text); cursor: pointer;
    }
    .app-sidebar {
        position: fixed; inset: 0 auto 0 0; z-index: 200; height: 100vh;
        transform: translateX(-100%); transition: transform .2s ease;
        box-shadow: 0 0 40px rgba(0, 0, 0, .35);
    }
    #nav-toggle:checked ~ .app-layout .app-sidebar { transform: none; }
    #nav-toggle:checked ~ .app-layout .nav-overlay {
        display: block; position: fixed; inset: 0; z-index: 150; background: rgba(0, 0, 0, .45);
    }
}

/* ============================================================
   Vitrine (marketing)
   ============================================================ */
.mkt-header { position: sticky; top: 0; z-index: 30; background: rgba(255, 255, 255, .85); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); }
.mkt-nav { display: flex; align-items: center; justify-content: space-between; height: 68px; }
.mkt-brand { display: flex; align-items: center; gap: .6rem; font-weight: 800; font-size: 1.15rem; color: var(--text); }
.mkt-brand .logo { width: 34px; height: 34px; border-radius: 9px; background: var(--primary); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; }
.mkt-nav-links { display: flex; align-items: center; gap: 1.5rem; }
.mkt-nav-links a { color: var(--text); font-weight: 500; }
.mkt-nav-links a:hover { color: var(--primary); text-decoration: none; }
.mkt-nav-links a.btn-primary, .mkt-nav-links a.btn-primary:hover { color: #fff; }
/* Navigation vitrine sur mobile : menu déroulant piloté par #mkt-nav-toggle.
   Le burger porte tabindex/role="button" (cf. contrôleur `mkt-nav`) : il lui
   faut donc un focus visible, qu'un <label> n'a pas par défaut. */
.mkt-burger { display: none; }
.mkt-burger:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (max-width: 768px) {
    .mkt-burger {
        display: inline-flex; align-items: center; justify-content: center;
        width: 44px; height: 44px; margin-left: auto; cursor: pointer;
        border: 1px solid var(--border); border-radius: 8px; background: #fff; color: var(--text);
    }
    .mkt-nav-links {
        display: none; position: absolute; top: 100%; left: 0; right: 0;
        flex-direction: column; align-items: stretch; gap: .25rem;
        background: #fff; border-bottom: 1px solid var(--border); padding: .5rem 1rem 1rem;
        box-shadow: 0 10px 20px rgba(0, 0, 0, .08);
        max-height: calc(100dvh - 68px); overflow-y: auto;
    }
    /* Cibles tactiles : chaque entrée du menu doit faire au moins 44px de haut. */
    .mkt-nav-links > a { display: flex; align-items: center; min-height: 44px; padding: .25rem .25rem; }
    .mkt-nav-links > a.btn { justify-content: center; padding: .7rem 1rem; margin-top: .35rem; }
    #mkt-nav-toggle:checked ~ .mkt-nav-links { display: flex; }
}

.hero { padding: 5rem 0 4rem; background: radial-gradient(900px 500px at 90% -10%, rgba(var(--primary-rgb), .14), transparent); overflow: hidden; }
.hero h1 { font-size: clamp(2.1rem, 4.5vw, 3.4rem); letter-spacing: -.02em; max-width: 14ch; }
.hero p.lead { font-size: 1.2rem; color: var(--muted); max-width: 46ch; margin: 1.25rem 0 2rem; }
.hero .cta-row { display: flex; gap: .75rem; flex-wrap: wrap; }
.hero .eyebrow { display: inline-flex; align-items: center; gap: .5rem; background: var(--primary-50); color: var(--primary); font-weight: 600; font-size: .82rem; padding: .35rem .8rem; border-radius: 999px; margin-bottom: 1.25rem; }

/* Hero à deux colonnes (texte + schéma SVG) - le schéma passe sous le texte
   en dessous de 900px (cf. media query). */
.hero-grid { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: 2.5rem; align-items: center; }
.hero-visual { display: flex; justify-content: center; }
.hero-perks { list-style: none; display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; padding: 0; margin: 1.5rem 0 0; }
.hero-perks li { display: flex; align-items: center; gap: .45rem; font-size: .85rem; color: var(--muted); }
.hero-perks li i { color: var(--success); }
@media (max-width: 900px) {
    .hero-grid { grid-template-columns: 1fr; gap: 2.25rem; }
    .hero-visual { order: 2; }
}

.section { padding: 4rem 0; }
.section-head { text-align: center; max-width: 40rem; margin: 0 auto 2.5rem; }
.section-head h2 { font-size: clamp(1.6rem, 3vw, 2.3rem); }
.section-head p { color: var(--muted); font-size: 1.05rem; }

.feature-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.75rem; }
.feature { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 2rem 1.5rem; box-shadow: var(--shadow-sm); transition: transform .2s, box-shadow .2s; text-align: center; }
.feature:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.feature .fic { width: 3rem; height: 3rem; border-radius: var(--radius); background: var(--primary-50); color: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 1.3rem; margin: 0 auto 1.25rem; }
.feature h3 { font-size: 1.1rem; margin-bottom: .5rem; }
/* Lien contextuel vers une page pilier (SEO-04). */
.feature-link { display: inline-flex; align-items: center; gap: .35rem; margin-top: .75rem; font-size: .88rem; font-weight: 600; }
.feature p { color: var(--muted); margin: 0; font-size: .95rem; line-height: 1.55; }

@media (max-width: 1024px) { .feature-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .feature-grid { grid-template-columns: 1fr; } }

.schema-wrap { width: 100%; overflow-x: auto; margin-bottom: 2.5rem; }

/* Quatre étapes : même rythme responsive que .feature-grid (4 / 2 / 1), pour
   eviter l'orphelin d'un auto-fit qui retombe a trois colonnes en tablette. */
.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.5rem; }
@media (max-width: 1024px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .steps { grid-template-columns: 1fr; } }
/* Pastille de numéro centrée dans sa carte, comme l'icône des cartes .feature. */
.step { text-align: center; }
.step .n { width: 2.25rem; height: 2.25rem; border-radius: 50%; background: var(--primary); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; margin: 0 auto .8rem; }

/* Migration depuis une autre Plateforme Agréée : réutilise les cartes .feature
   dans une grille à 3 colonnes qui se replie sans largeur minimale imposée
   (min(...,100%) évite tout débordement horizontal sous 260px utiles). */
.migration-grid {
    display: grid; gap: 1.5rem; align-items: stretch;
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
    max-width: 1140px; margin: 0 auto;
}
.migration-note { max-width: 52rem; margin: 1.75rem auto 0; text-align: center; font-size: .9rem; line-height: 1.6; }

.cta-band { background: linear-gradient(135deg, var(--primary), #5654c9); color: #fff; border-radius: var(--radius-lg); padding: 3rem; text-align: center; }
.cta-band h2 { color: #fff; }
.cta-band p { color: rgba(255, 255, 255, .85); max-width: 40rem; margin: .5rem auto 1.75rem; }
.cta-band .btn-primary { background: #fff; color: var(--primary); }
.cta-band .btn-primary:hover { background: #f1f1fb; }

.mkt-footer { border-top: 1px solid var(--border); padding: 2.5rem 0; color: var(--muted); font-size: .9rem; }
.mkt-footer a { color: var(--muted); }
.mkt-footer a:hover { color: var(--primary); }

/* État de service : pastille + libellé, lien vers la page de statut. Deux
   emplacements, même règle - le pied de page vitrine (`mkt-`) et celui de
   l'application (`app-`). Ils disent la même chose, lue dans le même cache ;
   deux jeux de couleurs qui divergeraient au fil des retouches seraient un
   piège pour qui compare les deux pendant un incident. */
.mkt-status,
.app-status { display: inline-flex; align-items: center; gap: .45rem; font-weight: 600; }
.mkt-status-dot,
.app-status-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted, #9ca3af); flex: none; }
.mkt-status--operational .mkt-status-dot,
.app-status--operational .app-status-dot { background: var(--success, #16a34a); animation: mktStatusPulse 2s infinite; }
.mkt-status--degraded .mkt-status-dot,
.app-status--degraded .app-status-dot { background: var(--warning, #d97706); }
.mkt-status--down .mkt-status-dot,
.app-status--down .app-status-dot { background: var(--danger, #dc2626); }
.mkt-status--unknown .mkt-status-dot,
.app-status--unknown .app-status-dot { background: var(--muted, #9ca3af); }
/* Dans le pied de page applicatif, l'état n'est ni un titre ni une alerte : il
   suit la couleur du texte alentour, et ne se colore que par sa pastille. */
.app-footer .app-status { color: var(--muted); text-decoration: none; white-space: nowrap; }
.app-footer .app-status:hover { color: var(--primary); }
@keyframes mktStatusPulse {
    0% { box-shadow: 0 0 0 0 rgba(22, 163, 74, .5); }
    70% { box-shadow: 0 0 0 6px rgba(22, 163, 74, 0); }
    100% { box-shadow: 0 0 0 0 rgba(22, 163, 74, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .mkt-status--operational .mkt-status-dot,
    .app-status--operational .app-status-dot { animation: none; }
}

/* Page Statut (Paramètres) : bandeau global + pastilles d'état par catégorie. */
.status-banner { display: flex; align-items: center; gap: .9rem; border-left: 4px solid var(--muted, #9ca3af); }
.status-dot-lg { width: 14px; height: 14px; border-radius: 50%; background: var(--muted, #9ca3af); flex: none; }
.status-banner--operational { border-left-color: var(--success, #16a34a); }
.status-banner--operational .status-dot-lg { background: var(--success, #16a34a); }
.status-banner--degraded { border-left-color: var(--warning, #d97706); }
.status-banner--degraded .status-dot-lg { background: var(--warning, #d97706); }
.status-banner--down { border-left-color: var(--danger, #dc2626); }
.status-banner--down .status-dot-lg { background: var(--danger, #dc2626); }
.status-pill { display: inline-flex; align-items: center; gap: .4rem; font-size: .82rem; font-weight: 600; white-space: nowrap; }
.status-pill .status-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted, #9ca3af); flex: none; }
.status-pill--operational { color: var(--success, #16a34a); }
.status-pill--operational .status-dot { background: var(--success, #16a34a); }
.status-pill--degraded { color: var(--warning, #d97706); }
.status-pill--degraded .status-dot { background: var(--warning, #d97706); }
.status-pill--down { color: var(--danger, #dc2626); }
.status-pill--down .status-dot { background: var(--danger, #dc2626); }
.status-pill--unknown { color: var(--muted, #9ca3af); }

/* Grille de cards d'état par sous-service (onglet « État du système »), une
   card par sonde `/health`, regroupées par catégorie fonctionnelle. Le liseré
   de gauche reprend le code couleur du bandeau global. */
.status-category { margin-top: 1.5rem; }
.status-category-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: .65rem; }
.status-category-title { font-size: 1rem; font-weight: 700; margin: 0; color: var(--text); }
.status-category-desc { font-size: .82rem; color: var(--muted); margin: .15rem 0 0; }
.status-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: .75rem; }
.status-card { display: flex; align-items: center; gap: .8rem; padding: .9rem 1rem; border-left: 3px solid var(--border); }
.status-card-icon {
    width: 2.25rem; height: 2.25rem; flex: none; border-radius: var(--radius);
    display: flex; align-items: center; justify-content: center;
    background: var(--surface-2); color: var(--muted); font-size: .95rem;
}
.status-card-body { display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
.status-card-label { font-size: .88rem; font-weight: 600; color: var(--text); }
.status-card--operational { border-left-color: var(--success, #16a34a); }
.status-card--degraded { border-left-color: var(--warning, #d97706); }
.status-card--down { border-left-color: var(--danger, #dc2626); }
.status-card--unknown { border-left-color: var(--muted, #9ca3af); }

/* Onglet « Mon compte » : le nom de l'entreprise bascule l'entreprise active. */
.status-company-link { display: inline-flex; align-items: center; gap: .45rem; color: inherit; text-decoration: none; }
.status-company-link:hover { color: var(--primary); text-decoration: underline; }
.status-company-link i { font-size: .72rem; color: var(--muted); }
.status-company-link:hover i { color: var(--primary); }

/* Interrupteur (toggle) rouge/vert - préférences de notifications. */
.toggle { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; }
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle .slider { position: absolute; inset: 0; background: var(--danger, #dc2626); border-radius: 999px; cursor: pointer; transition: background .15s; }
.toggle .slider::before { content: ""; position: absolute; height: 20px; width: 20px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform .15s; box-shadow: 0 1px 2px rgba(0, 0, 0, .3); }
.toggle input:checked + .slider { background: var(--success, #16a34a); }
.toggle input:checked + .slider::before { transform: translateX(20px); }
.toggle input:focus-visible + .slider { outline: 2px solid var(--primary); outline-offset: 2px; }
.notif-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .85rem 1rem; border: 1px solid var(--border); border-radius: .5rem; cursor: pointer; }
/* Intertitre de l'écran des notifications : sépare « de quoi » (les événements)
   de « par où » (les canaux), puis les appareils. Les gabarits l'employaient
   depuis le 2026-09-07 sans qu'aucune règle ne le définisse - le titre prenait
   la marge par défaut du navigateur, deux fois trop grande, et se collait au
   bloc suivant. Constaté en ajoutant le canal push le 2026-09-09. */
.notif-channels-title { font-size: 1rem; font-weight: 600; margin: 1.75rem 0 0; padding-top: 1.25rem; border-top: 1px solid var(--border); }

/* GED : filtres/recherche masquables (details) + bouton stockage discret. */
.ged-filters { margin: 0 0 1rem; }
.ged-filters-summary { display: inline-flex; align-items: center; gap: .5rem; cursor: pointer; font-size: .9rem; font-weight: 600; color: var(--muted); padding: .4rem .2rem; list-style: none; user-select: none; }
.ged-filters-summary::-webkit-details-marker { display: none; }
.ged-filters-summary::marker { content: ""; }
.ged-filters-summary:hover { color: var(--text); }
.ged-filters[open] .ged-filters-summary { color: var(--text); margin-bottom: .6rem; }
.ged-storage-btn.is-warning { color: var(--warning, #d97706); border-color: var(--warning, #d97706); }
.ged-storage-btn.is-critical { color: var(--danger, #dc2626); border-color: var(--danger, #dc2626); }

/* Import PDF : ajout dynamique d'un client (formulaire pré-rempli à confirmer). */
.import-addclient { margin-top: .6rem; border-top: 1px dashed var(--border); padding-top: .6rem; }
.import-addclient-summary { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; font-size: .82rem; font-weight: 600; color: var(--primary); list-style: none; user-select: none; }
.import-addclient-summary::-webkit-details-marker { display: none; }
.import-addclient-summary::marker { content: ""; }

/* Facture d'acompte depuis un devis (FX-24) : le mode, puis les deux valeurs
   côte à côte, empilées quand la carte est étroite. */
.deposit-form__mode { border: 0; padding: 0; margin: 0 0 .6rem; display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; }
.deposit-form__mode legend { margin-bottom: .3rem; }
.deposit-form__values { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 14rem)); gap: 0 1rem; }
/* Seul le champ du mode choisi se montre ; sans `:has`, les deux restent là
   et le serveur ne lit que celui du mode posté. */
.deposit-form:has(input[value="percent"]:checked) .deposit-form__amount,
.deposit-form:has(input[value="amount"]:checked) .deposit-form__percent { display: none; }

/* Lieu de livraison (FX-02) et motif d'exonération (FX-18) d'une facture,
   repliés tant que rien n'est saisi. */
.delivery-address, .vat-exemption { margin: .9rem 0; border-top: 1px dashed var(--border); padding-top: .6rem; }
.delivery-address > summary, .vat-exemption > summary { cursor: pointer; font-size: .9rem; font-weight: 600; color: var(--primary); }
.delivery-address[open] > summary, .vat-exemption[open] > summary { margin-bottom: .6rem; }
.delivery-address .form-hint, .vat-exemption .form-hint { margin: 0 0 .6rem; }
/* Une adresse ou un motif n'a pas besoin de toute la largeur de la carte. */
.delivery-address > .form-group, .delivery-address > .grid-ratio-1-2-1, .vat-exemption > .form-group { max-width: 44rem; }

.legal { max-width: 800px; margin: 0 auto; padding: 3rem 1.25rem; }
.legal h1 { margin-bottom: 1.5rem; }
.legal h2 { font-size: 1.2rem; margin-top: 2rem; }
.legal h3 { font-size: 1rem; margin-top: 1.25rem; }
.legal p { color: #333; line-height: 1.6; }
.legal ul { color: #333; line-height: 1.6; padding-left: 1.25rem; }
.legal li { margin-bottom: .4rem; }
.legal .draft-note { display: block; margin-top: .5rem; font-size: .85rem; font-style: italic; color: #92400e; background: #fffbeb; border-left: 3px solid #f59e0b; padding: .6rem .85rem; border-radius: 4px; }
.legal .todo-note { display: block; margin-top: .5rem; font-size: .85rem; font-style: italic; color: #1e40af; background: #eff6ff; border-left: 3px solid #3b82f6; padding: .6rem .85rem; border-radius: 4px; }

/* ---------- Admin : navigation par onglets (liens) ---------- */
.admin-tabs { display: flex; flex-wrap: wrap; gap: .25rem; border-bottom: 1px solid var(--border); margin-bottom: 1.5rem; }
.admin-tabs a {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .7rem 1.1rem; margin-bottom: -1px;
    font-weight: 600; font-size: .9rem; color: var(--muted);
    border-bottom: 2px solid transparent; text-decoration: none;
    transition: color .15s, border-color .15s;
}
.admin-tabs a:hover { color: var(--text); }
.admin-tabs a.active { color: var(--primary); border-bottom-color: var(--primary); }

/* ---------- Admin : monitoring des outils ---------- */
.health-summary { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin-bottom: 1.5rem; }
.health-pill { padding: .3rem .7rem; border-radius: 999px; border: 1px solid var(--border); font-size: .82rem; }
.health-pill strong { font-weight: 800; }
.health-docker { margin-left: auto; font-size: .85rem; }
.health-group-title { font-size: 1rem; font-weight: 700; margin: 1.5rem 0 .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

.tools-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr)); gap: 1rem; }
.tool-card { border-left: 4px solid var(--border); }
.tool-card.tool-ok { border-left-color: var(--success); }
.tool-card.tool-degraded { border-left-color: var(--warning); }
.tool-card.tool-down { border-left-color: var(--danger); }
.tool-card.tool-unknown { border-left-color: var(--muted); }

.tool-card-head { display: flex; align-items: center; gap: .75rem; }
.tool-icon { width: 2.4rem; height: 2.4rem; flex-shrink: 0; border-radius: var(--radius); background: var(--primary-50); color: var(--primary); display: flex; align-items: center; justify-content: center; font-size: 1.05rem; }
.tool-title { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.tool-title strong { font-size: .98rem; }
.tool-title .badge { align-self: flex-start; }
.tool-latency { margin-left: auto; font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.tool-message { font-size: .85rem; color: var(--text); margin: .8rem 0 .6rem; }
/* Synthèse bornée dans la carte (tâches planifiées) : le texte reste entier
   dans le DOM, seule sa hauteur est contenue. */
.tool-message-clamp { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }

.tool-details { display: grid; grid-template-columns: 1fr; gap: .25rem; margin: 0 0 .6rem; }
.tool-details > div { display: flex; justify-content: space-between; gap: 1rem; font-size: .8rem; border-bottom: 1px dashed var(--border); padding-bottom: .2rem; }
.tool-details dt { color: var(--muted); margin: 0; }
.tool-details dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; word-break: break-all; }

/* Tâches planifiées : ligne mise en évidence quand la tâche est en retard.
   Dégradé -> orange, hors service (retard massif) -> rouge, pour repérer
   immédiatement la tâche fautive sans lire chaque valeur. */
.tool-details > div.tool-detail-degraded,
.tool-details > div.tool-detail-down {
    margin: 0 -.4rem; padding: .25rem .4rem; border-radius: 6px;
    border-bottom-color: transparent;
}
.tool-details > div.tool-detail-degraded { background: #fef5e7; border-left: 3px solid var(--warning); }
.tool-details > div.tool-detail-degraded dt,
.tool-details > div.tool-detail-degraded dd { color: var(--warning); font-weight: 600; }
.tool-details > div.tool-detail-down { background: #fdecec; border-left: 3px solid var(--danger); }
.tool-details > div.tool-detail-down dt,
.tool-details > div.tool-detail-down dd { color: var(--danger); font-weight: 700; }

.tool-actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .25rem; }
.tool-actions form { margin: 0; }

/* ---------- Modale de confirmation (remplace window.confirm) ---------- */
.modal-overlay {
    position: fixed; inset: 0; z-index: 1000; padding: 1rem;
    display: flex; align-items: center; justify-content: center;
    background: rgba(16, 24, 40, .55); opacity: 0; transition: opacity .15s ease;
}
.modal-overlay.is-open { opacity: 1; }
.modal {
    background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
    width: 100%; max-width: 440px; padding: 1.5rem;
    transform: translateY(10px) scale(.98); transition: transform .15s ease;
}
.modal-overlay.is-open .modal { transform: none; }
.modal-head { display: flex; align-items: center; gap: .8rem; margin-bottom: .25rem; }
/* Le titre prend la place restante et la croix est poussée au bout de la ligne.
   Sans cela, `.modal-head` étant un flex de trois enfants, la croix se collait
   au titre - au milieu de l'en-tête sur une modale large, là où tout le monde
   la cherche en haut à droite. `min-width: 0` autorise le titre à se réduire
   plutôt qu'à repousser la croix hors de la boîte. */
.modal-head .modal-title { flex: 1 1 auto; min-width: 0; }
.modal-head .modal-close { margin-left: auto; flex-shrink: 0; }
.modal-icon {
    width: 2.5rem; height: 2.5rem; flex-shrink: 0; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--primary-50); color: var(--primary); font-size: 1.1rem;
}
.modal-icon-danger { background: #fdecec; color: var(--danger); }
.modal-title { margin: 0; font-size: 1.1rem; }
.modal-message { color: var(--muted); font-size: .92rem; margin: .5rem 0 0; line-height: 1.5; }
.modal-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1.5rem; }

/* ---------- Modale de confirmation SANS JavaScript (ancre :target) ----------
   Variante 100% CSS de la modale ci-dessus, sur le modèle des onglets en radios
   plus bas : la fermeture d'une ligne d'annuaire de routage est un acte
   irréversible qui doit rester confirmable même sans JS (CSP stricte, script
   bloqué, navigateur dégradé). L'ouverture est un simple lien `#id`, la
   fermeture un lien `#`. */
.modal-overlay-target { display: none; opacity: 1; }
.modal-overlay-target:target { display: flex; }
.modal-overlay-target:target .modal { transform: none; }

/* ---------- Mise à jour automatique de l'interface ----------
   Le bandeau réutilise `.announcement` : c'est le troisième bandeau de haut de
   contenu, il n'avait aucune raison d'inventer sa propre boîte. Seules les
   actions s'ajoutent, la bannière d'annonce n'en portant pas.

   Le voile monte à 1100, AU-DESSUS des modales ordinaires (1000). Il est le
   dernier écran avant le rechargement : rien ne doit pouvoir passer devant, pas
   même une modale ouverte au moment où le compte à rebours tombe. */
.app-update-actions { display: flex; gap: .5rem; align-items: center; flex-shrink: 0; flex-wrap: wrap; }
.app-update-overlay { z-index: 1100; }
.app-update-modal { max-width: 380px; text-align: center; }
.app-update-modal-icon {
    width: 3rem; height: 3rem; margin: 0 auto 1rem; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--primary-50); color: var(--primary); font-size: 1.25rem;
}
.app-update-modal-icon i { animation: app-update-spin 1.4s linear infinite; }
.app-update-modal .modal-message { margin-top: .5rem; }

/* Barre INDÉTERMINÉE : elle dit « ça travaille », jamais « on en est là ».
   Voir le commentaire du gabarit - il n'y a rien à mesurer entre l'annonce et
   le rechargement, et une progression chiffrée serait inventée. */
.app-update-progress {
    margin-top: 1.5rem; height: 4px; border-radius: 999px; overflow: hidden;
    background: var(--primary-50);
}
.app-update-progress-bar {
    display: block; height: 100%; width: 40%; border-radius: 999px;
    background: var(--primary);
    animation: app-update-slide 1.3s ease-in-out infinite;
}
@keyframes app-update-spin { to { transform: rotate(360deg); } }
@keyframes app-update-slide {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}

/* Mouvement réduit : l'indicateur ne disparaît pas, il cesse de bouger. Une
   barre pleine et un peu atténuée dit la même chose sans rien animer. */
@media (prefers-reduced-motion: reduce) {
    .app-update-modal-icon i { animation: none; }
    .app-update-progress-bar { width: 100%; animation: none; opacity: .55; }
}

/* Sur mobile, les actions passent sous le texte plutôt que de le comprimer à
   deux mots par ligne. */
@media (max-width: 640px) {
    .app-update-actions { width: 100%; }
    .app-update-actions .btn { flex: 1 1 auto; }
}

/* ---------- Annuaire de routage (adresses de réception) ---------- */
.directory-scope {
    display: flex; align-items: flex-start; gap: .75rem;
    padding: .8rem 1rem; margin-bottom: .5rem; cursor: pointer;
    border: 1px solid var(--border); border-radius: var(--radius);
    transition: border-color .12s, background .12s;
}
.directory-scope:hover { background: var(--surface-2); }
.directory-scope:has(input:checked) { border-color: var(--primary); background: var(--primary-50); }
.directory-scope > span { display: flex; flex-direction: column; gap: .2rem; }
.directory-scope .muted { font-size: .85rem; line-height: 1.45; }
/* Maille dont l'adresse est déjà ouverte : elle reste LISIBLE (l'utilisateur
   doit comprendre pourquoi elle lui est refusée) mais n'est plus choisissable.
   Le badge « déjà active » porte l'information, la couleur ne fait que
   l'appuyer - un daltonien lit le badge. */
.directory-scope.is-taken { cursor: default; background: var(--surface-2); opacity: .75; }
.directory-scope.is-taken:hover { background: var(--surface-2); }
.directory-scope.is-taken strong { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }

/* Chaque maille à complément a son champ propre : le SIRET et le suffixe
   n'attendent ni le même format, ni la même aide. Seul celui de la maille
   cochée est montré.
   Formulé en `:not(:has(...))` À DESSEIN : un navigateur sans `:has()` ignore
   la règle entière et affiche les deux champs, ce qui reste utilisable. La
   forme directe (masquer par défaut, révéler par `:has()`) les aurait au
   contraire masqués tous les deux, rendant le formulaire inutilisable. */
.directory-form:not(:has(#scope_siren_siret:checked)) .directory-siret { display: none; }
.directory-form:not(:has(#scope_siren_suffixe:checked)) .directory-suffixe { display: none; }
.directory-duplicate { color: var(--danger); }

/* Champ précédé d'une partie non saisissable (ici le SIREN de l'entreprise, qui
   vient de la base et jamais de la saisie). Le préfixe est un `<span>`, pas un
   champ désactivé : il n'y a donc rien à soumettre, ni à neutraliser côté
   serveur. */
.input-group { display: flex; align-items: stretch; }
.input-group-text {
    display: flex; align-items: center;
    padding: .55rem .75rem;
    border: 1px solid var(--border); border-right: 0;
    border-radius: var(--radius) 0 0 var(--radius);
    background: var(--surface-2); color: var(--muted);
    font-size: .95rem; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.input-group > .form-control { min-width: 0; border-radius: 0 var(--radius) var(--radius) 0; }
/* Le préfixe peut dépasser la largeur disponible sur un petit écran : il passe
   alors au-dessus du champ plutôt que de l'écraser. */
@media (max-width: 480px) {
    .input-group { flex-direction: column; }
    .input-group-text {
        border-right: 1px solid var(--border); border-bottom: 0;
        border-radius: var(--radius) var(--radius) 0 0;
    }
    .input-group > .form-control { border-radius: 0 0 var(--radius) var(--radius); }
}

/* ---------- Onglets (CSS pur, radios masqués - aucune dépendance JS) ---------- */
.tabs > .tab-radio,
.settings-shell > .tab-radio {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.tab-nav {
    display: flex; gap: .25rem; flex-wrap: wrap;
    border-bottom: 1px solid var(--border); margin-bottom: 1.5rem;
}
.tab-nav label {
    display: inline-flex; align-items: center; gap: .55rem;
    padding: .7rem 1.1rem; margin-bottom: -1px; cursor: pointer;
    font-weight: 600; font-size: .9rem; color: var(--muted);
    border: 1px solid transparent; border-bottom: 2px solid transparent;
    border-top-left-radius: var(--radius); border-top-right-radius: var(--radius);
    transition: color .15s, border-color .15s, background .15s;
}
.tab-nav label:hover { color: var(--text); background: var(--surface-2); }
.tab-nav label .tab-count {
    font-size: .72rem; font-weight: 700; line-height: 1; padding: .15rem .45rem;
    border-radius: 999px; background: var(--surface-2); color: var(--muted);
    border: 1px solid var(--border);
}
.tab-panel { display: none; }

/* Onglets e-reporting : le radio coché (général sibling ~) pilote l'onglet actif
   et l'affichage du panneau correspondant (data-tab). 100% CSS, aucun JS. */
#er-tab-tx:checked   ~ .tab-nav label[for="er-tab-tx"],
#er-tab-pay:checked  ~ .tab-nav label[for="er-tab-pay"],
#er-tab-intl:checked ~ .tab-nav label[for="er-tab-intl"],
#er-tab-ppf:checked  ~ .tab-nav label[for="er-tab-ppf"] {
    color: var(--primary); border-bottom-color: var(--primary);
}
#er-tab-tx:checked   ~ .tab-nav label[for="er-tab-tx"]   .tab-count,
#er-tab-pay:checked  ~ .tab-nav label[for="er-tab-pay"]  .tab-count,
#er-tab-intl:checked ~ .tab-nav label[for="er-tab-intl"] .tab-count,
#er-tab-ppf:checked  ~ .tab-nav label[for="er-tab-ppf"]  .tab-count {
    background: var(--primary-50); color: var(--primary); border-color: #d9d8f5;
}
#er-tab-tx:checked   ~ .tab-panels .tab-panel[data-tab="tx"],
#er-tab-pay:checked  ~ .tab-panels .tab-panel[data-tab="pay"],
#er-tab-intl:checked ~ .tab-panels .tab-panel[data-tab="intl"],
#er-tab-ppf:checked  ~ .tab-panels .tab-panel[data-tab="ppf"] { display: block; }

/* Accessibilité clavier : anneau de focus visible sur l'onglet ciblé. */
#er-tab-tx:focus-visible   ~ .tab-nav label[for="er-tab-tx"],
#er-tab-pay:focus-visible  ~ .tab-nav label[for="er-tab-pay"],
#er-tab-intl:focus-visible ~ .tab-nav label[for="er-tab-intl"],
#er-tab-ppf:focus-visible  ~ .tab-nav label[for="er-tab-ppf"] {
    outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius);
}

/* ---------- Configuration effective : onglets par domaine (CSS pur) ----------
   Les onglets sont numérotés et NON nommés d'après ConfigSectionKey : ajouter un
   domaine à l'enum ne demande donc aucune retouche ici, tant qu'il y en a moins
   de 8. Nommer les sélecteurs d'après l'enum aurait fait qu'un domaine ajouté
   plus tard aurait un onglet dont le panneau ne s'ouvre jamais - une page à
   moitié muette, sans rien d'autre pour le signaler. */
#cfg-tab-0:checked ~ .tab-nav label[for="cfg-tab-0"],
#cfg-tab-1:checked ~ .tab-nav label[for="cfg-tab-1"],
#cfg-tab-2:checked ~ .tab-nav label[for="cfg-tab-2"],
#cfg-tab-3:checked ~ .tab-nav label[for="cfg-tab-3"],
#cfg-tab-4:checked ~ .tab-nav label[for="cfg-tab-4"],
#cfg-tab-5:checked ~ .tab-nav label[for="cfg-tab-5"],
#cfg-tab-6:checked ~ .tab-nav label[for="cfg-tab-6"],
#cfg-tab-7:checked ~ .tab-nav label[for="cfg-tab-7"] {
    color: var(--primary); border-bottom-color: var(--primary);
}
#cfg-tab-0:checked ~ .tab-nav label[for="cfg-tab-0"] .tab-count,
#cfg-tab-1:checked ~ .tab-nav label[for="cfg-tab-1"] .tab-count,
#cfg-tab-2:checked ~ .tab-nav label[for="cfg-tab-2"] .tab-count,
#cfg-tab-3:checked ~ .tab-nav label[for="cfg-tab-3"] .tab-count,
#cfg-tab-4:checked ~ .tab-nav label[for="cfg-tab-4"] .tab-count,
#cfg-tab-5:checked ~ .tab-nav label[for="cfg-tab-5"] .tab-count,
#cfg-tab-6:checked ~ .tab-nav label[for="cfg-tab-6"] .tab-count,
#cfg-tab-7:checked ~ .tab-nav label[for="cfg-tab-7"] .tab-count {
    background: var(--primary-50); color: var(--primary); border-color: #d9d8f5;
}
#cfg-tab-0:checked ~ .tab-panels .tab-panel[data-tab="0"],
#cfg-tab-1:checked ~ .tab-panels .tab-panel[data-tab="1"],
#cfg-tab-2:checked ~ .tab-panels .tab-panel[data-tab="2"],
#cfg-tab-3:checked ~ .tab-panels .tab-panel[data-tab="3"],
#cfg-tab-4:checked ~ .tab-panels .tab-panel[data-tab="4"],
#cfg-tab-5:checked ~ .tab-panels .tab-panel[data-tab="5"],
#cfg-tab-6:checked ~ .tab-panels .tab-panel[data-tab="6"],
#cfg-tab-7:checked ~ .tab-panels .tab-panel[data-tab="7"] { display: block; }
#cfg-tab-0:focus-visible ~ .tab-nav label[for="cfg-tab-0"],
#cfg-tab-1:focus-visible ~ .tab-nav label[for="cfg-tab-1"],
#cfg-tab-2:focus-visible ~ .tab-nav label[for="cfg-tab-2"],
#cfg-tab-3:focus-visible ~ .tab-nav label[for="cfg-tab-3"],
#cfg-tab-4:focus-visible ~ .tab-nav label[for="cfg-tab-4"],
#cfg-tab-5:focus-visible ~ .tab-nav label[for="cfg-tab-5"],
#cfg-tab-6:focus-visible ~ .tab-nav label[for="cfg-tab-6"],
#cfg-tab-7:focus-visible ~ .tab-nav label[for="cfg-tab-7"] {
    outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius);
}

/* ---------- Fiche client admin : onglets latéraux (CSS pur) ----------
   Réutilise l'apparence de la sous-nav Paramètres (.settings-subnav) mais
   pilotée par des radios masqués (.tab-radio, déjà défini plus haut) plutôt
   que par des liens : un seul panneau .tab-panel visible à la fois, aucune
   dépendance JS (CSP stricte à nonce). */
.settings-subnav label {
    display: flex; align-items: center; gap: .65rem; padding: .6rem .7rem; border-radius: var(--radius);
    color: var(--text); font-weight: 500; font-size: .9rem; cursor: pointer; transition: background .12s, color .12s;
}
.settings-subnav label i { width: 1.1rem; text-align: center; color: var(--muted); }
.settings-subnav label:hover { background: var(--surface-2); }
.settings-subnav label .badge { margin-left: auto; }

#sub-tab-overview:checked     ~ .settings-subnav label[for="sub-tab-overview"],
#sub-tab-subscription:checked ~ .settings-subnav label[for="sub-tab-subscription"],
#sub-tab-moderation:checked   ~ .settings-subnav label[for="sub-tab-moderation"],
#sub-tab-companies:checked    ~ .settings-subnav label[for="sub-tab-companies"],
#sub-tab-activity:checked     ~ .settings-subnav label[for="sub-tab-activity"] {
    background: var(--primary-50); color: var(--primary);
}
#sub-tab-overview:checked     ~ .settings-subnav label[for="sub-tab-overview"] i,
#sub-tab-subscription:checked ~ .settings-subnav label[for="sub-tab-subscription"] i,
#sub-tab-moderation:checked   ~ .settings-subnav label[for="sub-tab-moderation"] i,
#sub-tab-companies:checked    ~ .settings-subnav label[for="sub-tab-companies"] i,
#sub-tab-activity:checked     ~ .settings-subnav label[for="sub-tab-activity"] i {
    color: var(--primary);
}
#sub-tab-overview:checked     ~ .settings-content .tab-panel[data-tab="overview"],
#sub-tab-subscription:checked ~ .settings-content .tab-panel[data-tab="subscription"],
#sub-tab-moderation:checked   ~ .settings-content .tab-panel[data-tab="moderation"],
#sub-tab-companies:checked    ~ .settings-content .tab-panel[data-tab="companies"],
#sub-tab-activity:checked     ~ .settings-content .tab-panel[data-tab="activity"] {
    display: block;
}
#sub-tab-overview:focus-visible     ~ .settings-subnav label[for="sub-tab-overview"],
#sub-tab-subscription:focus-visible ~ .settings-subnav label[for="sub-tab-subscription"],
#sub-tab-moderation:focus-visible   ~ .settings-subnav label[for="sub-tab-moderation"],
#sub-tab-companies:focus-visible    ~ .settings-subnav label[for="sub-tab-companies"],
#sub-tab-activity:focus-visible     ~ .settings-subnav label[for="sub-tab-activity"] {
    outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius);
}

/* --- Grille tarifaire (vitrine) --------------------------------------- */
.hero-compact { padding-bottom: 1rem; }
/* `.mt-3` et `.text-center` étaient REDÉCLARÉS ici, le premier à 1rem : la
   seconde déclaration gagnant, tout le projet croyait écrire 1,5rem et obtenait
   la valeur de `.mt-2`. Les utilitaires vivent une seule fois, avec le reste
   (cf. `.mt-1 … .mt-4`) ; toute exception se porte sur une classe de composant,
   jamais en redéfinissant un utilitaire. */

/* Cartes elargies (1140px au lieu de 1000px) pour limiter les retours a la
   ligne des libelles de fonctionnalites. `min(300px, 100%)` en borne basse :
   la piste ne peut jamais depasser la largeur utile du conteneur, donc pas de
   debordement horizontal sur les petits ecrans (< 340px de viewport). */
.pricing-grid {
    display: grid; gap: 1.5rem; align-items: stretch;
    grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
    max-width: 1140px; margin: 0 auto;
}
/* Offres non self-service (Cabinet + ETI) : 2 cartes centrees sous la ligne de 3.
   Le point de rupture suit l'elargissement : 2 x 280px + 1.5rem de gouttiere
   demandent ~584px utiles, soit ~660px de viewport avec le padding de
   .container-wide - d'ou le repli a une colonne des 680px. */
.pricing-grid--contact {
    max-width: 760px; margin-top: 1.5rem;
    grid-template-columns: repeat(2, minmax(min(280px, 100%), 1fr));
}
@media (max-width: 680px) { .pricing-grid--contact { grid-template-columns: 1fr; } }
.pricing-card {
    position: relative; display: flex; flex-direction: column;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
    padding: 1.75rem 1.75rem;
}
.pricing-card-featured {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .18), var(--shadow);
}
.pricing-badge {
    position: absolute; top: -.75rem; left: 50%; transform: translateX(-50%);
}
.pricing-name { margin: 0 0 .5rem; font-size: 1.15rem; }
.pricing-price { display: flex; align-items: baseline; gap: .4rem; margin-bottom: .25rem; flex-wrap: wrap; }
.pricing-amount { font-size: 2rem; font-weight: 700; color: var(--text); }
/* Offres « contact » : le montant est une phrase (« dès 31 € », « à partir de
   99 € »), pas un nombre - une taille plus mesuree le garde sur une ligne. */
.pricing-grid--contact .pricing-amount { font-size: 1.5rem; }
.pricing-period { font-size: .85rem; color: var(--muted); }
.pricing-tagline { margin: 0 0 1.25rem; min-height: 2.5em; }
.pricing-storage {
    display: flex; align-items: center; gap: .5rem; margin: -.5rem 0 1.25rem;
    font-size: .85rem; font-weight: 600; color: var(--primary);
}
.pricing-storage i { color: var(--accent); }
.pricing-features { list-style: none; padding: 0; margin: 0 0 1.5rem; flex: 1; }
.pricing-features li { display: flex; align-items: flex-start; gap: .55rem; padding: .35rem 0; font-size: .92rem; }
.pricing-features li i { color: var(--success); margin-top: .2rem; }

/* --- Bascule mensuel / annuel (pur CSS) ------------------------------- */
.pricing-toggle-input { position: absolute; opacity: 0; pointer-events: none; }
.pricing-toggle {
    display: inline-flex; gap: .25rem; margin: 0 auto 1.75rem; padding: .25rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: 999px; justify-content: center;
}
.pricing { display: flex; flex-direction: column; align-items: center; }
.pricing-toggle label {
    display: inline-flex; align-items: center; gap: .4rem; cursor: pointer;
    padding: .4rem 1rem; border-radius: 999px; font-size: .9rem; font-weight: 600;
    color: var(--muted); transition: background .15s, color .15s;
}
#bi-monthly:checked ~ .pricing-toggle label[for="bi-monthly"],
#bi-yearly:checked ~ .pricing-toggle label[for="bi-yearly"] {
    background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm);
}
.pricing-toggle-input:focus-visible ~ .pricing-toggle label[for="bi-monthly"],
.pricing-toggle-input:focus-visible ~ .pricing-toggle label[for="bi-yearly"] {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
.pricing-grid { width: 100%; }
.price-monthly, .price-yearly { display: inline-flex; align-items: baseline; gap: .4rem; }
.price-yearly { display: none; }
#bi-yearly:checked ~ .pricing-grid .price-monthly { display: none; }
#bi-yearly:checked ~ .pricing-grid .price-yearly { display: inline-flex; }
/* Toggle periodicite dans le tunnel d'onboarding (grille .offre-grid). */
#bi-yearly:checked ~ .offre-grid .price-monthly { display: none; }
#bi-yearly:checked ~ .offre-grid .price-yearly { display: inline; }
.offre-grid .price-yearly { display: none; }

/* Cartes de choix de plan (onboarding) : le radio est masque, la selection est
   materialisee par la bordure + la pastille de coche, en suivant le choix en
   direct (:has, sans JS - CSP-safe). */
.offre-card { cursor: pointer; display: block; border: 2px solid var(--border); transition: border-color .15s, box-shadow .15s; }
.offre-card input[name="plan"] { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.offre-card-check { color: var(--border); font-size: 1.15rem; transition: color .15s; }
.offre-card:hover { border-color: var(--primary-200, var(--primary)); }
.offre-card:has(input[name="plan"]:checked) { border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.offre-card:has(input[name="plan"]:checked) .offre-card-check { color: var(--primary); }
.offre-card:has(input[name="plan"]:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Offre mise en avant dans le wizard, alignée sur la vitrine /tarifs : les deux
   écrans doivent recommander la même chose, sous peine de donner l'impression
   que le conseil change entre la page publique et l'inscription. */
.offre-card-featured { position: relative; border-color: var(--primary-200, var(--primary)); }
.offre-card-badge { position: absolute; top: -.7rem; right: 1rem; }

/* Quota de stockage de l'offre (même information que .pricing-storage) */
.offre-card-storage {
    display: flex; align-items: center; gap: .4rem;
    margin: .4rem 0 0; font-size: .8rem; color: var(--muted);
}

/* Renvoi vers les offres sur devis (Cabinet, ETI), non self-service */
.offre-bigger-needs {
    margin: 1.25rem 0 0; padding: .85rem 1rem;
    background: var(--surface-2); border-radius: var(--radius, 8px);
    font-size: .85rem; color: var(--muted); line-height: 1.6;
}
.offre-bigger-needs strong { color: var(--text); margin-right: .25rem; }
.offre-bigger-needs a { white-space: nowrap; }

/* Features « a venir » (roadmap) sur les grilles tarifaires */
.pricing-features li.feature-coming { color: var(--muted); }

/* ============================================================
   Admin - Stockage objet (/admin/stockage)
   ============================================================ */

/* --- Carte de téléversement --- */
.storage-upload-card { max-width: 720px; margin-bottom: 1.25rem; }
.storage-upload-title {
    display: flex; align-items: center; gap: .6rem;
    font-size: .98rem; margin-bottom: 1rem;
}
.storage-upload-form { display: flex; flex-direction: column; gap: 0; }
.storage-upload-row { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; }

/* Input file custom : masque le natif, affiche un bouton cohérent avec .btn */
.storage-file-input {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.storage-file-label {
    display: flex; align-items: center; gap: .55rem;
    width: 100%; padding: .55rem .85rem;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); color: var(--muted);
    font-size: .92rem; cursor: pointer;
    transition: border-color .15s, box-shadow .15s, color .15s;
    user-select: none;
}
.storage-file-label:hover {
    border-color: var(--primary); color: var(--text);
}
.storage-file-input:focus-visible + .storage-file-label,
.storage-file-label:focus-within {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(var(--primary-rgb), .18);
    outline: none;
}
.storage-file-label.has-file {
    border-color: var(--primary); color: var(--text); font-weight: 600;
}
.storage-file-label.has-file .storage-file-icon { color: var(--primary); }
.storage-file-icon {
    width: 1.6rem; height: 1.6rem; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    border-radius: 6px; background: var(--primary-50);
    color: var(--primary); font-size: .85rem;
    transition: background .15s, color .15s;
}
.storage-file-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Barre de recherche + filtres --- */
.storage-toolbar {
    display: flex; gap: .75rem; align-items: center; flex-wrap: wrap;
    margin-bottom: 1rem; padding: .9rem 1.25rem;
}
.storage-search-wrap { position: relative; flex: 1 1 220px; min-width: 0; }
.storage-search-icon {
    position: absolute; left: .8rem; top: 50%; transform: translateY(-50%);
    color: var(--muted); font-size: .82rem; pointer-events: none;
}
.storage-search-input { padding-left: 2.2rem; }

.storage-filter-wrap { display: flex; gap: .5rem; flex-wrap: wrap; }
.storage-filter-icon {
    position: absolute; left: .8rem; top: 50%; transform: translateY(-50%);
    color: var(--muted); font-size: .8rem; pointer-events: none; z-index: 1;
}
.storage-filter-select { padding-left: 2.2rem; min-width: 160px; }

.storage-counter {
    margin-left: auto; font-size: .82rem; color: var(--muted);
    white-space: nowrap; padding: .3rem .6rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: 999px; font-weight: 600;
}

/* Pagination du tableau des objets. `wrap` par principe : deux boutons et un
   libelle tiennent large, mais `.btn` ne se compresse pas (white-space: nowrap). */
.storage-pagination {
    display: flex; align-items: center; justify-content: center;
    flex-wrap: wrap; gap: .75rem;
    padding: 1rem; border-top: 1px solid var(--border);
}
.storage-pagination .btn:disabled { opacity: .45; cursor: default; transform: none; }

/* --- Tableau des objets --- */
.storage-file-cell { max-width: 420px; }
.storage-file { display: flex; align-items: center; gap: .6rem; min-width: 0; }
.storage-file-thumb {
    flex: 0 0 auto; width: 38px; height: 38px; border-radius: 6px;
    object-fit: cover; background: var(--surface-2); border: 1px solid var(--border);
}
.storage-file-ico {
    flex: 0 0 auto; width: 38px; height: 38px; border-radius: 6px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 1rem; border: 1px solid var(--border); background: var(--surface-2);
}
.storage-file-meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.storage-file-name {
    font-weight: 600; font-size: .86rem; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}
.storage-file-dir {
    font-size: .72rem; color: var(--muted); white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}
.storage-company-name { display: block; font-weight: 600; font-size: .88rem; }
.storage-company-id { font-size: .73rem; }

/* --- Couleurs par type métier (icône de fichier + anciens badges) --- */
.storage-type-archive { background: #eaf1fe; color: var(--info);    border-color: #cfe0fc; }
.storage-type-import  { background: #fef5e7; color: var(--warning); border-color: #fbe3bd; }
.storage-type-ged     { background: #eafaf0; color: var(--success); border-color: #c9f0d8; }
.storage-type-autre   { background: var(--surface-2); color: var(--muted); border-color: var(--border); }

/* Responsive : resserre la cellule et masque la colonne ID sur les petits écrans */
@media (max-width: 1024px) {
    .storage-file-cell { max-width: 200px; }
    .storage-company-id { display: none; }
    .storage-filter-select { min-width: 120px; }
}
.pricing-features li.feature-coming i { color: var(--muted); }
.feature-soon { font-size: .62rem; vertical-align: middle; margin-left: .15rem; }
.feature-coming { opacity: .85; }

/* ---------- Modale de saisie (motif de refus) ---------- */
.modal-input { max-width: 520px; }
.modal-body { margin: .75rem 0 0; }
.modal-body .form-label { font-size: .85rem; }
.modal-body .form-control { margin-top: .35rem; }

/* ---------- Sélecteur de client destinataire (emission) ---------- */
.emit-client-header {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: .5rem; margin-bottom: .35rem;
}
.emit-client-header .form-label { margin: 0; }
.emit-client-actions { display: flex; gap: .4rem; flex-wrap: wrap; }
.required-star { color: var(--danger); margin-left: .15rem; }
.emit-no-client {
    display: flex; align-items: flex-start; gap: 1rem;
    padding: 1.25rem 1.5rem; margin-bottom: 0;
}
.emit-no-client > i { font-size: 1.4rem; color: var(--warning); margin-top: .15rem; flex-shrink: 0; }
.emit-no-client > div > p { margin: .35rem 0 .75rem; font-size: .92rem; }

/* ---------- Dropzone de dépôt de fichiers ---------- */
.dropzone {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: .75rem; padding: 3.5rem 1.5rem;
    border: 2px dashed var(--border); border-radius: var(--radius-lg);
    background: var(--surface-2); cursor: pointer; text-align: center;
    transition: border-color .15s, background .15s;
}
.dropzone.is-dragover,
.dropzone:hover { border-color: var(--primary); background: var(--primary-50); }
.dz-icon { font-size: 2.5rem; color: var(--primary); }
.dz-title { font-weight: 600; }
.dz-hint { font-size: .85rem; color: var(--muted); }

/* ---------- Import par lot - revue groupée ---------- */
.batch-item { display: grid; grid-template-columns: 1fr auto; gap: 1rem; align-items: start; }
.batch-item-info { min-width: 0; }
.batch-item-actions { display: flex; flex-direction: column; gap: .6rem; min-width: 240px; }
.batch-exclude-row { display: flex; align-items: center; gap: .5rem; padding-top: .25rem; }

/* ---------- Import en lot : modal (backdrop) + progression + rapport ---------- */
body.modal-open { overflow: hidden; }
.import-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: clamp(.5rem, 2.5vw, 1rem); }
/* L'attribut hidden doit primer sur display:flex (même spécificité sinon). */
.import-modal-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, .55); }
.import-modal-dialog {
    position: relative; z-index: 1; width: 100%; max-width: 520px; max-height: 90vh; overflow-y: auto;
    background: var(--surface); border-radius: 14px; border: 1px solid var(--border);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .25);
    /* 88 px de marges sur 390 ne laissaient que 302 px à la zone de dépôt :
       la marge suit la place, de 1 à 1,75 rem (relevé C, G16). */
    padding: clamp(1rem, 5vw, 1.75rem);
}
.import-modal-close {
    position: absolute; top: .85rem; right: .85rem; width: 34px; height: 34px; border-radius: 8px;
    border: 0; background: transparent; color: var(--muted); cursor: pointer; font-size: 1.1rem;
}
.import-modal-close:hover { background: var(--bg); color: var(--text); }
.import-modal-title { font-size: 1.15rem; margin: 0 0 .5rem; display: flex; align-items: center; gap: .5rem; }
.import-modal-title i { color: var(--primary); }
.import-modal-help { color: var(--muted); font-size: .9rem; margin: 0 0 1rem; }
.import-modal-model { margin-bottom: 1rem; }
.import-modal-form { display: flex; flex-direction: column; gap: 1rem; }
.import-dropzone {
    display: flex; flex-direction: column; align-items: center; gap: .5rem; padding: 1.5rem;
    border: 2px dashed var(--border); border-radius: 10px; cursor: pointer; color: var(--muted); text-align: center;
    transition: border-color .15s, color .15s;
}
.import-dropzone:hover { border-color: var(--primary); color: var(--primary); }
.import-dropzone i { font-size: 1.6rem; }
.import-progress-track { height: 10px; background: var(--bg); border-radius: 999px; overflow: hidden; }
.import-progress-track > span { display: block; height: 100%; width: 0; background: var(--primary); transition: width .3s ease; }
.import-progress-status { margin: .6rem 0 0; font-size: .88rem; color: var(--muted); text-align: center; }
.import-report-summary { display: flex; flex-wrap: wrap; gap: .5rem 1rem; padding: .75rem 1rem; border-radius: 10px; font-size: .9rem; }
.import-report-summary.is-ok { background: #eafaf7; border: 1px solid #bfe9e4; color: #0f6b60; }
.import-report-summary.is-fail { background: #fdecec; border: 1px solid #f5c6c6; color: #a02222; }
.import-report-list { list-style: none; margin: .75rem 0 0; padding: 0; max-height: 220px; overflow-y: auto; font-size: .85rem; display: flex; flex-direction: column; gap: .35rem; }
.import-report-list li { padding: .4rem .6rem; border-radius: 7px; background: var(--bg); }
.import-report-list li.import-report-error { background: #fdecec; color: #a02222; }
.import-report-list li.import-report-warning { background: #fef5e7; color: #7a4a06; }
.import-report-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1rem; }

/* ---------- Widget de retour/assistance (bulle bas-droite) ---------- */
.feedback-widget { position: fixed; right: 1.5rem; bottom: 1.5rem; z-index: 900; }
.feedback-bubble {
    width: 3.4rem; height: 3.4rem; border-radius: 50%; border: 0; cursor: pointer;
    display: flex; align-items: center; justify-content: center; font-size: 1.3rem;
    background: var(--primary); color: #fff; box-shadow: var(--shadow-lg);
    transition: transform .15s ease, background .15s ease;
}
.feedback-bubble:hover { background: var(--primary-600); transform: translateY(-2px); }
.feedback-bubble:focus-visible { outline: 3px solid var(--primary-50); outline-offset: 2px; }
.feedback-dismiss {
    position: absolute; top: -.35rem; right: -.35rem; width: 1.5rem; height: 1.5rem;
    border-radius: 50%; border: 1px solid var(--border); background: var(--surface);
    color: var(--muted); font-size: .65rem; cursor: pointer; z-index: 1;
    display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-sm);
}
.feedback-dismiss:hover { color: var(--danger); border-color: #f8cccc; }
.feedback-panel {
    position: absolute; right: 0; bottom: calc(100% + .85rem);
    width: min(340px, calc(100vw - 3rem));
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); padding: 1.1rem;
}
.feedback-panel-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-bottom: .35rem; }
.feedback-panel-title { font-size: 1rem; margin: 0; display: flex; align-items: center; gap: .5rem; color: var(--text); }
.feedback-panel-title i { color: var(--primary); }
.feedback-panel-close {
    border: 0; background: transparent; color: var(--muted); cursor: pointer;
    width: 1.8rem; height: 1.8rem; border-radius: 6px; font-size: 1rem; flex-shrink: 0;
}
.feedback-panel-close:hover { background: var(--surface-2); color: var(--text); }
.feedback-panel-intro { color: var(--muted); font-size: .85rem; margin: 0 0 .9rem; }
.feedback-submit { width: 100%; justify-content: center; }
@media (max-width: 480px) {
    .feedback-widget { right: 1rem; bottom: 1rem; }
    .feedback-panel { right: -.5rem; }
}

/* ============================================================
   Console d'administration : menu profil (dropdown) + thème admin
   dédié. Contexte indépendant de l'espace utilisateur (cf.
   templates/layout/admin.html.twig). Accent ambre = zone privilégiée.
   ============================================================ */
:root {
    --admin: #3937a3;      /* violet Diamontis (aligné sur la marque) */
    --admin-600: #2d2b81;  /* violet plus sombre (hover/actif) */
    --admin-50: #edeefb;
    --admin-sidebar: #201f3a;  /* nuit violette (cohérent avec l'accent) */
}

/* ---- Menu profil (topbar, dropdown <details>) ---- */
.user-menu { position: relative; display: inline-block; }
.user-menu > summary {
    list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: .5rem;
    padding: .3rem .6rem; border-radius: 8px; border: 1px solid transparent; user-select: none; color: inherit;
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu > summary:hover, .user-menu[open] > summary { background: var(--bg); border-color: var(--border); }
.user-menu-name { font-weight: 600; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-caret { font-size: .65rem; color: var(--muted); transition: transform .15s; }
.user-menu[open] .user-menu-caret { transform: rotate(180deg); }
.user-menu-list {
    position: absolute; top: calc(100% + 6px); right: 0; min-width: min(250px, calc(100vw - 1.5rem)); max-width: calc(100vw - 1.5rem); z-index: 50;
    background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12); padding: .4rem; display: flex; flex-direction: column;
}
.user-menu-head { padding: .5rem .6rem; border-bottom: 1px solid var(--border); margin-bottom: .25rem; }
.user-menu-fullname { display: block; font-weight: 600; font-size: .9rem; }
.user-menu-head small { color: var(--muted); font-size: .75rem; word-break: break-all; }
.user-menu-item {
    display: flex; align-items: center; gap: .6rem; padding: .5rem .6rem; border-radius: 7px;
    color: inherit; text-decoration: none; font-weight: 500; font-size: .9rem;
}
.user-menu-item i { width: 1.1rem; text-align: center; color: var(--muted); }
.user-menu-item:hover { background: var(--bg); }
.user-menu-admin, .user-menu-admin i { color: var(--admin-600); }
.user-menu-admin:hover { background: var(--admin-50); }
.user-menu-leave, .user-menu-leave i { color: var(--primary); }
.user-menu-logout { border-top: 1px solid var(--border); margin-top: .25rem; color: var(--danger); }
.user-menu-logout i { color: var(--danger); }

/* ---- Thème console admin (léger décalage de couleur + badge) ---- */
.admin-context .app-sidebar { background: var(--admin-sidebar); }
.admin-context .app-brand .logo { background: var(--admin); }
.admin-context .app-nav a.active,
.admin-context .app-nav-bottom a.active { background: var(--admin); color: #fff; }
.admin-context .app-nav-bottom { border-top: 1px solid rgba(255, 255, 255, .08); }
/* Topbar admin en thème sombre (cohérent avec la sidebar) + séparation par une
   bordure violette claire en bas (plus de barre violette en haut). */
.admin-context .app-topbar { background: var(--admin-sidebar); box-shadow: none; border-bottom: 1px solid #6f6dd0; }
.admin-context .app-topbar .admin-topbar-heading { color: #fff; }
/* Le fond blanc de `.nav-burger` n'était PAS repris ici, seule la couleur
   l'était : icône #d9d8ea sur fond blanc, soit 1,40:1. Le seul moyen d'ouvrir
   la navigation de /admin sur téléphone était donc illisible (WCAG 1.4.11
   exige 3:1). Le fond translucide de la topbar sombre rétablit le contraste. */
.admin-context .app-topbar .nav-burger {
    background: rgba(255, 255, 255, .08);
    border-color: rgba(255, 255, 255, .18);
    color: #e7e6f2;
}
.admin-context .app-topbar .user-menu > summary { color: #e7e6f2; }
.admin-context .app-topbar .user-menu > summary:hover,
.admin-context .app-topbar .user-menu[open] > summary { background: rgba(255, 255, 255, .08); border-color: rgba(255, 255, 255, .16); }
.admin-context .app-topbar .user-menu-caret { color: rgba(255, 255, 255, .6); }

.admin-badge {
    display: inline-flex; align-items: center; gap: .3rem; font-size: .62rem; font-weight: 800;
    letter-spacing: .06em; text-transform: uppercase; padding: .15rem .45rem; border-radius: 999px;
    background: var(--admin); color: #fff; white-space: nowrap;
}
.app-brand .admin-badge { margin-left: .1rem; }
.admin-badge-lg { font-size: .68rem; padding: .25rem .55rem; }
.admin-topbar-title { display: flex; align-items: center; gap: .6rem; min-width: 0; flex: 1; }
.admin-topbar-heading { font-weight: 700; color: var(--text); font-size: .95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 640px) { .admin-topbar-heading { display: none; } }

/* ---- FAQ / aide admin (accordéon) ---- */
.help-item { border-top: 1px solid var(--border); padding: .25rem 0; }
.help-item > summary {
    cursor: pointer; padding: .85rem .25rem; font-weight: 600; font-size: .95rem;
    list-style: none; display: flex; align-items: center; gap: .5rem;
}
.help-item > summary::-webkit-details-marker { display: none; }
/* Chevron des questions de la FAQ. DEUX défauts cumulés corrigés le 2026-08-11,
   chacun suffisant à ne rien afficher :

   1. la famille demandée était « Font Awesome 6 Free » alors que le projet est
      en Font Awesome 7 (cf. les `@font-face` de fontawesome.css). Une famille
      inconnue fait retomber le navigateur sur une police système, où le point
      de code n'a aucun glyphe ;
   2. le glyphe `\f105` (fa-angle-right) n'était pas dans le SOUS-ENSEMBLE :
      `bin/fa-subset.php` collecte les CLASSES `fa-*` employées dans les
      gabarits et ici, et ne voit donc pas une icône appelée par son point de
      code.

   D'où `\f054`, celui de `fa-chevron-right`. Le nom de classe est écrit dans ce
   commentaire À DESSEIN : le script scanne ce fichier en entier, commentaires
   compris, donc cette mention garantit que le glyphe reste dans le
   sous-ensemble même si plus aucun gabarit n'emploie `fa-chevron-right`.
   `tests/Shared/FontAwesomeSubsetTest` vérifie désormais aussi les `content`
   littéraux, ce qui aurait attrapé le second défaut. */
.help-item > summary::before { content: '\f054'; font-family: 'Font Awesome 7 Free'; font-weight: 900; color: var(--muted); transition: transform .15s; }
.help-item[open] > summary::before { transform: rotate(90deg); }
.help-answer { padding: 0 .25rem 1rem 1.35rem; font-size: .9rem; color: var(--text); line-height: 1.6; }
.help-answer code { background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px; padding: .05rem .3rem; font-size: .85em; }

/* ============================================================
   GED - explorateur (arborescence), jauge de stockage, corbeille
   (templates/ged/index.html.twig, templates/ged/trash.html.twig,
   templates/admin/ged.html.twig, templates/admin/ged_trash.html.twig)
   ============================================================ */

/* --- Conteneur général de l'explorateur (fil d'Ariane + dossiers + table) --- */
.ged-explorer { display: flex; flex-direction: column; gap: 1.25rem; }

/* --- Fil d'Ariane --- */
.ged-breadcrumb { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; font-size: .9rem; color: var(--muted); }
.ged-breadcrumb a { color: var(--muted); font-weight: 600; display: inline-flex; align-items: center; gap: .4rem; }
.ged-breadcrumb a:hover { color: var(--primary); }
.ged-breadcrumb [aria-current="page"] { color: var(--text); font-weight: 700; display: inline-flex; align-items: center; gap: .4rem; }
.ged-breadcrumb .crumb-sep { color: var(--border); font-size: .72rem; }

/* --- Grille des dossiers (cartes cliquables) --- */
.ged-folder-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: .75rem; }
.ged-folder-card {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    padding: .7rem .6rem .7rem 1rem; border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--surface); transition: border-color .15s, box-shadow .15s;
}
.ged-folder-card:hover { border-color: var(--primary); box-shadow: var(--shadow-sm); }
.ged-folder-link { display: flex; align-items: center; gap: .6rem; min-width: 0; flex: 1; color: var(--text); font-weight: 600; font-size: .92rem; }
.ged-folder-link i { color: var(--primary); font-size: 1.1rem; flex-shrink: 0; }
.ged-folder-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Carte de dossier CLIENT (racine « Clients », vue dérivée) : deux lignes
   empilées - la raison sociale, puis le nombre de pièces et le SIREN.
   `.ged-folder-card` seule ne convient pas : elle range ses enfants EN LIGNE
   avec `space-between`, disposition faite pour le couple « nom + menu
   d'actions » de « Mes dossiers ». Appliquée à deux blocs empilés, elle
   comprimait la raison sociale dans une demi-carte, où elle se cassait sur
   plusieurs lignes faute d'ellipsis (celle de `.ged-folder-link span` ne porte
   pas jusqu'ici).

   Une raison sociale est longue par nature (forme juridique comprise) : d'où
   des colonnes plus larges, la ligne de nom qui prend toute la carte, et
   l'ellipsis avec le nom complet en `title`. */
.ged-client-grid { grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr)); }
.ged-client-card {
    flex-direction: column; align-items: stretch; justify-content: flex-start;
    gap: .3rem; padding: .8rem 1rem;
}
.ged-client-card .ged-client-name {
    display: flex; align-items: baseline; gap: .5rem; min-width: 0;
    color: var(--text); font-weight: 600; font-size: .95rem;
}
.ged-client-card .ged-client-name i { color: var(--primary); flex-shrink: 0; }
.ged-client-card .ged-client-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ged-client-card .ged-client-meta { color: var(--muted); font-size: .82rem; }

/* Menu d'actions par dossier (renommer / déplacer / supprimer), même motif
   <details>/<summary> que .user-menu / .company-switch-menu (sans JS). */
.ged-folder-menu { position: relative; flex-shrink: 0; }
.ged-folder-menu > summary {
    list-style: none; cursor: pointer; width: 2rem; height: 2rem; border-radius: 8px;
    display: flex; align-items: center; justify-content: center; color: var(--muted);
}
.ged-folder-menu > summary::-webkit-details-marker { display: none; }
.ged-folder-menu > summary:hover, .ged-folder-menu[open] > summary { background: var(--surface-2); color: var(--text); }
.ged-folder-menu-list {
    position: absolute; top: calc(100% + 4px); right: 0; z-index: 40; min-width: min(230px, calc(100vw - 1.5rem)); max-width: calc(100vw - 1.5rem);
    background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .12); padding: .65rem; display: flex; flex-direction: column; gap: .5rem;
}
.ged-folder-menu-list form { margin: 0; display: flex; gap: .35rem; }
.ged-folder-menu-list .form-control, .ged-folder-menu-list .form-select { padding: .45rem .6rem; font-size: .85rem; }

/* --- Jauge de consommation du quota de stockage (partial _storage_gauge) --- */
.ged-storage-bar { grid-template-columns: 11rem 1fr 3.5rem; }
.ged-storage-gauge.is-warning .bar-fill { background: var(--warning); }
.ged-storage-gauge.is-critical .bar-fill { background: var(--danger); }
.ged-storage-gauge.is-critical { border-color: #f8cccc; background: #fdf7f7; }
.ged-storage-detail { margin: .65rem 0 0; font-size: .85rem; color: var(--muted); }
.ged-storage-trash a { color: var(--muted); font-weight: 600; }
.ged-storage-trash a:hover { color: var(--primary); }
.ged-storage-alert {
    margin: .65rem 0 0; font-size: .85rem; color: var(--danger); font-weight: 600;
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
}
.ged-storage-alert a { color: var(--danger); text-decoration: underline; }

/* Bannière de blocage de quota (page abonnement) : le flash reçoit un CTA. */
.ged-storage-alert-banner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }

/* Carte de stockage sur le tableau de bord (variante compacte de .stat-card).
   Ici .bar-track/.bar-fill sont utilisées SANS le conteneur .bar-row (mini
   barre sous la valeur) - règles autonomes, pas de dépendance à .bar-row. */
.ged-storage-stat-card { text-decoration: none; color: inherit; transition: box-shadow .2s, transform .2s; }
.ged-storage-stat-card:hover { box-shadow: var(--shadow); transform: translateY(-2px); opacity: 1; }
/* La barre se lit en une fraction de seconde parce que ses deux moitiés ne
   parlent pas de la même chose : ce qui RESTE est un vert à peine posé, ce qui
   est CONSOMMÉ est un rouge franc. Le gris de `--surface-2` disait « fond de
   composant » et n'apprenait rien ; un remplissage bleu (`--primary`) se lisait
   comme une décoration, pas comme une consommation.
   Le vert est translucide à dessein : il doit tenir sur la carte comme sur la
   ligne survolée, sans jamais peser autant que le rouge. */
.ged-storage-stat-card .bar-track {
    display: block; background: rgba(22, 163, 74, .12); border-radius: 999px; height: .4rem; overflow: hidden;
}
.ged-storage-stat-card .bar-fill { display: block; height: 100%; background: var(--danger); border-radius: 999px; transition: width .3s ease; }
/* Les paliers 75 % et 90 % ne colorent plus le remplissage - il est déjà rouge -
   mais ils restent posés sur la carte : c'est la bordure qui porte désormais
   l'alerte, sans quoi franchir un seuil ne se verrait plus du tout. */
.ged-storage-stat-card.is-warning { border-color: #f5d9a8; }
.ged-storage-stat-card.is-critical { border-color: #f8cccc; background: #fdf7f7; }

/* --- Barre d'actions groupées (bulk), sous le tableau documents/corbeille --- */
.ged-bulk-bar {
    display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap;
    padding: .75rem 1.25rem; border-top: 1px solid var(--border); background: var(--surface-2);
    border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg);
}
.ged-bulk-hint { font-size: .82rem; color: var(--muted); }
/* Pagination de la liste de documents (lot L6). `flex-wrap` obligatoire : les
   boutons portent `white-space: nowrap`, sans quoi c'est la page qui défile. */
.ged-pagination {
    display: flex; align-items: center; justify-content: center; gap: .5rem; flex-wrap: wrap;
    padding: .75rem 1.25rem; font-size: .85rem;
}

@media (max-width: 640px) {
    .ged-storage-bar { grid-template-columns: 1fr; gap: .35rem; }
    .ged-storage-bar .bar-count { text-align: left; }
    .ged-folder-grid { grid-template-columns: 1fr; }
}

/* --- Glisser-déposer (contrôleur Stimulus ged-dnd) + arbre indenté ---
   Amélioration progressive des actions de déplacement <select> + bouton déjà
   existantes (templates/ged/index.html.twig, templates/admin/ged.html.twig) :
   `draggable` n'est posé qu'en JS (connect() du contrôleur), donc ces styles
   n'ont aucun effet visuel sans JavaScript. */
.ged-layout { display: flex; align-items: flex-start; gap: 1.25rem; }

/* Panneau latéral (arbre des dossiers), profondeur pilotée par la variable CSS
   --ged-tree-depth (posée par nœud dans templates/ged/index.html.twig via la
   macro fmt.gedFolderTree, cf. GedFolder::getDepth()). */
.ged-tree-panel { flex: 0 0 15.5rem; width: 15.5rem; position: sticky; top: 1rem; align-self: flex-start; }
.ged-tree-panel-head {
    display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem;
    font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.ged-tree { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .1rem; max-height: 60vh; overflow-y: auto; container-type: inline-size; }
.ged-tree-link {
    display: flex; align-items: center; gap: .5rem; min-width: 0;
    padding: .4rem .6rem;
    /* Le pas d'indentation se mesure sur le panneau : 1,05 rem au plus, 5 % de
       sa largeur au moins. À la profondeur 5, il mangeait 94 px sur 334 et le
       nom du dossier se réduisait à quelques lettres (relevé C, G17). */
    padding-left: calc(.6rem + var(--ged-tree-depth, 0) * min(1.05rem, 5cqi));
    border-radius: 8px; color: var(--text); font-size: .85rem; font-weight: 600;
    text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: background-color .12s, color .12s;
}
.ged-tree-link i { color: var(--muted); flex-shrink: 0; font-size: .9em; }
.ged-tree-link span { overflow: hidden; text-overflow: ellipsis; }
.ged-tree-link:hover { background: var(--surface-2); }
.ged-tree-link.is-current { background: var(--primary-50); color: var(--primary); }
.ged-tree-link.is-current i { color: var(--primary); }

.ged-explorer { min-width: 0; flex: 1; }

/* Élément en cours de glissé (document ou dossier). */
[data-ged-dnd-target~="draggable"] { cursor: grab; }
[data-ged-dnd-target~="draggable"].is-dragging { opacity: .4; cursor: grabbing; }

/* Cible de dépôt survolée par un glissé valide. */
.ged-folder-card.is-dragover {
    border-color: var(--primary); background: var(--primary-50);
    box-shadow: 0 0 0 2px var(--primary-50), var(--shadow-sm);
}
.ged-tree-link.is-dragover,
.ged-breadcrumb a.is-dragover {
    background: var(--primary-50); color: var(--primary); outline: 2px dashed var(--primary); outline-offset: -2px;
}

@media (max-width: 900px) {
    .ged-layout { flex-direction: column; }
    .ged-tree-panel { width: 100%; flex: 1 1 auto; position: static; }
    .ged-tree { max-height: 14rem; }
}

/* ============================================================
   Schémas SVG animés (vitrine) - CSS pur / SMIL uniquement, aucune
   dépendance JS. Illustratifs (flux métier), sans jamais nommer de
   prestataire ou de brique technique interne. Techniques : flux via
   stroke-dasharray/stroke-dashoffset, respiration et ondes via
   transform:scale (transform-box:fill-box requis sur les éléments SVG),
   flottement via translateY. Toutes tournent en boucle infinie et sont
   coupées via prefers-reduced-motion (cf. media query en fin de bloc).
   ============================================================ */
.schema { width: 100%; height: auto; display: block; }
.schema-hero { max-width: 420px; }
.schema-steps { max-width: 760px; margin: 0 auto; }

/* Flux le long des liaisons (tracé animé). La période de pointillés (6+20=26)
   est EGALE au delta d'offset anime (-26) : le motif retombe exactement sur
   lui-meme a chaque cycle -> boucle sans saut (l'ancien rendu "saccade" venait
   d'une periode qui ne tuilait pas l'offset). will-change limite les
   re-layouts sur le trace SVG. */
.schema-link {
    stroke-dasharray: 6 20;
    animation: schema-flow 1.8s linear infinite;
    will-change: stroke-dashoffset;
}
@keyframes schema-flow {
    to { stroke-dashoffset: -26; }
}
.schema-link--1 { animation-delay: 0s; }
.schema-link--2 { animation-delay: .3s; }
.schema-link--3 { animation-delay: .6s; }
.schema-link--4 { animation-delay: .9s; }

/* Respiration du noeud central (schéma hero). */
.schema-core {
    transform-box: fill-box; transform-origin: center;
    animation: schema-breathe 4s ease-in-out infinite;
}
@keyframes schema-breathe {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.035); }
}

/* Ondes concentriques autour du noeud central (schéma hero). */
.schema-ring {
    transform-box: fill-box; transform-origin: center;
    animation: schema-pulse 3s ease-out infinite;
}
@keyframes schema-pulse {
    0% { transform: scale(.82); opacity: .55; }
    100% { transform: scale(1.25); opacity: 0; }
}
.schema-ring--1 { animation-delay: 0s; }
.schema-ring--2 { animation-delay: 1.5s; }

/* Flottement léger des puces (chips) métier. */
.schema-chip {
    transform-box: fill-box; transform-origin: center;
    animation: schema-float 3.6s ease-in-out infinite;
}
@keyframes schema-float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-4px); }
}
.schema-chip--1 { animation-delay: 0s; }
.schema-chip--2 { animation-delay: .45s; }
.schema-chip--3 { animation-delay: .9s; }
.schema-chip--4 { animation-delay: 1.35s; }

@media (prefers-reduced-motion: reduce) {
    .schema-link, .schema-core, .schema-ring, .schema-chip { animation: none; }
}

/* Suggestions d'autocomplétion (adresse BAN, réutilisable). */
.ac-suggestions {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% - 1.1rem);
    z-index: 30;
    background: var(--surface, #fff);
    border: 1px solid var(--border, #e2e8f0);
    border-radius: var(--radius, 8px);
    box-shadow: var(--shadow, 0 6px 24px rgba(15, 23, 42, .12));
    display: none;
    max-height: 260px;
    overflow: auto;
}
.ac-item {
    padding: .5rem .8rem;
    cursor: pointer;
    font-size: .88rem;
    border-bottom: 1px solid var(--border, #e2e8f0);
}
.ac-item:last-child { border-bottom: 0; }
.ac-item:hover { background: var(--surface-2, #f1f5f9); }
/* Suggestion sur deux lignes (annuaire des entreprises) : le nom, puis le SIREN
   et la commune - deux entreprises homonymes ne se distinguent que par là. */
.ac-item-title { font-weight: 600; font-size: .9rem; }
.ac-item-meta { font-size: .78rem; }

/* Suggestions du catalogue sous la désignation d'une ligne (contrôleur
   `line-autocomplete`). Position FIXE, calculée sous le champ : les tableaux de
   lignes vivent dans un conteneur à défilement qui rognerait une liste absolue.
   L'option active se voit sans la souris - c'est la seule trace, à l'écran, du
   parcours au clavier. */
.line-autocomplete-list {
    position: fixed; z-index: 60; margin: 0; padding: .25rem 0; list-style: none;
    max-height: 260px; overflow: auto; max-width: calc(100vw - 16px);
    background: var(--surface, #fff); border: 1px solid var(--border, #e2e8f0);
    border-radius: var(--radius, 8px); box-shadow: var(--shadow, 0 6px 24px rgba(15, 23, 42, .12));
}
.line-autocomplete-option {
    display: flex; flex-direction: column; gap: .1rem; padding: .5rem .8rem;
    cursor: pointer; min-height: 44px; justify-content: center;
}
.line-autocomplete-option:hover { background: var(--surface-2, #f1f5f9); }
.line-autocomplete-option[aria-selected="true"] {
    background: rgba(var(--primary-rgb), .12); box-shadow: inset 3px 0 0 var(--primary);
}
.line-autocomplete-title { font-weight: 600; font-size: .9rem; overflow-wrap: anywhere; }
.line-autocomplete-meta { font-size: .78rem; }

/* Grille des cartes d'infos entreprise : 2 colonnes (2x2), 1 sur mobile. */
.info-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
}
@media (max-width: 720px) {
    .info-grid { grid-template-columns: 1fr; }
}

/* Liste de définitions clé/valeur (fiche Infos entreprise) - une ligne par info. */
.info-list { margin: 0; }
/* La valeur passe SOUS son intitulé quand les deux ne tiennent plus côte à
   côte, au lieu d'être réduite à l'ellipse. C'était le cas de tout l'écran
   entreprise à 390 px : SIREN, SIRET, identifiant d'annuaire, IBAN réduits à
   trois lettres, et deux boutons rognés dans leur `dd` (relevé C, B2). Aucun
   seuil : c'est `flex-wrap` qui mesure, ligne par ligne. */
.info-list > div {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: .25rem 1rem;
    padding: .5rem 0;
    border-bottom: 1px solid var(--border, #e2e8f0);
}
.info-list > div:last-child { border-bottom: 0; }
.info-list dt { color: var(--muted, #64748b); font-size: .85rem; margin: 0; flex: 0 1 auto; }
.info-list dd {
    margin: 0 0 0 auto;
    text-align: right;
    font-weight: 600;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Portail client final - cercle 1, page publique /f/{token} (docs/15 §5.1). */
.portal-wrap { min-height: 100vh; background: var(--bg); }
.portal-header {
    background: var(--surface);
    border-bottom: 3px solid var(--portal-brand, var(--primary));
    box-shadow: var(--shadow-sm);
}
.portal-header-inner {
    max-width: 720px;
    margin: 0 auto;
    padding: 1.25rem 1.25rem;
    display: flex;
    align-items: center;
    gap: .85rem;
    flex-wrap: wrap;
}
.portal-logo { max-height: 42px; max-width: 220px; object-fit: contain; }
.portal-brand-name { font-weight: 800; font-size: 1.15rem; color: var(--portal-brand, var(--primary)); }
.portal-siren { color: var(--muted); font-size: .85rem; margin-left: auto; }
.portal-main { max-width: 720px; margin: 0 auto; padding: 1.75rem 1.25rem 2.5rem; }
.portal-eyebrow { color: var(--muted); font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; font-weight: 700; margin: 0 0 .35rem; }
.portal-amount { font-size: 2.4rem; font-weight: 800; line-height: 1.1; margin: 0 0 .3rem; }
.portal-dates {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}
.portal-actions { display: flex; gap: .75rem; flex-wrap: wrap; margin-top: 1.25rem; }
.portal-actions form { margin: 0; }
.portal-preview-frame { width: 100%; height: 70vh; min-height: 420px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
.portal-teaser { text-align: center; font-size: .85rem; margin-top: 1.5rem; }
.portal-footer { text-align: center; padding: 1.5rem 1.25rem 2.5rem; color: var(--muted); font-size: .82rem; }
@media (max-width: 560px) {
    .portal-dates { grid-template-columns: 1fr; }
    .portal-actions { flex-direction: column; }
    .portal-actions .btn { width: 100%; justify-content: center; }
}

/* Hub de l'espace client final (docs/15 §5.3, lot C4). */

/* Espace fournisseur (/espace/{fournisseur}) : variante large de .portal-main/
   .portal-header-inner (720px), réservée aux pages tableau denses - la page
   facture unique du cercle 1 reste étroite (cf. portal/_layout.html.twig). */
.portal-main-wide { max-width: 1100px; }
.portal-header-inner-wide { max-width: 1100px; }

.hub-totals-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; }
.hub-totals-value { font-size: 1.5rem; font-weight: 800; line-height: 1.15; }

.hub-filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); gap: .85rem; align-items: end; }
.hub-filters-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }

.table-cards-download-label { display: none; }

/* Barre d'export du hub (lot C5) : sélection multiple -> CSV/Excel/ZIP. */
.hub-export-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .75rem; }
.hub-export-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

@media (max-width: 640px) {
    .hub-totals-grid { grid-template-columns: 1fr; gap: .75rem; }
    .hub-export-bar { flex-direction: column; align-items: stretch; }
    .hub-export-actions .btn { flex: 1; justify-content: center; }
}

/* Cartes empilées sous 768px (spec §5.3) : le tableau du hub porte désormais
   `.table-cards` (§4b), patron unique de l'application. Ne reste ici que ce qui
   lui est propre : l'action « télécharger » au pouce, dont le libellé
   n'apparaît qu'en mode carte (le tableau dense n'a que l'icône). */
@media (max-width: 768px) {
    .table-cards-download-label { display: inline; }
}

/* Bandeau de totaux de la liste des factures /app/factures (lot D1, docs/14
   §6 constat 1) : 4 tuiles (total, dû, échu, prochaine échéance), même patron
   visuel que .hub-totals-grid (portail) mais nom dédié - grille à 4 colonnes
   au lieu de 3. */
.invoice-totals-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr)); gap: clamp(.75rem, 2vw, 1.5rem); }
.invoice-totals-value { font-size: 1.5rem; font-weight: 800; line-height: 1.15; }
.invoice-totals-value--overdue { color: var(--danger); }


/* Barre d'actions de lot de la liste des factures /app/factures (lot D2,
   docs/14 §6 constat 2) : sélection multiple -> export CSV / ZIP groupé.
   Masquée par défaut (attribut HTML `hidden`), révélée par le contrôleur
   Stimulus `bulk-select` dès qu'au moins une case est cochée. Même patron
   visuel que .hub-export-bar (portail), nom dédié côté /app. */
.bulk-action-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: .75rem; }
.bulk-action-bar-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

@media (max-width: 640px) {
    .bulk-action-bar { flex-direction: column; align-items: stretch; }
    .bulk-action-bar-actions .btn { flex: 1; justify-content: center; }
}

/* Checklist de conformité pré-réforme (lot UX D4, docs/14 §6 constats 5 et
   10) : remplace l'état vide de la liste des factures d'un compte neuf par un
   parcours d'amorçage en 3 étapes (raccordement PA, adresse d'annuaire,
   premier client). templates/_partials/_conformity_checklist.html.twig,
   partagée avec le tableau de bord à terme. */
.checklist-intro { display: flex; align-items: flex-start; gap: 1rem; border-bottom: 1px solid var(--border); }
.checklist-intro-icon {
    width: 2.75rem; height: 2.75rem; border-radius: 50%; flex-shrink: 0;
    background: var(--primary-50); color: var(--primary);
    display: flex; align-items: center; justify-content: center; font-size: 1.1rem;
}
.checklist-intro h3 { margin: 0 0 .25rem; }
.checklist-intro p { margin: 0; }
/* Fermeture définitive (préférence par personne et par entreprise) : la croix
   se cale en haut à droite de l'en-tête, le texte garde toute la largeur. */
.checklist-intro-text { flex: 1; min-width: 0; }
.checklist-dismiss { flex-shrink: 0; margin: -.35rem -.5rem 0 0; }
.checklist-dismiss-btn { padding: .35rem .5rem; }
.checklist-dismissed-notice { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem .5rem; }

.checklist { list-style: none; margin: 0; padding: 0; }
.checklist-item { display: flex; align-items: flex-start; gap: .85rem; padding: 1.1rem 1.5rem; border-bottom: 1px solid var(--border); }
.checklist-item:last-child { border-bottom: 0; }
.checklist-item-status { font-size: 1.25rem; color: var(--warning); flex-shrink: 0; margin-top: .1rem; }
.checklist-item.is-done .checklist-item-status { color: var(--success); }
.checklist-item-icon { font-size: 1rem; color: var(--muted); flex-shrink: 0; margin-top: .2rem; width: 1.2rem; text-align: center; }
.checklist-item-body { flex: 1; min-width: 0; }
.checklist-item-title { font-weight: 600; }
.checklist-item.is-done .checklist-item-title { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--border); }
.checklist-item-desc { margin: .2rem 0 0; color: var(--muted); font-size: .85rem; }
.checklist-item-action { flex-shrink: 0; margin-top: .1rem; }

@media (max-width: 560px) {
    .checklist-item { flex-wrap: wrap; }
    .checklist-item-body { flex-basis: 100%; }
    .checklist-item-action { margin-left: auto; }
}

/* Barre de recherche unifiée + filtre "à payer" de la liste des factures
   /app/factures et /app/factures/emises (lot UX D6, docs/14 §6 constats 7,
   8, 9) : un seul champ de recherche (numéro, tiers, SIREN, montant,
   désignation de ligne) au lieu de filtres empilés, cf.
   InvoiceRepository::search(). */
.invoice-search-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; }
/* Ce formulaire débordait de 43 px à 375 px, mesuré le 2026-09-12. La cause
   n'était PAS le `min-width: 220px` qu'il portait - premier correctif tenté,
   sans effet - mais son bouton : `.btn` pose `white-space: nowrap`, « Rechercher »
   fait donc 120 px incompressibles, et le champ ne pouvait pas se réduire assez
   pour compenser dans un parent de 336 px.
   La sortie n'est pas de rogner le champ mais d'autoriser le retour à la ligne :
   le bouton passe SOUS le champ quand la place manque. Mesuré après correctif -
   374 px de formulaire dans 336 deviennent 302, et la page ne déborde plus. */
.invoice-search { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; flex: 1; }
.invoice-search-input {
    /* Base de 10 rem plutôt que 0 : le champ reste utilisable une fois seul sur
       sa ligne, et c'est lui qui absorbe la place rendue par le bouton. */
    flex: 1 1 10rem;
    min-width: 0;
    padding: .55rem .85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font: inherit;
}
.invoice-search-input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.invoice-search-clear { font-size: .85rem; color: var(--muted); white-space: nowrap; }
.invoice-search-clear:hover { color: var(--text); }

/* Segmented control "Toutes" / "À payer" : deux liens simples (GET), pas de
   JS - l'état actif s'appuie sur `to_pay_active` posé côté contrôleur. */
.filter-toggle {
    display: inline-flex;
    /* Les deux options se replient l'une sous l'autre plutôt que de sortir de
       l'écran : `flex-shrink: 0` sur un bloc dont les options portent
       `white-space: nowrap` ne laissait aucune issue à l'étroit. */
    flex-wrap: wrap;
    max-width: 100%;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}
.filter-toggle-option {
    padding: .5rem 1rem;
    font-size: .85rem;
    font-weight: 600;
    color: var(--muted);
    background: var(--surface);
    white-space: nowrap;
}
.filter-toggle-option:hover { color: var(--text); background: var(--surface-2); text-decoration: none; }
.filter-toggle-option:not(:last-child) { border-right: 1px solid var(--border); }
.filter-toggle-option.is-active { background: var(--primary); color: #fff; }
.filter-toggle-badge {
    display: inline-block;
    margin-left: .35rem;
    padding: .05rem .4rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, .25);
    font-size: .75rem;
}
.filter-toggle-option:not(.is-active) .filter-toggle-badge { background: var(--primary-50); color: var(--primary); }

@media (max-width: 640px) {
    .invoice-search-bar { flex-direction: column; align-items: stretch; }
    .invoice-search { min-width: 0; }
    .filter-toggle { align-self: stretch; }
    .filter-toggle-option { flex: 1; text-align: center; }
}

/* ---------- Aperçu PDF en modale (partial _partials/_pdf_preview) ---------- */
.pdf-preview-actions { margin-left: auto; display: flex; gap: .4rem; flex-wrap: wrap; }
.pdf-preview-dialog { display: flex; flex-direction: column; max-height: 92vh; }
.pdf-preview-frame {
    width: 100%; flex: 1; min-height: min(70vh, 780px);
    margin-top: .75rem; border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--bg);
}

/* ---------- Revue d'import : recherche et création du destinataire ---------- */
.import-client-tools { display: flex; gap: .4rem; flex-wrap: wrap; margin-top: .6rem; }
.discover-status { margin: .35rem 0 0; font-size: .82rem; }
.discover-status-success { color: var(--success); }
.discover-status-danger { color: var(--danger); }
.discover-status-muted { color: var(--muted); }
[data-batch-card].is-excluded { opacity: .45; }
.import-candidates { display: flex; flex-direction: column; gap: .25rem; margin: .5rem 0; }
.import-candidate { justify-content: flex-start; text-align: left; font-size: .82rem; }

/* ---------- Étape 3 : récapitulatif cliquable + carte d'aperçu ---------- */
.import-confirm-row { cursor: pointer; }
.import-confirm-row.is-muted { opacity: .6; }
.import-confirm-row.is-selected { background: var(--primary-50); }
.import-preview-card { position: sticky; top: 1rem; }
.import-preview-empty { font-size: .88rem; margin: 0; }
.import-preview-detail + .import-preview-detail { border-top: 1px solid var(--border); margin-top: 1rem; padding-top: 1rem; }
.import-preview-filename { font-weight: 600; font-size: .9rem; word-break: break-all; margin: 0 0 .6rem; }
.import-preview-list { display: grid; grid-template-columns: auto 1fr; gap: .2rem .75rem; font-size: .85rem; margin: 0; }
.import-preview-list dt { color: var(--muted); }
.import-preview-list dd { margin: 0; text-align: right; }
.import-preview-flag { margin: .7rem 0 0; }
.import-preview-subtitle { font-weight: 600; font-size: .85rem; margin: .9rem 0 .3rem; }
.import-preview-lines { font-size: .82rem; }
.import-preview-lines th, .import-preview-lines td { padding: .3rem .4rem; }
.import-preview-actions { margin-top: .9rem; }

/* ---------- Suggestion générée (dernier recours LLM) : signalement visuel ----------
   Une valeur proposée par l'analyse automatique ne doit jamais se présenter
   comme une donnée vérifiée : bandeau d'avertissement + marquage champ par
   champ, distincts d'une valeur issue de SIRENE. */
.ai-suggestion-notice { display: flex; gap: .6rem; align-items: flex-start; margin: .75rem 0; }
.ai-flag {
    display: inline-flex; align-items: center; gap: .25rem;
    font-size: .72rem; font-weight: 600; text-transform: none;
    color: var(--warning); white-space: nowrap;
}
.form-control.is-ai-suggested,
.form-select.is-ai-suggested {
    border-color: var(--warning);
    box-shadow: inset 3px 0 0 var(--warning);
}

/* ============================================================
   Adaptation mobile - audit UI/UX du 2026-07-26
   Complète les lots responsive du 2026-07-03 (docs/audit-responsive.md), dont
   plusieurs n'avaient pas produit l'effet attendu - voir §4a : une table en
   `width: 100%` ne déborde jamais, donc son `overflow-x: auto` ne défilait pas.
   Ordre : saisie -> cibles tactiles -> en-têtes -> tableaux -> grilles ->
   navigation -> modales -> densité -> hauteur de fenêtre -> paysage -> divers.
   ============================================================ */

/* --- 1. Zoom automatique de Safari iOS -------------------------------
   Sous 16 px, iOS recadre la page au focus d'un champ et ne dézoome jamais.
   Toutes les tailles du design system sont sous ce seuil (`.form-control`
   vaut .95rem, soit 14,25 px pour un `body` à 15 px). C'est le défaut mobile
   le plus universel de l'application : il frappe chaque écran de saisie. */
@media (max-width: 900px) {
    .form-control, .form-select, .invoice-search-input,
    .invoice-lines .form-control, .invoice-lines .form-select,
    .ged-folder-menu-list .form-control, .ged-folder-menu-list .form-select,
    .storage-filter-select,
    :where(input[type="date"], input[type="datetime-local"],
           input[type="month"], input[type="time"], input[type="week"]),
    textarea.form-control, .app-content textarea {
        font-size: 16px;
    }
}

/* --- 2. Cibles tactiles (WCAG 2.2 AA, 2.5.8) -------------------------
   Conditionné au pointeur grossier : la densité des tableaux sur grand écran
   reste inchangée. */
@media (pointer: coarse) {
    .btn { min-height: 40px; }
    .btn-sm { min-height: 38px; padding: .45rem .8rem; }
    .table td .btn, .table td .btn-sm, .table th .btn {
        min-height: 40px; min-width: 40px; justify-content: center;
    }
    .table td .flex.gap-2 { gap: .65rem; }
    /* `.auth-card` et `.portal-main` aussi : la case des CGU, obligatoire pour
       s'inscrire, restait à 13 px sur le premier écran d'un prospect. */
    .app-content input[type="checkbox"], .app-content input[type="radio"],
    .auth-card input[type="checkbox"], .auth-card input[type="radio"],
    .portal-main input[type="checkbox"], .portal-main input[type="radio"] { width: 20px; height: 20px; }
    .tag-color { width: 2.75rem; height: 2.75rem; }
    .table td > input[type="checkbox"], .table th > input[type="checkbox"] { width: 22px; height: 22px; }
    .nav-burger { width: 44px; height: 44px; }
    .app-nav a, .app-nav-bottom a, .settings-subnav a, .settings-subnav label,
    .company-switch-item, .user-menu-item { min-height: 44px; }
    .ged-folder-menu > summary { width: 2.75rem; height: 2.75rem; }
    .line-remove, .import-modal-close, .password-toggle-btn,
    .feedback-panel-close, .announcement-close, .checklist-dismiss-btn { min-width: 44px; min-height: 44px; }
    /* Badge devenu bouton (relance de consultation d'annuaire) : au doigt, un
       badge de 24 px reste une cible manquée. */
    .badge-action { min-height: 36px; padding-inline: .85rem; }
    .table td .badge-action { min-height: 40px; }
}

/* --- 3. En-têtes de page : les actions ne débordent plus -------------
   `.page-actions` n'avait pas de `flex-wrap`, et `.btn` porte
   `white-space: nowrap` : la largeur minimale d'un bouton est celle de son
   libellé complet. Les quatre boutons en tête de la GED réclamaient ainsi
   720 px dans les 272 px utiles d'un écran de 375. */
@media (max-width: 640px) {
    .page-header { flex-wrap: wrap; align-items: flex-start; }
    .page-header > div { min-width: 0; }
    .page-header .page-title { font-size: 1.25rem; }
    .page-header .page-actions { margin-left: 0; width: 100%; flex-wrap: wrap; }
    .page-header .page-actions > .btn,
    .page-header .page-actions > form > .btn { flex: 1 1 auto; justify-content: center; }
    .seats-badge { margin-left: 0; }
}

/* --- 4a. Tableaux : la largeur qui compte est celle du CONTENEUR ------
   Un tableau ne sait rien de la taille de l'écran. Il connaît la place qu'on
   lui laisse, et elle n'a pas grand-chose à voir : à 1024 px de fenêtre, la
   barre latérale déduite, il reste 800 px ; à 1090, il en reste 834. C'est
   pour cela qu'aucun point de rupture de FENÊTRE ne pouvait couvrir la bande
   769-1090, où le texte se lisait une lettre par ligne alors que tous les
   seuils étaient respectés - défaut rapporté le 2026-09-12.

   `.table-container` se déclare donc conteneur de requête, et tout ce qui suit
   se règle sur SA largeur. Le même tableau bascule en cartes dans une colonne
   étroite d'un grand écran, et reste en tableau sur une tablette en paysage :
   c'est le comportement qu'on veut, et il n'y a plus qu'un seuil à tenir.

   `container-type: inline-size` fait de la boîte le bloc contenant de ses
   descendants absolus. Sans conséquence ici : `overflow-x: auto` les
   rognait déjà, rien de positionné ne peut sortir de ce cadre, avant comme
   après. */
.table-container { container-type: inline-size; }

/* Plus de largeur plancher arbitraire. Les 34 rem qui régnaient ici ne
   correspondaient à aucun tableau réel - trop pour les étroits, qu'elles
   étiraient à vide, trop peu pour les larges, qui coupaient quand même, et il
   avait fallu une exception à 44 rem pour la saisie de lignes. `max-content`
   demande à chaque tableau la largeur qu'il lui faut, ni plus ni moins, et
   rend l'exception inutile. */
@container (max-width: 48rem) {
    /* `.line-cards` en est exclu au même titre que `.table-cards` : les deux
       replient le tableau en fiches, et un plancher `max-content` les
       remettrait en tableau large qui défile - exactement ce que le repli
       existe pour éviter. Oubli mesuré le 2026-09-12 : la fiche faisait 581 px
       dans un écran de 360. */
    .table-container > table.table:not(.table-cards):not(.line-cards) { min-width: max-content; }
}

/* La coupure des mots longs ne dépend d'aucun seuil : une référence, un URN ou
   une adresse électronique n'a aucune raison de déborder, à quelque largeur que
   ce soit.

   `break-word` et NON `anywhere`, et la nuance vaut d'être écrite parce que le
   premier jet s'est trompé. `anywhere` autorise la coupure dans le calcul de la
   largeur MINIMALE du contenu : le tableau en déduit qu'une colonne peut se
   réduire à une lettre, et il le fait. Résultat mesuré le 2026-09-12 sur les
   factures reçues : « 732829320-FE- / 2026-0512 », « 1 008,0 / 0 € »,
   « Approuv / ée ». Un montant coupé après la virgule n'est pas seulement laid,
   il se lit faux.

   `break-word` ne coupe QUE lorsqu'un mot ne tient pas seul sur sa ligne, et
   n'entre pas dans le calcul du minimum. Le filet reste, la casse disparaît. */
.table td { overflow-wrap: break-word; }

/* Un montant ne se coupe jamais. Il n'a pas de « mot long » : il a une partie
   entière, une virgule et des centimes, et les séparer produit un nombre faux
   à la lecture. La colonne s'élargit ou le tableau défile, mais le nombre reste
   entier. */
.table td.num, .table th.num { white-space: nowrap; }

/* Une pastille non plus. « Prise en charge » se replie sur deux lignes DANS sa
   pastille, ce qui va ; « Approuv / ée » coupe un mot en deux, ce qui ne va
   pas. */
.badge { overflow-wrap: normal; word-break: keep-all; }

/* --- 4a bis. On doit VOIR qu'un tableau défile -----------------------
   Un tableau coupé sans le dire n'est pas inconfortable : c'est une
   information qu'on ne saura pas manquante. C'est ce qui rendait la colonne
   d'actions introuvable sur la file d'attente et l'annuaire, et c'est le
   constat le plus fréquent des quatre audits du 2026-09-12.

   Les deux voiles et les deux ombres n'apparaissent QUE du côté où il reste
   quelque chose à lire : `background-attachment: local` fait défiler les
   voiles avec le contenu, `scroll` fixe les ombres sur le cadre, et l'ombre se
   retrouve donc recouverte par le voile en bout de course. Quand rien ne
   déborde, les deux voiles sont en place et aucune ombre ne se voit - le
   dispositif s'efface tout seul, sans mesurer quoi que ce soit. Aucun JS,
   aucune icône, rien qui dépende de la CSP.

   Posé sur `.table-container` et non sur une classe à demander : le défaut
   valait pour les 93 tableaux, pas pour ceux qu'on aurait pensé à marquer. */
.table-container {
    background-image:
        linear-gradient(to right, var(--surface) 40%, rgba(255, 255, 255, 0)),
        linear-gradient(to left, var(--surface) 40%, rgba(255, 255, 255, 0)),
        linear-gradient(to right, rgba(16, 24, 40, .13), rgba(16, 24, 40, 0)),
        linear-gradient(to left, rgba(16, 24, 40, .13), rgba(16, 24, 40, 0));
    background-position: 0 0, 100% 0, 0 0, 100% 0;
    background-repeat: no-repeat;
    background-size: 2.5rem 100%, 2.5rem 100%, .8rem 100%, .8rem 100%;
    background-attachment: local, local, scroll, scroll;
}

/* --- 4a ter. `.table-scroll` : le défilement ASSUMÉ à toute largeur ---
   Le pendant de `.table-cards`, et il manquait. Un tableau de cinq colonnes ou
   plus, ou qu'on lit en comparant ses lignes entre elles, perd à l'empilement
   ce qu'il gagne en largeur : le portail en donne la mesure, dix colonnes en
   cartes font 6 600 px de défilement vertical pour vingt factures.

   La règle de tri est le nombre de colonnes, jamais l'importance de l'écran :
   quatre colonnes ou moins et une ligne = un objet, donc `.table-cards` ;
   cinq et plus, ou lecture par comparaison, donc `.table-scroll`. Une colonne
   d'actions ne passe jamais derrière un défilement sur un écran qu'on ouvre
   sous alerte. Tout `<table class="table">` porte l'une ou l'autre.

   Ce que celle-ci ajoute au comportement de conteneur ci-dessus : elle vaut à
   TOUTE largeur et non sous 48 rem seulement, parce qu'un tableau à douze
   colonnes déborde aussi d'un contenu de 900 px. */
.table-container.table-scroll > table.table { min-width: max-content; }

/* La première colonne reste, sinon on défile en perdant de quoi on parle.

   `border-collapse: collapse` donne les bordures à la TABLE et non aux
   cellules : une cellule collante passe par-dessus et ses traits disparaissent.
   D'où les deux ombres internes, qui les redessinent - c'est le seul moyen de
   tenir les deux à la fois, et le défaut ne se voit qu'une fois qu'on défile. */
.table-container.table-scroll > table.table > thead > tr > th:first-child,
.table-container.table-scroll > table.table > tbody > tr > td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--surface);
    box-shadow: inset -1px 0 0 var(--border), inset 0 -1px 0 var(--border);
}
.table-container.table-scroll > table.table > thead > tr > th:first-child {
    background: var(--surface-2); z-index: 3;
}

/* --- 4b. Mode carte, patron UNIQUE de l'application -------------------
   L'en-tête disparaît, chaque ligne devient un bloc, chaque cellule affiche son
   libellé via `data-label`. Absorbe les deux classes qui faisaient exactement la
   même chose et qui ont été supprimées : `.invoice-table` (liste des factures et
   relevé) et `.hub-table` (portail). 100 % CSS, aucun JS (CSP stricte).
   Prérequis côté gabarit : un `data-label` sur chaque `<td>`.

   Bascule sur la largeur du CONTENEUR et non sur celle de la fenêtre, comme le
   reste du bloc : c'est la place laissée au tableau qui décide, pas la taille
   de l'écran. */
@container (max-width: 48rem) {
    .table-cards thead { display: none; }
    .table-cards, .table-cards tbody, .table-cards tr, .table-cards td { display: block; width: 100%; }
    .table-cards tr {
        border: 1px solid var(--border); border-radius: var(--radius);
        margin-bottom: .75rem; padding: .25rem .9rem;
    }
    /* Ligne d'erreur de la liste des factures : elle prolonge la carte
       précédente au lieu d'en ouvrir une nouvelle (spécificité héritée de
       l'ancienne `.invoice-table`, cf. templates/invoices/index.html.twig). */
    .table-cards tr.invoice-error-row { border: 0; margin-top: -.75rem; padding: 0 .9rem .75rem; }
    .table-cards td { border-bottom: 1px dashed var(--border); padding: .6rem 0; text-align: right; }
    .table-cards td:last-child { border-bottom: 0; }
    /* L'alignement à droite ci-dessus vaut pour une VALEUR posée en face de son
       intitulé. Une cellule qui couvre toute la ligne n'a pas d'intitulé en
       face d'elle - c'est l'état vide, « aucune facture » - et se retrouvait
       plaquée à droite, sous une classe `.text-center` qu'elle porte pourtant :
       en spécificité, `.table-cards td` (0,1,1) bat `.text-center` (0,1,0). Le
       sélecteur composé rétablit l'ordre sans redéclarer l'utilitaire, ce que
       `ResponsiveTablePatternTest` interdit. */
    .table-cards td[colspan] { text-align: center; }
    .table-cards td[data-label]:not([data-label=""])::before {
        content: attr(data-label); float: left; font-weight: 600;
        color: var(--muted); font-size: .82rem;
    }
    /* Un bouton d'action est parfois enveloppé dans un `<form style="display:inline">`
       (attribut HTML, cf. le bouton « Relancer » de templates/invoices/index.html.twig) :
       il l'emporte sur la cascade externe, d'où le !important pour le repasser en
       bloc et laisser `.btn { width: 100% }` s'appliquer réellement. */
    .table-cards td form { display: block !important; }
    .table-cards td .btn { width: 100%; justify-content: center; }
}

/* --- 5. (supprimé) Filet des grilles posées en style inline -----------
   Il ramenait à une colonne, en `!important`, tout attribut `style` contenant
   `1fr 1fr` / `2fr 1fr` / `1fr 2fr` / `repeat(3, 1fr)`. Deux défauts : il ne
   couvrait que `.app-content` (la page publique de collecte, qui étend
   `base.html.twig`, y échappait) et il traitait le symptôme. Les gabarits sont
   passés aux classes `.grid-2` / `.grid-3` / `.grid-ratio-*` déclarées plus
   haut, le filet n'a plus lieu d'être. */

/* --- 6. Tiroir de navigation : clavier, focus, défilement de fond -----
   La case portait `hidden`, donc `display: none` : elle sortait du parcours
   de tabulation et le tiroir devenait inatteignable au clavier comme au
   lecteur d'écran (échec WCAG 2.1.1). Elle est désormais masquée par
   `.visually-hidden` et reste focusable ; le focus doit donc se voir sur le
   déclencheur, qui est un `<label>` distinct.
   `visibility` retire par ailleurs les liens du parcours quand le tiroir est
   fermé, et le fond ne défile plus derrière lui. */
#mkt-nav-toggle:focus-visible + .mkt-burger,
#nav-toggle:focus-visible ~ .app-layout .nav-burger,
#nav-toggle:focus-visible ~ .app-layout .app-main .nav-burger {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
@media (max-width: 1024px) {
    .app-sidebar {
        width: min(var(--sidebar-w), 82vw);
        visibility: hidden; transition: transform .2s ease, visibility 0s .2s;
    }
    #nav-toggle:checked ~ .app-layout .app-sidebar {
        visibility: visible; transition: transform .2s ease;
    }
    body:has(#nav-toggle:checked) { overflow: hidden; }
    .app-nav { overscroll-behavior: contain; }
}

/* --- 7. Modales : défilement interne, actions au pouce ---------------
   `.modal` n'avait ni hauteur maximale ni défilement : en paysage, une modale
   plus haute que la fenêtre était rognée en haut ET en bas, ses boutons de
   validation devenant inatteignables. */
.modal { max-height: calc(100dvh - 2rem); overflow-y: auto; overscroll-behavior: contain; }
.modal-head { flex-wrap: wrap; }
@media (max-width: 640px) {
    .modal-overlay { padding: .5rem; }
    .modal { padding: 1.15rem; max-height: calc(100dvh - 1rem); }
    .modal-actions { flex-direction: column-reverse; gap: .5rem; }
    .modal-actions .btn { width: 100%; justify-content: center; }
}
@media (max-width: 768px) {
    .modal.modal-xl.pdf-preview-dialog {
        max-width: 100%; width: 100%; max-height: 100dvh;
        padding: .75rem; border-radius: 0;
    }
    .modal-overlay:has(.pdf-preview-dialog) { padding: 0; }
    .pdf-preview-frame { min-height: 0; margin-top: .5rem; }
    .pdf-preview-actions { margin-left: 0; width: 100%; }
}

/* --- 8. Densité : 96 px de gouttières sur un écran de 320 px ---------- */
@media (max-width: 640px) {
    .app-topbar { padding: 0 .75rem; }
    /* La réserve du bas doit couvrir le pied de page COLLANT, qui fait trois
       lignes à 390 px (crédits sur deux, version et état sur la troisième) :
       79 px mesurés, contre 72 que réservait `4.5rem` - le bas du contenu
       passait dessous. */
    .app-content { padding: 1rem .75rem 5.5rem; }
    .card-pad { padding: 1rem; }
    .card-header { padding: .85rem 1rem; }
    .table thead th { padding: .6rem .7rem; }
    .table tbody td { padding: .65rem .7rem; }
    /* Sous 640 px, les trois zones ne tiennent plus sur une ligne : la grille
       laisse la place à un empilement, plutôt qu'à trois colonnes écrasées où
       les crédits se coupent en quatre lignes.

       Deux lignes et pas trois : ce pied de page est COLLANT, chaque ligne de
       plus est prise sur la hauteur utile, et une barre de 87 px sur un écran
       de 800 en mange 11 %. Les crédits occupent leur ligne, la version et
       l'état se partagent la suivante. */
    .app-footer {
        padding: .5rem .75rem; row-gap: .25rem;
        display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;
    }
    .app-footer-credits { flex-basis: 100%; text-align: center; }
    .app-footer-credits, .app-footer .app-status, .app-version { justify-self: auto; }
    .user-menu-name { max-width: 6rem; }
    /* Une infobulle au doigt ne s'affiche qu'au moment du tap, juste avant que
       la page change : elle gêne plus qu'elle n'informe. */
    .tip::after { display: none; }
}

/* --- 9. Hauteur réelle de la fenêtre (barre d'URL rétractable) --------
   Les écrans d'authentification gardent `svh` (cf. section Auth) : `dvh` y
   ferait sauter une carte centrée au fil des rétractations de la barre. */
@supports (height: 100dvh) {
    .app-layout { min-height: 100dvh; }
    .app-sidebar { height: 100dvh; }
}

/* --- 10. Paysage bas : les barres collantes mangeaient 26 % de l'écran */
@media (max-height: 480px) and (orientation: landscape) {
    .app-topbar { height: 50px; }
    .app-footer { position: static; }
    .settings-subnav, .import-preview-card, .ged-tree-panel { position: static; }
}

/* --- 11. Sélecteur d'entreprise : jamais coupé hors écran ------------- */
@media (max-width: 640px) {
    .company-switch-list {
        min-width: 0; width: max-content; max-width: calc(100vw - 1.5rem);
        left: 0; right: auto;
    }
    .company-switch-item > span { min-width: 0; overflow: hidden; }
    .company-switch-item > span > small {
        display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
}

/* --- 12. Sous-navigation des Paramètres en barre d'onglets ------------
   Empilée, elle place douze liens (~470 px) AVANT le contenu : la première vue
   de l'équipe, des notifications ou de la sécurité n'affichait plus rien
   d'utile sur un téléphone. En barre horizontale défilante, elle tient sur une
   ligne et reste atteignable d'un seul geste. */
@media (max-width: 1024px) {
    .settings-subnav {
        display: flex; flex-direction: row; align-items: stretch;
        gap: .35rem; overflow-x: auto; overscroll-behavior-x: contain;
        padding-bottom: .35rem; margin-bottom: 1rem;
    }
    .settings-subnav > .settings-subnav-title,
    .settings-subnav > hr { display: none; }
    .settings-subnav > a,
    .settings-subnav > form > button,
    .settings-subnav > .btn,
    .settings-subnav > .settings-subnav-action {
        flex: 0 0 auto; white-space: nowrap; min-height: 44px;
    }
    /* L'action « Ajouter une entreprise » est dans un `div` : oubliée ici, elle
       gardait sa marge de colonne et un bouton pleine largeur sans largeur. */
    .settings-subnav > .settings-subnav-action { padding: 0; }
}

/* --- 13. Correctifs ponctuels ---------------------------------------- */
@media (max-width: 980px) { .import-preview-card { position: static; } }
@media (max-width: 720px) {
    /* `.batch-item-actions` exigeait 240 px à lui seul dans 224 px utiles. */
    .batch-item { grid-template-columns: minmax(0, 1fr); gap: .75rem; }
    .batch-item-actions { min-width: 0; }
}
@media (max-width: 640px) {
    .app-content .flex.gap-2, .app-content .flex.gap-3 { flex-wrap: wrap; }
    .tag-row { flex-wrap: wrap; }
    .tag-row .tag-edit { flex-basis: 100%; }
    .ged-toolbar .form-select, .ged-toolbar .btn { width: 100% !important; }
    .ged-search { min-width: 0; flex-basis: 100%; }
    .emit-totals { min-width: 0; width: 100%; margin-left: 0; }
    .wizard-step-label { hyphens: auto; overflow-wrap: anywhere; }
    /* La bulle d'assistance recouvrait le bouton d'enregistrement et la
       dernière ligne d'action de tous les tableaux. */
    .feedback-widget { right: .75rem; bottom: calc(.75rem + env(safe-area-inset-bottom, 0px)); }
    .feedback-bubble { width: 3rem; height: 3rem; font-size: 1.1rem; }
}
/* L'import d'un dossier entier (`webkitdirectory`) est sans effet sur iOS et
   Android : ne pas proposer une action morte. */
@media (max-width: 1024px) {
    label[for="pdfs_dir"], label[for="pdfs_dir"] + span { display: none; }
}

/* ============================================================
   Adaptation mobile - vitrine, espace client et administration
   (même audit du 2026-07-26). Séparé du bloc précédent, qui ne
   traite que l'application fournisseur.
   ============================================================ */

/* L'en-tête vitrine est collant sur 68 px, la topbar applicative sur 62 px :
   sans marge de défilement, toute cible d'ancre atterrit dessous. Concernait
   les quatre sections de l'accueil, les quatre de la FAQ et les sept parcours
   de la documentation. */
[id] { scroll-margin-top: 5.5rem; }

/* --- Vitrine ---------------------------------------------------------- */
@media (max-width: 768px) {
    /* Le panneau déroulant ne stylait que son conteneur : les liens faisaient
       23 px de haut, espacés de 4 px. Et sans hauteur maximale, il sortait de
       l'écran en paysage sans pouvoir défiler. */
    .mkt-nav-links { gap: 0; padding: .5rem; overscroll-behavior: contain; }
    .mkt-nav-links > a { min-height: 48px; padding: .25rem .75rem; border-radius: var(--radius); }
    .mkt-nav-links > a:not(.btn):active { background: var(--surface-2); }
    .mkt-nav-links > a.btn { margin: .6rem 0 .35rem; }
    .mkt-nav-links .lang-toggle { margin: .35rem .75rem; }

    /* Hero : à 320 px la `clamp` du titre retombe sur sa borne basse (33,6 px)
       et le `max-width: 14ch`, utile à la mise en page à deux colonnes, n'a
       plus de raison d'être une fois la grille repliée. Le seul bouton de
       conversion de la page arrivait vers 700 px de haut. */
    .hero { padding: 2.25rem 0 2.5rem; }
    .hero h1 { max-width: none; font-size: clamp(1.75rem, 7.5vw, 2.4rem); }
    .hero p.lead { max-width: none; font-size: 1.02rem; margin: .9rem 0 1.5rem; }
    .hero .eyebrow { font-size: .76rem; margin-bottom: .9rem; }
    .hero .cta-row { flex-direction: column; align-items: stretch; }
    .hero .cta-row .btn { justify-content: center; }
    .hero-perks { gap: .4rem 1rem; }

    /* Rythme vertical : six sections à 4 rem faisaient près de 770 px de vide
       sur la seule page d'accueil. */
    .section { padding: 2.5rem 0; }
    .section-head { margin-bottom: 1.75rem; }
    .cta-band { padding: 2rem 1.25rem; border-radius: var(--radius); }
    .cta-band p { margin-bottom: 1.25rem; }

    .mkt-footer { padding: 2rem 0 2.5rem; }
    .mkt-footer a { display: inline-flex; align-items: center; min-height: 40px; padding: 0 .3rem; }
}

/* La pastille du hero passe sur deux lignes sous 400 px : un rayon de 999 px
   y produit une forme bâtarde. */
@media (max-width: 400px) {
    .hero .eyebrow { border-radius: var(--radius); font-size: .74rem; }
}

/* Bascule mensuel/annuel : contrôle central de la page Tarifs, à 35 px. */
.pricing-toggle label { min-height: 44px; padding: .5rem 1.15rem; }
@media (max-width: 480px) {
    .pricing-card { padding: 1.35rem 1.15rem; }
    .pricing-amount { font-size: 1.75rem; }
    /* Hauteur plancher utile en grille, superflue une fois les cartes empilées. */
    .pricing-tagline { min-height: 0; }
}

@media (max-width: 640px) {
    .legal { padding: 1.75rem 1.15rem 3rem; }
}

/* Sélecteur de langue : 60 x 30 px, dont une zone réellement visée de
   18 x 12 px - le plus petit contrôle du site. */
@media (pointer: coarse) {
    .lang-toggle-track { width: 76px; height: 44px; padding: 4px; }
    .lang-toggle-thumb { width: 36px; height: 36px; top: 4px; left: 4px; }
    .lang-toggle-track.is-en .lang-toggle-thumb { transform: translateX(32px); }
    .lang-toggle-option svg { width: 22px; height: 15px; }
}

/* --- Espace client final ---------------------------------------------- */

.portal-amount { font-size: clamp(1.9rem, 8vw, 2.4rem); }

/* Aperçu PDF : `min-height: 420px` imposait un cadre plus haut que l'écran
   entier en paysage, alors que l'action utile (télécharger) est au-dessus. */
@media (max-width: 768px) { .portal-preview-frame { height: 58vh; min-height: 300px; } }
@media (max-height: 520px) { .portal-preview-frame { height: 75vh; min-height: 0; } }

@media (max-width: 768px) {
    /* Cases de sélection restées natives (~13 px) dans un tableau en cartes.
       Le portail n'est pas sous `.app-content` : la règle de cibles tactiles du
       §2 ne l'atteint pas, celle-ci reste donc nécessaire. */
    .table-cards input[type="checkbox"] { width: 1.4rem; height: 1.4rem; accent-color: var(--primary); margin: .15rem 0; }
    .table-cards td:first-child { padding: .7rem 0; }
    .table-cards .btn { min-height: 44px; }
}

@media (max-width: 640px) {
    /* Le total TTC garde toute la largeur, les deux autres se partagent la ligne. */
    .hub-totals-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .hub-totals-grid > div:first-child { grid-column: 1 / -1; }
}

/* Aperçu d'e-mail de l'espace fournisseur : la hauteur était posée en style
   inline, donc hors de portée de toute media query. */
.portal-email-preview {
    width: 100%; height: clamp(320px, 60vh, 640px);
    border: 1px solid var(--border); border-radius: var(--radius); background: #fff;
}

/* --- Coque applicative et administration ------------------------------ */

/* Topbar : hauteur figée à 62 px, sans repli, pour près de 480 px de contenu
   incompressible sur le hub client (marque + sélecteur de société + adresse +
   deux boutons en `nowrap`) -> toute la page défilait horizontalement. */
/* --- Panneau unique de la barre du haut, mobile seulement -------------- */

/* Masqué par défaut : au-dessus de 640 px, la barre garde ses contrôles
   séparés, qui ont la place de s'y déployer. */
.topbar-mobile { display: none; position: relative; }
.topbar-mobile > summary {
    list-style: none; cursor: pointer; user-select: none;
    display: flex; align-items: center; gap: .45rem; min-width: 0;
    max-width: 62vw; padding: .4rem .6rem; border-radius: 8px;
    border: 1px solid transparent; font-weight: 600; font-size: .9rem;
}
.topbar-mobile > summary::-webkit-details-marker { display: none; }
.topbar-mobile > summary:hover,
.topbar-mobile[open] > summary { background: var(--bg); border-color: var(--border); }
.topbar-mobile-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.topbar-mobile-caret { font-size: .65rem; color: var(--muted); transition: transform .15s; }
.topbar-mobile[open] .topbar-mobile-caret { transform: rotate(180deg); }

/* Le panneau s'ancre sur la barre et non sur le bouton : sur un écran de
   360 px, un menu aligné à droite du bouton déborde de la fenêtre. */
.topbar-mobile-panel {
    position: absolute; top: calc(100% + 8px); right: -.35rem; z-index: 50;
    width: max-content; min-width: min(19rem, 88vw); max-width: 92vw;
    max-height: calc(100vh - 5rem); overflow-y: auto;
    background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
    box-shadow: 0 12px 32px rgba(0, 0, 0, .16); padding: .4rem;
}
.topbar-mobile-group + .topbar-mobile-group { border-top: 1px solid var(--border); margin-top: .35rem; padding-top: .35rem; }
.topbar-mobile-user { padding: .35rem .6rem .5rem; line-height: 1.3; }
.topbar-mobile-user .user-menu-fullname { display: block; font-weight: 600; font-size: .92rem; }
.topbar-mobile-user small { color: var(--muted); font-size: .78rem; overflow-wrap: anywhere; }
.topbar-mobile-tools { display: flex; align-items: center; gap: .75rem; padding: .5rem .6rem .35rem; }

@media (max-width: 640px) {
    /* UNE seule rangée, et rien qui se replie : le sélecteur d'entreprise, le
       compte, la langue, l'aide et l'installation passent tous dans le panneau
       unique ci-dessous. Ce qui reste dans la barre, c'est le burger et le nom
       de l'entreprise courante - le contexte, qui doit se lire sans ouvrir. */
    .app-topbar { height: 52px; min-height: 52px; padding: 0 .75rem; flex-wrap: nowrap; gap: .5rem; }
    .app-topbar .app-brand > span:not(.logo) { display: none; }
    /* MASQUER N'EST LÉGITIME QUE S'IL EXISTE UN RELAIS, d'où le modificateur.
       Ces règles s'appliquaient à tout `.app-topbar`, y compris celle de
       `layout/admin.html.twig` qui n'inclut PAS `_topbar_mobile_menu` : sous
       640 px, l'administration perdait son menu de compte, sa déconnexion et
       son sélecteur de langue, sans rien pour les remplacer. Ce n'était pas de
       l'inconfort, c'était une fonction perdue. Trouvé le 2026-09-12. */
    .app-topbar--mobile-panel .user,
    .app-topbar--mobile-panel .company-switch,
    .app-topbar--mobile-panel .company-switch-menu { display: none; }
    .topbar-mobile { display: block; margin-left: auto; min-width: 0; }
    /* L'administration garde donc les siens, resserrés pour tenir sur la
       rangée unique à côté du badge et du burger. */
    .app-topbar:not(.app-topbar--mobile-panel) .user { gap: .35rem; }

    /* LA CLOCHETTE RESTE, et c'est la seule exception a la rangee unique.
       Une pastille dont l'objet est de prevenir sans qu'on regarde ne peut pas
       vivre derriere un panneau qu'il faut ouvrir - ce serait exactement la
       fonction qu'elle remplace. Elle ne rouvre pas le probleme des trois
       rangees du 06/09 : c'est une icone de 36 px sur la meme ligne, a cote du
       nom de l'entreprise, pas un controle de plus qui se replie. */
    .app-topbar--mobile-panel .user { display: flex; margin-left: auto; gap: 0; }
    .app-topbar--mobile-panel .user > *:not(.notif-bell) { display: none; }
    /* `z-index` explicite : sans lui, le `<summary>` du panneau mobile - declare
       APRES dans le flux - recouvre la cloche et capte son clic. On ouvrait le
       selecteur d'entreprise en visant la clochette. */
    .app-topbar .user .notif-bell { position: relative; z-index: 2; }
    .topbar-mobile { margin-left: .25rem; flex: 0 1 auto; min-width: 0; }
}


/* La borne basse de 330 px dépassait la largeur utile (327 px à 375 px), et la
   variante à trois colonnes de /admin/systeme était figée en style inline. */
.tools-grid { grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr)); }
.tools-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1024px) { .tools-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) { .tools-grid--3 { grid-template-columns: 1fr; } }

/* --- La falaise de 861 px : COMBLÉE le 2026-09-12 ---------------------
   Elle venait du seuil du tiroir. La barre latérale basculait en tiroir sous
   860 px et reprenait ses 256 px à 861 : la zone de contenu tombait de 812 px
   (fenêtre 860) à 557 px (fenêtre 861). On perdait 255 px de contenu en
   gagnant 1 px de fenêtre, et toutes les adaptations calées sur la FENÊTRE
   cessaient justement là où le contenu était le plus étroit.

   Le seuil est passé à 1024 px, décision de Jordan. Le calcul tombe juste : à
   1025 px, barre latérale déduite, le contenu vaut 769 px - juste au-dessus du
   seuil des tableaux. Il n'y a donc plus de bande où le contenu est plus étroit
   qu'au palier précédent. Le coût est assumé : plus de barre latérale
   permanente sur un portable de 900 à 1024 px, qui reçoit le tiroir.

   Or toutes les adaptations mobiles sont calées sur la FENÊTRE (768, 640 px) :
   elles ne s'appliquent donc jamais entre 861 et ~1090 px, précisément la
   plage où le contenu est plus étroit qu'à 860 px, où elles s'appliquaient
   toutes. Les tableaux y perdent leur plancher de défilement, le mode cartes
   n'est pas encore actif, et les grilles restent à trois ou quatre colonnes.

   CE BLOC FOND À MESURE. Il ne rattrapait rien d'autre qu'une mesure prise au
   mauvais endroit : la fenêtre plutôt que la place réellement disponible. Les
   tableaux n'y sont plus - `.table-container` est devenu conteneur de requête
   et se règle sur sa propre largeur, à toute taille d'écran. `.grid-3` et
   `.invoice-totals-grid` n'y sont plus non plus, passées en `auto-fit` avec
   `min(…, 100%)`, qui se replient d'elles-mêmes sans nommer aucun seuil.
   Ce qui reste ici attend le même traitement, et le bloc disparaîtra. */
@media (min-width: 1025px) and (max-width: 1090px) {
    .hub-totals-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- Grilles IMBRIQUÉES : replier sur la colonne, pas sur la fenêtre ---
   Les points de rupture généraux parlent de la fenêtre. Une `.grid-ratio-1-2-1`
   (code postal / ville / pays) placée dans la colonne d'une `.grid-2` ne
   dispose pas de 720 px mais de la moitié : à 1024 px de fenêtre, la piste du
   code postal tombe à 66 px, dont 27,6 de chrome de champ - 39 px de saisie,
   inutilisable. Mesuré sur le formulaire client passé en deux colonnes. */
@media (max-width: 1180px) {
    .grid-2 .grid-2, .grid-2 .grid-3,
    .grid-2 .grid-ratio-2-1, .grid-2 .grid-ratio-1-2, .grid-2 .grid-ratio-1-2-1,
    .grid-ratio-2-1 .grid-2, .grid-ratio-2-1 .grid-3,
    .grid-ratio-1-2 .grid-2, .grid-ratio-1-2 .grid-3 {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* --- Jetons de TEXTE, distincts des jetons de fond --------------------
   `--warning` (#d97706) sur son propre fond de badge (#fef5e7) donne 2,95:1,
   très en dessous des 4,5:1 exigés pour du texte de .73rem - or ces badges
   portent « Non vérifié », « Programmée », « En attente de transmission ».
   Le fichier avait déjà les teintes sombres pour les alertes, il manquait les
   jetons pour les textes colorés. */
:root {
    --success-text: #15803d;
    --warning-text: #92620a;
    --danger-text: #b91c1c;
}
.badge-warning { color: var(--warning-text); }
.badge-danger { color: var(--danger-text); }
.status-pill--operational { color: var(--success-text); }
.status-pill--degraded { color: var(--warning-text); }
.discover-status-success { color: var(--success-text); }
.ai-flag { color: var(--warning-text); }

/* --- Focus visible : filet de sécurité --------------------------------
   `:where()` a une spécificité NULLE : les règles de focus déjà écrites plus
   haut continuent de l'emporter, celle-ci ne couvre que ce qu'elles oublient -
   les `<summary>` désolidarisés de leur marqueur, les cartes-liens, les
   onglets d'administration. */
:where(a, button, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}
/* Sur fond sombre, l'indigo #3937a3 donne 1,77:1 contre #1b1c39 : sous les
   3:1 qu'exige WCAG 1.4.11 pour un indicateur de focus. */
.app-sidebar :where(a, summary, button):focus-visible,
.admin-context .app-topbar :where(a, button, summary):focus-visible {
    outline-color: #fff;
}

/* Au-dessus du point de rupture, le déclencheur du tiroir est en
   `display: none` : sa case ne doit plus être atteignable au clavier, sinon la
   PREMIÈRE tabulation de chaque page tombe sur un contrôle invisible. */
@media (min-width: 1025px) { #nav-toggle { display: none; } }
@media (min-width: 769px) { #mkt-nav-toggle { display: none; } }

/* Lien d'évitement : hors écran tant qu'il n'a pas le focus. Sans lui, un
   utilisateur au clavier traverse les 16 liens de la sidebar, le sélecteur
   d'entreprise et le menu profil avant d'atteindre le contenu, sur CHAQUE
   page (WCAG 2.4.1, niveau A). */
.skip-link {
    position: absolute; left: .5rem; top: -3.5rem; z-index: 1000;
    padding: .55rem 1rem; border-radius: var(--radius);
    background: var(--primary); color: #fff; font-weight: 600;
    transition: top .15s ease;
}
.skip-link:focus { top: .5rem; color: #fff; outline: 2px solid #fff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .skip-link { transition: none; } }

/* ---------- Écran « Application » (PWA) ----------
   templates/app/pwa.html.twig, piloté par les contrôleurs Stimulus
   pwa-install et pwa-manager. */

/* Liste de faits en deux colonnes : le libellé garde une largeur fixe pour que
   les valeurs s'alignent, sinon « standalone » et « browser » sautent d'une
   ligne à l'autre à chaque rafraîchissement. */
.pwa-facts { display: grid; grid-template-columns: minmax(0, 14rem) 1fr; gap: .5rem 1rem; margin: 0; }
.pwa-facts dt { color: var(--muted); font-size: .88rem; }
.pwa-facts dd { margin: 0; font-size: .92rem; font-weight: 500; }

.pwa-cache-list { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.pwa-cache-list li { padding: .3rem 0; border-bottom: 1px solid var(--border); }
.pwa-cache-list li:last-child { border-bottom: 0; }

/* Sous 640 px, deux colonnes rendent le libellé illisible : on empile, et le
   fait garde son étiquette juste au-dessus de sa valeur. */
@media (max-width: 640px) {
    .pwa-facts { grid-template-columns: 1fr; gap: .15rem; }
    .pwa-facts dd { margin-bottom: .6rem; }
}

/* ============================================================================
   §N - Planning et Échéances (docs/48)

   Aucune bibliothèque, aucune dépendance : la grille mensuelle est une grille
   CSS, l'échéancier est un `.table-cards` de plus. C'est la décision ferme du
   §3.7 de l'étude - la CSP interdit tout CDN, et une bibliothèque de
   calendrier formaterait les dates côté client, ce qui casserait la parité
   FR/EN vérifiée par `LocaleRenderingTest`.
   ========================================================================= */

/* --- Convention de signe. Celle du produit, pas une nouvelle : ce qui rentre
   est positif, ce qui sort est négatif (cf. `statement.html.twig`). La couleur
   ne dit RIEN à elle seule - le signe et le libellé portent l'information, la
   teinte ne fait que la souligner (contrainte d'accessibilité, §3.2). */
.planning-amount-in { color: var(--success); font-weight: 600; }
.planning-amount-out { color: var(--danger); font-weight: 600; }

/* --- Barre de filtres. Le `fieldset` porte la légende accessible et ne doit
   rien peindre : sans ce dégrossissage, le navigateur y met sa propre bordure
   et la barre ne ressemble plus à celle du reporting. */
.planning-filters-fieldset { border: 0; margin: 0; padding: 0; display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; width: 100%; }
.planning-filter-field { flex: 1 1 160px; min-width: 0; }
.planning-filter-actions { display: flex; gap: .5rem; align-items: center; }

/* --- Le périmètre du cumul, collé au chiffre. Il n'est PAS décoratif : un
   cumul net attendu présenté seul se lit comme une position de trésorerie,
   et fx ne connaît ni salaires, ni cotisations, ni TVA, ni loyers (§3.3). */
.planning-scope { font-size: .78rem; margin: .35rem 0 0; }
.planning-disclaimer { font-size: .82rem; margin: 1rem 0 0; padding-top: .75rem; border-top: 1px solid var(--border); }

/* --- Périodes vides : atténuées, jamais masquées. Une semaine sans échéance
   est une information ; la retirer du tableau ferait croire à un trou dans le
   temps plutôt qu'à un trou dans le carnet. */
.planning-period--empty td { color: var(--muted); }

/* --- Repli par période (`<details>` natif, sans JavaScript). */
.planning-details > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.planning-details > summary::-webkit-details-marker { display: none; }
.planning-details > summary::before { content: '\f105'; font-family: 'Font Awesome 7 Free'; font-weight: 900; color: var(--muted); transition: transform .15s; }
.planning-details[open] > summary::before { transform: rotate(90deg); }
.planning-summary-net { margin-left: auto; }

/* --- Blocs hors horizon (retard, sans échéance, au-delà, total inconnu). */
.planning-block { border-left: 4px solid var(--border); }
.planning-block--danger { border-left-color: var(--danger); }
.planning-block--warning { border-left-color: var(--warning); }
.planning-block-title { font-size: 1rem; margin: 0 0 .35rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.planning-block-hint { font-size: .83rem; margin: 0 0 .75rem; }
.planning-block-totals { display: flex; gap: 1.5rem; flex-wrap: wrap; font-size: .88rem; margin-bottom: .75rem; }

/* --- Mention de retard ou de glissement, sous la date. */
.planning-note { display: block; font-size: .78rem; color: var(--muted); }
.planning-note--late { color: var(--danger); font-weight: 600; }

/* --- Grille mensuelle. */
.planning-month-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.planning-month-title { font-size: 1.15rem; margin: 0; }

.planning-grid-head,
.planning-grid-body { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
/* Le corps est une `<ul>` : la puce et les marges par défaut n'ont rien à faire
   dans une grille de calendrier. La liste est sémantique, pas décorative -
   c'est ce qui fait qu'un lecteur d'écran annonce « liste de 35 éléments »
   plutôt qu'un empilement de divisions muettes. */
.planning-grid-body { list-style: none; margin: 0; padding: 0; }
.planning-grid-weekday { font-size: .78rem; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; padding: .35rem .5rem; text-align: center; }
.planning-day { background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; min-height: 6.5rem; padding: .35rem; display: flex; flex-direction: column; gap: .2rem; }
.planning-day--outside { opacity: .45; }
.planning-day--today { border-color: var(--primary); box-shadow: inset 0 0 0 1px var(--primary); }
.planning-day-number { font-size: .8rem; font-weight: 700; color: var(--muted); }

/* Une pastille par mouvement. `overflow: hidden` sur la référence, jamais sur
   le montant : un numéro tronqué reste identifiable, un montant tronqué est un
   mensonge. */
.planning-chip { display: flex; align-items: center; gap: .3rem; font-size: .72rem; padding: .15rem .3rem; border-radius: 4px; background: var(--surface); border: 1px solid var(--border); text-decoration: none; color: var(--text); }
.planning-chip:hover { border-color: var(--primary); }
.planning-chip-ref { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.planning-chip-amount { flex: 0 0 auto; font-weight: 600; }
.planning-chip--overdue { border-left: 3px solid var(--danger); }
.planning-chip--due { border-left: 3px solid var(--primary); }
.planning-chip--settled { border-left: 3px solid var(--success); }
.planning-chip--more { color: var(--muted); justify-content: center; background: transparent; border-style: dashed; }

/* Sept colonnes ne tiennent pas sur un téléphone - c'était l'argument
   principal du §3.1 contre cette représentation. La grille devient donc une
   LISTE de jours, où l'en-tête de colonne n'a plus de sens et disparaît : le
   numéro de jour porte alors sa date complète, sinon un « 14 » seul ne dit
   plus de quel jour de la semaine il s'agit. */
@media (max-width: 768px) {
    .planning-grid-head { display: none; }
    .planning-grid-body { grid-template-columns: 1fr; }
    .planning-day { min-height: 0; }
    .planning-day:not(:has(.planning-chip)) { display: none; }
    .planning-day--outside { display: none; }
    /* L'intention du commentaire ci-dessus n'était pas rendue : le « 14 » nu
       restait affiché et la date complète réservée au lecteur d'écran. En
       liste, c'est la date complète qui se voit, et elle est lue une seule
       fois (relevé B, G16). */
    .planning-day-number > [aria-hidden="true"] { display: none; }
    .planning-day-number > .visually-hidden {
        position: static; width: auto; height: auto; margin: 0;
        overflow: visible; clip: auto; white-space: normal;
    }
}

/* Résultat d'un essai sur la page du carillon (/admin/debug/notifications-sonores).

   L'état voyage par un attribut `data-etat` et non par une classe : le
   contrôleur Stimulus le pose et le repose à chaque essai, et un attribut se
   remplace là où une classe se cumule - un « ok » qui resterait accroché sous
   un « ko » suivant dirait le contraire de ce qui vient d'arriver. */
.debug-chime-result { margin: .7rem 0 0; padding: .5rem .7rem; border-radius: 6px; font-size: .87rem; border: 1px solid var(--border); background: var(--surface); }
.debug-chime-result[data-etat="ok"] { border-color: var(--success); }
.debug-chime-result[data-etat="ko"] { border-color: var(--danger); }
.debug-chime-result[data-etat="attente"] { color: var(--muted); }

/* ---------- Catégories de factures (classeur fournisseurs) ------------
   Chantier du 2026-09-12. Tout y suit la doctrine de `docs/52` : aucune
   largeur fixe, `auto-fit` avec `min(…, 100%)`, et rien qui dépende d'un
   seuil de fenêtre. */

/* Pastille : la couleur vient de la base, donc d'un style inline qui ne pose
   qu'un jeton. Le fond est la couleur à 12 % via `color-mix`, pour rester
   lisible quelle que soit la teinte choisie - un fond plein en couleur libre
   rendrait le texte illisible une fois sur deux. */
.category-pill {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .2rem .6rem; border-radius: 999px;
    font-size: .82rem; font-weight: 600; line-height: 1.5;
    color: var(--pill, var(--muted));
    background: color-mix(in srgb, var(--pill, var(--muted)) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--pill, var(--muted)) 28%, transparent);
    max-width: 100%;
    overflow-wrap: anywhere;
}
.category-pill i { flex-shrink: 0; }

/* Groupe de pastilles - fiche de facture, liste. */
.category-pills { display: flex; flex-wrap: wrap; gap: .35rem; }

/* Formulaire replié dans un `<details>`. Le `<summary>` porte déjà `.btn`, il
   ne faut donc que retirer le marqueur natif et ouvrir le panneau. */
.disclosure > summary { list-style: none; cursor: pointer; }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure-panel {
    margin-top: .75rem; padding-top: .75rem;
    border-top: 1px dashed var(--border);
}

/* Grille de champs d'une catégorie ou d'une règle. `auto-fit` + `min(…, 100%)` :
   trois colonnes au large, une seule dans une carte étroite, sans média. */
.form-grid-cat {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
    gap: 1rem;
}
.form-grid-cat .form-group { margin-bottom: 0; }
.form-control-color { padding: .2rem; height: 2.6rem; cursor: pointer; }

.form-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }

/* Une règle. Volontairement une carte et non une ligne de tableau : elle porte
   un titre, un résumé en langue naturelle et deux actions, ce qui ne se met pas
   en colonnes sans devenir illisible au premier repli. */
.rule-row {
    border: 1px solid var(--border); border-radius: var(--radius);
    padding: .9rem 1rem; margin-bottom: .75rem;
}
.rule-row.is-disabled { opacity: .62; }
.rule-row-head {
    display: flex; flex-wrap: wrap; gap: .6rem;
    align-items: center; justify-content: space-between;
}
.rule-row-title { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; min-width: 0; }
.rule-row-actions { display: flex; flex-wrap: wrap; gap: .35rem; }
.rule-row-summary { font-size: .86rem; margin: .5rem 0 .6rem; }

/* Chaque condition se lit comme un fragment de phrase, et ne se coupe pas au
   milieu quand la ligne se replie. */
.rule-condition {
    display: inline-block;
    padding: .1rem .45rem; margin: .1rem .15rem;
    border-radius: 6px; background: var(--surface-2);
    overflow-wrap: anywhere;
}

/* Une ligne de condition : trois menus, une valeur, un bouton de retrait. La
   valeur prend la place restante, et tout se replie sur une colonne quand la
   carte est étroite - sans seuil de fenêtre, là encore. */
.condition-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
    gap: .5rem; align-items: center;
    margin-bottom: .5rem;
}
.condition-row .condition-remove { justify-self: start; }

/* Pastille attribuée sur la fiche d'une facture : la catégorie, et le bouton
   qui la retire, soudés en un seul objet. Le bouton n'apparaît qu'au survol sur
   un pointeur fin - il est TOUJOURS visible au doigt, où il n'y a pas de survol
   et où un contrôle qui ne se montre pas est un contrôle qui n'existe pas. */
.category-assigned { display: inline-flex; align-items: center; gap: .15rem; }
.category-assigned .category-remove { padding: .15rem .3rem; opacity: 0; transition: opacity .15s; }
.category-assigned:hover .category-remove,
.category-assigned:focus-within .category-remove { opacity: 1; }
@media (hover: none) {
    .category-assigned .category-remove { opacity: 1; }
}

/* Ajout d'une catégorie : le menu prend la place restante, le bouton reste
   entier. `min(12rem, 100%)` plutôt qu'un plancher fixe - dans une carte
   étroite, le menu descend sous le bouton au lieu de déborder. */
.category-add {
    display: flex; flex-wrap: wrap; gap: .5rem;
    align-items: center; margin-top: .85rem;
}
.category-add .form-select { flex: 1 1 min(12rem, 100%); }

/* Filtre de catégorie des listes de factures.
   Il vit DANS la carte de recherche, sur une rangée à lui : `flex-basis: 100%`
   le met seul sur sa ligne. Ce n'est pas un détail de goût - posé comme
   troisième enfant du bandeau, il atterrissait sur une ligne déjà pleine,
   poussé à droite par le `space-between`, et débordait de 166 px sans jamais se
   replier (mesuré le 2026-09-12). Une carte à lui tout seul, essayée ensuite,
   faisait trois cartes empilées avant le tableau et un menu perdu au milieu
   d'un cadre vide. */
.category-filter { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
/* Le menu ne s'étire pas sur toute la largeur : un nom de catégorie fait trois
   mots, un champ de 60 rem pour l'accueillir a l'air d'une erreur. */
.category-filter .form-select { flex: 0 1 min(22rem, 100%); min-width: 0; }
.form-select-sm { padding: .35rem .6rem; font-size: .85rem; }

/* La référence de facture : PAS de `white-space: nowrap`, et c'est un essai
   qui a été fait puis défait le 2026-09-12.

   L'intention était bonne - un identifiant coupé se recopie de travers - mais
   le remède était pire : la colonne réservait alors la largeur de la plus
   longue référence, le tableau dépassait son conteneur même à 1280 px, et le
   bouton « Voir » de chaque ligne se retrouvait coupé au bord droit. On échange
   une coupure typographique contre une action inatteignable.

   Ce qui reste est suffisant : `overflow-wrap: break-word` n'autorise la
   coupure qu'au TIRET, jamais au milieu d'un groupe de chiffres. « 732829320-FE-
   / 2026-0512 » se lit ; « 1 008,0 / 0 € » ne se lisait pas, et c'est ce
   dernier cas que le passage de `anywhere` à `break-word` a supprimé. */

/* ---------- Saisie de lignes : le repli en fiches -------------------
   Le pire cas de mise en page du produit, et il touche quatre écrans :
   brouillon, émission, devis et correction d'import. Huit colonnes de SAISIE -
   désignation, nature, chiffre d'affaires, quantité, prix, TVA, total, retrait
   - dans un conteneur qui n'en fait pas 400.

   Pourquoi un patron à part et pas `.table-cards` : celui-là empile une cellule
   par ligne, ce qui convient à de la LECTURE. Sur de la saisie, il donnerait
   huit rangées par ligne de facture, soit un formulaire interminable pour une
   facture de cinq lignes. Ici, une ligne de facture devient une FICHE à deux
   colonnes : ce qui va par paire reste par paire, et seule la désignation
   occupe toute la largeur.

   Prérequis côté gabarit : un `data-label` sur chaque `<td>`, comme pour
   `table-cards`. Une cellule sans intitulé donne un champ nu, et sur un
   formulaire c'est bien pire que sur une liste - on ne sait plus ce qu'on
   saisit.

   Le seuil est celui du CONTENEUR, pas de la fenêtre : la saisie vit dans une
   carte qui n'occupe pas toujours toute la largeur. */
@container (max-width: 40rem) {
    .line-cards thead { display: none; }
    .line-cards, .line-cards tbody, .line-cards tr { display: block; width: 100%; }

    .line-cards tr {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: .5rem .6rem;
        border: 1px solid var(--border);
        border-radius: var(--radius);
        padding: .75rem;
        margin-bottom: .75rem;
    }

    .line-cards td {
        display: block;
        border: 0;
        padding: 0;
        min-width: 0;
    }

    /* L'intitulé au-dessus du champ, et pas à côté : à deux colonnes dans une
       carte étroite, un intitulé flottant à gauche mangerait la moitié de la
       place utile du champ. */
    .line-cards td[data-label]:not([data-label=""])::before {
        content: attr(data-label);
        display: block;
        font-size: .75rem;
        font-weight: 600;
        color: var(--muted);
        margin-bottom: .2rem;
    }

    .line-cards td .form-control,
    .line-cards td .form-select { width: 100%; }

    /* La désignation prend toute la largeur : c'est le champ le plus long, et
       le seul qu'on relit pour savoir de quelle ligne il s'agit. */
    .line-cards td:first-child { grid-column: 1 / -1; }

    /* Le total se lit, il ne se saisit pas : aligné à droite comme dans un
       tableau, sur toute la largeur. */
    .line-cards td.line-total {
        grid-column: 1 / -1;
        text-align: right;
        font-weight: 700;
        padding-top: .2rem;
        border-top: 1px dashed var(--border);
    }

    /* Le bouton de retrait était le défaut le plus grave : 33 px de large,
       en huitième colonne, donc hors écran. Ici il occupe la largeur et fait
       44 px de haut - la cible minimale au doigt. */
    .line-cards td:last-child {
        grid-column: 1 / -1;
    }
    .line-cards td:last-child .btn {
        width: 100%;
        min-height: 44px;
        justify-content: center;
    }
}

/* --- La colonne d'ACTIONS reste, même quand le tableau défile ------------
   La doctrine de docs/52 dit qu'une colonne d'actions ne passe jamais derrière
   un défilement sur un écran qu'on ouvre sous alerte. Deux issues existaient :
   forcer le mode cartes - qui dénature un tableau de huit colonnes qu'on lit en
   comparant ses lignes - ou rendre la colonne collante. C'est la seconde, parce
   qu'elle garde les deux : la comparaison ET le geste.

   Mesuré le 2026-09-12 sur `/admin/pdp` : la seule action de la ligne était à
   460 px du bord droit d'un écran de 360.

   Mêmes ombres internes que pour la première colonne collante, et pour la même
   raison : `border-collapse: collapse` donne les bordures à la table, une
   cellule collante passe par-dessus et les efface. */
.table-container.table-scroll--actions > table.table > thead > tr > th:last-child,
.table-container.table-scroll--actions > table.table > tbody > tr > td:last-child {
    position: sticky; right: 0; z-index: 2;
    background: var(--surface);
    box-shadow: inset 1px 0 0 var(--border), inset 0 -1px 0 var(--border);
}
.table-container.table-scroll--actions > table.table > thead > tr > th:last-child {
    background: var(--surface-2); z-index: 3;
}

/* En mode cartes, la colonne cesse d'être une colonne : la fixer n'a plus de
   sens et casserait l'empilement. */
@container (max-width: 48rem) {
    .table-container.table-scroll--actions > table.table.table-cards > thead > tr > th:last-child,
    .table-container.table-scroll--actions > table.table.table-cards > tbody > tr > td:last-child {
        position: static;
        box-shadow: none;
    }
}

/* --- Barre du haut du hub client : tout reste, rien ne déborde ----------
   Le hub reprend `.app-topbar` sans le panneau mobile de l'application : il
   n'a nulle part où déporter ses actions, et les masquer lui ferait perdre la
   déconnexion et le changement de société - c'était le défaut B1 du relevé C.
   Il les gardait donc toutes, et à 375 px la barre faisait 459 px : la page
   entière défilait de côté, « Se déconnecter » à moitié hors champ. Mesuré le
   2026-09-13 sur la démo, avec un vrai accès au portail.

   Rien ne disparaît ici, tout se RESSERRE, et d'après la place de la barre
   elle-même : l'adresse électronique d'abord, qui ne sert qu'à se reconnaître,
   puis les libellés des deux boutons, qui gardent leur nom accessible et leur
   infobulle. Le nom de la société se tronque au lieu de pousser le reste. */
.portal-hub-topbar { container-type: inline-size; gap: .75rem; }
.portal-hub-topbar > .company-switch { min-width: 0; flex: 0 1 auto; }
.portal-hub-topbar .company-switch-menu summary { max-width: 100%; }
.portal-hub-topbar .company-switch-menu summary > span {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.portal-hub-topbar .user { flex: 0 0 auto; }

@container (max-width: 56rem) {
    .portal-hub-email { display: none; }
}

@container (max-width: 40rem) {
    .portal-hub-label {
        position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
        overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
    }
    .portal-hub-topbar .user { gap: .4rem; }
    .portal-hub-topbar .user .btn { min-width: 40px; min-height: 40px; justify-content: center; }

    /* Le menu des sociétés s'ancrait sous son bouton, lui-même au milieu de la
       barre : sa liste partait vers la droite et sortait de l'écran, même
       fermée - un `<details>` fermé garde la largeur de son contenu dans le
       calcul du défilement. Il s'ancre désormais à la barre entière, qui est
       positionnée (`sticky`), et en prend la largeur utile. */
    .portal-hub-topbar .company-switch-menu { position: static; }
    .portal-hub-topbar .company-switch-list {
        top: 100%; left: .75rem; right: .75rem;
        width: auto; min-width: 0; max-width: none;
    }
}

/* --- Liste de cases à cocher : la ligne entière est la cible ------------
   Pour choisir des éléments dans une liste, pas pour les comparer : un
   tableau n'y apportait que sa largeur minimale, celle de l'adresse la plus
   longue, et faisait défiler la liste de côté. Hauteur bornée, défilement
   vertical seulement, et l'adresse se coupe au lieu de pousser. */
.pick-list {
    list-style: none; margin: 0; padding: 0;
    max-height: 16rem; overflow-y: auto; overscroll-behavior: contain;
    border: 1px solid var(--border); border-radius: var(--radius);
}
.pick-list li + li { border-top: 1px solid var(--border); }
.pick-list label {
    display: flex; align-items: flex-start; gap: .65rem;
    padding: .6rem .75rem; min-height: 44px; cursor: pointer;
}
.pick-list input[type="checkbox"] { flex: 0 0 auto; margin-top: .2rem; }
.pick-list label > span { min-width: 0; display: flex; flex-direction: column; }
.pick-list small { font-size: .8rem; overflow-wrap: anywhere; }

/* --- Liste clé/valeur en grille : l'intitulé passe au-dessus quand la place
   manque ---------------------------------------------------------------------
   Quatorze listes portaient `grid-template-columns: max-content 1fr` en style
   inline, que rien ne pouvait redresser : à 390 px, « Chiffre d'affaires TTC »
   prenait 165 px et laissait 155 px à la valeur, quand ce n'était pas un bloc
   TVA entier avec son bouton (relevés C, B3 et G12 ; B, G17).

   La liste est son propre conteneur de requête. Elle ne peut pas changer ses
   propres pistes selon sa largeur, mais ses ENFANTS le peuvent : sous 28 rem,
   chacun occupe la ligne entière, et l'intitulé se retrouve au-dessus de sa
   valeur. */
.kv-grid {
    display: grid; grid-template-columns: max-content minmax(0, 1fr);
    container-type: inline-size;
}
.kv-grid > * { min-width: 0; overflow-wrap: anywhere; }
@container (max-width: 28rem) {
    .kv-grid > * { grid-column: 1 / -1; }
    .kv-grid > dd, .kv-grid > dt + * { margin-bottom: .35rem; }
}

/* Dans une fiche de saisie, l'intitulé se lit à gauche même quand la cellule
   aligne sa valeur à droite (`.num`) ou au centre : « Quantité » et « TVA % »
   flottaient à droite au-dessus de leur champ sur la correction d'import, vue
   pour la première fois le 2026-09-13. */
@container (max-width: 40rem) {
    .line-cards td[data-label]:not([data-label=""])::before { text-align: left; }
    .line-cards td { text-align: left; }
}

/* L'appel à l'action des pages piliers débordait de 5 px à 360 : « Créer un
   compte gratuitement » en `btn-lg` insécable dans un bandeau de 305 px. Le
   libellé passe à la ligne plutôt que de pousser la page - mesuré le
   2026-09-13 sur les trois pages piliers. */
.cta-band .btn { white-space: normal; max-width: 100%; }

/* --- Schémas de la page d'accueil : on ne montre pas un timbre-poste -----
   Mesuré le 2026-09-13 à 375 px : le schéma des étapes rend ses libellés à
   5,6 px et celui du hero à 8,7 px. Les quatre étapes sont reprises en toutes
   lettres juste dessous, et les quatre piliers du hero par le paragraphe qui
   le précède : sous la largeur où ils se lisent, les deux schémas s'effacent
   au lieu de coûter de la hauteur sans rien dire. Le seuil est celui où le
   plus petit libellé repasse au-dessus de 11 px, mesuré sur leur conteneur. */
.schema-wrap, .hero-visual { container-type: inline-size; }
@container (max-width: 40rem) {
    .schema-wrap > .schema-steps { display: none; }
}
@container (max-width: 25rem) {
    .hero-visual > .schema-hero { display: none; }
}

/* --- Nouveautés du produit (tableau de bord et administration).
   Une liste, pas des cartes : cinq entrées courtes se lisent en continu, et
   cinq encadrés dans un encadré feraient un empilement de bordures. La date
   passe devant le titre - on parcourt cette carte pour voir ce qui est récent
   avant de lire quoi. --- */
.app-news { list-style: none; margin: 0; padding: 0; }
.app-news-item + .app-news-item {
    margin-top: 1.1rem;
    padding-top: 1.1rem;
    border-top: 1px solid var(--border);
}
.app-news-date {
    display: block;
    font-size: .78rem;
    font-weight: 600;
    letter-spacing: .02em;
    text-transform: uppercase;
    color: var(--muted, #6b7280);
}
.app-news-title { margin: .2rem 0 .35rem; font-size: 1rem; font-weight: 650; }
.app-news-body { font-size: .92rem; line-height: 1.55; }
.app-news-body p { margin: 0; }
.app-news-body p + p { margin-top: .45rem; }
.app-news-body ul { margin: .35rem 0 0; padding-left: 1.2rem; }
.app-news-body a { text-decoration: underline; }
