/* ------------------------------------------------------------------ socle */

/* Palette.
 *
 * Deux partis pris, contre l'habitude.
 *
 * 1. Le fond n'est pas gris mais PAPIER — une pointe de chaleur (#F6F4F0 plutôt
 *    que #F5F5F7). C'est une nuance de deux points, invisible isolément, mais
 *    l'écran cesse d'avoir l'air d'un tableur. On facture sur du papier depuis
 *    six siècles ; l'œil le sait.
 *
 * 2. L'accent n'est pas le bleu SaaS par défaut. Ce bleu-là est celui de tout le
 *    monde — se poser dessus, c'est accepter d'être oubliable. Le pétrole
 *    profond garde le sérieux qu'exige un document opposable, sans ressembler à
 *    la centième application de gestion.
 *
 * Le rouge d'erreur reste franc : c'est la seule couleur qui doit interrompre.
 */
:root {
    color-scheme: light dark;

    --bg: #f6f4f0;
    --surface: #ffffff;
    --surface-2: #faf8f4;
    --text: #17202a;
    --text-2: #5a6572;
    --text-3: #8a94a0;
    --line: rgba(23, 32, 42, .10);
    --line-strong: rgba(23, 32, 42, .18);
    --accent: #1c4571;
    /* Le texte posé SUR l'accent. Il bascule avec le thème : en sombre
       l'accent est clair, et du blanc dessus devient illisible — d'autant que
       le survol l'éclaircit encore. */
    --sur-accent: #ffffff;
    --accent-soft: rgba(28, 69, 113, .10);
    --ok: #1d7a4a;
    --ok-soft: rgba(29, 122, 74, .10);
    --warn: #96620a;
    --warn-soft: rgba(150, 98, 10, .12);
    --err: #b6362c;
    --err-soft: rgba(182, 54, 44, .10);

    --radius: 11px;
    --radius-lg: 15px;
    --shadow: 0 1px 2px rgba(23, 32, 42, .05), 0 8px 24px rgba(23, 32, 42, .06);
    --ease: cubic-bezier(.25, .1, .25, 1);

    /* Palette catégorielle des rapports. L'ordre est fixe et ne se recycle
       jamais : la couleur suit le poste, pas son rang du moment, sinon changer
       de période repeindrait tout l'écran.
       Six teintes au plus. Elles ont été vérifiées pour rester distinguables en
       vision daltonienne — l'écart le plus faible est entre le jaune et le vert
       d'eau — et les intitulés sont toujours écrits à côté, jamais la couleur
       seule. « Autres » est gris : ce n'est pas une catégorie, c'est un reste. */
    --cat-1: #2a78d6;
    --cat-2: #eb6834;
    --cat-3: #1baf7a;
    --cat-4: #eda100;
    --cat-5: #e87ba4;
    --cat-6: #008300;
    --cat-other: #9aa4ad;
}

@media (prefers-color-scheme: dark) {
    :root {
        /* Le sombre n'est pas noir : le noir pur fait vibrer le texte clair et
           fatigue à la lecture longue. Un bleu-ardoise très sombre tient mieux
           un écran qu'on regarde une heure. */
        --bg: #0c1117;
        --surface: #141c24;
        --surface-2: #1b242e;
        --text: #edf1f5;
        --text-2: #9fadba;
        --text-3: #7d8b98;
        --line: rgba(255, 255, 255, .09);
        --line-strong: rgba(255, 255, 255, .17);
        --accent: #8ab4e8;
        --sur-accent: #0a1622;
        --accent-soft: rgba(138, 180, 232, .15);
        --ok: #4ec98a;
        --ok-soft: rgba(78, 201, 138, .14);
        --warn: #e8b45c;
        --warn-soft: rgba(232, 180, 92, .14);
        --err: #f2736a;
        --err-soft: rgba(242, 115, 106, .14);
        --shadow: 0 1px 2px rgba(0, 0, 0, .5), 0 8px 24px rgba(0, 0, 0, .4);

        /* Les mêmes six teintes, redescendues pour le fond sombre. Ce n'est pas
           une inversion automatique : chaque marche a été reprise pour tenir le
           contraste sur cette surface-là, et l'ensemble revérifié. */
        --cat-1: #3987e5;
        --cat-2: #d95926;
        --cat-3: #199e70;
        --cat-4: #c98500;
        --cat-5: #d55181;
        --cat-6: #008300;
        --cat-other: #6b7885;
    }
}

* { box-sizing: border-box; }

/* Un « display » d'auteur l'emporte sur la règle [hidden] de la feuille par
   défaut du navigateur. Sans cette ligne, .gate et .shell — tous deux en flex —
   restent visibles malgré l'attribut hidden. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI",
                 "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 15px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    letter-spacing: -.01em;
}

h1, h2, h3 { letter-spacing: -.022em; font-weight: 600; margin: 0; }
h1 { font-size: 28px; }
h2 { font-size: 20px; }
h3 { font-size: 15px; }

/* ----------------------------------------------------------- ossature */

.shell { display: flex; min-height: 100vh; }

.sidebar {
    width: 232px;
    flex: 0 0 232px;
    padding: 26px 14px;
    border-right: 1px solid var(--line);
    background: var(--surface);
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    gap: 22px;
}

/* ------------------------------------------------------------------ marque */

/* Le sigle : une feuille au coin plié, dont le « m » est formé de deux pans de
   toiture. Le document et le métier dans une seule forme, lisible à 16 px comme
   à 200 — c'est la seule contrainte qui compte pour un sigle, et celle que la
   plupart des logos d'application ratent. */
.mark { flex: 0 0 auto; display: block; }

.mark .fold { fill: rgba(255, 255, 255, .30); }
.mark .letter { stroke: #fff; stroke-width: 3.4; fill: none; stroke-linecap: round; stroke-linejoin: round; }

.brand { padding: 0 12px; display: flex; align-items: center; gap: 11px; }
.brand-text { min-width: 0; }
.brand strong {
    display: block; font-size: 16.5px; letter-spacing: -.03em; line-height: 1.15;
}
.brand strong em { font-style: normal; color: var(--accent); }
.brand span { font-size: 11.5px; color: var(--text-3); }

/* Ce qu'un comptable ne voit pas.
   ---------------------------------------------------------------------------
   Le serveur REFUSE déjà ces gestes — c'est là qu'est la sécurité, et elle y
   reste. Ici on évite seulement de proposer l'impossible : un bouton qui rend
   un refus se lit comme une panne, pas comme une règle.

   Les actions principales des en-têtes de page d'abord — « Nouvelle facture »,
   « Nouveau devis », « Enregistrer un mouvement » — puis les écrans qui n'ont
   aucun sens pour un cabinet : les réglages de l'entreprise, ses abonnements. */
body[data-role="Accountant"] .page-head .primary,
body[data-role="Accountant"] nav [data-view="settings"],
body[data-role="Accountant"] nav [data-view="modules"],

/* Les commandes qui écrivent, nommées une à une. La liste est longue et c'est
   voulu : la nommer oblige à se demander, pour chacune, si un cabinet a une
   raison de la voir. Deux y échappent — `job-attach` et `job-detach` —
   parce que rattacher une dépense à un projet est précisément son métier, et
   que le serveur l'y autorise. Les réponses aux factures reçues aussi. */
body[data-role="Accountant"] [data-act="pay"],
body[data-role="Accountant"] [data-act="settle"],
body[data-role="Accountant"] [data-act="duplicate"],
body[data-role="Accountant"] [data-act="q-accept"],
body[data-role="Accountant"] [data-act="q-refuse"],
body[data-role="Accountant"] [data-act="q-invoice"],
body[data-role="Accountant"] [data-act="q-send"],
body[data-role="Accountant"] [data-act="q-edit"],
body[data-role="Accountant"] [data-act="contract-issue"],
body[data-role="Accountant"] [data-act="contract-edit"],
body[data-role="Accountant"] [data-act="contract-delete"],
body[data-role="Accountant"] [data-act="contract-active"],
body[data-role="Accountant"] [data-act="stock-move"],
body[data-role="Accountant"] [data-act="stock-reverse"],
body[data-role="Accountant"] [data-act="retry-transmission"],
body[data-role="Accountant"] [data-act="rebuild-transmission"],
body[data-role="Accountant"] [data-act="subscribe"],
body[data-role="Accountant"] [data-act="unsubscribe"],
body[data-role="Accountant"] [data-act="activate"],
body[data-role="Accountant"] [data-act="draft-open"],
body[data-role="Accountant"] [data-act="draft-delete"],
body[data-role="Accountant"] [data-act="job-edit"],
body[data-role="Accountant"] [data-act="job-state"],
body[data-role="Accountant"] [data-act="sale-remove"] { display: none; }

/* Le bloc d'identité mène aux réglages, qui lui sont fermés. */
body[data-role="Accountant"] .identity .who { pointer-events: none; }

/* L'entreprise active. Elle se lit sans qu'on la cherche : un comptable qui a
   trente clients doit savoir chez qui il est AVANT d'agir, pas après. */
.org-switch { padding: 10px 12px 0; }
.org-switch label {
    display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: 5px;
}
.org-switch select {
    width: 100%; font-size: 13.5px; font-weight: 550; padding: 7px 9px;
    border: 1px solid var(--line); border-radius: 8px;
    background: var(--surface); color: var(--text-1);
}
.org-switch .hint { margin: 5px 0 0; font-size: 11.5px; }

/* La navigation prend la place qui reste et défile si elle en manque : c'est
   elle qui cède, jamais le bloc d'identité. Sans cela, une fenêtre courte
   poussait « Se déconnecter » sous le bord de l'écran, hors d'atteinte — la
   barre fait 100vh de haut et ne défile pas avec la page. */
nav {
    display: flex; flex-direction: column; gap: 2px;
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    /* Une barre de défilement système prendrait une gouttière permanente dans
       une colonne de 232 px qui n'a pas de place à donner. */
    scrollbar-width: thin;
}

nav button {
    display: flex; align-items: center; gap: 10px;
    /* Six pixels et non huit : les intitulés de groupe ont ajouté six lignes à
       une colonne qui en portait dix-neuf, et quatre entrées passaient sous le
       pli d'un écran de neuf cents. La barre du téléphone garde son propre
       dégagement, plus large, dans la requête média. */
    padding: 6px 12px;
    border: 0; border-radius: 8px;
    background: transparent; color: var(--text);
    font: inherit; font-size: 14px; text-align: left;
    cursor: pointer;
    transition: background .18s var(--ease);
}
nav button:hover { background: var(--line); }
nav button[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
/* Les pictogrammes héritent de currentColor : ils passent à l'accent avec
   l'onglet actif, sans règle supplémentaire. C'est ce que les émojis ne
   savaient pas faire — ils gardaient leurs couleurs et leur aspect système. */
.ic {
    width: 19px; height: 19px; flex: 0 0 auto;
    fill: none; stroke: currentColor;
    stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
    opacity: .9;
}
nav button[aria-current="page"] .ic { opacity: 1; }
.sprite { display: none; }

.identity {
    margin-top: auto;
    padding: 12px;
    border-radius: var(--radius);
    background: var(--surface-2);
    border: 1px solid var(--line);
    font-size: 12px;
}
.identity .who {
    font-weight: 600; color: var(--text);
    display: block; width: 100%; text-align: left;
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; font-weight: 600; font-size: 12px; cursor: pointer;
}
.identity .who:hover { color: var(--accent); }
.identity .siren { color: var(--text-3); font-variant-numeric: tabular-nums; }
.identity button {
    margin-top: 8px; width: 100%;
    background: transparent; border: 1px solid var(--line-strong);
    border-radius: 8px; padding: 5px; font: inherit; font-size: 12px;
    color: var(--text-2); cursor: pointer;
    transition: border-color .18s var(--ease), color .18s var(--ease);
}
.identity button:hover { border-color: var(--accent); color: var(--accent); }

/* Le contenu est centré dans la place restante, pas collé à la barre latérale.
   La largeur maximale reste bornée — au-delà d'environ 1400 px, un tableau
   s'étire et l'œil perd la ligne entre deux colonnes éloignées. Mais le laisser
   à gauche avec 300 px de vide à droite donnait l'impression d'une page mal
   finie plutôt que d'une mesure de confort de lecture. */
main {
    flex: 1;
    width: 100%;
    max-width: 1400px;
    margin-inline: auto;
    padding: 40px 44px 72px;
}

.page-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 20px; margin-bottom: 26px;
}
.page-head p { margin: 6px 0 0; color: var(--text-2); font-size: 14px; max-width: 62ch; }
.head-actions { display: flex; gap: 10px; flex: 0 0 auto; }

/* ------------------------------------------------- tableau de bord réarrangeable */

/* La grille est en quarts : un indicateur en occupe un, une liste deux, un
   graphique les quatre. Les widgets déclarent leur encombrement, la grille ne
   sait rien d'eux. */
/* Les cartes d'une même rangée s'alignent sur la plus haute : quatre
   indicateurs de hauteurs différentes donnent un peigne, et le regard s'arrête
   sur l'irrégularité plutôt que sur les chiffres. */
.board {
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 16px; align-items: stretch;
}
.board > .w-petit { grid-column: span 1; }
.board > .w-moyen { grid-column: span 2; }
.board > .w-grand { grid-column: span 4; }

/* Le widget est un conteneur transparent : ce qu'il enveloppe porte déjà sa
   propre carte. Seule la personnalisation lui donne un cadre. */
.widget { position: relative; min-width: 0; }
.widget > .card, .widget > .kpi { margin: 0; height: 100%; }

/* --- mode personnalisation --- */

.board.editing .widget {
    user-select: none; border-radius: var(--radius-lg);
    outline: 1px dashed var(--line-strong); outline-offset: 3px;
}
.board.editing .widget:hover { outline-color: var(--accent); }

/* Les cartes deviennent inertes : en personnalisation, un clic ne doit pas
   ouvrir le détail de l'indicateur qu'on est en train de manipuler. Seules la
   poignée et la croix restent vivantes. */
.board.editing .widget * { pointer-events: none; }
.board.editing .widget .w-remove,
.board.editing .widget .w-grip { pointer-events: auto; }

/* La poignée, et non la carte entière. Sur un téléphone, rendre toute la carte
   saisissable impose « touch-action: none » sur toute sa surface — et le doigt
   ne fait alors plus défiler la page : on ne peut plus atteindre les cartes du
   bas, précisément quand on cherche à les réorganiser. */
.w-grip {
    position: absolute; top: -9px; right: -9px; z-index: 3;
    width: 30px; height: 30px; padding: 0; border-radius: 50%;
    display: none; align-items: center; justify-content: center;
    background: var(--surface); color: var(--text-2);
    border: 1px solid var(--line-strong);
    cursor: grab; touch-action: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .10);
}
.board.editing .w-grip { display: flex; }
.w-grip:hover { border-color: var(--accent); color: var(--accent); }
.w-grip svg { width: 15px; height: 15px; }

.w-remove {
    position: absolute; top: -9px; left: -9px; z-index: 3;
    width: 26px; height: 26px; padding: 0; border-radius: 50%;
    display: none; align-items: center; justify-content: center;
    background: var(--err); color: #fff; border: 2px solid var(--surface);
    font-size: 17px; line-height: 1; cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.board.editing .w-remove { display: flex; }
.w-remove:hover { transform: scale(1.12); }

/* La carte saisie suit le doigt ; les autres glissent à leur nouvelle place. */
.widget.dragging {
    opacity: .92; z-index: 5; cursor: grabbing;
    transform: scale(1.03); box-shadow: 0 18px 40px rgba(0, 0, 0, .18);
    outline-color: var(--accent); outline-style: solid;
}
.widget.sliding { transition: transform .22s cubic-bezier(.2, .9, .3, 1); }

/* L'emplacement libéré, pour que la grille ne se referme pas sous le doigt. */
.widget.ghost { opacity: 0; }

.board-bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
    background: var(--accent-soft); border: 1px solid var(--accent);
    border-radius: var(--radius-lg); padding: 12px 16px; margin-bottom: 18px;
}

.board-add { margin-top: 18px; }
.board-choices { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }

.board-choice {
    text-align: left; padding: 14px 16px; border-radius: var(--radius);
    border: 1px solid var(--line-strong); background: var(--surface);
    cursor: pointer; display: block; width: 100%;
}
.board-choice:hover { border-color: var(--accent); background: var(--accent-soft); }
.board-choice strong { display: block; font-size: 13.5px; margin-bottom: 3px; }
.board-choice span { font-size: 12px; color: var(--text-2); line-height: 1.4; }

/* Le déplacement au doigt n'a pas de sens sans retour visuel ; il en a encore
   moins pour qui a demandé moins d'animations. */
@media (prefers-reduced-motion: reduce) {
    .widget.sliding { transition: none; }
    .widget.dragging { transform: none; }
    .w-remove:hover { transform: none; }
}

/* -------------------------------------------------------------- composants */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 24px;
    box-shadow: var(--shadow);
}
.card + .card { margin-top: 18px; }
.card > h2 { margin-bottom: 4px; }
.card > .sub { color: var(--text-2); font-size: 13px; margin: 0 0 18px; }

button.primary {
    /* Deux points d'écart entre les deux arrêts : assez pour que le bouton ait
       un volume, pas assez pour ressembler aux dégradés des années 2000. */
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 92%, #fff) 0%, var(--accent) 100%);
    color: var(--sur-accent);
    border: 0; border-radius: 980px;
    padding: 11px 24px; font: inherit; font-size: 14px; font-weight: 500;
    cursor: pointer;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent),
                0 4px 14px color-mix(in srgb, var(--accent) 22%, transparent);
    transition: filter .18s var(--ease), transform .12s var(--ease), box-shadow .18s var(--ease);
}
button.primary:hover {
    /* Le fond change, et lui seul. « filter: brightness » agissait sur tout le
       bouton, texte compris : en thème sombre il éclaircissait un fond déjà
       clair sous un texte blanc, et le libellé disparaissait. */
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 84%, #fff) 0%,
        color-mix(in srgb, var(--accent) 92%, #fff) 100%);
    box-shadow: 0 2px 4px color-mix(in srgb, var(--accent) 40%, transparent),
                0 8px 22px color-mix(in srgb, var(--accent) 28%, transparent);
}
button.primary:active { transform: scale(.97); }
button.primary:disabled { opacity: .45; cursor: default; transform: none; filter: none; }

button.ghost {
    background: transparent; color: var(--accent);
    border: 1px solid var(--line-strong); border-radius: 980px;
    padding: 8px 18px; font: inherit; font-size: 13px; cursor: pointer;
    transition: border-color .18s var(--ease), background .18s var(--ease);
}
button.ghost:hover { border-color: var(--accent); background: var(--accent-soft); }

/* La destruction ne partage jamais la couleur de l'action courante : l'œil doit
   pouvoir distinguer « retirer » d'« enregistrer » sans lire. */
/* Une action principale qui DÉTRUIT ne se peint pas de la couleur d'accent :
   celle-ci dit « continuez », et le geste dit l'inverse. La confirmation en est
   le seul usage, et c'est là qu'un coup d'œil doit suffire. */
button.primary.danger { background: var(--err); }
button.primary.danger:hover { filter: brightness(1.08); }

button.ghost.danger { color: var(--err); }
button.ghost.danger:hover { border-color: var(--err); background: var(--err-soft); }

.grid { display: grid; gap: 14px; }
.grid.two { grid-template-columns: 1fr 1fr; }
.grid.three { grid-template-columns: repeat(3, 1fr); }

label { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 5px; font-weight: 500; }

input, select, textarea {
    width: 100%;
    padding: 10px 13px;
    border: 1px solid var(--line-strong);
    border-radius: 11px;
    background: var(--surface-2);
    color: var(--text);
    font: inherit; font-size: 14px;
    transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background .18s var(--ease);
}
input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover { border-color: var(--text-3); }
input:focus, select:focus, textarea:focus {
    outline: 0;
    border-color: var(--accent);
    background: var(--surface);
    box-shadow: 0 0 0 3.5px var(--accent-soft);
}
input:disabled {
    color: var(--text-3); cursor: not-allowed;
    background: color-mix(in srgb, var(--line) 40%, transparent);
}
input[type="number"] { font-variant-numeric: tabular-nums; }

/* Les listes déroulantes natives sont laides et différentes sur chaque système.
   On retire l'habillage du navigateur et on dessine le chevron nous-mêmes — en
   gardant le <select> réel, donc le clavier, le lecteur d'écran et la roue
   native du téléphone, qu'aucun composant maison ne remplace correctement. */
select {
    appearance: none; -webkit-appearance: none;
    padding-right: 34px;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%238a94a0' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 13px center;
    background-size: 11px;
    cursor: pointer;
}
@media (prefers-color-scheme: dark) {
    select {
        background-image:
            url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%237d8b98' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    }
}
select:focus { background-color: var(--surface); }

/* Le sélecteur de date : l'icône du navigateur est grise, minuscule et
   inamovible. On la remplace par la nôtre, aux couleurs de l'interface, et on
   étend la zone cliquable à tout le champ. */
input[type="date"] { position: relative; cursor: pointer; }
input[type="date"]::-webkit-calendar-picker-indicator {
    position: absolute; inset: 0; width: auto; height: auto;
    margin: 0; padding: 0; opacity: 0; cursor: pointer;
}
input[type="date"] {
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2.5' y='4' width='15' height='13.5' rx='2.5' fill='none' stroke='%238a94a0' stroke-width='1.6'/%3E%3Cpath d='M2.5 8.5h15M6.5 2.5v3M13.5 2.5v3' stroke='%238a94a0' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 17px;
    padding-right: 38px;
}
@media (prefers-color-scheme: dark) {
    input[type="date"] {
        background-image:
            url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Crect x='2.5' y='4' width='15' height='13.5' rx='2.5' fill='none' stroke='%237d8b98' stroke-width='1.6'/%3E%3Cpath d='M2.5 8.5h15M6.5 2.5v3M13.5 2.5v3' stroke='%237d8b98' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    }
}

.hint { font-size: 11.5px; color: var(--text-3); margin-top: 5px; }

/* --------------------------------------------------------- tableau de bord */

.kpis {
    display: grid;
    /* auto-fit + minmax : la grille choisit son nombre de colonnes selon la
       place disponible. Aucun point de rupture à maintenir — quatre cartes sur
       un écran large, deux sur une tablette, une sur un téléphone. */
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
}

/* La carte est un bouton : elle mène au détail. Le curseur, le survol et le
   focus doivent l'annoncer — un chiffre cliquable qui ne se signale pas ne sera
   jamais cliqué. */
.kpi {
    display: block; width: 100%; text-align: left;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 20px;
    box-shadow: var(--shadow);
    font: inherit; color: inherit; cursor: pointer;
    transition: transform .18s var(--ease), border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.kpi:hover { transform: translateY(-2px); border-color: var(--line-strong); }
.kpi:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.kpi:active { transform: translateY(0); }
.kpi.alert { border-color: color-mix(in srgb, var(--err) 40%, var(--line)); }

.kpi-label {
    display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
    font-size: 12px; color: var(--text-2); font-weight: 500;
}
/* L'invite n'apparaît qu'au survol : présente en permanence, elle ferait du
   bruit sur quatre cartes à la fois. */
.kpi-more { color: var(--accent); font-size: 11px; opacity: 0; transition: opacity .18s var(--ease); }
.kpi:hover .kpi-more, .kpi:focus-visible .kpi-more { opacity: 1; }

.spark-wrap { margin: 2px 0 8px; }
.spark { width: 100%; height: 34px; display: block; }

/* Les extrêmes de la série, sous la courbe. L'échelle épouse les données pour
   que la variation se voie ; sans ces deux bornes, le même dessin décrirait
   aussi bien un écart de mille euros que de dix. */
/* Une seule étendue, alignée à droite sous la fin de la courbe. Elle était
   étalée aux deux bords — minimum à gauche, maximum à droite — ce qui laissait
   croire que chacun tombait à ce moment-là de l'année. */
.spark-scale {
    display: flex; justify-content: flex-end;
    font-size: 10px; color: var(--text-3);
    font-variant-numeric: tabular-nums; margin-top: 1px;
}
.kpi-value {
    font-size: 30px; font-weight: 600; letter-spacing: -.01em;
    margin: 8px 0 6px; font-variant-numeric: tabular-nums;
    /* Les chiffres ne doivent jamais déborder de leur carte sur un petit écran. */
    overflow-wrap: anywhere;
}
.kpi.alert .kpi-value { color: var(--err); }
.kpi-foot {
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    font-size: 12px; color: var(--text-3);
}
.kpi-trend {
    font-weight: 600; padding: 2px 7px; border-radius: 980px;
    font-variant-numeric: tabular-nums;
}
.kpi-trend.ok { background: var(--ok-soft); color: var(--ok); }
.kpi-trend.err { background: var(--err-soft); color: var(--err); }

/* Sur grand écran seulement : le bouton « Plus » n'a aucune raison d'exister
   quand tous les onglets tiennent dans la colonne latérale. */
.only-mobile { display: none; }

/* --------------------------------------------------------- feuille « Plus »

   Elle porte douze vues, le compte et la déconnexion. Deux colonnes plutôt
   qu'une liste : douze lignes identiques obligent à lire chaque libellé, là où
   une grille se balaie du regard — et la feuille tient sans défilement, ce qui
   compte quand on la consulte d'une main. */
#moreList {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
}
/* Icône et libellé sur une ligne. Empilés, douze entrées faisaient une feuille
   plus haute que l'écran, et le pied — donc la déconnexion — passait dessous. */
#moreList button {
    display: flex; align-items: center; gap: 10px;
    padding: 0 12px; border: 1px solid var(--line); border-radius: 12px;
    background: var(--surface-2); color: var(--text);
    font: inherit; font-size: 14px; text-align: left; cursor: pointer;
    /* 52 px : au-dessus des 44 px que réclame un pouce, et six rangées tiennent
       dans la moitié basse de l'écran. */
    min-height: 52px;
    transition: border-color .18s var(--ease), color .18s var(--ease);
}
#moreList button span {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#moreList button:hover { border-color: var(--line-strong); }
#moreList button[aria-current="page"] {
    border-color: var(--accent); color: var(--accent); background: var(--accent-soft);
}
#moreList .ic { width: 21px; height: 21px; }

/* Le compte, en tête : c'est ce qu'on vient vérifier avant de se déconnecter,
   et la colonne latérale qui le porte n'existe pas sur téléphone. */
.sheet-id {
    display: flex; align-items: center; gap: 12px; width: 100%;
    padding: 12px; margin-bottom: 14px;
    border: 1px solid var(--line); border-radius: 12px;
    background: var(--surface-2); color: var(--text);
    font: inherit; text-align: left; cursor: pointer;
}
.sheet-id:hover { border-color: var(--accent); }
.sheet-avatar {
    flex: 0 0 auto;
    width: 38px; height: 38px; border-radius: 50%;
    display: grid; place-items: center;
    background: var(--accent); color: var(--sur-accent);
    font-size: 14px; font-weight: 600; letter-spacing: .02em;
}
.sheet-id-text { min-width: 0; display: block; }
.sheet-id-text strong { display: block; font-size: 14.5px; }
/* Une adresse longue doit se couper, jamais élargir la feuille. */
.sheet-id-text span {
    display: block; font-size: 12px; color: var(--text-3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sheet-id .ic { margin-left: auto; color: var(--text-3); }

/* La poignée n'a d'usage que sur téléphone, où la feuille monte du bas. */
.sheet-grip { display: none; }

.card-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
}
.card-head .sub { margin-bottom: 0; }

/* Sélecteur segmenté : trois choix exclusifs, sans le poids d'une liste
   déroulante qu'il faudrait ouvrir pour voir ce qu'elle contient. */
.segmented {
    display: inline-flex; padding: 2px; gap: 2px;
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: 980px; flex: 0 0 auto;
}
.segmented button {
    border: 0; border-radius: 980px; padding: 6px 14px;
    background: transparent; color: var(--text-2);
    font: inherit; font-size: 12.5px; cursor: pointer;
    transition: background .18s var(--ease), color .18s var(--ease);
}
.segmented button:hover { color: var(--text); }
.segmented button[aria-pressed="true"] {
    background: var(--surface); color: var(--text); font-weight: 500;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .12);
}

/* En thème sombre, --surface est plus SOMBRE que --surface-2 : le segment actif
   s'enfonçait au lieu de ressortir. On l'éclaire au lieu de le creuser — la
   même règle ne peut pas servir dans les deux sens. */
@media (prefers-color-scheme: dark) {
    .segmented button[aria-pressed="true"] {
        background: rgba(255, 255, 255, .12);
        box-shadow: none;
    }
}

/* ------------------------------------------------------------------ rapports */

.report-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 18px; }

.donut { width: 100%; max-width: 300px; height: auto; display: block; margin: 0 auto; }
/* L'anneau s'éteint sauf là où l'on pointe : c'est ce qui permet de suivre un
   secteur étroit du regard jusqu'à son libellé. */
.donut-slice { transition: opacity .15s var(--ease); cursor: pointer; }
/* Éteint par le JAVASCRIPT et non par `:hover`, parce que le survol peut venir
   de la liste à côté — et un sélecteur CSS ne franchit pas cette distance. */
.donut-slice.donut-eteint { opacity: .28; }

/* Le centre LIT le secteur survolé.

   Une bulle flottante suivait le curseur et recouvrait les secteurs voisins —
   on masquait ce qu'on cherchait à comparer. Le centre est un endroit calme,
   toujours au même point, et il ne servait qu'à répéter un total déjà écrit en
   gros au-dessus.

   C'est ce qui rend un anneau lisible : on ne compare plus des angles, ce que
   l'œil fait le plus mal — on pointe et on lit. */
.donut-libelle {
    font-size: 7.5px;
    fill: var(--text-2);
}

.donut-montant {
    font-size: 13px;
    font-weight: 600;
    fill: var(--text);
    font-variant-numeric: tabular-nums;
}

.donut-part {
    font-size: 7.5px;
    fill: var(--text-3);
    font-variant-numeric: tabular-nums;
}

/* Aucune transition sur les trois : un texte qui se fondrait à chaque
   déplacement du curseur donnerait un scintillement au lieu d'une lecture. */

/* Le classement chiffré à côté de l'anneau. Il n'est pas décoratif : trois des
   six teintes passent sous le seuil de contraste sur fond clair, et c'est cette
   liste — intitulé, montant, part — qui rend le rapport lisible sans la
   couleur. La retirer casserait l'accessibilité du graphe. */
.report-list { display: flex; flex-direction: column; gap: 2px; }
.report-row {
    display: grid; grid-template-columns: 12px minmax(0, 1fr) auto;
    align-items: center; gap: 10px; padding: 9px 4px;
    border-bottom: 1px solid var(--line); font-size: 13px;
}
.report-row:last-child { border-bottom: 0; }
.report-swatch { width: 12px; height: 12px; border-radius: 3px; }
.report-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.report-amount { font-variant-numeric: tabular-nums; font-weight: 600; }
.report-share { color: var(--text-3); font-size: 11.5px; margin-left: 6px; font-weight: 400; }

.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 14px; font-size: 12px; }
.legend span { display: inline-flex; align-items: center; gap: 6px; color: var(--text-2); }
.legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }

.stack { cursor: pointer; }
.stack .stack-hit { fill: transparent; }
.stack:hover .stack-seg { filter: brightness(1.12); }

@media (max-width: 768px) {
    .report-split { grid-template-columns: 1fr; }
}

/* Affectation d'un achat à un projet, dans la ligne de réception. Compacte :
   elle occupe une colonne d'un tableau déjà large, et ne doit pas repousser le
   montant hors de l'écran. */
.job-picker {
    font-size: 12px; padding: 5px 26px 5px 9px;
    max-width: 190px; background-position: right 8px center;
}

.chart-wrap { position: relative; }
.chart { width: 100%; height: auto; display: block; overflow: visible; }
/* Les unités du viewBox, pas des pixels : le SVG est mis à l'échelle, ce corps
   de 10,5 tombe à 5 px réels sur un téléphone. La règle mobile le compense. */
.chart-axis { font-size: 10.5px; fill: var(--text-3); font-family: inherit; }
.chart-axis.now { fill: var(--accent); font-weight: 600; }

/* L'infobulle suit le curseur : sur douze barres serrées, une bulle ancrée
   recouvrirait ses voisines. pointer-events none, sinon elle se placerait sous
   le curseur et déclencherait son propre masquage. */
.chart-tip {
    position: absolute; transform: translate(-50%, -100%);
    background: var(--text); color: var(--bg);
    padding: 8px 12px; border-radius: 10px;
    font-size: 12px; line-height: 1.45; white-space: nowrap;
    pointer-events: none; z-index: 5; box-shadow: var(--shadow);
    display: flex; flex-direction: column;
}
.chart-tip strong { font-size: 12.5px; }
.chart-tip span { opacity: .8; }

dialog.wide { max-width: 780px; }
dialog .sub { color: var(--text-2); font-size: 13px; margin: 0 0 16px; }
#drillBody { max-height: 58vh; overflow-y: auto; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 18px; }
.split > .card + .card { margin-top: 0; }

.bar-row + .bar-row { margin-top: 14px; }
.bar-head { display: flex; justify-content: space-between; gap: 12px; font-size: 13.5px; }
.bar-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-value { font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }
.bar-track {
    height: 7px; border-radius: 980px; background: var(--line);
    margin: 6px 0 4px; overflow: hidden;
}
.bar-fill {
    height: 100%; border-radius: 980px; background: var(--accent);
    animation: grow .5s var(--ease);
}
@keyframes grow { from { width: 0 !important; } }
.bar-sub { font-size: 11.5px; color: var(--text-3); }

/* ---------------------------------------------------------------- modules */

.modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; }

.module {
    display: flex; flex-direction: column;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2); padding: 18px;
    transition: border-color .18s var(--ease);
}
.module.on { border-color: color-mix(in srgb, var(--ok) 45%, var(--line)); }

.module-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.module-head h3 { font-size: 15px; }
.module-purpose { color: var(--text-2); font-size: 13px; margin: 8px 0 16px; flex: 1; }
.module-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.module-price { font-size: 15px; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ lignes */

.lines { display: flex; flex-direction: column; gap: 10px; }

.line {
    display: grid;
    /* Les colonnes de listes déroulantes sont plus larges depuis que le chevron
       est dessiné : il occupe 34 px que le texte n'a plus. « Service » y était
       tronqué en « Servic ». */
    grid-template-columns: 1fr 86px 104px 116px 98px 112px 30px;
    gap: 8px; align-items: end;
    padding: 12px; border-radius: var(--radius);
    background: var(--surface-2); border: 1px solid var(--line);
    animation: rise .28s var(--ease);
}
@keyframes rise { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

.line .rm {
    background: transparent; border: 0; color: var(--text-3);
    font-size: 18px; line-height: 1; cursor: pointer; padding: 8px 0;
    border-radius: 8px; transition: color .18s var(--ease);
}
.line .rm:hover { color: var(--err); }

/* La remise de document reprend l'habillage d'une ligne, mais pas ses colonnes :
   celles-ci sont taillées pour une désignation, une quantité et un prix. Le
   motif y héritait de 86 px pendant que « Remise / Frais » occupait la colonne
   large — l'inverse de ce qu'on lit. */
.line.adjustment { grid-template-columns: 116px 1fr 104px 116px 116px 30px; }

/* Huit colonnes pour une pension : la désignation cède ce qu'il faut au
   pensionnaire, et les listes déroulantes se resserrent d'une dizaine de pixels.

   Les valeurs sont mesurées sur les libellés les plus longs de chaque menu —
   « Franchise en base » pour la TVA, « Main-d'œuvre » pour la nature. En
   dessous, le chevron mange le texte et « Franchise » se lit « Franchi ». */
.line--pension {
    grid-template-columns: 1fr 76px 96px 108px 104px 108px 112px 30px;
}

/* Un brouillon reprend l'habillage d'une ligne, sans ses colonnes : il porte un
   libellé de longueur libre et deux boutons, pas une grille de saisie. */
.draft-row {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    padding: 12px; border-radius: var(--radius);
    background: var(--surface-2); border: 1px solid var(--line);
}
.draft-row b { font-size: 13.5px; }

/* Celui qu'on modifie. Le liseré suffit : une couleur de fond ferait passer un
   brouillon pour un état d'alerte, alors que c'est l'état normal du travail. */
.draft-row.courant { border-color: var(--accent); }

/* Une ligne et ses remises forment un bloc : le trait vertical et le retrait
   disent à quoi la remise se rattache. Posées à plat dans la même liste, on ne
   saurait plus si « −50 € » s'applique à la ligne du dessus ou à la facture. */
.line-block { display: flex; flex-direction: column; gap: 6px; }

.subline {
    display: grid;
    grid-template-columns: 116px 1fr 104px 116px 30px;
    gap: 8px; align-items: end;
    margin-left: 22px; padding: 10px 12px;
    border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong, var(--line));
    animation: rise .28s var(--ease);
}

.subline .rm {
    background: transparent; border: 0; color: var(--text-3);
    font-size: 18px; line-height: 1; cursor: pointer; padding: 8px 0;
    border-radius: 8px; transition: color .18s var(--ease);
}
.subline .rm:hover { color: var(--err); }

/* Discret par nature : la remise de ligne est l'exception, pas la règle. Un
   bouton pleine taille sous chaque ligne encombrerait la saisie courante. */
.add-subline {
    align-self: flex-start; margin-left: 22px;
    background: transparent; border: 0; padding: 2px 0;
    color: var(--text-3); font-size: 12.5px; cursor: pointer;
    transition: color .18s var(--ease);
}
.add-subline:hover { color: var(--brand, var(--text)); }

/* Le détail de la prestation (BT-154), sous sa ligne.

   Aligné sur le retrait des remises : ce qui est en retrait appartient à la
   ligne du dessus. Sa zone de texte grandit avec le propos plutôt que de le
   faire défiler dans trois lignes fixes. */
.line-detail { display: flex; flex-direction: column; margin-left: 22px; }

.line-detail .detail-toggle {
    align-self: flex-start;
    background: transparent; border: 0; padding: 2px 0;
    color: var(--text-3); font-size: 12.5px; cursor: pointer;
    transition: color .18s var(--ease);
}
.line-detail .detail-toggle:hover { color: var(--brand, var(--text)); }

.line-detail textarea {
    width: 100%; min-height: 66px; resize: vertical;
    padding: 10px 12px; border-radius: var(--radius);
    background: var(--surface); border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong, var(--line));
    color: var(--text); font: inherit; font-size: 13.5px; line-height: 1.45;
    animation: rise .28s var(--ease);
}
.line-detail textarea::placeholder { color: var(--text-3); }

/* Le montant impayé dans le pied de liste : c'est le chiffre qu'on vient
   chercher, il ne doit pas se fondre dans le décompte. */
.pager-unpaid { color: var(--err); font-weight: 600; }

/* Le sélecteur de lignes, dans le pied de liste : discret, aligné sur les
   boutons de navigation. */
.pager-size {
    display: inline-flex; align-items: center; gap: 6px;
    color: var(--text-3); font-size: 12.5px; white-space: nowrap;
}
.pager-size select { padding: 4px 8px; font-size: 12.5px; width: auto; }

/* --------------------------------------------------- volets des réglages */

/* Les onglets reprennent le composant « segmented » déjà employé pour la nature
   d'un client : même geste, même apparence, rien de neuf à apprendre. */
#settingsTabs { margin-bottom: 18px; flex-wrap: wrap; }

.settings-pane { animation: rise .22s var(--ease); }

/* L'apparence et son aperçu, côte à côte tant que la place le permet. */
.brand-edit {
    display: grid; gap: 24px; margin-top: 16px;
    grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
    align-items: start;
}

.brand-fields { min-width: 0; }

.brand-preview { min-width: 0; position: sticky; top: 12px; }

.preview-label {
    font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: 8px;
}

/* La feuille : assez de la facture pour juger d'une couleur, pas assez pour
   prétendre la remplacer. */
.preview-sheet {
    background: #fff; color: #1d1d1f;
    border: 1px solid var(--line); border-radius: var(--radius);
    padding: 16px; font-size: 9px; line-height: 1.45;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}

.preview-head { display: flex; justify-content: space-between; gap: 12px; }
.preview-issuer { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.preview-issuer img { height: 22px; max-width: 120px; object-fit: contain; margin-bottom: 3px; }
.preview-issuer strong { font-size: 10px; }
.preview-issuer span { color: #6e6e73; font-size: 8px; }

.preview-title { text-align: right; white-space: nowrap; }
.preview-title b { display: block; font-size: 15px; letter-spacing: -.01em; }
.preview-title span { color: #6e6e73; font-size: 8px; }

.preview-rule { border-top: 1px solid #e5e5e7; margin: 12px 0 8px; }

.preview-lines,
.preview-total { display: flex; justify-content: space-between; gap: 12px; }
.preview-lines { color: #1d1d1f; padding-bottom: 8px; border-bottom: 1px solid #f0f0f2; }
.preview-total { margin-top: 10px; font-size: 10px; }

.preview-foot {
    display: flex; flex-direction: column; gap: 2px;
    margin-top: 14px; padding-top: 8px; border-top: 1px solid #e5e5e7;
    color: #86868b; font-size: 7px;
}

/* La feuille reste blanche en thème sombre : c'est du papier, et une facture
   n'a pas de mode nuit. Seul son cadre s'accorde à l'interface. */
@media (max-width: 900px) {
    .brand-edit { grid-template-columns: 1fr; }
    .brand-preview { position: static; }
}

/* ------------------------------------------------------------------ totaux */

.totals { border-top: 1px solid var(--line); margin-top: 18px; padding-top: 16px; }
.totals .row {
    display: flex; justify-content: space-between; align-items: baseline;
    padding: 5px 0; font-size: 14px;
}
.totals .row span:last-child { font-variant-numeric: tabular-nums; }
.totals .row.grand {
    border-top: 1px solid var(--line); margin-top: 8px; padding-top: 12px;
    font-size: 20px; font-weight: 600; letter-spacing: -.02em;
}
.totals .breakdown { font-size: 12.5px; color: var(--text-2); }

/* ------------------------------------------------------------------ tables */

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
    text-align: left; font-size: 11px; text-transform: uppercase;
    letter-spacing: .05em; color: var(--text-3); font-weight: 600;
    padding: 0 10px 10px; border-bottom: 1px solid var(--line);
}
td { padding: 13px 10px; border-bottom: 1px solid var(--line); }
tbody tr { transition: background .15s var(--ease); }
tbody tr:hover { background: var(--surface-2); }
td.num { font-variant-numeric: tabular-nums; text-align: right; }
td.mono { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; font-size: 12.5px; }

/* ------------------------------------------------------------------ badges */

.badge {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 9px; border-radius: 980px;
    font-size: 11.5px; font-weight: 500; white-space: nowrap;
}
.badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.err { background: var(--err-soft); color: var(--err); }
.badge.neutral { background: var(--line); color: var(--text-2); }

/* ------------------------------------------------- actions d'une ligne */

/* UNE action visible par ligne — celle qui vient ensuite — et le reste sous un
   menu.

   Vingt-cinq lignes portant chacune trois à cinq boutons faisaient jusqu'à
   soixante-quinze commandes à l'écran, toutes au même poids visuel : rien
   n'était mis en avant, et le regard ne se posait plus sur les montants, qui
   sont pourtant ce qu'on vient lire. Les largeurs variables déchiquetaient en
   prime la colonne. */
.row-actions {
    display: inline-flex; align-items: center; gap: 6px;
    justify-content: flex-end; white-space: nowrap;
}

.row-actions .primary,
.row-actions .ghost { padding: 5px 12px; font-size: 12px; }

/* L'action principale garde une largeur minimale : « Envoyer », « Facturer » et
   « Encaisser » ne font pas la même longueur, et sans elle la colonne danse
   d'une ligne à l'autre. */
.row-actions .row-main { min-width: 92px; text-align: center; }

.row-menu { position: relative; display: inline-flex; }

.row-menu > summary {
    list-style: none; cursor: pointer;
    padding: 5px 10px; border-radius: 8px;
    border: 1px solid var(--line); color: var(--text-3);
    font-size: 13px; line-height: 1.1;
    transition: background .18s var(--ease), color .18s var(--ease);
}
.row-menu > summary::-webkit-details-marker { display: none; }
.row-menu > summary:hover { background: var(--line); color: var(--text); }

.row-menu-items {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
    min-width: 190px; padding: 6px;
    display: flex; flex-direction: column; gap: 2px;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: 0 10px 30px rgba(0, 0, 0, .18);
    animation: rise .16s var(--ease);
}

.row-menu-items a,
.row-menu-items button {
    display: block; width: 100%; text-align: left;
    padding: 8px 10px; border: 0; border-radius: 8px;
    background: transparent; color: var(--text);
    font: inherit; font-size: 13px; text-decoration: none; cursor: pointer;
}
.row-menu-items a:hover,
.row-menu-items button:hover { background: var(--line); }

/* Les corrections — avoir, annulation, suppression — se distinguent : elles
   sont rares, lourdes, et ne doivent pas se cliquer par inadvertance. */
.row-menu-items .danger { color: var(--err); }
.row-menu-items hr { border: 0; border-top: 1px solid var(--line); margin: 4px 2px; }

/* La ligne entière ouvre le détail : c'est le geste que tout le monde essaie
   d'abord, et il ne faisait rien. */
tbody tr.openable { cursor: pointer; }
tbody tr.openable:hover { background: var(--line); }

@media (max-width: 900px) {
    /* Chaque ligne est déjà une carte : l'action principale prend la largeur,
       le menu reste à côté. */
    .row-actions { display: flex; width: 100%; }
    .row-actions .row-main { flex: 1; min-width: 0; padding: 9px 12px; }
}

/* --------------------------------------------------------------- violations */

.violation {
    display: flex; gap: 12px; align-items: flex-start;
    padding: 12px 14px; border-radius: var(--radius);
    margin-top: 8px; font-size: 13.5px;
}
.violation.err { background: var(--err-soft); }
.violation.warn { background: var(--warn-soft); }
.violation .rule {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 11.5px; font-weight: 600; padding-top: 1px; white-space: nowrap;
}
.violation.err .rule { color: var(--err); }
.violation.warn .rule { color: var(--warn); }
.violation .path { color: var(--text-3); font-size: 11.5px; }

/* ---------------------------------------------------------------- résultat */

.result { text-align: center; padding: 34px 24px; animation: pop .35s var(--ease); }
@keyframes pop { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
.result .mark { font-size: 42px; line-height: 1; }
.result .number {
    font-size: 30px; font-weight: 600; letter-spacing: -.02em;
    margin: 14px 0 4px; font-variant-numeric: tabular-nums;
}
.result .meta { color: var(--text-2); font-size: 13px; }
.result .hash {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 11.5px; color: var(--text-3); margin-top: 12px; word-break: break-all;
}
.result .actions { margin-top: 22px; display: flex; gap: 10px; justify-content: center; }

/* ------------------------------------------------------------------ divers */

.empty { text-align: center; padding: 48px 20px; color: var(--text-3); font-size: 14px; }
.empty .mark { font-size: 30px; opacity: .5; display: block; margin-bottom: 10px; }

/* Le message flotte dans la couche supérieure (popover), et non dans le flux :
   une boîte de dialogue ouverte avec showModal() y monte aussi, et aucun
   z-index ne passe devant elle. Sans cela, « Renseignez le montant » s'affiche
   derrière le voile de la modale qui l'a provoqué — invisible, au moment précis
   où il compte. Les règles ci-dessous écrasent la mise en forme par défaut du
   navigateur pour un popover (bordure, fond, centrage). */
/* Un motif de refus tient en deux lignes dans un tableau : au-delà il pousse
   la colonne et déforme la ligne entière. Le texte complet reste au survol. */
/* Dans la vue en cartes, la cellule de transmission empile son verdict, son
   motif et son bouton : côte à côte, le motif se réduisait à deux mots coupés
   et le bouton à trois lettres. */
@media (max-width: 760px) {
    td[data-label]:has(.clamp) { flex-direction: column; align-items: flex-start; gap: 4px; }
    td[data-label] .clamp { max-width: 100%; }
}

.clamp {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden; max-width: 34ch;
}

.toast, .toast:popover-open {
    position: fixed; inset: auto; bottom: 24px; left: 50%; margin: 0;
    transform: translateX(-50%) translateY(8px);
    background: var(--text); color: var(--bg);
    border: 0; padding: 11px 20px; border-radius: 980px;
    font-size: 13.5px; font-weight: 500; text-align: center;
    max-width: min(420px, calc(100% - 32px)); overflow: visible;
    opacity: 0; pointer-events: none; transition: opacity .25s var(--ease), transform .25s var(--ease);
    z-index: 50; box-shadow: var(--shadow);
}
/* Repli pour un navigateur sans couche supérieure : le message reste un
   élément ordinaire, animé par la classe. */
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Le cas normal. Une animation, et non une transition : l'élément vient de
   passer de display:none à display:block en entrant dans la couche supérieure,
   et une transition déclenchée dans la même image est simplement sautée. */
.toast:popover-open { animation: toast-in .25s var(--ease) forwards; }
.toast:popover-open.leaving { animation: toast-out .2s var(--ease) forwards; }

@keyframes toast-in {
    from { opacity: 0; transform: translateX(-50%) translateY(8px); }
    to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes toast-out {
    from { opacity: 1; transform: translateX(-50%) translateY(0); }
    to { opacity: 0; transform: translateX(-50%) translateY(8px); }
}

/* Un utilisateur qui a demandé moins d'animations voit le message apparaître,
   sans le glissement. */
@media (prefers-reduced-motion: reduce) {
    .toast, .toast:popover-open, .toast:popover-open.leaving {
        animation: none; transition: none; transform: translateX(-50%);
    }
    .toast:popover-open { opacity: 1; }
}

dialog {
    border: 0; border-radius: var(--radius-lg); padding: 26px;
    background: var(--surface); color: var(--text);
    box-shadow: var(--shadow); max-width: 440px; width: calc(100% - 40px);
}
dialog::backdrop { background: rgba(0, 0, 0, .35); backdrop-filter: blur(3px); }
dialog h2 { margin-bottom: 16px; }
dialog .actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }

.note {
    font-size: 12.5px; color: var(--text-2);
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 12px 14px; margin-top: 16px;
}
.note strong { color: var(--text); }

/* Une note qui appelle une décision, et non une simple information : elle
   emprunte la teinte de l'état plutôt qu'une couleur à elle, pour que le même
   jaune veuille dire la même chose partout dans le produit. */
.note.warn { background: var(--warn-soft); border-color: var(--warn); color: var(--text); }
.note.err { background: var(--err-soft); border-color: var(--err); color: var(--text); }
.note.decision { display: flex; gap: 14px; align-items: center; justify-content: space-between; }
.note.decision button { flex-shrink: 0; }

/* Consentement explicite. La case et son libellé forment une seule cible :
   viser un carré de 16 px au doigt fait manquer le geste, et manquer un
   consentement n'est pas une erreur comme une autre. */
.check {
    display: flex; gap: 11px; align-items: flex-start;
    margin-top: 16px; padding: 12px 14px;
    border: 1px solid var(--line-strong); border-radius: var(--radius);
    font-size: 13px; line-height: 1.45; cursor: pointer;
}
.check:hover { border-color: var(--accent); background: var(--accent-soft); }
.check input {
    width: 18px; height: 18px; margin: 1px 0 0; flex: 0 0 auto;
    accent-color: var(--accent); cursor: pointer;
}
.check strong { color: var(--text); }

.rule-row { display: flex; gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 13.5px; }
.rule-row:last-child { border-bottom: 0; }
.rule-row code {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 12px; color: var(--accent); min-width: 82px; font-weight: 600;
}

/* ------------------------------------------------------------- adaptation */

@media (max-width: 1040px) {
    .split { grid-template-columns: 1fr; }
}

/* Tablette et téléphone. La barre latérale devient une barre d'onglets ancrée
   en bas : c'est là qu'un pouce atteint sans effort, alors que le haut de
   l'écran exige de changer la prise en main. */
@media (max-width: 860px) {
    .shell { flex-direction: column; }

    .sidebar {
        position: fixed; bottom: 0; left: 0; right: 0; top: auto;
        width: auto; flex: none; height: auto; z-index: 40;
        flex-direction: row; gap: 0;
        padding: 6px 4px;
        /* La zone d'accueil de l'iPhone mange le bas de l'écran : sans cette
           marge, le dernier onglet passe sous la barre d'accueil du système. */
        padding-bottom: max(6px, env(safe-area-inset-bottom));
        border-right: 0; border-top: 1px solid var(--line);
        background: color-mix(in srgb, var(--surface) 88%, transparent);
        backdrop-filter: saturate(180%) blur(20px);
        -webkit-backdrop-filter: saturate(180%) blur(20px);
    }

    .brand, .identity { display: none; }

    nav {
        flex-direction: row; width: 100%;
        justify-content: space-around;
    }

    /* La colonne défile sur grand écran ; ici c'est une barre, elle ne défile
       pas et ne doit pas se laisser comprimer. */
    nav { flex: none; overflow: visible; }

    /* Quatre vues visibles et « Plus ». Au-delà, chaque cible passe sous la
       largeur d'un pouce : à neuf onglets, « Clients » et « Prestations » se
       touchaient et « E-reporting » se coupait en deux lignes. */
    /* La barre du téléphone ne range que des cibles : un intitulé de groupe y
       serait une case morte qui vole soixante-quinze pixels au pouce.

       « display: contents » et non « none » : deux entrées non secondaires —
       les factures émises, les clients — vivent dans un groupe, et les masquer
       avec lui les ferait disparaître de la barre. Le groupe s'efface, ses
       boutons prennent sa place dans la rangée.

       Un <details> replié cacherait ses enfants malgré cela : un garde en
       JavaScript rouvre les groupes dès que la barre passe en mode étroit. */
    /* Préfixées par « nav » pour peser plus lourd que les règles de base, qui
       vivent plus bas dans ce fichier : une requête média n'ajoute aucune
       spécificité, et à égalité c'est l'ordre qui tranche. Sans ce poids, les
       six intitulés restaient affichés — plus de cinq cents pixels dans une
       barre qui en fait trois cent soixante-sept, et les quatre onglets réduits
       à quatre pixels chacun. Mesuré au navigateur, pas déduit. */
    /* La barre du téléphone ne range que des cibles : les intitulés s'effacent,
       et le repli ne doit jamais y masquer un onglet. Préfixées par « nav » pour
       peser plus lourd que les règles de base, plus bas dans ce fichier — une
       requête média n'ajoute aucune spécificité. */
    nav .nav-groupe { display: none; }

    /* Le repli n'existe pas ici : la barre du bas n'a pas de groupes, et un
       groupe replié sur grand écran ne doit pas amputer ses quatre cibles.
       Cette règle disait « none » — l'inverse de son intention — et « Émises »
       comme « Clients » disparaissaient dès qu'on avait replié leur groupe
       ailleurs. Vu au navigateur, pas déduit.

       Elle passe AVANT celle des entrées secondaires : à spécificité égale,
       c'est la dernière qui gagne, et les secondaires doivent rester masquées. */
    nav button.replie { display: flex; }

    nav button.secondary { display: none; }
    .only-mobile { display: flex; }

    /* Icône au-dessus, libellé dessous : la cible tactile fait 56 px de haut,
       au-delà des 44 px recommandés, et reste lisible. */
    nav button {
        flex: 1 1 0; min-width: 0;
        flex-direction: column; gap: 3px;
        padding: 7px 2px; border-radius: 10px;
        font-size: 10px; text-align: center; line-height: 1.15;
        color: var(--text-3);
    }
    nav button[aria-current="page"] { background: transparent; color: var(--accent); }
    nav button .ic { width: 21px; height: 21px; }

    /* Les libellés longs sont raccourcis en CSS plutôt que dans le HTML : la
       version large garde ses intitulés complets. « E-reporting » n'y figure
       plus — il est passé derrière « Plus », où il tient en entier. */
    /* « Émises » ne veut rien dire seule : dans la colonne elle vit sous un
       intitulé « Factures » qui lui donne son sens, et la barre du bas n'a pas
       d'intitulés. Elle y reprend donc le mot entier. */
    nav button[data-view="home"] .lbl::after { content: attr(data-court); }
    nav button[data-view="new"] .lbl::after { content: attr(data-court); }
    nav button[data-view="invoices"] .lbl::after { content: attr(data-court); }
    nav button[data-view="home"] .lbl,
    nav button[data-view="new"] .lbl,
    nav button[data-view="invoices"] .lbl { font-size: 0; }
    nav button[data-view="home"] .lbl::after,
    nav button[data-view="new"] .lbl::after,
    nav button[data-view="invoices"] .lbl::after { font-size: 11px; }

    /* Cinq cibles dans 375 px : chacune dispose de 75 px, largement au-dessus
       des 44 px recommandés. Le libellé peut donc reprendre une taille lisible
       et s'interdire de passer à la ligne. */
    nav button { font-size: 11px; }
    nav button .lbl { white-space: nowrap; }

    /* --------------------------------------------------- feuille « Plus »
       Elle monte du bas, là où le pouce se trouve déjà après avoir touché
       l'onglet. Une boîte centrée aurait obligé à remonter la main. */
    dialog.sheet {
        max-width: none; width: 100%;
        margin: auto auto 0; border-radius: 20px 20px 0 0;
        padding: 10px 16px calc(16px + env(safe-area-inset-bottom));
        max-height: 88vh; overflow-y: auto;
        animation: monte .24s var(--ease);
    }
    @keyframes monte { from { transform: translateY(100%); } }
    @media (prefers-reduced-motion: reduce) {
        dialog.sheet { animation: none; }
    }

    .sheet-grip {
        display: block; width: 38px; height: 4px; border-radius: 980px;
        background: var(--line-strong); margin: 0 auto 14px;
    }

    /* Les deux boutons de pied côte à côte, à parts égales : « Se déconnecter »
       n'est pas une action secondaire ici, c'est la seule qui existe. */
    dialog.sheet .actions { margin-top: 14px; }
    dialog.sheet .actions button { flex: 1 1 0; padding: 13px; }

    /* La barre d'onglets flotte au-dessus du contenu : on lui réserve sa place. */
    main { padding: 20px 16px calc(84px + env(safe-area-inset-bottom)); max-width: none; }

    h1 { font-size: 24px; }
    .page-head { flex-direction: column; align-items: stretch; gap: 14px; }
    .page-head button { width: 100%; padding: 13px 22px; }

    .card { padding: 18px; border-radius: var(--radius); }
    .grid.two, .grid.three { grid-template-columns: 1fr; }

    /* Deux colonnes au lieu de quatre : un indicateur tient dans une moitié
       d'écran, tout le reste prend la largeur. Un graphique à un quart de
       téléphone ne se lit pas. */
    .board { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
    .board > .w-moyen, .board > .w-grand { grid-column: span 2; }

    /* Les commandes rentrent dans la carte : débordantes, celles de deux cartes
       voisines se rejoignaient dans l'espace de 12 px qui les sépare, et le
       doigt ne savait plus laquelle il visait. */
    .w-remove { top: 6px; left: 6px; }
    .w-grip { top: 6px; right: 6px; }

    /* Et le contenu leur laisse la place : posées sur la carte, elles couvraient
       le début du libellé — « ncours client » au lieu d'« Encours client ». */
    .board.editing .widget > .card,
    .board.editing .widget > .kpi { padding-top: 44px; }

    .board-bar { flex-direction: column; align-items: stretch; }
    .board-bar .head-actions button { flex: 1; padding: 11px; }
    .page-head .head-actions { flex-direction: row; }
    .page-head .head-actions button { width: auto; flex: 1; }

    /* Champs à 16 px : en dessous, iOS zoome automatiquement à la mise au point
       et l'utilisateur se retrouve avec une page décadrée. */
    input, select, textarea { font-size: 16px; padding: 12px 14px; }

    dialog { padding: 20px; border-radius: var(--radius-lg); }
    dialog .actions { flex-direction: column-reverse; }
    dialog .actions button { width: 100%; padding: 13px; }

    /* La feuille garde ses deux boutons sur une ligne. Empilés, ils poussaient
       « Se déconnecter » sous le bord de l'écran — le défaut même qu'on est en
       train de corriger, reproduit deux crans plus bas. */
    dialog.sheet .actions { flex-direction: row; margin-top: 14px; }
    dialog.sheet .actions button { width: auto; flex: 1 1 0; }

    .toast { bottom: calc(78px + env(safe-area-inset-bottom)); width: max-content; max-width: calc(100% - 32px); }

    /* Une ligne de facture devient une fiche : deux colonnes lisibles plutôt que
       sept colonnes écrasées. La désignation prend toute la largeur, le reste se
       range par paires. */
    .line {
        grid-template-columns: 1fr 1fr;
        gap: 12px; padding: 14px; position: relative;
    }
    .line > div:first-child { grid-column: 1 / -1; }
    .line .rm {
        position: absolute; top: 8px; right: 8px;
        width: 32px; height: 32px; font-size: 20px;
        display: flex; align-items: center; justify-content: center;
    }

    /* Sur une remise, c'est le MOTIF qui prend toute la largeur, pas le premier
       champ : « Remise / Frais » tient dans une demi-colonne. */
    .line.adjustment { grid-template-columns: 1fr 1fr; }
    .line.adjustment > div:first-child { grid-column: auto; }
    .line.adjustment > div:nth-child(2) { grid-column: 1 / -1; }

    .subline {
        grid-template-columns: 1fr 1fr;
        gap: 12px; padding: 12px; position: relative;
        margin-left: 12px;
    }
    .subline > div:nth-child(2) { grid-column: 1 / -1; }
    .subline .rm {
        position: absolute; top: 6px; right: 6px;
        width: 30px; height: 30px; font-size: 19px;
        display: flex; align-items: center; justify-content: center;
    }
    .add-subline { margin-left: 12px; font-size: 13.5px; padding: 4px 0; }

    /* Le retrait suit celui des remises : sur téléphone, dix pixels de moins
       rendent une ligne entière de texte à la zone de saisie. */
    .line-detail { margin-left: 12px; }
    .line-detail .detail-toggle { font-size: 13.5px; padding: 4px 0; }

    .kpi { padding: 16px; }
    .kpi-value { font-size: 26px; }

    /* Le graphe est réduit d'environ moitié : sans compensation, les libellés
       tomberaient à 5 px. Un mois sur deux est masqué pour éviter le chevauchement. */
    .chart-axis { font-size: 19px; }
    .chart-axis.skip { display: none; }

    /* Un tableau à sept colonnes ne se lit pas sur un téléphone. Chaque ligne
       devient une carte, et l'en-tête de colonne s'affiche devant sa valeur —
       c'est data-label, posé côté JavaScript, qui porte le libellé. */
    table, thead, tbody, tr, th, td { display: block; }
    thead { display: none; }

    tbody tr {
        border: 1px solid var(--line); border-radius: var(--radius);
        background: var(--surface-2); padding: 12px 14px; margin-bottom: 10px;
    }
    tbody tr:hover { background: var(--surface-2); }

    td {
        border: 0; padding: 4px 0;
        display: flex; justify-content: space-between; align-items: baseline; gap: 14px;
        text-align: right;
    }
    td::before {
        content: attr(data-label);
        font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
        color: var(--text-3); font-weight: 600; text-align: left; flex: 0 0 auto;
    }
    /* Une cellule sans libellé porte les actions : elle occupe toute la largeur. */
    td:not([data-label]) { justify-content: flex-end; padding-top: 10px; flex-wrap: wrap; }
    td.num { text-align: right; }

    /* Le détail qui ne survit pas à la réduction. Sur un téléphone, ce qu'on
       cherche dans une facture c'est son numéro, son client et son TTC ; la
       ventilation HT/TVA et le cadre réglementaire restent à un doigt, sur la
       fiche complète ou sur un écran plus large. */
    td.on-wide { display: none; }
}

/* Téléphone. Deux colonnes tiennent jusqu'à 375 px pour des quantités et des
   prix — empiler six champs obligerait à faire défiler chaque ligne de facture,
   ce qui est pire qu'un champ un peu étroit. */
@media (max-width: 480px) {
    .kpis { grid-template-columns: 1fr 1fr; gap: 10px; }
    .kpi-value { font-size: 22px; }
    .kpi-foot { font-size: 11px; }
}

/* En dessous, plus rien ne tient à deux colonnes. */
@media (max-width: 340px) {
    .line { grid-template-columns: 1fr; }
    .kpis { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------- porte d'entrée */

.gate {
    min-height: 100vh;
    /* `align-items: center` sur un enfant PLUS HAUT que la fenêtre pousse son
       sommet hors de la zone défilable : il devient inatteignable, et l'on
       arrive sur un écran dont le titre est au-dessus du vide. C'est ce qui
       arrivait à l'enrôlement au second facteur, le plus haut de nos volets.
       `flex-start` plus `margin: auto` sur la carte centre exactement de la même
       façon quand elle tient, et laisse défiler quand elle déborde. */
    display: flex; align-items: flex-start; justify-content: center;
    padding: 24px;
    /* Deux halos très diffus : la page respire au lieu d'être un aplat, et rien
       ne bouge — une animation sur l'écran de connexion se remarque au
       deuxième passage, et agace au dixième. */
    background:
        radial-gradient(60rem 40rem at 20% -10%, var(--accent-soft), transparent 60%),
        radial-gradient(50rem 35rem at 85% 110%, var(--accent-soft), transparent 55%),
        var(--bg);
}

.gate-card {
    margin: auto;
    width: 100%; max-width: 410px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 32px;
    box-shadow: var(--shadow);
    animation: rise .3s var(--ease);
}

.gate-brand { text-align: center; margin-bottom: 26px; }
.gate-brand .mark { margin: 0 auto 12px; }
.gate-brand strong { display: block; font-size: 23px; letter-spacing: -.03em; }
.gate-brand strong em { font-style: normal; color: var(--accent); }
.gate-brand span {
    display: block; font-size: 13px; color: var(--text-2);
    margin: 6px auto 0; max-width: 30ch; line-height: 1.45;
}

.gate-card h2 { font-size: 17px; text-align: center; }

.gate-switch { text-align: center; font-size: 13px; color: var(--text-2); margin: 16px 0 0; }

button.link {
    background: none; border: 0; padding: 0;
    color: var(--accent); font: inherit; font-size: 13px; cursor: pointer;
}
button.link:hover { text-decoration: underline; }

.gate-error {
    margin-top: 14px; padding: 10px 13px;
    background: var(--err-soft); color: var(--err);
    border-radius: var(--radius); font-size: 13px;
}

/* Accusé de réception, pas confirmation : le lien a été demandé, ce qui ne dit
   pas qu'un compte existe. Le ton reste neutre pour la même raison. */
.gate-note {
    margin-top: 14px; padding: 10px 13px;
    background: var(--ok-soft); color: var(--ok);
    border-radius: var(--radius); font-size: 13px;
}

/* -------------------------------------------------- filtres et pagination */

.filters {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
    margin-bottom: 16px;
}
.filters > div { flex: 0 0 auto; }
.filters .filter-search { flex: 1 1 260px; min-width: 200px; }
.filters button { align-self: flex-end; }

.pager {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; flex-wrap: wrap; margin-top: 16px;
}
.pager-count { font-size: 13px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.pager-nav { display: flex; align-items: center; gap: 12px; }
.pager-pos { font-size: 13px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.pager button:disabled { opacity: .4; cursor: default; }
.pager button:disabled:hover { border-color: var(--line-strong); background: transparent; }

/* ------------------------------------------------------ aperçu de facture */

/* L'aperçu occupe l'écran : une facture lue dans une vignette n'est pas lue.
   Le téléchargement reste offert, mais il n'est plus le seul moyen de voir. */
/* Le display n'est posé que sur l'état ouvert. Un dialogue fermé vaut
   « display: none » par la feuille du navigateur, et une règle d'auteur sans
   [open] l'emporte sur elle : l'aperçu restait alors visible en permanence,
   dans le flux de la page, poussant tout ce qui suit. C'est le piège déjà
   rencontré avec .gate et .shell, et documenté en tête de ce fichier. */
dialog.viewer[open] {
    display: flex; flex-direction: column;
}

dialog.viewer {
    max-width: min(960px, 94vw); width: 100%;
    /* Hauteur explicite, pas seulement une borne : dans un conteneur en colonne,
       un enfant en flex-basis 0 ne réclame aucune place, et le dialogue se
       réduisait à son en-tête — le document restait à la hauteur par défaut
       d'un cadre, 150 px. */
    height: 90vh;
    padding: 0; overflow: hidden;
}
.viewer-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.viewer-head h2 {
    font-size: 16px; margin: 0;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}
.viewer-actions { display: flex; align-items: center; gap: 10px; }
.viewer-actions .ghost {
    background: transparent; color: var(--accent);
    border: 1px solid var(--line-strong); border-radius: 980px;
    padding: 7px 16px; font: inherit; font-size: 13px; cursor: pointer; text-decoration: none;
}
.viewer-actions .ghost:hover { border-color: var(--accent); background: var(--accent-soft); }
#pdfFrame { flex: 1 1 auto; width: 100%; min-height: 0; border: 0; background: var(--surface-2); }

@media (max-width: 860px) {
    dialog.viewer { max-width: 100vw; height: 100vh; border-radius: 0; }
    .filters { gap: 10px; }
    .filters > div { flex: 1 1 140px; }
    .filters .filter-search { flex: 1 1 100%; }
}

/* Compteur de consommation réseau
   ------------------------------------------------------------------
   La jauge n'est pas décorative : c'est le seul endroit du produit où
   le client voit venir une dépense. Elle reste verte tant qu'il n'y a
   rien à signaler — une jauge alarmiste dès le premier envoi apprend
   surtout à ne plus la regarder. */
.conso { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.conso-tete {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 12px; font-size: 14px; color: var(--text-2);
}
.conso-tete b { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; }
.conso-tete b span { font-size: 13px; font-weight: 400; color: var(--text-3); }
.conso-tete b.ok { color: var(--text); }
.conso-tete b.warn { color: var(--warn); }
.conso-tete b.err { color: var(--err); }

.conso-jauge {
    margin-top: 8px; height: 6px; border-radius: 999px;
    background: var(--line); overflow: hidden;
}
.conso-jauge i {
    display: block; height: 100%; border-radius: 999px;
    transition: width .4s cubic-bezier(.22,.61,.36,1);
}
.conso-jauge i.ok { background: var(--accent); }
.conso-jauge i.warn { background: var(--warn); }
.conso-jauge i.err { background: var(--err); }

.conso-detail { margin: 10px 0 0; font-size: 13px; color: var(--text-3); line-height: 1.55; }
.conso-detail b { color: var(--text-2); font-weight: 600; }
.conso-alerte { margin: 10px 0 0; font-size: 13.5px; line-height: 1.55; }
.conso-alerte.warn { color: var(--warn); }
.conso-alerte.err { color: var(--err); }

/* Dépôt d'un relevé bancaire
   ------------------------------------------------------------------
   Un champ de fichier natif est minuscule et illisible : on l'habille
   d'une zone qui dit ce qu'on attend et où cliquer. Le champ reste dans
   le DOM — c'est lui qui ouvre le sélecteur, et le label le déclenche
   au clavier comme à la souris. */
.depot {
    display: flex; flex-direction: column; gap: 3px; align-items: center;
    margin-top: 14px; padding: 22px; cursor: pointer;
    border: 1.5px dashed var(--line-strong); border-radius: var(--radius);
    background: var(--surface-2);
    transition: border-color .18s var(--ease), background .18s var(--ease);
}
.depot:hover, .depot:focus-within { border-color: var(--accent); background: var(--accent-soft); }
.depot b { font-size: 15px; }
.depot span { font-size: 12.5px; color: var(--text-3); }

/* Le résultat du rapprochement.
   Le motif est un mot, pas une couleur seule : « montant seul » se lit,
   un point orange se devine. */
.rappro { margin-top: 20px; }
.rappro h3 {
    font-size: 13px; letter-spacing: .04em; text-transform: uppercase;
    color: var(--text-3); margin: 22px 0 8px;
}
.rappro h3:first-child { margin-top: 0; }
.rappro .motif { font-size: 11.5px; color: var(--text-3); display: block; }
.rappro .ecart { color: var(--warn); font-weight: 600; }
.rappro .lu { font-size: 13px; color: var(--text-3); margin-top: 12px; }
.rappro td.pick { width: 34px; }
.rappro input[type=checkbox] { width: 17px; height: 17px; accent-color: var(--accent); }

/* L'accès libre à la démonstration.
   ------------------------------------------------------------------
   Il vit sur l'écran de connexion et non derrière un lien qu'il faut
   avoir reçu : un lien partagé perd le texte qui l'accompagnait, et le
   visiteur d'un repartage se retrouvait sans moyen d'essayer.

   Le bloc est discret — un cadre, pas un bouton principal : il ne doit
   pas détourner celui qui vient créer son compte, qui est le geste qu'on
   préfère. */
.essai-libre {
    margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line);
    display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
.essai-libre b { font-size: 14.5px; }
.essai-libre p { font-size: 13px; color: var(--text-3); line-height: 1.55; margin: 0; }
.essai-libre button { align-self: stretch; }
.essai-libre .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px; color: var(--text-3); align-self: center;
    user-select: all;
}

/* La carte de fermeture de compte.
   ------------------------------------------------------------------
   Elle est en dernier, et signalée : on ne place pas la sortie au
   milieu du couloir. Le filet rouge n'est pas décoratif — il annonce
   que ce qui suit ne se défait pas. */
.danger-card { border-left: 3px solid var(--err); }
.danger-card h2 { color: var(--err); }

.ack { display: grid; grid-template-columns: 20px 1fr; gap: 10px; align-items: start;
       font-size: 13.5px; color: var(--text-2); line-height: 1.5; cursor: pointer; }
.ack input { width: 17px; height: 17px; accent-color: var(--err); margin-top: 1px; }

/* Le motif d'un statut qui arrête la facture — refus, litige, rejet, échec de
   dépôt. Il ne vivait qu'en infobulle, donc invisible au doigt comme au lecteur
   d'écran, alors que c'est la seule chose qui dise quoi faire. Le dossier fonde
   tout le cycle de vie sur « une vision partagée du traitement de la facture
   pour l'ensemble des acteurs intéressés » (§3.6.4) : un motif qu'on ne lit pas
   ramène au téléphone, que la réforme cherche justement à supprimer.

   Discret mais lisible : la pastille alerte, le motif explique. */
.motif-statut {
    margin-top: 4px;
    font-size: 11.5px;
    line-height: 1.35;
    color: var(--text-3);
    max-width: 26ch;
}

/* Le détail d'un statut, sous sa pastille. Le motif n'a aucune borne — ni chez
   la plateforme, ni à l'annexe 2 qui nomme les champs sans leur donner de
   longueur — et une colonne ne peut pas l'accueillir. La pastille alerte, le
   clic explique. */
.detail-statut > summary {
    list-style: none;
    cursor: pointer;
}

.detail-statut > summary::-webkit-details-marker { display: none; }

/* Le chevron dit qu'il y a quelque chose à ouvrir : sans lui, la pastille
   ressemble à toutes les autres et personne ne clique. */
.detail-statut > summary::after {
    content: '▾';
    margin-left: 4px;
    font-size: 10px;
    color: var(--text-3);
    display: inline-block;
    transition: transform .15s;
}

.detail-statut[open] > summary::after { transform: rotate(180deg); }

.detail-statut[open] .motif-statut {
    margin-top: 6px;
    padding: 6px 8px;
    background: var(--surface-2, rgba(127, 127, 127, .08));
    border-radius: 6px;
    max-width: 42ch;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* L'intitulé d'un groupe d'onglets. Il nomme ce que les entrées ont en commun —
   « Factures » au-dessus d'« Émises » et « Reçues » — pour que le mot ne soit
   pas répété deux fois dans une colonne qui n'en a pas la largeur.

   Ce n'est pas une destination : il ne réagit pas au clic, et il est masqué aux
   lecteurs d'écran, qui suivent déjà la liste des boutons. */
/* L'intitulé d'un groupe d'onglets. Il nomme ce que les entrées ont en commun —
   « Factures » au-dessus d'« Émises » et « Reçues » — et il les replie.

   C'est un frère des boutons, pas leur parent : un <details> qui les enveloppait
   cassait la barre du téléphone, où « display: contents » ne promeut pas ses
   enfants au rang d'éléments flexibles. Le repli se fait donc par une classe
   posée sur les boutons du groupe. */
.nav-groupe {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    border: 0;
    background: none;
    /* Serré : six intitulés dans une colonne qui portait déjà dix-neuf entrées
       ont coûté cent quatre-vingts pixels, et cinq destinations sont passées
       sous le pli d'un écran de neuf cents. Le dégagement au-dessus sépare les
       groupes, celui du dessous n'a rien à séparer. */
    padding: 10px 12px 1px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-3);
    text-align: left;
    cursor: pointer;
}

/* Le premier groupe suit un onglet, pas un bord. */
.nav-groupe:first-of-type { padding-top: 6px; }

/* Le chevron dit que le groupe se replie. Sans lui, l'intitulé passe pour un
   simple titre et personne n'essaie. */
.nav-groupe::after {
    content: '▾';
    font-size: 10px;
    transition: transform .15s;
}

.nav-groupe[aria-expanded="false"]::after { transform: rotate(-90deg); }

/* Replié, l'intitulé reste seul : il doit garder l'air d'un titre et non d'une
   ligne orpheline. */
.nav-groupe[aria-expanded="false"] { padding-bottom: 10px; }

/* Un onglet dont le groupe est replié. La règle vit ici, avec les autres, pour
   qu'on la trouve en cherchant « replie » — et elle est neutralisée sur
   téléphone, où les groupes n'existent pas. */
nav button.replie { display: none; }

/* Le motif se recopie : c'est ce qu'on colle dans un courriel au fournisseur.
   La ligne autour interdit la sélection pour que le glissement ouvre la facture
   sans surligner la moitié du tableau — ce texte-ci fait exception. */
.detail-statut .motif-statut {
    user-select: text;
    cursor: text;
}

/* Le geste de réparation, sous la cause qu'il répare. Il n'a pas l'allure d'une
   pastille : celle-ci constate, lui agit — les confondre ferait déclencher la
   relance au clic qui voulait seulement lire. */
.lien-relance {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: var(--accent);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* ----------------------------------------------------------------- agenda */

/* Le mois d'une pension, une ligne par séjour.

   Une ligne par séjour plutôt qu'un empilement calculé : le mois le plus chargé
   du premier dossier repris en porte une trentaine, et une ligne par séjour se
   lit sans avoir à deviner quelle barre appartient à qui.

   Les cases sont ÉTROITES et de largeur fixe : trente et une colonnes doivent
   tenir, et un mois qui déborde se fait défiler plutôt que d'écraser les noms. */
.agenda {
    border-collapse: collapse;
    table-layout: fixed;
    min-width: 720px;
    width: 100%;
}

.agenda th {
    width: 3.2%;
    padding: 4px 0;
    font-size: 11px;
    font-weight: 500;
    color: var(--text-2);
    text-align: center;
    border-bottom: 1px solid var(--line);
}

.agenda th.agenda-we { color: var(--text-3, var(--text-2)); opacity: .6; }

.agenda td {
    height: 26px;
    padding: 0;
    border-right: 1px solid var(--line);
    overflow: hidden;
}

/* La barre du séjour. Le jour de DÉPART est plus pâle : l'animal repart ce
   jour-là, il n'y dort pas — et c'est ce qui fait qu'un séjour du 1er au 8
   compte sept nuits et non huit. */
.agenda td.sejour-plein { background: var(--accent); }
.agenda td.sejour-fin { background: var(--accent); opacity: .35; }

/* Le jour même se marque sur son EN-TÊTE et non sur la case.

   Une barre s'étend maintenant sur tous ses jours d'un seul tenant : elle ne
   peut plus en surligner un. L'en-tête, lui, garde une colonne par jour. */
.agenda th.agenda-today {
    color: var(--warn);
    font-weight: 700;
    opacity: 1;
    box-shadow: inset 0 -2px 0 var(--warn);
}

/* Le nom du maître, DANS sa barre et coupé par elle.

   Il débordait sur les cases suivantes, hors flux : « mathilde DUBOIS DA COSTA »
   traversait le mois au-dessus de trois jours de garde, et l'on ne pouvait plus
   lire où le séjour finissait. Depuis que les barres se partagent une voie, il
   serait en plus passé par-dessus le séjour suivant.

   Une barre trop courte pour son nom n'en montre donc rien plutôt qu'un
   fragment trompeur — l'infobulle le porte, et le survol est le geste qu'on
   fait déjà sur un agenda. */
.sejour-nom {
    display: block;
    padding: 0 5px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 11px;
    font-weight: 500;
    line-height: 26px;
    color: #fff;
    pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}

/* L'initiale du jour, au-dessus de son quantième. « 14 » seul ne dit pas si
   c'est un samedi, et un agenda de pension se lit d'abord par les fins de
   semaine. */
.agenda-jsem {
    display: block;
    font-size: 9px;
    line-height: 1;
    opacity: .55;
    text-transform: uppercase;
}

/* ------------------------------------------------------- ce qui attend */

/* La bande d'actions, au-dessus des chiffres.

   Des tuiles LARGES et peu nombreuses plutôt qu'une grille : il y en a zéro à
   quatre, jamais douze, et une grille à colonnes fixes laisserait des trous là
   où il n'y a rien à faire. */
.todo-bande {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 20px;
}

.todo-tuile {
    flex: 1 1 190px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 14px 18px;
    border: 1px solid var(--line);
    border-left-width: 4px;
    border-radius: 12px;
    background: var(--surface, #fff);
    cursor: pointer;
    text-align: left;
    transition: border-color .15s, transform .15s;
}

.todo-tuile:hover { transform: translateY(-1px); }

/* La couleur porte l'URGENCE, et le bord gauche la porte seul : teinter tout le
   fond ferait crier les quatre tuiles ensemble, et l'œil ne saurait plus par
   laquelle commencer. */
.todo-tuile.ton-err { border-left-color: var(--err); }
.todo-tuile.ton-warn { border-left-color: var(--warn); }
.todo-tuile.ton-accent { border-left-color: var(--accent); }
.todo-tuile.ton-neutral { border-left-color: var(--line); }

.todo-compte {
    font-size: 26px;
    font-weight: 600;
    line-height: 1.1;
}

.todo-libelle { font-size: 13px; color: var(--text-2); }

.todo-montant {
    margin-top: 4px;
    font-size: 13px;
    font-weight: 500;
}

/* Rien à faire se dit en une phrase, pas en quatre zéros. */
.todo-vide {
    margin-bottom: 20px;
    padding: 14px 18px;
    color: var(--text-2);
    font-size: 14px;
}

/* --------------------------------------------------- listes cherchables */

/* L'en-tête d'une liste : la recherche, et ce qu'elle a retenu.

   Le compte à droite dit toujours DEUX choses quand un filtre est posé — « 12
   sur 782 » — parce qu'un nombre seul laisse croire qu'on voit tout. */
.liste-tete {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
}

.liste-tete input[type="search"] {
    flex: 1 1 260px;
    max-width: 420px;
    margin: 0;
}

.liste-tete .sub { white-space: nowrap; margin: 0; }

/* La pagination : deux flèches et la position. Pas de numéros de page — sur une
   liste qu'on CHERCHE, personne ne vise la page sept. */
.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-top: 16px;
    font-size: 13px;
    color: var(--text-2);
}

.pagination button {
    min-width: 36px;
    padding: 4px 10px;
    font-size: 16px;
    line-height: 1.2;
}

.pagination button[disabled] { opacity: .35; cursor: default; }

/* ------------------------------------------------- confirmation d'émission */

/* Ce qui va être consommé, montré AVANT le clic.

   Le numéro en grand, parce que c'est lui qui ne recule pas : la séquence ne
   se rembobine pas, et un trou dans la suite se justifie devant
   l'administration. Le reste sert à reconnaître la pièce qu'on s'apprête à
   émettre — un « êtes-vous sûr » sans contenu s'apprend par cœur et se clique
   sans lire. */
.issue-resume {
    padding: 16px 18px;
    margin-bottom: 16px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--accent-soft, var(--line));
    text-align: center;
}

.issue-numero {
    font-size: 24px;
    font-weight: 600;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
}

.issue-resume .sub { margin: 6px 0 0; }

.issue-recap { width: 100%; }

.issue-recap th {
    width: 40%;
    padding: 7px 0;
    text-align: left;
    font-weight: 400;
    color: var(--text-2);
    font-size: 13px;
}

.issue-recap td {
    padding: 7px 0;
    text-align: right;
    font-weight: 500;
}

/* ------------------------------------------- chiffre d'affaires par mois */

/* Des barres qui portent leur valeur.

   C'était une courbe d'aire. Sur une entreprise à dix mille euros l'an — la
   cible de ce produit — elle ne disait rien que le total au-dessus ne disait
   mieux : on y lisait une pente, jamais un montant. Une barre par mois, avec
   son chiffre écrit dessus, répond à la question qu'on se pose : « combien en
   juillet ? »
   Pas de graduation verticale : les montants sont écrits. Une échelle en plus
   des valeurs fait lire deux fois la même chose. */
.chart-barres {
    width: 100%;
    height: auto;
    display: block;
}

.bar-mois {
    fill: var(--accent);
    transition: opacity .15s;
}

/* Un mois négatif — un avoir sans facture en face — se distingue par la
   couleur, et non par la seule position sous la ligne de zéro : sur un écran
   étroit, cette ligne se confond avec le bas du cadre. */
.bar-negatif { fill: var(--err); }

.bar-zero {
    stroke: var(--line);
    stroke-width: 1;
}

.bar-montant {
    font-size: 11px;
    font-weight: 600;
    fill: var(--text);
    font-variant-numeric: tabular-nums;
}

.bar-mois-nom {
    font-size: 11px;
    fill: var(--text-2);
}

/* La zone cliquable couvre toute la colonne, pas la seule barre : viser trois
   pixels de haut sur un mois creux est un geste qu'on rate. */
.bar-zone {
    fill: transparent;
    cursor: pointer;
}

.bar-groupe:hover .bar-zone { fill: var(--line); opacity: .35; }
.bar-groupe:hover .bar-mois { opacity: .85; }
.bar-groupe:focus-visible .bar-zone { fill: var(--line); opacity: .5; }

/* -------------------------------------------------------------- résultat */

/* Facturé − Dépensé = Résultat, posé comme une opération.

   La forme n'est pas décorative : elle dit d'où sort le chiffre. Un « résultat :
   20 € » seul se prend pour un solde de compte, et c'est précisément la
   confusion qui coule une entreprise rentable — une facture émise compte dans le
   résultat avant d'être payée. */
.card.resultat { padding: 18px 20px; margin-bottom: 20px; }

.resultat-ligne {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 12px 18px;
}

.resultat-ligne > div {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.resultat-libelle {
    font-size: 12px;
    color: var(--text-2);
}

.resultat-valeur {
    font-size: 19px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}

/* Le résultat plus gros que ses deux termes : c'est lui qu'on vient lire. */
.resultat-valeur.fort { font-size: 26px; font-weight: 600; }
.resultat-valeur.negatif { color: var(--err); }

.resultat-signe {
    font-size: 18px;
    color: var(--text-3);
    align-self: center;
}

.resultat-note { margin: 12px 0 0; }

/* La trésorerie, séparée du résultat par un trait — parce que ce sont deux
   nombres différents, et que les coller les ferait lire comme un total. */
.resultat-tresorerie {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
}

/* La ligne survolée se signale, comme son secteur d'anneau. */
.report-row { transition: background .15s var(--ease); }
.report-row:hover { background: var(--line); border-radius: 8px; }

/* --------------------------------------------------------- fiche client */

/* Ce qu'un client a acheté, ce qu'il doit, et depuis quand.

   AUCUN CHAMP VIDE. D'autres produits alignent vingt lignes — Height, Weigth,
   Profession — dont dix-huit sont blanches, et on apprend à ne plus les lire.
   Ici, ce qui n'est pas renseigné ne s'affiche pas ; ce qui manque VRAIMENT — le
   SIREN d'un professionnel — se dit comme un manque. */
.fiche-tete {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 4px;
}

.fiche-tete h2 { margin: 0; }

/* De quoi le JOINDRE, en tête et cliquable : c'est ce qu'on vient chercher neuf
   fois sur dix en ouvrant une fiche. */
.fiche-contact {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin: 0 0 16px;
    font-size: 13.5px;
    color: var(--text-2);
}

.fiche-manque {
    margin: 0 0 14px;
    padding: 9px 13px;
    border-radius: 9px;
    background: var(--warn-soft);
    color: var(--warn);
    font-size: 13px;
}

.fiche-chiffres {
    display: flex;
    flex-wrap: wrap;
    gap: 14px 28px;
    padding: 14px 0;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
}

.fiche-chiffres > div { display: flex; flex-direction: column; gap: 2px; }

.fiche-detail { width: 100%; margin-top: 14px; }
.fiche-detail th {
    width: 32%;
    padding: 5px 0;
    text-align: left;
    font-weight: 400;
    color: var(--text-2);
    font-size: 13px;
}
.fiche-detail td { padding: 5px 0; font-size: 13.5px; }

.fiche-titre { margin: 22px 0 8px; font-size: 15px; }

.fiche-pieces { width: 100%; }
.fiche-pieces td { padding: 7px 0; font-size: 13.5px; }

/* ================================================================ mobile */

/* Tout ce qui a été ajouté ce soir, repris en étroit.

   Le défaut commun de ces blocs est le même : ils rangent des chiffres CÔTE À
   CÔTE. Sur un écran de 375 px, « Facturé − Dépensé = Résultat » sur une ligne
   pousse le résultat hors de l'écran — ou, pire, le laisse déborder sans que
   rien ne le signale. */
@media (max-width: 720px) {

    /* CE QUI ATTEND. Les tuiles s'empilent plutôt que de se serrer : à trois par
       ligne sur un téléphone, chacune fait cent pixels et le libellé se coupe. */
    .todo-tuile { flex: 1 1 100%; }

    /* CE QUI RESTE. L'opération passe en colonne, et les signes disparaissent :
       un « − » isolé sur sa ligne ne veut plus rien dire. Chaque terme garde son
       libellé, qui suffit à la lire. */
    .resultat-ligne { flex-direction: column; gap: 10px; }
    .resultat-signe { display: none; }
    .resultat-valeur.fort { font-size: 23px; }

    /* La trésorerie garde sa séparation mais passe en colonne comme le reste. */
    .resultat-tresorerie { flex-direction: column; align-items: flex-start; gap: 2px; }

    /* FICHE CLIENT. Les quatre chiffres passent à deux par ligne — à quatre ils
       font 80 px chacun, et « 10 089 € » n'y tient pas. */
    .fiche-chiffres { gap: 12px 20px; }
    .fiche-chiffres > div { flex: 1 1 42%; }

    /* Le contact s'empile : trois éléments côte à côte poussent l'adresse
       hors du cadre, et c'est justement ce qu'on vient lire. */
    .fiche-contact { flex-direction: column; gap: 4px; }

    /* Un libellé de colonne à 32 % laisse 68 % à une adresse : trop peu. */
    .fiche-detail th { width: 44%; }


    /* LISTES CHERCHABLES. La recherche prend la largeur, le compte passe
       dessous : côte à côte, le champ tombe à 120 px. */
    .liste-tete { flex-wrap: wrap; }
    .liste-tete input[type="search"] { flex: 1 1 100%; max-width: none; }

    /* AGENDA. Trente et une colonnes ne tiennent pas : il défile, et le
       défilement horizontal est ici la bonne réponse — comprimer les jours
       rendrait les barres illisibles. */
    .agenda { min-width: 620px; }

    /* Le nom du maître sur une barre d'un jour déborde forcément. On le laisse
       déborder plutôt que de le couper : l'infobulle porte le détail, et un nom
       tronqué à trois lettres ne sert personne. */
    .sejour-nom { font-size: 10px; }

    /* GRAPHE EN BARRES. Sur douze mois et 375 px, chaque barre fait 28 px : le
       montant écrit dessus se chevaucherait. Il tombe, et le survol le donne. */
    .bar-montant { display: none; }
    .bar-mois-nom { font-size: 13px; }

    /* CONFIRMATION D'ÉMISSION. Le numéro reste lisible, le récapitulatif
       s'aère. */
    .issue-numero { font-size: 20px; }
    .issue-recap th { width: 46%; }
}

/* Sur les écrans les plus étroits, le graphe mensuel devient illisible à douze
   barres : il défile plutôt que de se comprimer. */
@media (max-width: 480px) {
    .chart-barres { min-width: 480px; }
    .chart-wrap { overflow-x: auto; }
}

/* ------------------------------------------------ champs en deux colonnes */

/* Employée par les boîtes de saisie du module de pension. Elle était écrite
   dans le balisage et n'existait nulle part : les champs s'empilaient en un
   seul rang, ce qui n'était pas cassé mais faisait une boîte deux fois trop
   haute — un formulaire de séjour tenait mal dans une fenêtre de portable.

   `minmax(0, 1fr)` et non `1fr` : sans le minimum à zéro, un champ au contenu
   long refuse de se réduire et déborde de la grille. C'est le débordement le
   plus fréquent d'une grille CSS, et le plus discret. */
.grid2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px 16px;
}

.grid2 > div { min-width: 0; }

/* Une colonne en étroit : deux champs de 150 px côte à côte sur un téléphone
   coupent leurs libellés. */
@media (max-width: 560px) {
    .grid2 { grid-template-columns: 1fr; }
}

/* ============================ tableaux qui ne sont pas des listes ============ */

/* La règle mobile transforme TOUS les tableaux en cartes — une ligne par carte,
   le libellé de colonne devant chaque valeur. Elle est juste pour une liste de
   factures ou de clients : sept colonnes ne se lisent pas sur un téléphone.

   Elle est FAUSSE pour trois tableaux ajoutés depuis, qui ne sont pas des
   listes :

     — l'AGENDA est une grille de trente et un jours. Éclaté en cartes, il perd
       ce qui le fait : la continuité horizontale d'un séjour ;
     — le RÉCAPITULATIF d'émission et le DÉTAIL d'une fiche sont des paires
       clé-valeur à deux colonnes. Ils sont déjà lisibles en étroit, et la règle
       leur ajoutait le libellé de colonne DEVANT leur propre libellé ;
     — les PIÈCES d'une fiche tiennent en quatre colonnes courtes.

   Chacun reprend donc son comportement de tableau, et déborde en défilant
   plutôt qu'en s'éclatant. La règle est posée APRÈS pour la surcharger, et bornée
   au même point de bascule. */
@media (max-width: 860px) {
    .agenda, .agenda thead, .agenda tbody, .agenda tr { display: revert; }
    .agenda th { display: table-cell; }
    .agenda td { display: table-cell; }

    .issue-recap, .issue-recap tbody, .issue-recap tr,
    .fiche-detail, .fiche-detail tbody, .fiche-detail tr,
    .fiche-pieces, .fiche-pieces tbody, .fiche-pieces tr { display: revert; }

    .issue-recap th, .issue-recap td,
    .fiche-detail th, .fiche-detail td,
    .fiche-pieces td { display: table-cell; }

    /* Ces lignes ne sont pas des cartes : ni cadre, ni fond, ni marge. */
    .agenda tbody tr,
    .issue-recap tbody tr,
    .fiche-detail tbody tr,
    .fiche-pieces tbody tr {
        border: 0; border-radius: 0; background: none; padding: 0; margin: 0;
    }

    /* Et surtout pas de libellé de colonne ajouté devant la valeur : ces
       tableaux portent déjà le leur. */
    .agenda td::before,
    .issue-recap td::before,
    .fiche-detail td::before,
    .fiche-pieces td::before { content: none; }

    /* Le débordement se fait par le DÉFILEMENT, dans un conteneur propre. */
    .fiche-pieces, .agenda { width: 100%; }
}

/* Le conteneur qui fait défiler les pièces d'une fiche plutôt que de les
   écraser : un numéro tronqué ne désigne plus rien. */
.fiche-defile { overflow-x: auto; }

/* ------------------------------------------------- enrôlement au second facteur */

.totp-etapes {
  margin: 16px 0 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.65;
  color: var(--text-2);
}

.totp-etapes li + li { margin-top: 6px; }

/* Le QR est rendu par le serveur, en SVG. Fond blanc imposé : la page suit le
   thème du lecteur, et un code QR sombre sur fond sombre inverse le contraste
   que tous les lecteurs supposent. Certains y arrivent, la plupart non. */
.totp-qr {
  margin: 16px auto 0;
  width: 208px;
  padding: 10px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}

.totp-qr svg { display: block; width: 100%; height: auto; }

.totp-manuel { margin-top: 14px; font-size: 12.5px; color: var(--text-3); }
.totp-manuel summary { cursor: pointer; color: var(--accent); }
.totp-manuel p { margin: 8px 0; }

/* La clé en groupes de quatre, dans une police à chasse fixe : elle se recopie
   à la main sur un clavier de téléphone, et une suite de trente-deux caractères
   sans respiration s'y perd à la douzième. */
.totp-secret {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  letter-spacing: .12em;
  word-break: break-all;
  padding: 10px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  user-select: all;
}

.totp-manuel button { margin-top: 8px; }

.secours-liste {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-top: 16px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 15px;
  letter-spacing: .08em;
}

.secours-liste span {
  padding: 9px 10px;
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  user-select: all;
}

.secours-actions { display: flex; gap: 8px; margin-top: 14px; }
.secours-actions button { flex: 1; }

.secours-vu {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
}

.secours-vu input { width: auto; margin: 0; }

/* À l'impression, seuls les codes. Une page de secours imprimée avec le
   bandeau de l'application et les boutons est une page qu'on ne range pas. */
@media print {
  body > *:not(#gate) { display: none !important; }
  #gate > *:not(#secoursPane) { display: none !important; }
  .secours-actions, .secours-vu, #secoursBtn { display: none !important; }
  .secours-liste { grid-template-columns: repeat(2, 1fr); }
}

.totp-etat {
  margin-top: 14px;
  font-size: 13px;
  color: var(--text-2);
}

.totp-etat b { color: var(--text); }

/* L'heure du dernier enregistrement automatique. Discrète par construction :
   elle rassure d'un coup d'œil et ne doit jamais attirer l'attention — sauf
   quand elle annonce un échec, qui est le seul moment où l'automatisme doit se
   faire entendre. */
.draft-saved {
  font-size: 12.5px;
  color: var(--text-3);
  transition: color .2s;
}

.draft-saved[data-etat="err"] { color: var(--err); font-weight: 600; }

/* La proposition d'harmonisation d'une adresse.

   Discrète : elle n'annonce pas une erreur, elle offre une écriture. Un bandeau
   d'alerte à côté d'une adresse parfaitement valable ferait douter d'une saisie
   qui n'a rien de faux — « BESANCON » est juste, « Besançon » est mieux. */
.harmonie {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-3);
}

.harmonie b { color: var(--text-2); font-weight: 600; }

.harmonie button {
  width: auto;
  padding: 2px 10px;
  font-size: 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
}

.harmonie button:hover { border-color: var(--accent); }

/* Une case à cocher et son libellé, sur la même ligne.

   La règle générale du produit étire tout champ de saisie sur la largeur de son
   conteneur — juste pour un texte, désastreux pour une case : elle passait
   au-dessus de son libellé, et un carnet de santé de cinq lignes devenait dix
   lignes illisibles. */
label.case {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
}

label.case + label.case { margin-top: 6px; }
label.case input { width: auto; margin: 0; flex: none; }

/* La journée d'une pension. Deux blocs courts plutôt qu'un tableau : à quatre
   lignes, un tableau coûte ses en-têtes pour ranger ce qu'on lit d'un coup. */
.journee-bloc { margin-top: 14px; }

.journee-titre {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-3);
}

.journee-titre span {
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    color: var(--text);
}

.journee-liste {
    margin: 6px 0 0;
    padding: 0;
    list-style: none;
}

.journee-liste li {
    padding: 3px 0;
    border-bottom: 1px solid var(--line);
    font-size: 14px;
}

.journee-liste li:last-child { border-bottom: 0; }

/* Une carte que le rôle ne peut pas enregistrer. Elle reste LISIBLE — on ne
   cache pas à quelqu'un les réglages sous lesquels il travaille — mais elle ne
   se laisse pas remplir pour rien. */
.carte-close { background: var(--surface-2, transparent); }

.carte-close .carte-close-note {
    margin: 8px 0 0;
    padding: 8px 10px;
    border-left: 2px solid var(--line);
    background: rgba(23, 32, 42, .03);
}

.carte-close input:disabled,
.carte-close select:disabled,
.carte-close textarea:disabled { opacity: .7; }
