/*
 * Habillage de l'application. Bootstrap 5.3 est chargé avant ce fichier et fournit la grille,
 * les composants et les variables de thème (--bs-*). On s'appuie sur ces variables partout pour
 * que le mode sombre fonctionne sans dupliquer les règles.
 */

/*
 * Inter, auto-hébergée (SIL Open Font License). Une seule fonte variable couvre 400 à 700.
 * Choisie pour ses chiffres tabulaires : les montants s'alignent en colonne, ce que la pile
 * système ne garantit pas d'un appareil à l'autre.
 */
@font-face {
    font-family: 'Inter';
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("../fonts/inter-latin-variable-1b3ec095194194abe1625b02c945e2f1.woff2") format('woff2');
}

:root {
    --bs-font-sans-serif: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

    /*
     * Échelle typographique, alignée sur le §2.2 du handover « Registre ». Application dense en
     * chiffres : on part de 15px plutôt que des 16px de Bootstrap, et on garde peu de paliers pour
     * que la hiérarchie reste lisible.
     */
    --bs-body-font-size: 0.9375rem;   /* 15px — texte courant, libellés de transaction */
    --fs-nav: 0.6875rem;              /* 11px — étiquette de cellule de navigation basse */
    --fs-micro: 0.75rem;              /* 12px — intitulés de section en capitales, badges, bandeau de jour */
    --fs-meta: 0.8125rem;             /* 13px — dates, texte secondaire, phrase d'explication */
    --fs-control: 0.875rem;           /* 14px — ligne de graphe, bouton secondaire, onglet */
    --fs-field: 1rem;                 /* 16px — valeur dans un champ (jamais moins : zoom iOS) */
    --fs-title: 1.0625rem;            /* 17px — nom de l'écran (h1), titre de partie (h2) */
    --fs-value: 1.25rem;              /* 20px — valeurs de tuile de premier niveau */
    --fs-value-sm: 1rem;              /* 16px — valeurs de tuile imbriquée */
    --fs-screen: 1.375rem;            /* 22px — titre d'état plein écran (accès refusé, choix du code) */
    --fs-hero: 2.5rem;                /* 40px — montant héroïque, plafond du clamp() */

    /*
     * **Toutes les couleurs vivent dans `telire-tokens.css`**, chargé juste avant ce fichier.
     *
     * Le lot 0 avait gardé quatre alias `--telire-accent`, `--telire-in`… le temps que les blocs
     * soient réécrits ; le lot 1 les a tous réécrits, et les alias sont tombés avec. Une règle de
     * ce fichier nomme donc son jeton, et il n'y a plus deux noms pour une couleur.
     *
     * Le rayon, lui, n'est plus une variable du tout : il vaut 0 partout, avec deux exceptions
     * écrites en dur là où elles s'appliquent — les pastilles d'identité (999px) et la cellule
     * « + » (12px). Une variable qui ne vaut qu'une seule valeur n'est plus une variable.
     */
    --telire-topbar-h: 3.5rem;        /* 56px */
    --telire-bottomnav-h: 4.25rem;    /* 68px */
}

/*
 * **Les ponts que `telire-tokens.css` a oubliés.**
 *
 * Bootstrap 5.3 ne lit pas `--bs-link-color` pour colorer un `<a>` : la règle est
 * `a { color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)) }`. Redéfinir la couleur
 * sans redéfinir son triplet ne change donc rien, et tous les liens de l'application sont restés au
 * bleu par défaut de la bibliothèque, dans les deux thèmes — vérifié au rendu.
 *
 * Même famille pour `--bs-body-color-rgb` (les couleurs `.text-body-*` en dérivent) et
 * `--bs-danger-rgb` / `--bs-success-rgb` (les fonds `.bg-*-subtle` et les anneaux de focus).
 *
 * Les triplets sont écrits en clair et non calculés : une variable CSS ne sait pas décomposer un
 * `#RRGGBB`, et ajouter une couche `color-mix()` pour ça coûterait plus que de recopier six
 * nombres qui sont, par construction, ceux du tableau §2.1 du handover.
 */
:root,
[data-bs-theme="light"] {
    --bs-link-color-rgb: 11, 74, 82;            /* --tl-accent #0B4A52 */
    --bs-link-hover-color-rgb: 11, 74, 82;
    --bs-body-color-rgb: 21, 25, 28;            /* --tl-ink    #15191C */
    --bs-body-bg-rgb: 246, 243, 236;            /* --tl-bg     #F6F3EC */
    --bs-success-rgb: 27, 107, 74;              /* --tl-in     #1B6B4A */
    --bs-danger-rgb: 163, 39, 47;               /* --tl-out    #A3272F */
}

[data-bs-theme="dark"] {
    --bs-link-color-rgb: 77, 212, 232;          /* --tl-accent #4DD4E8 */
    --bs-link-hover-color-rgb: 77, 212, 232;
    --bs-body-color-rgb: 236, 238, 239;         /* --tl-ink    #ECEEEF */
    --bs-body-bg-rgb: 16, 19, 20;               /* --tl-bg     #101314 */
    --bs-success-rgb: 74, 222, 128;             /* --tl-in     #4ADE80 */
    --bs-danger-rgb: 248, 113, 113;             /* --tl-out    #F87171 */
}

/*
 * Le thème sombre ne redéfinit rien ici : les jetons `--tl-*` basculent seuls sur
 * `[data-bs-theme="dark"]`, et les alias ci-dessus les suivent.
 */

body {
    /* `--bs-body-bg` est ponté sur `--tl-bg` par les jetons. L'ancien `--bs-tertiary-bg` était un
       gris de Bootstrap : il annulait le fond crème avant même qu'il s'applique. */
    background-color: var(--bs-body-bg);
    color: var(--tl-ink);
    -webkit-font-smoothing: antialiased;

    /*
     * Coquille d'application (§4 du handover) : `main` est la **seule** zone qui défile, l'en-tête
     * et la barre basse sont `flex: 0 0 auto`. C'est ce qui permet aux listes d'aller bord à bord
     * sans que la page entière glisse sous la barre du haut.
     *
     * `100dvh` et non `100vh` : sur un téléphone, `vh` compte la barre d'adresse du navigateur même
     * quand elle est rétractée, et la barre basse tombe alors sous le pli.
     *
     * **`height` et non `min-height`** : avec un minimum, `body` grandit avec son contenu, la
     * colonne dépasse la fenêtre et la barre basse part sous le pli — c'est exactement ce qui s'est
     * produit au premier essai. Il faut une hauteur *fixée* pour que `main` soit la seule zone qui
     * puisse déborder.
     */
    height: 100vh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* ------------------------------------------------- États d'interaction */

/*
 * Retour au toucher.
 *
 * Tout l'habillage interactif de ce fichier était en `:hover`, qui n'existe pas sur un téléphone :
 * appuyer sur une ligne ne produisait rien jusqu'au changement de page, et sur une connexion lente
 * on appuie deux fois. `:active` marche partout, y compris au doigt.
 *
 * Le halo gris d'Android se déclenche en plus du nôtre et déborde des coins arrondis : on le coupe
 * là où on fournit notre propre retour.
 */
.txn__link,
.app-bottomnav__item,
.app-topbar__icon-btn,
.btn,
.segmented .nav-link,
.details-block > summary {
    -webkit-tap-highlight-color: transparent;
    transition: background-color 0.12s ease, opacity 0.12s ease, transform 0.12s ease;
}

.txn__link:active { background: var(--tl-band); }
.app-bottomnav__item:active { background: var(--tl-band); }
.app-topbar__icon-btn:active { background: var(--tl-band); }
.btn:active { transform: scale(0.985); }

/*
 * Focus clavier.
 *
 * Plusieurs éléments posent `border: 0` pour l'esthétique — les boutons de la barre du haut, les
 * pastilles d'onglet — et perdaient du même coup l'anneau par défaut du navigateur. `:focus-visible`
 * et non `:focus` : au clic à la souris ou au doigt, l'anneau ne se justifie pas.
 */
:focus-visible {
    outline: 2px solid var(--tl-accent);
    outline-offset: 2px;
}

/* La barre du haut n'a plus de fond teal : elle n'a donc plus besoin d'un anneau de focus à elle,
   celui de la page se lit sur `--tl-surface` dans les deux thèmes. */

/* Barre de progression de Turbo : c'était la seule couleur de l'application qui n'était pas
   l'accent — elle héritait du bleu par défaut de la bibliothèque. */
.turbo-progress-bar {
    height: 3px;
    background-color: var(--tl-accent);
}

/* ---------------------------------------------------------------- Coquille */

/*
 * Barre du haut — 56 px, fond `--tl-surface`, filet bas `--tl-rule` (§4.0 du handover).
 *
 * Le teal plein a disparu avec la refonte « Registre » : c'était la seule nappe de couleur de
 * l'application, et elle poussait tout le reste — pastille blanche, anneau de focus blanc, halo de
 * pression translucide — à exister uniquement pour tenir dessus. Le fond de surface les rend tous
 * inutiles d'un coup.
 *
 * Elle ne défile plus : `body` est une colonne flex et `main` est la seule zone qui bouge. `sticky`
 * ne servirait donc plus à rien, et `flex: 0 0 auto` dit mieux ce qui se passe.
 */
/*
 * Le bandeau d'incarnation (« Vous voyez l'application comme Bineta »).
 *
 * `flex: 0 0 auto` comme la barre du haut : il est le premier enfant de `body`, qui est une colonne
 * flex dont `main` est la seule zone à défiler. Il ne défile donc pas — c'est le point : la
 * question qu'il répond (« suis-je moi ? ») se pose à n'importe quel moment du défilement.
 *
 * **La teinte est celle de l'avertissement, pas celle d'une sortie d'argent.** L'incarnation n'est
 * ni une erreur ni une perte : c'est un état inhabituel dont il faut se souvenir. Le rouge de
 * `--tl-out` est réservé à l'argent qui sort et aux gestes destructeurs, et le confondre ici
 * userait le seul rouge que l'application possède.
 *
 * Le texte porte l'information — « Rien ne peut être enregistré d'ici » —, jamais la couleur seule :
 * même règle que le « Échec » du journal de connexion.
 */
.impersonation {
    flex: 0 0 auto;
    z-index: 1040;
    padding: env(safe-area-inset-top) 0 0;
    background: var(--tl-warn);
    color: #fff;
}

/*
 * ⚠️ La zone sûre du haut n'appartient qu'au **premier** bandeau. `.app-topbar` la réclame elle
 * aussi, faute de savoir ce qu'il y a au-dessus : les deux cumulées laisseraient une bande morte au
 * milieu de l'écran, sous le bandeau d'incarnation, sur tout téléphone à encoche.
 */
.impersonation + .app-topbar {
    padding-top: 0;
}

/* Même colonne que la page : sur grand écran, le bandeau se collerait sinon aux bords. */
.impersonation__inner {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
    padding: 0.5rem 1rem;
}

/* Une pastille, pas une icône du jeu : le bandeau ne renvoie à aucun des objets de l'application. */
.impersonation__eye {
    flex: 0 0 auto;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentcolor;
}

.impersonation__text {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-meta);
    line-height: 1.35;
    text-wrap: pretty;
}

/* La seconde phrase passe à la ligne dès que la place manque, et le nom reste sur la première. */
.impersonation__note {
    opacity: 0.85;
}

/*
 * La sortie est un bouton, pas un lien de texte : c'est la seule chose à faire depuis ce bandeau,
 * et elle doit se toucher au pouce. 44 px de haut, minimum tactile du handover.
 */
.impersonation__exit {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    min-height: 2rem;
    padding: 0.25rem 0.75rem;
    border: 1px solid rgb(255 255 255 / 55%);
    border-radius: 999px;
    color: #fff;
    font-size: var(--fs-meta);
    font-weight: 600;
    text-decoration: none;
}

.impersonation__exit:hover,
.impersonation__exit:focus-visible {
    background: rgb(255 255 255 / 15%);
    color: #fff;
}

.app-topbar {
    flex: 0 0 auto;
    z-index: 1030;
    /* `block` et non `flex` : la barre peut porter un second niveau sous sa rangée principale —
       le feuilletage de mois de `/stats` — et une rangée flex les mettrait côte à côte. */
    display: block;
    /* La zone sûre s'ajoute au padding : avec une hauteur fixe elle écraserait le contenu. */
    padding: env(safe-area-inset-top) 0 0;
    background: var(--tl-surface);
    color: var(--tl-ink);
    border-bottom: 1px solid var(--tl-rule);
}

/* Aligne le contenu du bandeau sur la même colonne que la page, sinon il se colle
   aux bords de la fenêtre sur grand écran alors que le contenu reste centré. */
.app-topbar__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
    padding: 0 1rem;
    min-height: var(--telire-topbar-h);
}

/*
 * Le titre et la marque forment un seul bloc, et c'est lui qui rétrécit.
 *
 * `min-width: 0` est indispensable — un enfant de conteneur flex refuse de descendre sous sa
 * largeur de contenu sans lui, et `text-overflow` ne se déclenche jamais.
 */
.app-topbar__heading {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex: 1 1 auto;
    min-width: 0;
}

/*
 * Le titre de l'écran, et le seul `<h1>` de la page.
 *
 * Il porte un nom de membre sur `/membres/{id}` : sans troncature, « Boubacar Bailo BA »
 * pousserait le sélecteur de thème et la déconnexion hors de l'écran.
 */
.app-topbar__title {
    flex: 0 1 auto;
    /* `min-width: 0` et non une largeur plancher : un plancher de 6rem réservait la place même
       quand le titre était court, et « Accueil » se retrouvait à trois centimètres de la marque.
       C'est le facteur de rétrécissement de la marque (12 contre 1) qui la fait céder la première,
       et il suffit. */
    min-width: 0;
    font-size: var(--fs-title);
    font-weight: 650;
    letter-spacing: -0.01em;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * « TELIRE » en capitales discrètes, à côté du nom de l'écran.
 *
 * Le nom de l'application n'est plus le titre : il l'était sur les onze écrans, et la barre ne
 * disait donc jamais où l'on était. Il reste comme **marque**, au corps et à la couleur d'un repère
 * secondaire — et il s'efface le premier quand la place manque, avant le titre.
 */
.app-topbar__wordmark {
    /* Facteur de rétrécissement élevé : à facteurs égaux, le titre et la marque se réduisent
       proportionnellement et l'on obtient « Nouvelles dépen… TELI… », deux mots coupés au lieu
       d'un. La marque doit céder la première — c'est elle qui n'apprend rien. */
    flex: 0 12 auto;
    min-width: 0;
    font-size: var(--fs-micro);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--tl-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * Écran de détail : flèche de retour 40 × 40 à gauche, à l'accent (§4.0).
 *
 * Elle remplace la marque, pas le titre — un écran de détail dit d'où l'on revient, pas quelle
 * application on regarde. Voir `.app-shell--detail` pour l'autre moitié de la règle : ces écrans
 * n'ont pas de barre basse.
 */
.app-topbar__back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.5rem;
    height: 2.5rem;
    margin-left: -0.625rem;
    color: var(--tl-accent);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

.app-topbar__back .icon { width: 20px; height: 20px; }
.app-topbar__back:active { background: var(--tl-band); }

.app-topbar__actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex: none;
}

/*
 * Pastille d'initiales, lien vers « Mon compte ».
 *
 * `color` est **obligatoire** ici : c'est un `<a>`, et sans règle il prend la couleur de lien de
 * Bootstrap. Le bouton d'icône voisin s'en sort parce qu'il pose `color: inherit`.
 *
 * C'est l'une des deux seules formes arrondies de la refonte — elle porte des initiales, pas une
 * information tabulaire, et un carré s'y lirait comme une vignette.
 */
.app-topbar__user {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.875rem;
    height: 1.875rem;
    border-radius: 999px;
    background: var(--tl-accent-soft);
    color: var(--tl-accent);
    font-size: var(--fs-micro);
    font-weight: 650;
    letter-spacing: 0.02em;
    text-decoration: none;
}

.app-topbar__user:hover,
.app-topbar__user:focus { color: var(--tl-accent); }
.app-topbar__user:active { opacity: 0.7; }

/*
 * Boutons d'icône de la barre.
 *
 * 34 px de boîte visible, mais 44 px de zone cliquable : la cible tactile minimum se prend en
 * `padding`, jamais en taille (§2.4 du handover) — grossir le carré aurait fait une barre de 56 px
 * remplie de deux ronds gris.
 */
.app-topbar__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.125rem;
    height: 2.125rem;
    border: 0;
    padding: 0;
    background: transparent;
    color: var(--tl-muted);
    position: relative;
}

.app-topbar__icon-btn::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 2.75rem;
    height: 2.75rem;
    transform: translate(-50%, -50%);
}

.app-topbar__icon-btn:hover { color: var(--tl-ink); }
.app-topbar__icon-btn .icon { width: 19px; height: 19px; }

/* Une seule des deux icônes du sélecteur de thème est visible à la fois. */
[data-bs-theme="dark"] .icon-when-light { display: none; }
:root:not([data-bs-theme="dark"]) .icon-when-dark { display: none; }

/*
 * `main` est la seule zone qui défile.
 */
#main-content {
    flex: 1 1 auto;

    /*
     * **Les deux minimums sont obligatoires**, et pour deux raisons différentes.
     *
     * `min-height: 0` : dans un conteneur flex en colonne, un élément a par défaut
     * `min-height: auto`, c'est-à-dire « au moins la hauteur de mon contenu ». Il refuse donc de
     * rétrécir, `overflow-y` ne se déclenche jamais, et c'est la page entière qui pousse — la barre
     * basse passe sous le pli.
     *
     * `min-width: 0` : même règle sur l'autre axe. Les montants sont insécables (voir
     * MoneyExtension) ; sans ce zéro, la largeur minimale du contenu devient celle du plus long
     * d'entre eux, et tout l'écran s'élargit au-delà de la fenêtre.
     */
    min-height: 0;
    min-width: 0;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}

/*
 * La colonne de lecture reste bornée sur grand écran, mais la barre de défilement reste au bord de
 * la fenêtre : c'est l'intérieur qui est centré, pas la zone qui défile.
 *
 * La **gouttière de 16 px vit ici** et non sur chaque bloc (§2.3 : « gouttière horizontale
 * d'écran : 16 px »). Ce qui doit aller bord à bord — bandeau de jour, liste de transactions — la
 * déborde avec `.bleed`, qui est exactement l'inverse. Poser la gouttière sur chaque carte aurait
 * demandé de la reposer sur chaque bloc écrit ensuite, et d'y penser à chaque fois.
 */
#main-content > .app-shell__col {
    max-width: 720px;
    margin: 0 auto;
    padding: 1rem 1rem 1.5rem;
}

/* Écrans sans coquille (connexion, choix du code) : rien à compenser, et la colonne s'efface. */
#main-content.is-bare > .app-shell__col {
    max-width: none;
    padding: 0;
}

/*
 * Débord de gouttière. Les listes vont **bord à bord**, les cartes ont 16 px de marge (§2.3) :
 * c'est ce qui fait qu'un bandeau de jour coupe l'écran d'un bord à l'autre et qu'une carte flotte
 * dessus. Sur grand écran la colonne est bornée à 720 px, et le débord s'arrête donc avec elle —
 * ce qui est voulu : « bord à bord » veut dire « bord de la colonne », pas « bord du moniteur ».
 */
.bleed {
    margin-left: -1rem;
    margin-right: -1rem;
}

/* ------------------------------------------------------- Navigation basse */

/*
 * 68 px, une grille régulière de N colonnes égales (§4.0).
 *
 * Elle ne flotte plus : `body` étant une colonne flex, elle est simplement le dernier bloc. Un
 * `position: fixed` demandait de réserver sa hauteur en bas de `main` — et cette réserve était
 * fausse dès qu'une barre d'action collante s'ajoutait par-dessus.
 */
.app-bottomnav {
    flex: 0 0 auto;
    z-index: 1030;
    display: flex;
    justify-content: center;
    background: var(--tl-surface);
    border-top: 1px solid var(--tl-rule);
    padding-bottom: env(safe-area-inset-bottom);
}

.app-bottomnav__inner {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    width: 100%;
    max-width: 720px;
}

.app-bottomnav__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    min-height: var(--telire-bottomnav-h);
    padding: 0.5rem 0.25rem;
    color: var(--tl-muted);
    text-decoration: none;
    font-size: var(--fs-nav);
    font-weight: 450;
    line-height: 1.1;
    text-align: center;
}

.app-bottomnav__item:hover { color: var(--tl-ink); }

.app-bottomnav__item.is-active {
    color: var(--tl-accent);
    font-weight: 600;
}

.app-bottomnav__item .icon { width: 21px; height: 21px; }

/*
 * Bouton central de saisie (D-UX-2). Il occupe une case comme les autres pour que la barre reste
 * une grille régulière, mais son contenu est un bloc : c'est une action, pas une destination,
 * et rien ne doit laisser croire qu'on y « est ».
 *
 * 52 × 40 à `border-radius: 12px` — l'autre exception au rayon 0, avec la pastille d'identité. Le
 * rond flottant à ombre portée de l'existant appartenait à l'autre direction, écartée.
 */
.app-bottomnav__item--action {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
}

.app-bottomnav__fab {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.25rem;
    height: 2.5rem;
    padding: 0;
    border: 0;
    border-radius: 12px;
    background: var(--tl-accent);
    color: var(--tl-on-accent);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.12s ease;
}

.app-bottomnav__fab:active { transform: scale(0.94); }
.app-bottomnav__fab .icon { width: 22px; height: 22px; }

/* Tuiles de la feuille de choix du « + » */
.choice-tile {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem;
    color: inherit;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

/* Chaque porte est cernée : deux cibles pleine largeur, pas deux lignes d'une liste. */
.choice-tile { border: 1px solid var(--tl-rule); }
.choice-tile + .choice-tile { margin-top: 0.5rem; }
.choice-tile:hover, .choice-tile:active { background: var(--tl-band); color: inherit; }
.choice-tile > span { flex: 1 1 auto; display: flex; flex-direction: column; }
.choice-tile small { font-size: var(--fs-meta); color: var(--tl-muted); }
.choice-tile > .icon:first-child { width: 22px; height: 22px; }

/* Une action et non une destination : mêmes repères visuels, remise à plat du bouton natif. */
.choice-tile--button {
    width: 100%;
    border: 0;
    background: none;
    text-align: left;
    font: inherit;
}

.choice-tile--button > span { align-items: flex-start; }

/* ------------------------------------------------------------- Composants */

/*
 * Une carte est un rectangle blanc sur le fond crème, cerné d'un filet. **Aucune ombre** : la
 * refonte n'en porte qu'une seule dans toute l'application, sous la feuille modale (§2.3). Le
 * relief venait de l'ombre et du rayon ; il vient maintenant du contraste des deux fonds.
 */
.card {
    background-color: var(--tl-surface);
    border: 1px solid var(--tl-rule);
    border-radius: 0;
    box-shadow: none;
}

/*
 * Intitulé de section. C'est un `<h2>` depuis le lot C — il l'était déjà à l'œil, il ne l'était pas
 * pour un lecteur d'écran, et aucune page ne portait de titre du tout.
 *
 * La remise à plat est explicite : les styles de titre de Bootstrap ne doivent pas transparaître,
 * l'échelle typographique du fichier fait foi.
 */
/*
 * Partie d'écran — le niveau **au-dessus** de `.section-title` (10/08/2026).
 *
 * `/statistiques` porte trois parties qui ne parlent pas de la même chose : tout l'historique, une
 * année, puis les années entre elles. Elles étaient annoncées par des `.section-title`,
 * c'est-à-dire du même petit gris capitalisé que « Mois par mois » ou « En moyenne, chaque mois » —
 * la page se lisait donc comme une suite plate de sections, et la coupure ne se sentait pas.
 */
.screen-part {
    margin: 1.375rem 0 0.875rem;
}

/*
 * La tête d'une partie : le titre à gauche, et ce qui la commande à droite — des onglets sur
 * l'accueil, rien ailleurs. Alignés sur le **bas** et non au centre : les onglets sont soulignés,
 * et c'est ce soulignement qui doit se poser sur la ligne de base du titre.
 */
.screen-part__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
}

/*
 * Le repère de partie. **Un trait accentué et court, pas un filet gris pleine largeur** : mesuré,
 * `--bs-border-color` ne donne que 1,24 de contraste sur le fond de page en clair et 1,63 en
 * sombre — le filet était bien là, et invisible. `--tl-accent` tient 8,1 et 7,6.
 *
 * Il est passé **à gauche du titre** avec la refonte, au lieu d'être posé au-dessus (§4.1 : « un
 * tiret 20 × 2 px à gauche »). En bloc au-dessus il prenait une ligne à lui pour dire une chose
 * qui n'en vaut pas une ; à côté du titre il se lit comme un tiret cadratin, c'est-à-dire comme de
 * la ponctuation.
 */
.screen-part__title {
    display: flex;
    align-items: center;
    gap: 0.5625rem;
    margin: 0;
    min-width: 0;
    font-size: var(--fs-title);
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.25;
}

.screen-part__title::before {
    content: "";
    flex: 0 0 auto;
    width: 20px;
    height: 2px;
    background: var(--tl-accent);
}

.screen-part__lead {
    margin: 0.25rem 0 0;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    text-wrap: pretty;
}

.section-title {
    font-size: var(--fs-micro);
    font-weight: 600;
    line-height: 1.3;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--tl-muted);
    margin: 0 0 0.5rem;
}

/* ------------------------------------------------------ 3.1 Bandeau de solde */

/*
 * **Le solde est un chiffre nu sur le fond crème**, plus une nappe de couleur.
 *
 * Il était un dégradé teal — vert quand positif, rouge quand négatif — avec du blanc dedans. Trois
 * choses en découlaient, toutes mauvaises : la couleur du *fond* portait le signe, donc le montant
 * lui-même ne le portait pas ; le bandeau était la seule surface colorée de l'écran, donc il
 * écrasait tout ce qui venait après ; et à l'impression, où les navigateurs ne rendent pas les
 * fonds, il sortait blanc sur blanc.
 *
 * Désormais c'est une surface blanche cernée d'un filet, et **c'est le montant qui est coloré**,
 * `--tl-in` ou `--tl-out` selon le signe. La couleur dit la même chose, à l'endroit où on la lit.
 */
.balance {
    padding: 1.25rem 1.125rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
    color: var(--tl-ink);
    /* La marge du haut est celle de la colonne, qu'un bandeau en tête d'écran annule (voir
       `--screen`). */

    /* Conteneur de requête : le corps du montant se règle sur la largeur du bandeau, pas sur
       celle de la fenêtre. Voir `.balance__amount`. */
    container-type: inline-size;
}

/*
 * Variante négative : un filet gauche épais, et **pas un chiffre rouge nu** (§3.1).
 *
 * Un solde négatif veut dire « on vous a versé plus que votre part acquise », pas « vous devez de
 * l'argent ». Le rouge seul dit le second. Le filet marque la ligne comme étant à lire, et
 * `.balance__hint` y passe à l'encre pleine plutôt qu'au gris secondaire : la phrase qui explique
 * n'est pas une note de bas de page, c'est la moitié de l'information.
 */
/*
 * **En tête d'écran, le bandeau va bord à bord et n'a qu'un filet bas** (§3.1, et les captures 01,
 * 03 et 05 le montrent toutes les trois).
 *
 * C'est ce qui le distingue du même bandeau posé dans une carte : en tête, il *est* le haut de
 * l'écran, et un cadre l'aurait fait flotter à 16 px sous une barre du haut dont il prolonge la
 * surface. Il remonte donc dans la gouttière de la colonne, d'où le `.bleed` et l'annulation de
 * son padding vertical d'en haut.
 */
.balance--screen {
    margin-top: -1rem;
    padding: 1.625rem 1rem 1.25rem;
    border-left: 0;
    border-right: 0;
    border-top: 0;
}

.balance--negative {
    border-left: 3px solid var(--tl-out);
}

/* Le filet de la variante négative survit au bord à bord : c'est lui qui porte le sens. */
.balance--screen.balance--negative {
    border-left: 3px solid var(--tl-out);
}

.balance--negative .balance__hint {
    color: var(--tl-ink);
}

.balance__label {
    font-size: var(--fs-micro);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--tl-muted);
    margin-bottom: 0;
}

/*
 * Le montant est **insécable de bout en bout** — espaces fines insécables entre les milliers,
 * insécable avant « F CFA » (voir MoneyExtension). Il ne peut donc ni se couper ni se réduire :
 * à corps fixe, un solde du foyer à neuf chiffres mesure 319 px pour 288 px disponibles sur un
 * téléphone de 360 px, et sortait du bandeau.
 *
 * Le corps est donc fluide, indexé sur la largeur du bandeau (`cqi`) et non sur celle de la
 * fenêtre : le même bandeau vit en pleine page et dans une carte. Le plafond garde les 40 px de
 * l'échelle dès que la place existe ; le plancher évite qu'un montant démesuré ne finisse en
 * corps de note.
 */
.balance__amount {
    margin-top: 0.5rem;
    font-size: clamp(1.625rem, 10.5cqi, var(--fs-hero));
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.025em;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * C'est le montant qui porte le signe, maintenant que le fond ne le porte plus.
 *
 * **Un zéro ne porte aucun des deux.** Il n'est ni une entrée ni une sortie, et le peindre en vert
 * — ce que fait un simple `net < 0 ? out : in` — lui donnerait le sens « de l'argent est entré »
 * sur un mois où il ne s'est rien passé. Les gabarits n'ajoutent donc de modificateur qu'à partir
 * du moment où le montant en a un.
 */
.balance__amount--in { color: var(--tl-in); }
.balance__amount--out { color: var(--tl-out); }

.balance__hint {
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    margin: 0.375rem 0 0;
    text-wrap: pretty;
}

/*
 * Lien logé dans le bandeau, sous un filet. Il était en blanc sur le dégradé ; il passe à l'accent,
 * souligné par un filet clair — la forme que prend un lien partout ailleurs dans la refonte.
 */
.balance__link {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    margin-top: 1.125rem;
    padding-bottom: 2px;
    border-bottom: 1px solid var(--tl-accent-line);
    color: var(--tl-accent);
    font-size: var(--fs-control);
    font-weight: 550;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

.balance__link:hover, .balance__link:active { color: var(--tl-accent); opacity: 0.8; }
.balance__link .icon { width: 14px; height: 14px; }

/* ------------------------------------------------------- 3.2 Ligne de total */

/*
 * La décomposition d'un solde : un libellé à gauche, un montant à droite, alignés sur la **ligne
 * de base** et non sur le centre — les deux corps sont différents (13 px contre 16 px) et un
 * centrage les ferait flotter l'un par rapport à l'autre.
 *
 * Elles s'empilent dans un bloc `.total-rows` qui porte le fond et le filet ; chaque ligne ne
 * porte qu'un filet bas, et la dernière n'en a pas.
 */
.total-rows {
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
}

/* Dans un bandeau de solde, les lignes prolongent le bloc : ni fond ni cadre à elles. */
.balance .total-rows {
    margin-top: 1.25rem;
    background: none;
    border: 0;
    border-top: 1px solid var(--tl-rule-soft);
}

.total-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.8125rem 1rem;
    border-bottom: 1px solid var(--tl-rule-soft);
}

.balance .total-row { padding-left: 0; padding-right: 0; }

.total-row:last-child { border-bottom: 0; }

.total-row__label {
    display: flex;
    align-items: center;
    gap: 0.4375rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    min-width: 0;
}

.total-row__label .icon { width: 15px; height: 15px; }

.total-row__value {
    flex: 0 0 auto;
    font-size: var(--fs-value-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.total-row__value--in { color: var(--tl-in); }
.total-row__value--out { color: var(--tl-out); }

/* Tuiles de totaux */
.stat-tile {
    height: 100%;
    padding: 0.875rem 1rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);

    /* Même raison que pour `.balance` : une tuile occupe la moitié de la page ou sa totalité
       selon l'écran, et c'est cette largeur-là qui décide du corps du montant. */
    container-type: inline-size;
}

/* Ces libellés nomment la valeur qu'ils surmontent : ils ne doivent pas être plus discrets
   que les dates. Ils sont donc au corps du texte secondaire, pas au corps « micro ». */
.stat-tile__label {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    font-size: var(--fs-meta);
    font-weight: 500;
    color: var(--tl-muted);
    margin-bottom: 0.2rem;
}

.stat-tile__label .icon { width: 14px; height: 14px; }

/* Fluide pour la même raison que `.balance__amount` : le montant ne se coupe pas, c'est donc au
   corps de céder. Le plafond reste la valeur de l'échelle. */
.stat-tile__value {
    font-size: clamp(0.875rem, 11cqi, var(--fs-value));
    font-weight: 600;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}

/* Tuiles imbriquées dans une carte de période : un cran en dessous des totaux du haut,
   sinon les deux niveaux de hiérarchie se disputent l'attention. */
.stat-tile--compact {
    padding: 0.625rem 0.75rem;
    background: var(--tl-band);
    border-color: transparent;
}

.stat-tile--compact .stat-tile__value { font-size: clamp(0.8125rem, 12cqi, var(--fs-value-sm)); }

.stat-tile__value--in { color: var(--tl-in); }
.stat-tile__value--out { color: var(--tl-out); }

.icon { width: 16px; height: 16px; flex: none; }
.icon--in { color: var(--tl-in); }
.icon--out { color: var(--tl-out); }

/* Dans un bouton, l'icône doit s'aligner sur la ligne de base du texte. */
.btn .icon { vertical-align: -0.15em; margin-right: 0.15rem; }

/* ------------------------------------------------- Onglets, pastilles, encarts */

/*
 * **Un onglet est un mot souligné, pas une gélule** (§5).
 *
 * Ils étaient des pastilles blanches à ombre portée sur un lit gris — un composant de plus, avec sa
 * couleur de fond, son rayon et son relief, pour dire une chose que deux traits disent mieux. Le
 * soulignement se lit à la même vitesse et ne pèse rien.
 *
 * La classe garde son nom, `.segmented` : elle est posée sur un `ul.nav` de Bootstrap dont le
 * comportement (`data-bs-toggle="tab"`) est intact, et la renommer aurait touché des gabarits sans
 * rien changer à ce qu'ils font.
 */
.segmented {
    display: inline-flex;
    flex: none;
    flex-wrap: nowrap;
    gap: 1rem;
    padding: 0;
    background: none;
    border: 0;
}

.segmented .nav-link {
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    padding: 0 0 0.375rem;
    background: none;
    font-size: var(--fs-meta);
    font-weight: 450;
    color: var(--tl-muted);
}

.segmented .nav-link.active {
    background: none;
    border-bottom-color: var(--tl-accent);
    color: var(--tl-ink);
    font-weight: 650;
    box-shadow: none;
}

/*
 * Barre d'onglets pleine largeur, posée sous un titre d'écran ou sous la barre du haut. Le filet
 * bas court d'un bord à l'autre et le soulignement de l'onglet actif se pose *dessus*, d'où le
 * `margin-bottom: -1px` : sans lui les deux traits s'empilent et l'actif paraît deux fois plus
 * épais que le repos.
 */
.tabs {
    display: flex;
    gap: 1rem;
    border-bottom: 1px solid var(--tl-rule);
}

.tabs__item {
    display: inline-block;
    padding: 0 0 0.375rem;
    margin-bottom: -1px;
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    font-size: var(--fs-control);
    font-weight: 450;
    color: var(--tl-muted);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

.tabs__item.is-active {
    border-bottom-color: var(--tl-accent);
    color: var(--tl-ink);
    font-weight: 650;
}

/*
 * Les onglets dans la barre du haut, sur un second niveau — comme le feuilletage de `/stats`
 * (§4.3). Ce sont des **commandes de l'écran**, pas un de ses contenus : ils ne doivent pas
 * défiler avec ce qu'ils gouvernent, sinon revenir à l'autre onglet demande de remonter d'abord.
 *
 * Ils n'ont pas de filet à eux : la barre du haut porte déjà le sien, et le soulignement de
 * l'onglet actif vient se poser dessus — d'où le `margin-bottom` négatif conservé sur l'item.
 * Deux traits empilés feraient paraître l'actif deux fois plus épais qu'au repos.
 *
 * Les cellules sont **d'égale largeur et centrées** : à deux onglets alignés à gauche, la moitié
 * droite de la barre reste vide et la cible du second est deux fois plus courte que celle du
 * premier.
 */
.tabs--topbar {
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
    padding: 0 0.75rem;
    border-bottom: 0;
    gap: 0;
}

.tabs--topbar .tabs__item {
    flex: 1 1 0;
    padding-bottom: 0.5625rem;
    text-align: center;
}

/*
 * Pastille de filtre (§5). À ne pas confondre avec un onglet : un onglet **remplace** ce qu'on
 * regarde, une pastille **restreint** ce qu'on regarde. D'où deux formes différentes — l'onglet est
 * un mot souligné, la pastille est un rectangle cerné qui se remplit quand il est actif.
 */
.pill {
    display: inline-block;
    padding: 0.4375rem 0.8125rem;
    border: 1px solid var(--tl-rule);
    background: transparent;
    color: var(--tl-muted-strong);
    font-size: var(--fs-meta);
    font-weight: 550;
    text-decoration: none;
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
}

.pill.is-active {
    background: var(--tl-accent);
    border-color: var(--tl-accent);
    color: var(--tl-on-accent);
}

.pill-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/*
 * Encart à filet gauche. Il dit une chose que l'écran ne peut pas montrer : pourquoi une flèche est
 * désactivée, ce qu'un geste va faire au solde, ce qu'on n'a pas le droit de modifier.
 *
 * Trois teintes, et **aucune n'est le rouge d'une sortie d'argent** : `--accent` explique,
 * `--warn` avertit, `--out` annonce une action destructive. Cette dernière porte aussi un fond,
 * parce qu'elle accompagne toujours un bouton qu'on ne doit pas appuyer par inadvertance.
 */
.callout {
    padding: 0.75rem 0.875rem;
    border-left: 3px solid var(--tl-accent);
    background: var(--tl-accent-soft);
    font-size: var(--fs-meta);
    color: var(--tl-ink);
    text-wrap: pretty;
}

.callout > :last-child { margin-bottom: 0; }

.callout__title {
    display: block;
    margin-bottom: 0.125rem;
    font-weight: 600;
}

.callout--warn {
    border-left-color: var(--tl-warn);
    background: none;
    border-top: 1px solid var(--tl-warn-line);
    border-right: 1px solid var(--tl-warn-line);
    border-bottom: 1px solid var(--tl-warn-line);
}

.callout--out {
    border-left-color: var(--tl-out);
    background: var(--tl-out-band);
}

/*
 * Le terme d'une définition, dans une légende (« Versement — de l'argent qui vous a été remis »).
 *
 * En gras **et** suivi d'un tiret cadratin : le gras seul ne survit ni à l'impression en noir et
 * blanc bon marché ni à un lecteur d'écran, qui lit la phrase d'une traite. Le tiret est posé en
 * `::after` plutôt que dans la phrase, pour qu'aucun appelant n'ait à s'en souvenir.
 */
.callout__term {
    font-weight: 600;
}

.callout__term::after {
    content: " — ";
    font-weight: 400;
}

/* Sans fond : pour une note qui accompagne une liste, là où un aplat ferait une seconde carte. */
.callout--plain {
    background: none;
    border-top: 1px solid var(--tl-rule);
    border-right: 1px solid var(--tl-rule);
    border-bottom: 1px solid var(--tl-rule);
}

/*
 * Confirmation destructive **sur la ligne même**, jamais dans une modale (§5).
 *
 * C'est ce qui remplace les huit modales de confirmation de la fiche membre. La règle qui compte
 * n'est pas la couleur, c'est le texte : le bloc doit dire **la conséquence chiffrée** — « le solde
 * remontera de 60 000 F CFA » — et non « êtes-vous sûr ». Une modale ne pouvait pas la dire, elle
 * ne savait pas de quelle ligne elle parlait.
 *
 * Annuler à gauche, l'action en plein à droite : l'ordre de lecture met le retour en arrière sous
 * le pouce qui vient de se tromper.
 */
.confirm-inline {
    padding: 0.875rem 1rem;
    background: var(--tl-out-band);
    border-top: 1px solid var(--tl-out-line);
    border-bottom: 1px solid var(--tl-out-line);
    font-size: var(--fs-meta);
    color: var(--tl-ink);
    text-wrap: pretty;
}

.confirm-inline__actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

.confirm-inline__actions > * { flex: 1 1 0; }

/* --------------------------------------- 3.3 / 3.4 Jour et ligne de transaction */

/*
 * **Le bandeau de jour** (§3.3) remplace les cartes par jour de l'existant. Sa moitié droite est
 * ce qui justifie le motif : elle porte le **net du jour** — « 3 lignes · −30 000 » — et c'est ce
 * qui permet à l'administrateur de vérifier sa saisie d'un regard, sans rien ouvrir.
 *
 * La forme de base vient de `telire-tokens.css` ; ici on ne pose que ce qui manque.
 */
.day-band__net {
    flex: 0 0 auto;
    font-size: var(--fs-meta);
    font-weight: 450;
    letter-spacing: 0;
    text-transform: none;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * Un bandeau de jour et sa liste forment un bloc : la liste porte les filets haut et bas, le
 * bandeau n'en a pas — ils se toucheraient et feraient un trait double.
 */
.day-block + .day-block { margin-top: 1.125rem; }

/* Liste de transactions — remplace les tableaux, illisibles sur mobile */
.txn-list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--tl-surface);
    border-top: 1px solid var(--tl-rule);
    border-bottom: 1px solid var(--tl-rule);
}

/*
 * Dans une carte, la liste ne se cerne pas : la carte le fait déjà, et deux filets se toucheraient.
 * Les lignes s'alignent alors sur le contenu de la carte plutôt que de porter leur propre
 * gouttière — sinon elles seraient rentrées de 16 px de plus que le titre qui les surmonte.
 */
.card .txn-list { border: 0; background: none; }
.card .txn__link { padding-left: 0; padding-right: 0; }

.txn {
    border-bottom: 1px solid var(--tl-rule-soft);
}

.txn:last-child { border-bottom: 0; }

/*
 * Toute la ligne est cliquable : cible tactile confortable, pas de petit bouton à viser.
 *
 * `align-items: center` et non `baseline` : le libellé fait deux lignes quand il est long, et un
 * alignement sur la ligne de base collait alors le montant à la *première* d'entre elles, très
 * au-dessus du milieu visuel de la ligne.
 */
.txn__link {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.8125rem 1rem;
    color: inherit;
    text-decoration: none;
}

.txn__link:hover { background: var(--tl-band); color: inherit; }

/* Ligne en lecture seule : rien ne doit laisser croire qu'elle mène quelque part. */
.txn__link--static, .txn__link--static:hover, .txn__link--static:active { background: none; }

/*
 * **Le chevron est le seul signal d'interactivité** (§3.4), et il n'apparaît que si la ligne mène
 * quelque part : présent chez l'administrateur, absent chez le membre. Il n'est donc pas décoratif,
 * et il ne doit jamais être posé « pour l'équilibre » sur une ligne inerte — c'est exactement ce
 * que `/ma-part` interdit.
 *
 * Pleine opacité : à 0,6 il passait sous le seuil de contraste et ne se voyait plus au soleil.
 */
.txn__chevron {
    flex: 0 0 auto;
    align-self: center;
    width: 16px;
    height: 16px;
    color: var(--tl-muted);
}

/* Le libellé absorbe l'espace restant : sans cela, space-between répartissait les trois
   blocs et le montant flottait à une position différente selon la longueur du libellé. */
.txn__main {
    flex: 1 1 auto;
    min-width: 0;
}

/*
 * Le libellé passe à la ligne ; il n'est plus coupé à l'ellipse (15/09/2026).
 *
 * La coupe à une ligne (§3.4) gardait la colonne des montants droite, mais sur un téléphone de
 * 360 px le montant insécable et le chevron laissent ~170 px au libellé : « Dépense journalière Du
 * 15-09-2026 » devenait « Dépense journal… », et deux lignes d'un même jour ne se distinguaient
 * plus. Un libellé qu'on ne lit pas ne dit rien. La colonne reste droite horizontalement — le
 * montant est `flex: 0 0 auto`, calé à droite — et `align-items: center` le garde au milieu de la
 * ligne quand elle grandit.
 */
.txn__label {
    font-size: var(--bs-body-font-size);
    font-weight: 500;
    overflow-wrap: anywhere;
}

.txn__meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    margin-top: 0.125rem;
}

/*
 * La sous-ligne « type · date » passe à la ligne, comme le libellé au-dessus d'elle (15/09/2026).
 *
 * Coupée à l'ellipse, « Pain petit-déjeuner/Petit déjeuner élèves » perdait la date sur un
 * téléphone — la seule chose qui distingue deux lignes de même type. Même raisonnement que
 * `.txn__label`.
 *
 * Une classe à elle plutôt que `.txn__meta > span` : le conteneur porte aussi des badges, qui eux
 * restent insécables.
 */
.txn__meta-text {
    min-width: 0;
    overflow-wrap: anywhere;
}

/*
 * Badge de ligne. Un filet et rien dedans : le fond plein en faisait une pastille de couleur, et
 * dans une liste où chaque ligne peut en porter deux, deux aplats côte à côte pèsent plus que le
 * montant qu'ils accompagnent.
 */
.txn__tag {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid var(--tl-rule);
    background: none;
    color: var(--tl-muted-strong);
    font-size: var(--fs-micro);
    white-space: nowrap;
}

.txn__amount {
    flex: 0 0 auto;
    font-size: var(--bs-body-font-size);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.txn__amount--in { color: var(--tl-in); }
.txn__amount--out { color: var(--tl-out); }

/*
 * **Un seul bouton d'action par ligne** (§3.4) : le `⋮`, 36 × 36. L'existant en posait deux —
 * modifier et supprimer — sur des lignes de 44 px de haut, soit 88 px de cibles pour 300 px de
 * ligne, et le libellé passait à la ligne pour leur faire place.
 */
.txn__action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--tl-muted);
    -webkit-tap-highlight-color: transparent;
}

.txn__action:hover { color: var(--tl-ink); background: var(--tl-band); }
.txn__action .icon { width: 18px; height: 18px; margin: 0; }

/* ------------------------------------------------------------------ Repli */

/*
 * Un bloc qui se déplie, en `<details>` natif : **aucun JavaScript**. Bootstrap propose `collapse`,
 * qui demande un script, un identifiant et deux attributs pour faire ce qu'une balise fait seule —
 * et qui, contrairement à `<details>`, ne se déplie pas quand le navigateur cherche du texte dans
 * la page.
 *
 * La règle du §5 s'applique à son résumé : **le libellé donne toujours le compte exact de ce qu'il
 * révèle**. « 12 sorties · 4 entrées » et les lignes du dépli viennent du même objet, ce qui est la
 * seule façon de garantir qu'ils ne divergent pas.
 */
.fold {
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
}

.fold__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.9375rem 1rem;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
}

/* Le triangle par défaut : Safari le pose par un pseudo-élément à lui, Chrome par `list-style`. */
.fold__summary::-webkit-details-marker { display: none; }

.fold__summary:hover { background: var(--tl-band); }

.fold__main { min-width: 0; }

.fold__title {
    display: block;
    font-size: var(--bs-body-font-size);
    font-weight: 550;
}

.fold__meta {
    display: block;
    margin-top: 0.125rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    font-variant-numeric: tabular-nums;
}

/* Le chevron pointe vers le bas fermé, vers le haut ouvert : c'est le sens du geste à venir. */
.fold__chevron {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    color: var(--tl-accent);
    transform: rotate(90deg);
    transition: transform 0.15s ease-out;
}

.fold[open] > .fold__summary .fold__chevron { transform: rotate(-90deg); }

.fold__body {
    padding: 0 0 0.75rem;
    border-top: 1px solid var(--tl-rule-soft);
}

/* Dans un repli, un bandeau de jour n'a pas de gouttière à déborder : la carte est déjà la borne. */
.fold__body .day-block:first-child { margin-top: 0; }

/*
 * La phrase de pied de l'accueil. Elle n'est ni un état vide ni une section : c'est une note sur la
 * fraîcheur des comptes, et elle se lit après tout le reste.
 */
.home__footnote {
    margin: 0.875rem 0 0;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    text-wrap: pretty;
}

/*
 * `.empty-state` est **une note grise centrée**, et c'est ce qu'elle a toujours été : quarante
 * gabarits s'en servent pour un « Aucune dépense sur cette période. » comme pour un « Page 2 sur
 * 7 » ou un lien de bas de bloc. Son nom promet plus qu'elle ne tient, mais la renommer serait
 * quarante modifications sans rien changer à l'écran.
 */
.empty-state {
    padding: 1.5rem 0;
    text-align: center;
    color: var(--tl-muted);
    font-size: var(--fs-meta);
}

/*
 * **Le vrai état vide du §4.2**, et il est distinct de la note ci-dessus. C'est la friction §6.5 du
 * brief : « les états vides sont textuels et ternes ». Ici c'est un bloc, avec une icône, un titre
 * en encre pleine et une phrase qui **donne la sortie** — « choisissez un autre mois avec les
 * flèches, ou saisissez une ligne » — pas seulement le constat.
 *
 * Bordure **en pointillés** : elle dit « il devrait y avoir quelque chose ici », là où un filet
 * plein dirait « voici un bloc, et il est vide ».
 */
.empty-box {
    padding: 1.625rem 1.25rem;
    text-align: center;
    background: var(--tl-surface);
    border: 1px dashed var(--tl-rule-strong);
    color: var(--tl-muted);
    font-size: var(--fs-meta);
}

.empty-box .icon { width: 24px; height: 24px; color: var(--tl-muted); }

.empty-box__title {
    margin: 0.625rem 0 0;
    font-size: var(--bs-body-font-size);
    font-weight: 550;
    color: var(--tl-ink);
}

.empty-box__hint {
    margin: 0.25rem 0 0;
    text-wrap: pretty;
}

/* ------------------------------------- Liste des types de dépenses (lot D) */

/*
 * Une liste de libellés courts, sans montant ni action — donc ni `.txn-list`, qui suppose une
 * ligne cliquable avec un montant à droite, ni une simple `<ul>`, dont les puces se lisent mal
 * à côté d'une icône.
 *
 * Deux colonnes dès qu'il y a la place : quatorze libellés courts en une colonne font une page
 * qu'on fait défiler sans la lire. `auto-fill` avec un minimum de 14rem plutôt qu'une bascule à
 * un point d'arrêt précis — la carte qui les contient n'a pas toujours la même largeur.
 */
.type-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: 0.35rem 1rem;
    list-style: none;
    margin: 0;
    padding: 0;
}

.type-list__item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--fs-meta);
    line-height: 1.35;
}

/* Le second groupe est en retrait : l'information y est « ceci ne compte pas », elle n'a pas à
   peser autant que la première. L'icône et le titre du bloc portent déjà la distinction — la
   couleur ne la porte jamais seule. */
.type-list--muted { color: var(--tl-muted); }

/* ------------------------------------------------------------ Formulaires */

.form-card { position: relative; }

/*
 * Le numéro de ligne d'une carte de saisie. Il était une gélule teal accrochée au bord supérieur
 * de la carte, à cheval sur son filet ; il redevient un **intitulé**, dans le flux, en capitales
 * discrètes comme tous les autres repères de section (§4.6 : « étiquette Ligne N »).
 *
 * Ce qui se perd : rien. Ce qui se gagne : la carte n'a plus besoin d'une marge haute pour laisser
 * dépasser une pastille, et le numéro se lit dans le même mouvement que le premier libellé.
 */
.form-card__index {
    display: block;
    margin-bottom: 0.75rem;
    font-size: var(--fs-micro);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--tl-muted);
}

/*
 * Barre d'actions collée au-dessus de la navigation basse.
 *
 * Réservée aux écrans à **formulaire unique** — la saisie de dépenses et de revenus. Deux barres
 * collantes sur la même page collent toutes les deux : elles se superposent au défilement et
 * s'empilent avec la navigation basse. Les pages qui portent deux formulaires (`/mon-compte`,
 * `/membres/{id}`) utilisent la variante `--inline`, qui reste dans le flux.
 */
.form-actions {
    position: sticky;
    /*
     * Les écrans à formulaire unique sont désormais des **écrans de détail**, donc sans barre
     * basse : la réserve de 68 px qu'on prenait ici laissait un trou sous la barre d'actions.
     */
    bottom: 0;
    display: flex;
    gap: 0.5rem;
    padding: 0.75rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
    box-shadow: none;
}

.form-actions--inline {
    position: static;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
}

.form-actions .btn { flex: 1 1 0; }

/*
 * Champ de code : le bouton de révélation est greffé par app.js, jamais écrit dans un gabarit.
 * Le rembourrage à droite réserve sa place, sinon les derniers chiffres passent dessous.
 */
.code-field { position: relative; }

.code-field .form-control { padding-right: 2.75rem; }

.code-field__reveal {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--tl-muted);
}

.code-field__reveal:hover { color: var(--tl-ink); }
.code-field__reveal .icon { width: 19px; height: 19px; }

.form-label { font-size: var(--fs-meta); font-weight: 550; margin-bottom: 0.3125rem; }

/* 16 px et jamais moins dans un champ : en dessous, iOS grossit la page à la mise au point et ne
   la redescend pas (§2.2). */
.form-control, .form-select { border-radius: 0; font-size: var(--fs-field); padding: 0.6875rem 0.75rem; }

.btn { border-radius: 0; font-weight: 550; padding: 0.75rem 0.875rem; font-size: var(--bs-body-font-size); }
.btn-sm { padding: 0.375rem 0.625rem; font-size: var(--fs-meta); }

/*
 * Erreur de champ (§4.6) : une bordure sur le champ, un message dessous avec son icône, **et pas
 * de résumé en tête de formulaire** — sur un téléphone, un résumé pousse le premier champ hors de
 * l'écran et oblige à faire deux fois le chemin.
 */
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--tl-out);

    /* Bootstrap greffe sa propre icône d'alerte **dans** le champ, en image de fond. Avec le
       message qui en porte déjà une, l'erreur se disait deux fois à deux centimètres d'écart — et
       celle du champ mangeait la place des derniers chiffres d'un montant. */
    background-image: none;
    padding-right: 0.75rem;
}

.invalid-feedback {
    display: flex;
    align-items: flex-start;
    gap: 0.375rem;
    font-size: var(--fs-meta);
    color: var(--tl-out);
}

.invalid-feedback .icon { width: 15px; height: 15px; margin-top: 2px; }

/*
 * **Le teal est la seule couleur d'action** (§2.1). Un bouton n'est jamais vert — le vert dit
 * « argent qui entre » — et jamais rouge sauf s'il détruit quelque chose.
 *
 * `--tl-on-accent` porte l'encre : en thème clair c'est du blanc, en sombre un teal très foncé,
 * parce que l'accent y devient un cyan clair sur lequel du blanc serait illisible. Les deux
 * surcharges `[data-bs-theme="dark"]` qui disaient cela en dur ont disparu avec le jeton.
 */
.btn-accent {
    background: var(--tl-accent);
    border-color: var(--tl-accent);
    color: var(--tl-on-accent);
    font-weight: 600;
}

.btn-accent:hover, .btn-accent:focus {
    background: var(--tl-accent);
    border-color: var(--tl-accent);
    color: var(--tl-on-accent);
    filter: brightness(1.08);
}

/* Bouton secondaire : un contour neutre, pas le gris de Bootstrap. */
.btn-outline-secondary {
    border-color: var(--tl-rule);
    color: var(--tl-ink);
    background: transparent;
}

.btn-outline-secondary:hover,
.btn-outline-secondary:focus {
    background: var(--tl-band);
    border-color: var(--tl-rule);
    color: var(--tl-ink);
}

/* Action destructive : contour `--tl-out`, jamais un aplat — sauf dans une confirmation, où le
   plein signale que c'est bien le geste final. */
.btn-outline-danger {
    border-color: var(--tl-out-line);
    color: var(--tl-out);
    background: transparent;
}

.btn-outline-danger:hover,
.btn-outline-danger:focus {
    background: var(--tl-out-band);
    border-color: var(--tl-out);
    color: var(--tl-out);
}

.btn-danger {
    background: var(--tl-out);
    border-color: var(--tl-out);
    color: #fff;
}

[data-bs-theme="dark"] .btn-danger { color: #2A1B1D; }

/* ------------------------------------------------- Code réinitialisé */

/*
 * Le code engendré par une réinitialisation, montré une seule fois (D-UX-5).
 *
 * Il partait dans un message d'alerte, au corps du texte courant, noyé dans une phrase de trois
 * lignes — l'information la moins reproductible de l'application présentée comme la plus banale.
 * Il est ici au corps du solde, en chiffres tabulaires, avec de quoi le copier.
 */
.code-reveal {
    padding: 1rem;
    border: 1px solid var(--tl-accent);
    background: var(--tl-accent-soft);
}

.code-reveal__label {
    font-size: var(--fs-micro);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--tl-muted);
    margin: 0 0 0.35rem;
}

.code-reveal__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.code-reveal__code {
    font-size: var(--fs-hero);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: 0.12em;
    font-variant-numeric: tabular-nums;
    /* Sélectionnable au doigt : le bouton « Copier » exige un contexte sécurisé, la sélection non. */
    user-select: all;
}

.code-reveal__hint {
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    margin: 0.6rem 0 0;
}

/* -------------------------------------------------- Navigation par mois */

/*
 * Feuilletage de mois ou d'année : deux flèches et un libellé, dans un bloc de surface.
 *
 * **Une flèche hors bornes est désactivée, jamais masquée** (§2.4). C'est une règle d'ergonomie et
 * non d'esthétique : si elle disparaissait, l'autre glisserait à sa place, et le pouce qui vient
 * d'appuyer sur « mois précédent » appuierait sur « mois suivant » sans avoir bougé.
 */
.month-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.625rem;
    padding: 0.625rem 0.75rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
}

/*
 * Dans la barre du haut, le feuilletage est un second niveau de l'en-tête et non un bloc de la
 * page : il n'a donc ni fond ni cadre à lui, la barre les fournit.
 */
.month-nav--topbar {
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
    padding: 0 0.75rem 0.75rem;
    background: none;
    border: 0;
}

.month-nav__arrow {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    border: 1px solid var(--tl-accent-line);
    color: var(--tl-accent);
}

.month-nav__arrow .icon { width: 17px; height: 17px; margin: 0; }

.month-nav__label {
    flex: 1 1 auto;
    min-width: 0;
    text-align: center;
    font-size: var(--bs-body-font-size);
    font-weight: 600;
}

/* --------------------------------------------------------------- Connexion */

.login-shell {
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.login-card { width: 100%; max-width: 24rem; }

.login-logo {
    width: 3.25rem;
    height: 3.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: var(--tl-accent);
    color: #fff;
    font-weight: 700;
    margin-bottom: 0.75rem;
}

/* En sombre l'accent devient clair : du blanc dessus serait illisible. */
[data-bs-theme="dark"] .login-logo { color: #06232a; }

/* ------------------------------------------- Retrait de ligne et navigation */

/*
 * L'en-tête d'une carte de saisie : « Ligne 2 » à gauche, les actions de ligne à droite.
 *
 * Il était à cheval sur le bord supérieur de la carte, en `position: absolute` — deux pastilles
 * flottantes qui obligeaient la carte à réserver du vide en haut. Dans le flux, il ne coûte rien et
 * se lit dans le même mouvement que le premier libellé.
 */
.form-card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.625rem;
    margin-bottom: 0.75rem;
}

.form-card__head .form-card__index { margin-bottom: 0; }

.form-card__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.875rem;
    height: 1.875rem;
    padding: 0;
    border: 1px solid var(--tl-rule);
    background: var(--tl-surface);
    color: var(--tl-out);
}

.form-card__remove:hover {
    background: var(--tl-out);
    border-color: var(--tl-out);
    color: #fff;
}

.form-card__remove .icon { width: 15px; height: 15px; }

.back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-bottom: 0.75rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    text-decoration: none;
}

.back-link:hover { color: var(--tl-ink); }
.back-link .icon { width: 15px; height: 15px; }

/* ------------------------------------------------ 3.6 Feuille modale */

/*
 * **Une seule couche flottante à la fois, jamais une modale par-dessus une autre** (§3.6).
 *
 * La feuille monte du bas et s'arrête à 88 % de la hauteur : ce qui reste de voile au-dessus est
 * cliquable et ferme — c'est le geste le plus naturel sur un téléphone, et le seul qui soit à
 * portée du pouce quand la feuille est haute.
 *
 * C'est **la seule ombre de toute l'application** (§2.3). Elle a une raison : la feuille flotte
 * au-dessus du contenu, et sans ombre son filet supérieur se confondrait avec n'importe quel filet
 * de liste.
 */
.modal-content {
    border-radius: 0;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
}

.modal-backdrop.show { opacity: 1; background: var(--tl-scrim); }

.modal-header {
    padding: 0.875rem 1rem;
    border-bottom: 1px solid var(--tl-rule);
}

.modal-title { font-size: var(--bs-body-font-size); font-weight: 650; }
.modal-body { padding: 0.75rem; }
.modal-footer form { margin: 0; }

/*
 * `.sheet` — la feuille proprement dite, ancrée en bas. `.modal-dialog` de Bootstrap est centré par
 * défaut ; ici on le colle au bas de l'écran, pleine largeur, et c'est le panneau qui défile.
 */
.modal-dialog.sheet {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
}

.sheet .modal-content {
    max-height: 88vh;
    max-height: 88dvh;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    box-shadow: 0 -16px 40px rgba(0, 0, 0, 0.18);
}

/* Le panneau défile, pas la page derrière. `min-height: 0` pour la même raison que `#main-content` :
   sans lui, l'élément refuse de descendre sous la hauteur de son contenu et `overflow` est mort. */
.sheet .modal-body {
    min-height: 0;
    overflow-y: auto;
}

/*
 * En-tête de feuille : une croix, et **une flèche de retour quand la feuille a plusieurs étapes**
 * (§3.6). C'est ce qui remplace les trois modales d'ajout de la fiche membre par une seule — la
 * flèche ramène au choix des trois portes, sans refermer.
 */
.sheet__back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    margin-left: -0.375rem;
    padding: 0;
    border: 0;
    background: none;
    color: var(--tl-accent);
}

.sheet__back .icon { width: 18px; height: 18px; margin: 0; }

/* ---------------------------------------------------------------- Membres */

/* .txn__link aligne sur la ligne de base ; l'avatar et le bouton doivent être recentrés. */
.member-avatar {
    flex: none;
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 999px;
    background: var(--tl-accent-soft);
    color: var(--tl-accent);
    font-size: var(--fs-meta);
    font-weight: 600;
}

.txn__link > .btn { flex: none; align-self: center; }
.txn__link > .btn .icon { width: 15px; height: 15px; }

/*
 * **Un avertissement n'est pas rouge.** C'est la règle absolue du §2.1, reprise du §4.3 du brief :
 * le vert et le rouge ne servent qu'au *sens de l'argent*. « Code à changer » était un aplat rouge
 * plein — la même couleur qu'une sortie, sur la même ligne qu'un solde en vert — et il se lisait
 * comme une dette.
 */
.txn__tag--warn {
    border-color: var(--tl-warn-line);
    color: var(--tl-warn);
}

/* « Dans le partage » : le teal, qui est la seule couleur d'action et de rattachement. */
.txn__tag--share {
    border-color: var(--tl-accent-line);
    color: var(--tl-accent);
}

/* Un compte désactivé : en retrait, mais pas caché — il garde sa place et son solde. L'étiquette
   « Désactivé » porte l'information ; l'opacité ne fait que la confirmer. */
.txn--muted .txn__label,
.txn--muted .txn__amount { opacity: 0.55; }

/* Le détail mensuel court sur plusieurs années : replié par défaut. */
.details-block > summary {
    cursor: pointer;
    padding: 0.6rem 0;
    font-size: var(--fs-meta);
    font-weight: 600;
    color: var(--tl-muted);
}

.details-block > summary:hover { color: var(--tl-ink); }

/* ------------------------------------------------- Barres du détail mensuel */

/*
 * Barres proportionnelles en CSS pur : Chart.js ou équivalent casserait le « zéro Node »
 * d'AssetMapper pour une trentaine de points de données. La largeur est posée en style inline par
 * le gabarit, c'est la seule information qu'il ait à transmettre.
 */
.month-bar {
    height: 4px;
    margin-top: 0.35rem;
    background: var(--tl-track);
    overflow: hidden;
}

.month-bar__fill {
    display: block;
    height: 100%;
    background: var(--tl-accent);
}

/* ------------------------------------------ Cases à cocher qui parlent (§4.13) */

/*
 * Une case à cocher qui **dit son effet en une phrase**. C'étaient deux mots avec une aide en petit
 * gris ; l'une décide de ce que les filles acquièrent, l'autre ne décide de rien — et rien ne le
 * disait. Une case dont on ignore l'effet se coche au hasard.
 */
.check-row {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    border-bottom: 1px solid var(--tl-rule-soft);
}

.check-row:last-child { border-bottom: 0; }

/* 22 px, contour franc : une case de 13 px se rate au pouce. */
.check-row__box {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    margin: 2px 0 0;
    border: 1px solid var(--tl-rule-strong);
    border-radius: 0;
    accent-color: var(--tl-accent);
}

.check-row__main { min-width: 0; cursor: pointer; }

.check-row__label {
    display: block;
    font-size: var(--bs-body-font-size);
    font-weight: 550;
}

.check-row__hint {
    display: block;
    margin-top: 0.125rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    text-wrap: pretty;
}

/* ---------------------------------------- Signature brute (§4.10) */

/*
 * La chaîne d'agent complète, **rangée et non supprimée**.
 *
 * Traduite en français au-dessus, elle se lit ; entière ici, elle reste consultable quand la
 * traduction ne suffit pas. `word-break: break-all` parce qu'une signature n'a pas d'espaces où
 * couper et déborderait la colonne.
 */
.raw-agent { margin-top: 0.375rem; }

.raw-agent > summary {
    font-size: var(--fs-micro);
    color: var(--tl-muted);
    cursor: pointer;
    list-style: none;
}

.raw-agent > summary::-webkit-details-marker { display: none; }
.raw-agent > summary:hover { color: var(--tl-ink); }

.raw-agent code {
    display: block;
    margin-top: 0.25rem;
    padding: 0.5rem 0.625rem;
    background: var(--tl-band);
    color: var(--tl-muted-strong);
    font-size: 12px;
    word-break: break-all;
}

/* -------------------------------------- Force du code (§4.9, indicatif) */

/*
 * Les trois segments de force du choix de code.
 *
 * **Ils ne bloquent rien**, et c'est structurel : `CodePolicy` est la règle et elle exige quatre
 * chiffres. Un segment qui refuserait en dessous de six écrirait la règle à deux endroits, et les
 * deux divergeraient le jour où l'une bouge. Ce que montrent ces barres est un **conseil**, et la
 * phrase sous elles le dit en toutes lettres.
 *
 * `aria-hidden` : la même information est écrite juste en dessous, en français. Trois barres sans
 * texte ne disent rien à un lecteur d'écran.
 */
.strength {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    margin-top: 0.5rem;
}

.strength__bar {
    height: 4px;
    background: var(--tl-track);
}

.strength__bar.is-on { background: var(--tl-in); }

/* ------------------------------------------------ Mon compte (§4.8) */

/*
 * Le réglage d'apparence à trois segments (D-R3), **une phrase par option**.
 *
 * L'existant n'avait qu'un bouton lune/soleil muet : il ne disait ni ce qu'il ferait, ni dans quel
 * état on était, ni qu'un troisième état existait. Trois cellules qui se nomment et s'expliquent
 * coûtent trois lignes de plus et répondent aux trois questions.
 *
 * L'état actif est posé par `app.js` (`data-theme-choice`), pas par le serveur : le choix vit dans
 * `localStorage`, que le serveur ne voit pas.
 */
.theme-choice {
    display: grid;
    gap: 1px;
    background: var(--tl-rule);
    border: 1px solid var(--tl-rule);
}

.theme-choice__item {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 0.25rem 0.75rem;
    padding: 0.8125rem 1rem;
    border: 0;
    background: var(--tl-surface);
    color: var(--tl-ink);
    text-align: left;
}

.theme-choice__item .icon {
    grid-row: span 2;
    width: 20px;
    height: 20px;
    color: var(--tl-muted);
}

.theme-choice__label { font-size: var(--bs-body-font-size); font-weight: 550; }

.theme-choice__hint {
    grid-column: 2;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
}

/*
 * Le segment actif prend l'accent **sur son filet gauche et son fond**, pas en inversant la
 * cellule : trois cellules dont une pleine feraient de la couleur le seul repère, et le libellé
 * y perdrait son contraste.
 */
.theme-choice__item.is-active {
    background: var(--tl-accent-soft);
    box-shadow: inset 3px 0 0 var(--tl-accent);
}

.theme-choice__item.is-active .icon,
.theme-choice__item.is-active .theme-choice__label { color: var(--tl-accent); }

/*
 * Un formulaire déplié à la demande. Il remplace une ligne de la liste, il ne s'ajoute pas en
 * dessous : la ligne et son formulaire sont la même chose à deux états.
 */
.unfold {
    padding: 0.875rem 1rem 1rem;
    background: var(--tl-band);
    border-bottom: 1px solid var(--tl-rule-soft);
}

.unfold:last-child { border-bottom: 0; }

.unfold__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}

.unfold__title { font-size: var(--bs-body-font-size); font-weight: 600; }

.unfold__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    color: var(--tl-muted);
}

.unfold__close:hover { color: var(--tl-ink); }
.unfold__close .icon { width: 17px; height: 17px; }

/* La pastille d'identité en tête d'écran personnel : 48 px au lieu de 36 (§4.8). */
.member-avatar--lg {
    width: 3rem;
    height: 3rem;
    font-size: var(--fs-control);
}

/* « — cet appareil » : un repère, pas un badge. Il accompagne l'appareil, il ne le remplace pas. */
.attempt__here { color: var(--tl-accent); font-weight: 550; }

/* ------------------------------------------------ Saisie express (§4.6) */

/*
 * Le montant en héroïque. C'est **un vrai champ**, pas un affichage piloté par le pavé : sans
 * JavaScript on tape dedans au clavier du téléphone et l'écran fonctionne. Un écran de saisie qui
 * ne saisit plus quand un script ne charge pas est le pire des défauts silencieux.
 */
.amount-hero {
    padding: 1.25rem 1.125rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
    container-type: inline-size;
}

.amount-hero__row {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    margin-top: 0.5rem;
}

/*
 * Le champ n'a ni cadre ni fond : le bloc les porte déjà. Sa taille suit celle du bloc, en unités
 * de conteneur — même règle que `.balance__amount`, et pour la même raison : un montant à neuf
 * chiffres est insécable de bout en bout, c'est le corps qui doit céder.
 */
.amount-hero__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    color: var(--tl-out);
    font-size: clamp(1.75rem, 11cqi, var(--fs-hero));
    font-weight: 600;
    letter-spacing: -0.025em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;

    /* Les flèches d'un `input[type=number]` volent la moitié de la largeur sur certains
       navigateurs, et un montant ne s'incrémente pas de un. */
    appearance: textfield;
}

.amount-hero__input::-webkit-outer-spin-button,
.amount-hero__input::-webkit-inner-spin-button { appearance: none; margin: 0; }

.amount-hero__input:focus { outline: none; box-shadow: none; }

.amount-hero__unit {
    flex: 0 0 auto;
    font-size: var(--fs-value-sm);
    font-weight: 550;
    color: var(--tl-muted);
}

/* Le pendant vert, sur `/revenus/nouveaux`. */
.amount-hero--in .amount-hero__input { color: var(--tl-in); }

/*
 * Le pavé 3 × 4.
 *
 * **Les cellules sont séparées par un fond, pas par des bordures** (§4.6) : deux cellules voisines
 * portant chacune sa bordure font un trait double. Une grille à `gap: 1px` posée sur un fond
 * `--tl-rule` donne un filet unique, et la grille elle-même fait le dessin.
 */
.keypad {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1px;
    margin-top: 1px;
    background: var(--tl-rule);
    border: 1px solid var(--tl-rule);
}

.keypad__key {
    padding: 0.9375rem 0;
    border: 0;
    background: var(--tl-surface);
    color: var(--tl-ink);
    font-size: 1.25rem;
    font-weight: 550;
    font-variant-numeric: tabular-nums;
    -webkit-tap-highlight-color: transparent;

    /* La sélection du texte d'une touche pendant une frappe rapide fait apparaître la loupe de
       sélection iOS par-dessus le pavé. */
    user-select: none;
}

.keypad__key:active { background: var(--tl-band); }
.keypad__key--erase { color: var(--tl-out); }

/*
 * Une pastille qui **choisit** : un bouton radio habillé. Le `<input>` reste dans le flux pour le
 * clavier et les lecteurs d'écran, il est seulement rendu invisible — `display: none` le sortirait
 * de l'ordre de tabulation et le rendrait inatteignable autrement qu'à la souris.
 */
.pill--choice { cursor: pointer; }

.pill--choice input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.pill--choice:has(input:checked) {
    background: var(--tl-accent);
    border-color: var(--tl-accent);
    color: var(--tl-on-accent);
}

.pill--choice:has(input:focus-visible) {
    outline: 2px solid var(--tl-accent);
    outline-offset: 2px;
}

/*
 * Un raccourci « en un geste ». C'est un bouton de soumission : **un seul appui enregistre**, sans
 * JavaScript. C'est exactement ce que la friction §7 demande — le geste quotidien ne doit pas
 * coûter quatre champs.
 */
.quick-entry {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    padding: 0.8125rem 1rem;
    border: 0;
    border-bottom: 1px solid var(--tl-rule-soft);
    background: none;
    color: inherit;
    text-align: left;
}

.quick-entry:last-child { border-bottom: 0; }
.quick-entry:hover, .quick-entry:active { background: var(--tl-band); }
.quick-entry > .icon { flex: 0 0 auto; width: 19px; height: 19px; color: var(--tl-accent); }

.quick-entry__sigle {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.875rem;
    height: 1.875rem;
    border-radius: 999px;
    background: var(--tl-accent-soft);
    color: var(--tl-accent);
    font-size: var(--fs-micro);
    font-weight: 650;
}

.quick-entry__main { flex: 1 1 auto; min-width: 0; }

.quick-entry__name {
    display: block;
    font-size: var(--bs-body-font-size);
    font-weight: 550;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.quick-entry__detail {
    display: block;
    margin-top: 0.0625rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
}

.quick-entry__amount {
    flex: 0 0 auto;
    font-size: var(--fs-value-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * « Ajouter une ligne » : **en pointillés**, comme `.empty-box`. C'est une place à remplir, pas un
 * bloc — un filet plein en ferait une carte vide de plus.
 */
.add-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    width: 100%;
    padding: 0.75rem;
    border: 1px dashed var(--tl-rule-strong);
    background: none;
    color: var(--tl-accent);
    font-size: var(--fs-control);
    font-weight: 550;
}

.add-row:hover { background: var(--tl-band); }
.add-row .icon { width: 17px; height: 17px; }

/*
 * La barre d'action gagne ses chiffres (§4.6) : résumé à gauche, total à droite, puis le bouton.
 * Elle passe donc en colonne — les trois sur une ligne rognent le bouton sous 360 px.
 */
.form-actions--summary { flex-direction: column; align-items: stretch; gap: 0.625rem; }

.form-actions__figures {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

.form-actions__summary { font-size: var(--fs-meta); color: var(--tl-muted); }

.form-actions__total {
    font-size: var(--fs-value-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * **Bootstrap dessine l'erreur deux fois.** `.form-control.is-invalid` reçoit une icône d'alerte en
 * `background-image` dans le champ ; le message du §4.6 en porte déjà une, et l'icône du champ
 * mange la place des derniers chiffres d'un montant. Déjà neutralisé au lot 1 ; rappelé ici parce
 * que c'est l'écran qui en souffrait.
 */
.form-actions--summary .btn { flex: 1 1 auto; }

/* --------------------------------------------- Recherche et filtre (§4.7) */

/*
 * Le champ de recherche de `/membres`. C'est un `<form method="get">` : il marche sans JavaScript,
 * il se partage, et il survit au rechargement — la recherche est un paramètre d'URL comme le reste
 * de l'état de l'écran.
 *
 * `min-width: 0` sur le champ n'est pas décoratif : un enfant de conteneur flex refuse de descendre
 * sous sa largeur intrinsèque sans lui, et le bouton sortirait de la colonne sur un téléphone.
 */
.search-field {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.375rem 0.375rem 0.75rem;
    background: var(--tl-surface);
    border: 1px solid var(--tl-rule);
}

.search-field__icon {
    flex: 0 0 auto;
    width: 17px;
    height: 17px;
    color: var(--tl-muted);
}

.search-field .form-control {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.3125rem 0;
    border: 0;
    background: none;
}

.search-field .form-control:focus { box-shadow: none; }
.search-field .btn { flex: 0 0 auto; }

/*
 * Le rappel d'un filtre actif. Un écran filtré qui ne dit pas qu'il l'est se lit comme un écran
 * incomplet — et on cherche longtemps le membre qu'il ne montre pas.
 */
.filter-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.5rem 0.75rem;
    background: var(--tl-band);
    font-size: var(--fs-meta);
    color: var(--tl-muted-strong);
}

.filter-bar__clear {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--tl-accent);
    font-weight: 550;
    text-decoration: none;
    white-space: nowrap;
}

.filter-bar__clear .icon { width: 14px; height: 14px; }

/* ---------------------------------------------- Ligne de membre (§4.7) */

/*
 * La moitié droite d'une ligne de membre : le solde, et le profil sous lui.
 *
 * `text-align: right` et non un alignement de flex : les montants d'une colonne doivent tomber les
 * uns sous les autres, et le profil qui les légende avec eux.
 */
.member-row__figure {
    flex: 0 0 auto;
    text-align: right;
    min-width: 0;
}

.member-row__profile {
    margin-top: 0.0625rem;
    font-size: var(--fs-micro);
    color: var(--tl-muted);
    white-space: nowrap;
}

/*
 * **« Pas de part » en toutes lettres, jamais un tiret** (§4.7).
 *
 * Un tiret dans une colonne de montants se lit comme un zéro, c'est-à-dire comme un solde soldé —
 * alors que ces comptes n'ont pas de part du tout (D6). Il prend le corps d'un montant pour tenir
 * la ligne de base de la colonne, mais l'encre discrète : ce n'est pas un chiffre.
 */
.member-row__noshare {
    font-size: var(--fs-value-sm);
    font-weight: 550;
    color: var(--tl-muted);
    white-space: nowrap;
}

/* ------------------------------------------- Feuille rendue par le serveur */

/*
 * **La feuille du §3.6, sans Bootstrap et sans JavaScript.**
 *
 * C'est la friction §6.4 du brief, prise à la racine. La mécanique d'avant — trois modales, un
 * `openModal` renvoyé par le contrôleur, un `data-modal-autoshow` posé sur la bonne, et un retrait
 * de cet attribut avant la mise en cache de Turbo faute de quoi une navigation arrière rouvrait
 * une modale que personne n'avait demandée — était la partie la plus fragile de l'interface.
 *
 * Ici l'ouverture **est un paramètre d'URL**. La feuille est du HTML rendu ou non rendu : elle
 * n'a pas d'état à restaurer, un 422 la re-rend déjà ouverte sur le bon formulaire avec ses
 * erreurs dedans, et Turbo peut mettre la page en cache telle quelle sans rien à défaire.
 *
 * Corollaire : le voile et la croix sont des **liens** vers la même page sans le paramètre. Le
 * piège n°3 du plan — `data-bs-dismiss="modal"` sur un `<a>` ne fait rien — ne s'applique donc
 * plus ici : il n'y a plus de délégué Bootstrap dans l'histoire.
 */
.sheet-layer {
    position: fixed;
    inset: 0;
    z-index: 1050;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

/*
 * Le voile. C'est un `<a>` qui couvre tout : fermer en touchant à côté est le geste attendu, et il
 * ne coûte pas une ligne de script.
 */
.sheet-layer__scrim {
    position: absolute;
    inset: 0;
    background: var(--tl-scrim);
    /* Un lien sans texte reste focalisable ; il porte son intitulé en `aria-label`. */
    display: block;
}

.sheet-layer__panel {
    position: relative;
    width: 100%;
    max-width: 720px;
    margin: 0 auto;
    max-height: 88dvh;
    overflow-y: auto;
    background: var(--tl-surface);
    border-top: 1px solid var(--tl-rule);

    /*
     * **La seule ombre de toute l'application**, et elle a une raison : sans elle, le filet
     * supérieur de la feuille se confondrait avec n'importe quel filet de liste, et la feuille ne
     * se lirait pas comme une couche au-dessus de la page.
     */
    box-shadow: 0 -12px 32px rgb(0 0 0 / 22%);
    padding-bottom: env(safe-area-inset-bottom);
}

.sheet-layer__head {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.8125rem 1rem;
    border-bottom: 1px solid var(--tl-rule);
}

.sheet-layer__title {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    font-size: var(--bs-body-font-size);
    font-weight: 650;
}

.sheet-layer__icon-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    color: var(--tl-muted);
    border: 0;
    background: none;
    -webkit-tap-highlight-color: transparent;
}

.sheet-layer__icon-btn:hover { color: var(--tl-ink); background: var(--tl-band); }
.sheet-layer__icon-btn .icon { width: 18px; height: 18px; margin: 0; }

.sheet-layer__body { padding: 0.75rem 1rem 1rem; }

/*
 * La page derrière ne défile pas quand la feuille est ouverte. `:has()` évite d'ajouter une classe
 * sur `<body>` depuis le gabarit — donc de toucher au gabarit de base pour un état qui n'appartient
 * qu'à un écran. Un navigateur qui ne le comprend pas laisse simplement la page défiler dessous,
 * ce qui n'empêche rien.
 */
body:has(.sheet-layer) { overflow: hidden; }

/*
 * Les trois portes de la feuille. Elles sont annoncées **par leur effet sur le solde** — « De
 * l'argent remis à Bineta, son solde baisse » — et non par leur nom d'entité : « Nouveau
 * versement » ne dit pas dans quel sens ça pousse, et c'est exactement la question qu'on se pose
 * au moment de choisir.
 */
.sheet-choice {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    width: 100%;
    padding: 0.875rem;
    text-align: left;
    border: 1px solid var(--tl-rule);
    background: var(--tl-surface);
    color: inherit;
    text-decoration: none;
}

.sheet-choice + .sheet-choice { margin-top: 0.5rem; }
.sheet-choice:hover, .sheet-choice:active { background: var(--tl-band); color: inherit; }
.sheet-choice > .icon { flex: 0 0 auto; width: 20px; height: 20px; margin-top: 2px; }
.sheet-choice__main { flex: 1 1 auto; min-width: 0; }

.sheet-choice__name {
    display: block;
    font-size: var(--bs-body-font-size);
    font-weight: 600;
}

.sheet-choice__effect {
    display: block;
    margin-top: 0.125rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    text-wrap: pretty;
}

/* ------------------------------------------------ Panneau de ligne (§4.4) */

/*
 * Ce qui remplace les **huit modales de confirmation** de la fiche membre.
 *
 * Deux niveaux, tous deux en paramètre d'URL : le `⋮` déplie les actions de la ligne, « Supprimer »
 * arme la confirmation sur la ligne même. Une confirmation de suppression est exactement le genre
 * d'état qu'on ne veut pas voir s'évaporer à un rechargement ou à une navigation arrière — c'est
 * ce que faisaient les modales.
 *
 * Le panneau prolonge sa ligne : il porte le fond de bande, pas de cadre à lui.
 */
.row-panel {
    padding: 0.75rem 1rem 0.875rem;
    background: var(--tl-band);
    border-bottom: 1px solid var(--tl-rule-soft);
}

.row-panel__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.row-panel__actions > * { flex: 1 1 8rem; }

/*
 * La confirmation destructive : **jamais une modale** (§5). Fond `--tl-out-band`, filet gauche
 * `--tl-out-line`, et surtout **la conséquence chiffrée** — « le solde remontera de 60 000 F CFA :
 * ce montant ne sera plus considéré comme reçu ». Annuler à gauche, l'action en plein à droite.
 */
.row-panel--danger {
    background: var(--tl-out-band);
    border-left: 3px solid var(--tl-out);
}

.row-panel__title {
    display: block;
    margin-bottom: 0.125rem;
    font-size: var(--bs-body-font-size);
    font-weight: 600;
}

.row-panel__text {
    margin: 0 0 0.75rem;
    font-size: var(--fs-meta);
    color: var(--tl-ink);
    text-wrap: pretty;
}

/* ------------------------------------------- Part acquise par année (§4.4) */

/*
 * Une année close est une **carte repliée portant son total** : elle ne bouge plus, on ne l'ouvre
 * que pour vérifier quelque chose. Seule l'année en cours est dépliée.
 *
 * Le détail donnait six mois récents puis tout le reste en vrac. En février, ces six mois sont
 * quatre mois d'une année close et deux de l'année en cours, sans que rien ne le dise.
 */
.share-year + .share-year,
.movement-year + .movement-year { margin-top: 0.75rem; }

/*
 * Le lien qui révèle les mois à zéro. Son libellé **donne leur compte exact**, et ce compte vient
 * de la liste elle-même (`ShareYear::getZeroCount()`), pas d'un calcul à côté.
 */
.zero-months {
    display: block;
    padding: 0.625rem 1rem;
    border-top: 1px solid var(--tl-rule-soft);
    font-size: var(--fs-meta);
    color: var(--tl-accent);
    font-weight: 550;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
}

.zero-months::-webkit-details-marker { display: none; }
.zero-months:hover { background: var(--tl-band); }

/* -------------------------------------------- Connexion résumée (§4.10) */

/*
 * Une tentative de connexion, dite en français. La puce carrée porte l'issue — et elle la porte
 * **en plus** du libellé, jamais à sa place : une pastille de couleur seule ne dit rien à qui ne
 * distingue pas le vert du rouge.
 */
.attempt {
    display: flex;
    align-items: flex-start;
    gap: 0.625rem;
    padding: 0.6875rem 1rem;
    border-bottom: 1px solid var(--tl-rule-soft);
}

.attempt:last-of-type { border-bottom: 0; }

.attempt__dot {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-top: 0.4375rem;
    background: var(--tl-in);
}

.attempt__dot--failed { background: var(--tl-out); }

.attempt__main { min-width: 0; }

.attempt__when {
    font-size: var(--fs-control);
    font-weight: 550;
}

.attempt__device {
    margin-top: 0.0625rem;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
}

/* --------------------------------------- Répartition par poste (§4.3) */

/*
 * Un poste de dépense ou de revenu et son poids. Distinct de `.chart-row`, qui compare **deux
 * séries** sur une même période : ici il n'y en a qu'une, et la forme s'en trouve changée.
 *
 * Le libellé et le montant partagent la première ligne, la barre court sous eux sur toute la
 * largeur, et la part en pourcentage ferme le bloc avec, s'il y a lieu, l'étiquette « dans le
 * partage ». En `.chart-row`, le montant vivait à côté de la barre et la rognait d'autant : sur un
 * téléphone, un montant à neuf chiffres ne laissait à la barre qu'un tiers de la largeur, ce qui
 * écrase justement les écarts qu'elle est là pour montrer.
 */
.poste-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.poste__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.625rem;
}

.poste__name {
    min-width: 0;
    font-size: var(--fs-control);
    font-weight: 500;
    text-wrap: pretty;
}

.poste__amount {
    flex: 0 0 auto;
    font-size: var(--fs-control);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.poste__track {
    height: 6px;
    margin-top: 0.375rem;
    background: var(--tl-track);
    overflow: hidden;
}

.poste__fill {
    display: block;
    height: 100%;
    background: var(--tl-out);

    /* Un poste à 0 % de largeur serait une ligne absente, pas une petite ligne. */
    min-width: 2px;
}

.poste__fill--in { background: var(--tl-in); }

.poste__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0.4375rem 0 0;
    font-size: var(--fs-meta);
    color: var(--tl-muted);
    font-variant-numeric: tabular-nums;
}

/*
 * Le bouton qui ouvre le reste d'une répartition. C'est un `<summary>` et non un `<button>` :
 * `<details>` fait seul ce que Bootstrap demande un script pour faire, et lui seul se déplie quand
 * le navigateur cherche du texte dans la page.
 *
 * **Son libellé donne le compte exact de ce qu'il révèle** (§5 du handover, piège n°7 du plan) : il
 * le lit sur le tableau que le repli parcourt, jamais sur un compte calculé à part.
 */
.fold-more {
    display: block;
    width: 100%;
    padding: 0.6875rem 0.75rem;
    border: 1px solid var(--tl-rule);
    color: var(--tl-accent);
    font-size: var(--fs-control);
    font-weight: 550;
    text-align: center;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
}

.fold-more::-webkit-details-marker { display: none; }

.fold-more:hover { background: var(--tl-band); }

/* Le libellé dit le geste à venir, donc il change quand le repli s'ouvre — sans JavaScript. */
.fold-more__open { display: none; }
details[open] > .fold-more .fold-more__open { display: inline; }
details[open] > .fold-more .fold-more__closed { display: none; }

.fold-more__body { margin-top: 1rem; }

/* ------------------------------------------------ Graphes de /statistiques */

/*
 * Deux séries comparées, en barres CSS. Aucune bibliothèque (D-PS-8) : Chart.js et consorts
 * casseraient le « zéro Node » d'AssetMapper pour une centaine de points de données, et un
 * `<canvas>` ne suivrait ni le thème sombre ni la taille du texte du système.
 *
 * **En lignes horizontales, pas en colonnes verticales.** Douze mois × deux séries en vertical
 * donnent, sur un téléphone de 360 px, des barres de 13 px et des étiquettes qu'il faudrait
 * incliner. En horizontal chaque période garde son nom en toutes lettres et ses deux montants.
 *
 * La largeur des barres est posée en style inline par le gabarit — c'est la seule information
 * qu'il ait à transmettre —, et les ratios sont calculés dans le cas d'usage, où ils sont testés.
 */
.chart-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.chart-row {
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--tl-rule-soft);
}

.chart-row:last-child { border-bottom: 0; }

.chart-row--empty { opacity: 0.55; }

/* L'année consultée, repérée dans le comparatif. Un fond, pas seulement une graisse : la ligne
   doit se retrouver d'un coup d'œil au milieu de six autres. */
.chart-row--current { background: var(--tl-accent-soft); }

.chart-row__label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    margin-bottom: 0.3rem;
    font-size: var(--fs-meta);
    font-weight: 600;
}

.chart-row__label--wide { font-weight: 500; }

.chart-row__note,
.chart-row__none {
    margin: 0.25rem 0 0;
    font-size: var(--fs-micro);
    color: var(--tl-muted);
}

/*
 * Une barre et son montant sur la même ligne. `minmax(0, 1fr)` et non `1fr` : sans le minimum à
 * zéro, une piste de grille refuse de descendre sous la largeur de son contenu et le montant
 * — insécable de bout en bout — pousserait la barre hors de la carte.
 *
 * **Les deux séries d'une même période partagent la même échelle** (§3.5), c'est-à-dire le maximum
 * de la période, séries confondues. Deux échelles font un graphe qui ment : la barre la plus longue
 * de chaque série atteint le bord, et deux montants sans rapport paraissent égaux. Le calcul est
 * dans le cas d'usage, où il est testé ; le gabarit ne pose qu'un `width: X%`.
 */
.chart-bar {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.2rem;
}

.chart-bar__track {
    height: 8px;
    background: var(--tl-track);
    overflow: hidden;
}

.chart-bar__fill {
    display: block;
    height: 100%;
    background: var(--tl-accent);

    /* Une barre à 0 % serait invisible et se confondrait avec une ligne absente. Le minimum la
       laisse exister sans mentir sur l'ordre de grandeur. */
    min-width: 2px;
}

.chart-bar__fill--out { background: var(--tl-out); }
.chart-bar__fill--in { background: var(--tl-in); }

/* Le montant est écrit à côté de la barre, toujours : une barre ne porte jamais seule
   l'information, ni pour un lecteur d'écran, ni pour qui ne distingue pas le vert du rouge. */
.chart-bar__value {
    font-size: var(--fs-micro);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.chart-bar__value--out { color: var(--tl-out); }
.chart-bar__value--in { color: var(--tl-in); }

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    margin-bottom: 0.75rem;
    font-size: var(--fs-micro);
    color: var(--tl-muted);
}

.chart-legend__item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.chart-legend__swatch {
    width: 10px;
    height: 10px;
}

.chart-legend__swatch--out { background: var(--tl-out); }
.chart-legend__swatch--in { background: var(--tl-in); }

/*
 * Écart d'un mois sur l'autre. Le montant du mois seul ne dit pas s'il est bon : il faut un point
 * de comparaison, et le mois précédent est le seul que tout le monde a en tête.
 *
 * La flèche double la couleur plutôt que de la remplacer : le sens ne doit pas reposer sur le vert
 * et le rouge seuls.
 */
.trend {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    white-space: nowrap;
}

.trend .icon { width: 14px; height: 14px; }
.trend--up { color: var(--tl-in); }
.trend--down { color: var(--tl-out); }

/*
 * Groupe d'année dans le détail replié. Sans en-tête, l'historique complet est une colonne de
 * trente lignes où l'on ne sait plus quelle année on lit ; le sous-total évite d'avoir à
 * additionner de tête.
 *
 * L'en-tête était au corps « meta », en couleur secondaire, sous un simple filet : plus petit et
 * plus pâle que les mois qu'il contenait. La hiérarchie était à l'envers et, en dépliant, on ne
 * voyait plus où une année finissait et où la suivante commençait. Il est maintenant au corps des
 * titres de carte, sur une bande pleine, et les mois lui sont rattachés par un filet latéral.
 */
.year-group + .year-group { margin-top: 1.5rem; }

.year-group__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.35rem;
    padding: 0.4rem 0.7rem;
    background: var(--tl-band);
}

.year-group__year {
    margin: 0;
    font-size: var(--fs-title);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: var(--tl-ink);
    font-variant-numeric: tabular-nums;
}

/* Le sous-total est nommé : un second nombre à côté d'une année se lit sinon comme une donnée
   de plus, sans qu'on sache de quoi elle est la somme. */
.year-group__total {
    font-size: var(--fs-meta);
    font-weight: 500;
    color: var(--tl-muted);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

.year-group__months {
    margin-left: 0.7rem;
    padding-left: 0.85rem;
    border-left: 2px solid var(--tl-rule);
}

/* ------------------------------------------------- Mouvement réduit */

/*
 * Respecte le réglage système. Les transitions ajoutées plus haut sont un confort, pas une
 * information : aucune ne porte de sens qui serait perdu en les coupant.
 */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    .btn:active { transform: none; }
}

/* ------------------------------------------------------- Relevé imprimable */

/*
 * « Ma part » doit pouvoir se poser sur une table. Pas de génération de PDF : une feuille print
 * suffit, et le navigateur sait déjà exporter en PDF.
 */
@media print {
    .app-topbar,
    .app-bottomnav,
    .impersonation,
    .btn,
    .form-actions,
    .modal,
    .sheet-layer,
    .row-panel,
    .txn__action,
    .back-link,
    .balance__link,
    .no-print { display: none !important; }

    body { background: #fff; }

    /*
     * Le bandeau de solde est en blanc sur dégradé. Les navigateurs n'impriment pas les fonds sans
     * réglage explicite : laissé tel quel, le montant le plus important de la page sortirait en
     * blanc sur blanc — c'est-à-dire pas du tout.
     */
    .balance {
        background: none !important;
        color: #000;
        border: 1px solid #ddd;
    }

    .balance__label,
    .balance__hint { opacity: 1; }

    #main-content { padding: 0; max-width: none; }

    .card {
        border: 1px solid #ddd;
        break-inside: avoid;
    }

    /*
     * **Tout repli s'ouvre sur papier.** À l'écran, une année close ou des mois à zéro sont rangés
     * derrière un `<details>` ; sur un relevé imprimé, ce qui est rangé est perdu — on ne déplie pas
     * une feuille de papier.
     *
     * Les trois familles de repli de l'application y passent : `.details-block` (l'ancienne), et
     * depuis les lots 5 et 6 `.fold` (une année close) et `.zero-months` (les mois sans part).
     * Oublier les deux nouvelles a été exactement ce qui s'est produit au lot 5 : le relevé de
     * `/ma-part` sortait amputé de toutes ses années closes, sans que rien ne le signale à l'écran.
     */
    .details-block > summary,
    .zero-months { display: none; }

    .details-block[open],
    .details-block { display: block; }
    .details-block > *:not(summary) { display: block !important; }

    details > *:not(summary) { display: block !important; }

    /* Le résumé d'un repli d'année porte son total : il reste, sans son chevron ni son fond. */
    .fold { border-color: #ddd; break-inside: avoid; }
    .fold__summary { background: none !important; }
    .fold__chevron { display: none; }
    .fold__body { border-top: 1px solid #ddd; }

    /* Les barres ne survivent pas à l'impression en noir et blanc, et le chiffre suffit. Les
       montants sont écrits à côté de chaque barre, la page reste donc complète sans elles. */
    .month-bar,
    .chart-bar__track,
    .chart-legend { display: none; }

    /* Sans sa piste, la barre ne laisse qu'une colonne : le montant reprend toute la ligne. */
    .chart-bar { grid-template-columns: 1fr; }
    .chart-row--current { background: none; }

    /* Les bandeaux de jour et d'année portent un aplat que le navigateur n'imprime pas : sans
       filet, ils disparaîtraient et les lignes se suivraient sans séparation. */
    .day-band {
        background: none !important;
        border-top: 1px solid #ddd;
        border-bottom: 1px solid #ddd;
        color: #000;
    }

    a[href]::after { content: ""; }
}
