/* ============================================================
   STRALEAD — composants du design system
   Source de vérité : DESIGN_SYSTEM.md §3, §4, §6.
   Chargé APRÈS tokens.css.

   Règle d'or : le dégradé est réservé aux actions primaires, aux
   titres de module et à la donnée mise en avant. Partout ailleurs,
   du neutre. Si tout brille, plus rien ne ressort.
   ============================================================ */

@layer base, components, utilities;

/* ============================================================
   BASE
   ============================================================ */
@layer base {

  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }

  html {
    font-family: var(--font-b);
    -webkit-font-smoothing: antialiased;
    color: var(--ink-1);
  }

  body {
    background: var(--bg-0);
    min-height: 100vh;
    font-size: var(--t-md);
    line-height: 1.55;
  }

  [x-cloak] { display: none !important; }
  ::selection { background: var(--selection); }

  /* Plancher qualité : le focus clavier est toujours visible. */
  *:focus-visible {
    outline: 2px solid var(--g-b);
    outline-offset: 2px;
    border-radius: var(--r-sm);
  }

  a { color: inherit; text-decoration: none; }

  h1, h2, h3, h4 { font-family: var(--font-d); font-weight: 600; }

  button, input, select, textarea { font: inherit; color: inherit; }

  /* Tout chiffre est tabulaire : sans ça, les colonnes de montants
     dansent d'une ligne à l'autre. */
  .num { font-variant-numeric: tabular-nums; }
}

/* ============================================================
   COMPOSANTS
   ============================================================ */
@layer components {

  /* --- Atmosphère : une seule aurore fixe en haut de page ------- */
  .aurora {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    overflow: hidden;
  }
  .aurora::before {
    content: "";
    position: absolute;
    top: -38%;
    left: 50%;
    width: 1100px;
    height: 640px;
    max-width: 160vw;
    transform: translateX(-50%);
    border-radius: 50%;
    background: radial-gradient(closest-side,
                var(--aurora-a), var(--aurora-b) 55%, transparent 75%);
    filter: blur(50px);
    animation: breathe 9s var(--ease) infinite alternate;
  }
  .aurora::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: radial-gradient(var(--aurora-points) 1px, transparent 1px);
    background-size: 26px 26px;
    mask-image: radial-gradient(ellipse 70% 45% at 50% 0%, #000 30%, transparent 75%);
    -webkit-mask-image: radial-gradient(ellipse 70% 45% at 50% 0%, #000 30%, transparent 75%);
  }
  @keyframes breathe {
    from { opacity: .75; transform: translateX(-50%) scale(1); }
    to   { opacity: 1;   transform: translateX(-50%) scale(1.06); }
  }

  /* --- Entrées orchestrées ------------------------------------- */
  @keyframes rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }
  .rise { animation: rise .6s var(--ease) both; }
  .d1 { animation-delay: .05s; }
  .d2 { animation-delay: .12s; }
  .d3 { animation-delay: .19s; }
  .d4 { animation-delay: .26s; }

  /* --- Coquille applicative ------------------------------------ */
  .shell {
    position: relative;
    z-index: 1;
    display: grid;
    grid-template-columns: var(--nav-w) minmax(0, 1fr);
    min-height: 100vh;
  }

  /* Coquille sans navigation : le premier parcours d'inscription, où les
     modules ne sont pas encore joignables. Une seule colonne, sinon la page
     se rangerait dans celle de la navigation absente et resterait large de
     var(--nav-w). */
  .shell-solo { grid-template-columns: minmax(0, 1fr); }

  .page {
    min-width: 0;
    padding: 2.5rem 2rem 4rem;
  }
  .page-inner { max-width: 1080px; margin: 0 auto; }

  /* --- Navigation latérale ------------------------------------- */
  .nav {
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    padding: 1.5rem 1rem;
    border-right: 1px solid var(--line);
    background: var(--nav-fond);
    backdrop-filter: blur(14px);
    overflow-y: auto;
  }

  .nav-marque {
    font-family: var(--font-d);
    font-size: var(--t-lg);
    font-weight: 700;
    letter-spacing: .04em;
    padding: 0 .55rem;
  }

  .nav-cabinet {
    padding: .7rem .75rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-1);
  }
  .nav-cabinet-nom {
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--ink-1);
    overflow-wrap: anywhere;
  }
  .nav-cabinet-meta { font-size: var(--t-xs); color: var(--ink-3); margin-top: .15rem; }

  /* La fonction du membre, posée le 30/08/2026 sous le nom du cabinet. Plus
     appuyée que `.nav-cabinet-meta`, qui porte la ville : ce n'est pas une
     précision sur le cabinet, c'est ce que la personne y fait. */
  .nav-cabinet-fonction {
    font-size: var(--t-xs);
    font-weight: 600;
    color: var(--ink-2);
    margin-top: .2rem;
  }

  /* Le même bloc, mais cliquable : le sélecteur de praticien de SPEC D.1 bis.
     Un bouton n'hérite ni de la police ni de l'alignement du texte, d'où la
     remise à plat ; le reste vient de .nav-cabinet. */
  .nav-cabinet-bascule {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    cursor: pointer;
    /* La teinte du praticien courant, quand le compte en sert plusieurs. */
    border-left: 3px solid var(--pastille, var(--line));
  }
  .nav-cabinet-bascule:hover { background: var(--survol); }

  /* Une assistante peut être salariée de sept ou huit praticiens : la liste
     défile plutôt que de déborder de l'écran. */
  .menu-cabinets { max-height: 60vh; overflow-y: auto; }
  .nav-cabinet-bascule .nav-cabinet-nom,
  .nav-cabinet-bascule .nav-cabinet-meta { display: block; }

  .nav-groupe { display: flex; flex-direction: column; gap: 2px; }
  .nav-titre {
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: 0 .55rem;
    margin-bottom: .4rem;
  }

  .nav-lien {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: .55rem .55rem;
    border-radius: var(--r-md);
    font-size: var(--t-sm);
    font-weight: 500;
    color: var(--ink-2);
    transition: background .15s, color .15s;
  }
  .nav-lien:hover { background: var(--survol); color: var(--ink-1); }
  .nav-lien svg { flex-shrink: 0; opacity: .8; }

  /* En thème clair, `--grad-soft` est un lavande pâle : une encre blanche y
     disparaîtrait. L'encre du lien actif est donc un token, pas une valeur. */
  .nav-lien.on {
    color: var(--nav-actif-encre);
    background: var(--grad-soft);
    box-shadow: inset 2px 0 0 0 var(--g-b);
  }
  .nav-lien.on svg { opacity: 1; }

  /* Pastille de notification du menu. Posée le 30/08/2026 pour les tâches
     de Collaboration adressées au compte et pas encore faites.

     `margin-left: auto` la pousse au bout du lien, quelle que soit la
     longueur du libellé : aucune largeur n'est écrite en dur. Les chiffres
     tabulaires évitent que « 1 » et « 11 » ne dansent d'une page à l'autre. */
  .nav-pastille {
    margin-left: auto;
    min-width: 1.25rem;
    padding: 0 .35rem;
    border-radius: 999px;
    background: var(--bad);
    color: #fff;
    font-size: .7rem;
    font-weight: 600;
    line-height: 1.25rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }

  .nav-lien.off {
    color: var(--ink-3);
    cursor: not-allowed;
    opacity: .55;
  }
  .nav-lien.off:hover { background: none; color: var(--ink-3); }

  .nav-pied {
    margin-top: auto;
    padding-top: 1rem;
    border-top: 1px solid var(--line);
    font-size: var(--t-xs);
    color: var(--ink-3);
  }
  .nav-pied .email { display: block; color: var(--ink-2); overflow-wrap: anywhere; margin-bottom: .4rem; }
  .nav-lien-compact { padding: .35rem .55rem; }

  /* --- En-tête de module --------------------------------------- */
  .eyebrow {
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: .4rem;
  }
  .titre-module {
    font-family: var(--font-d);
    font-size: var(--t-2xl);
    font-weight: 700;
    letter-spacing: -.02em;
  }
  .grad-text {
    background: var(--grad);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
  .sous-titre { margin-top: .5rem; font-size: var(--t-md); color: var(--ink-2); }

  .module-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    /* Un en-tête visé par une ancre, comme celui des graphiques du tableau
       de bord, ne se colle pas au bord haut de la fenêtre. */
    scroll-margin-top: 1.5rem;
  }

  /* --- Verre ---------------------------------------------------- */
  .glass {
    background: linear-gradient(180deg, var(--glass-a), var(--glass-b));
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    backdrop-filter: blur(14px);
  }

  /* Panneau teinté, pour ce qui entre. Le prévisionnel oppose une recette à
     des charges : les distinguer d'un aplat vaut mieux que deux titres. La
     teinte se dérive de --ok par color-mix, comme tous les aplats de statut,
     et tient donc en clair comme en sombre. */
  .glass-ok {
    background: color-mix(in srgb, var(--ok) 7%, transparent);
    border-color: color-mix(in srgb, var(--ok) 28%, transparent);
  }

  .panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.4rem 1.25rem .9rem;
  }
  .panel-titre { font-family: var(--font-d); font-size: var(--t-lg); font-weight: 600; }
  /* Note de bas de panneau : alignée sur le retrait du panneau, sans quoi
     chaque écran redéfinirait son propre padding en style="". */
  .panel-note { padding: 0 1.25rem 1.4rem; font-size: var(--t-xs); color: var(--ink-3); }
  /* Corps et pied d'un panneau, même retrait que l'en-tête et la note.
     `.glass` ne porte aucun padding : chaque bloc pose le sien. Un titre de
     rubrique, un formulaire ou un bouton déposé directement dans un panneau
     se collait donc au bord gauche pendant que l'en-tête restait en retrait,
     et le dernier d'entre eux chevauchait la bordure du bas.
     `.panel-pied` est ce que `.themes` faisait déjà pour lui seul. */
  .panel-corps { padding: 0 1.25rem; }
  .panel-pied { padding: 0 1.25rem 1.4rem; }

  /* Bloc de contenu espacé dans un panneau : un formulaire, une liste de
     lignes. Même flux qu'un corps de modale, colonne et gouttière, mais le
     retrait du panneau. Mon compte empruntait `.modal-body`, dont le retrait
     est taillé pour une modale, 1.6rem : ses champs tombaient 5,6 px à
     droite du titre au-dessus, et 2,4 px à gauche sous 420 px, où la modale
     resserre le sien et pas le panneau. */
  .panel-bloc {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    padding: 1.25rem 1.25rem 1.4rem;
  }
  .panel-bloc-pied {
    display: flex;
    justify-content: flex-end;
    gap: .7rem;
    padding: 1.1rem 1.25rem;
    border-top: 1px solid var(--line);
    background: var(--surface-0);
  }

  /* --- Onglets : rail avec curseur dégradé coulissant ----------- */
  .tabs {
    position: relative;
    display: inline-flex;
    padding: 4px;
    gap: 2px;
    border: 1px solid var(--line);
    border-radius: var(--r-full);
    background: var(--rail-fond);
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar { display: none; }

  .tabs .slider {
    position: absolute;
    top: 4px;
    bottom: 4px;
    border-radius: var(--r-full);
    background: var(--grad);
    box-shadow: var(--glow);
    transition: left .35s var(--ease), width .35s var(--ease);
  }

  .tab {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .55rem 1.1rem;
    border: none;
    background: none;
    border-radius: var(--r-full);
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--ink-2);
    cursor: pointer;
    white-space: nowrap;
    transition: color .25s;
    /* Un onglet est tantôt un span, tantôt un bouton, tantôt un lien : un
       bouton hérite la police du système, un lien arrive souligné. */
    font-family: inherit;
    text-decoration: none;
  }
  .tab.on { color: var(--sur-grad); }
  .tab:hover:not(.on) { color: var(--ink-1); }
  /* Une étape encore fermée n'est ni cliquable ni trompeuse. */
  .tab[aria-disabled="true"] { color: var(--ink-3); cursor: not-allowed; }
  .tab[aria-disabled="true"]:hover { color: var(--ink-3); }

  /* --- Boutons -------------------------------------------------- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    font-size: var(--t-sm);
    font-weight: 600;
    border: none;
    border-radius: var(--r-md);
    padding: .6rem 1.05rem;
    cursor: pointer;
    white-space: nowrap;
    transition: transform .2s var(--ease), box-shadow .2s, opacity .2s, background .2s, color .2s;
  }
  .btn:active { transform: scale(.97); }

  .btn-grad {
    position: relative;
    color: var(--sur-grad);
    background: var(--grad);
    background-size: 180% 100%;
    box-shadow: var(--ombre-grad);
    /* Le dégradé dérive en continu, très lentement : la marque est vivante
       sans attirer l'œil. L'animation pilote background-position, le survol
       garde l'élévation et l'ombre. Neutralisé par prefers-reduced-motion. */
    animation: grad-derive 7s ease-in-out infinite alternate;
  }
  .btn-grad:hover {
    box-shadow: var(--ombre-grad-forte);
    transform: translateY(-1px);
  }
  @keyframes grad-derive {
    from { background-position: 0% 0; }
    to   { background-position: 100% 0; }
  }
  .btn-grad:disabled { opacity: .4; cursor: not-allowed; transform: none; box-shadow: none; }
  /* Un bouton d'écran à construire est affiché, pas masqué : l'utilisateur
     doit voir où va le produit. Encore faut-il qu'il se lise comme
     indisponible, sinon il cliquera dessus. */
  .btn:disabled { opacity: .45; cursor: not-allowed; transform: none; }
  .btn:disabled:hover { background: inherit; border-color: var(--line); }

  .btn-ghost {
    background: transparent;
    color: var(--ink-2);
    border: 1px solid var(--line-strong);
  }
  .btn-ghost:hover {
    color: var(--ink-1);
    border-color: var(--line-survol);
    background: var(--survol);
  }

  .btn-danger { background: color-mix(in srgb, var(--bad) 12%, transparent); color: var(--bad); border: 1px solid color-mix(in srgb, var(--bad) 30%, transparent); }
  .btn-danger:hover { background: color-mix(in srgb, var(--bad) 20%, transparent); }

  .btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: .45rem;
    border: none;
    background: none;
    border-radius: var(--r-sm);
    color: var(--ink-3);
    cursor: pointer;
    transition: color .15s, background .15s;
  }
  .btn-icon:hover { color: var(--ink-1); background: var(--survol); }

  /* --- Tableaux ------------------------------------------------- */
  /* `position: relative` n'est pas décoratif : les intitulés `.sr-only` sont
     positionnés en absolu. Sans bloc conteneur ici, ceux des dernières
     colonnes d'un tableau plus large que la page échappent au défilement
     interne et poussent la page entière vers la droite. */
  .table-wrap { width: 100%; overflow-x: auto; position: relative; }

  .tbl { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
  .tbl thead th {
    text-align: left;
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: .8rem 1.25rem;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
  }
  .tbl tbody td {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--line);
    color: var(--ink-2);
  }
  .tbl tbody tr { transition: background .15s; }
  .tbl tbody tr:hover { background: var(--survol-doux); }
  .tbl tbody tr:last-child td { border-bottom: none; }

  .tbl .droite { text-align: right; }
  .tbl .principal { font-weight: 500; color: var(--ink-1); }
  .tbl .actions { width: 56px; text-align: right; position: relative; }
  /* Ligne entière menant à une fiche (supervision) : le survol existant
     signale déjà la ligne, le curseur dit qu'elle s'ouvre. */
  .tbl .ligne-cliquable { cursor: pointer; }

  /* Un bouton qui a l'allure d'un lien. Pour une action secondaire posée
     DANS une cellule, là où un `.btn` pèserait plus lourd que la donnée
     qu'il accompagne. C'est un bouton et non un lien : il agit sur la page,
     il ne mène nulle part. Ajouté le 05/09/2026 avec « Lire la suite ». */
  .btn-lien {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: .82rem;
    color: var(--g-b);
    cursor: pointer;
  }
  .btn-lien:hover { text-decoration: underline; }

  /* Collaboration : le message ne mange plus la ligne (SPEC D.2). Huit
     colonnes se partagent le panneau, et une consigne de trois lignes
     s'écrivait un mot par ligne sur vingt centimètres de haut. La largeur
     minimale n'est pas décorative : sans elle, trois lignes coupées ne
     montreraient toujours rien. La coupe est visuelle, le texte part
     entier et reste sélectionnable. */
  .cellule-message { min-width: 14rem; }
  .cellule-message .texte-coupe {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Tableau à colonnes nombreuses. `white-space: nowrap` sur les intitulés
     tient tant qu'ils sont courts ; avec huit colonnes dont « Date
     d'acceptation du devis », il porte le tableau à 1363 px pour 967 px
     disponibles et impose une barre de défilement horizontale. Les intitulés
     passent donc sur deux lignes, exactement comme la maquette les dessine,
     et le pas horizontal se resserre. */
  .tbl-dense thead th {
    white-space: normal;
    padding: .8rem .85rem;
    line-height: 1.3;
  }
  .tbl-dense tbody td { padding: .85rem .85rem; }

  /* --- Tableau de saisie (Reporting planning réel) ------------------
     Un champ par cellule, et jusqu'à onze colonnes. Le `.input` ordinaire,
     dimensionné pour une modale, porte ici le tableau bien au-delà de la
     largeur disponible : ses cellules se chevauchaient et la dernière
     colonne sortait de la fenêtre. On resserre le pas, on retire les
     flèches des champs numériques, qui ne servent à rien pour saisir des
     heures, et le tableau garde sa barre de défilement horizontale quand
     le cabinet a beaucoup de types d'actes. */
  .tbl-saisie thead th { padding: .6rem .5rem; }
  .tbl-saisie tbody td, .tbl-saisie tfoot td, .tbl-saisie tfoot th {
    padding: .35rem .5rem;
  }
  .tbl-saisie .input {
    width: 100%;
    min-width: 0;
    padding: .35rem .5rem;
    font-size: var(--t-sm);
    text-align: right;
  }
  /* L'alignement à droite est fait pour les montants. Sur un `select`,
     Windows le reporte au menu déroulé, peint à la largeur du champ : les
     options s'y collaient à droite, rognées à gauche (22/09/2026). */
  .tbl-saisie select.input { text-align: left; }
  .tbl-saisie input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
  .tbl-saisie input[type="number"]::-webkit-outer-spin-button,
  .tbl-saisie input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  /* La colonne des mois ne se comprime pas, les colonnes de saisie oui. */
  .tbl-saisie th[scope="row"], .tbl-saisie .principal { white-space: nowrap; }
  .tbl-saisie tfoot tr { border-top: 1px solid var(--line); }

  /* Ligne de totaux placée en tête de liste, et non dans un `tfoot` : c'est
     le cas du tableau des encaissements, où la liste est longue. Le filet
     passe donc dessous, et la première colonne reste collante comme celle
     d'un pied. */
  .tbl-saisie .ligne-total { border-bottom: 1px solid var(--line); }

  /* Le règlement qui a soldé le devis, en couleur Stralead. Sur un champ de
     saisie, `grad-text` ne tient pas : le dégradé découpé sur le texte ne
     s'applique pas à un contrôle de formulaire. On reprend donc la teinte de
     la marque sur le texte et la bordure. */
  .cellule-solde .input {
    color: var(--g-b);
    border-color: var(--g-b);
    font-weight: 600;
  }

  /* Seize colonnes, dont douze mois : le tableau des encaissements demande un
     pas plus serré encore que le reporting, sinon il sort du panneau. */
  .tbl-serre thead th { padding: .5rem .3rem; font-size: 10px; }
  .tbl-serre tbody td, .tbl-serre tfoot td, .tbl-serre tfoot th { padding: .3rem .3rem; }
  .tbl-serre .input { padding: .3rem .35rem; font-size: var(--t-xs); }
  .tbl-serre .texte-meta { display: block; font-size: 10px; }

  /* Classement des correspondants (SPEC C.4 quater). Un tableau de lecture,
     sans une seule case de saisie : sa ligne TOTAL ne peut pas emprunter le
     filet de `.tbl-saisie`, qui suppose des champs et une colonne collante. */
  .tbl-classement .ligne-total { border-bottom: 1px solid var(--line); }
  .tbl-classement .ligne-total th[scope="row"] { color: var(--ink-1); }

  /* Chaque cellule porte deux valeurs, le nombre puis le montant : la seconde
     passe sous la première, plutôt que de doubler le nombre de colonnes. */
  .tbl-classement .texte-meta { display: block; font-weight: 400; }

  /* En-tête cliquable. Un lien nu dans un `th` prendrait la couleur des liens
     du produit et attirerait l'oeil plus que les chiffres : il garde donc
     celle de l'intitulé, et seule la colonne triée porte la teinte Stralead. */
  .th-tri { color: inherit; text-decoration: none; }
  .th-tri:hover { color: var(--ink-1); }
  .th-tri.on { color: var(--g-b); }
  .th-tri.texte-meta {
    display: block;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
  }

  /* En-tête et colonne des mois collants. Avec douze lignes et jusqu'à dix
     colonnes de saisie, on perd sinon de vue à quel mois et à quel type
     d'acte appartient la cellule qu'on remplit. */
  .tbl-saisie thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface-1);
  }
  .tbl-saisie tbody .principal,
  .tbl-saisie tbody th[scope="row"],
  .tbl-saisie tfoot th[scope="row"] {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface-1);
  }
  .tbl-saisie thead th:first-child { left: 0; z-index: 3; }

  /* La teinte du type d'acte, celle de Tarifs et traitements, souligne sa
     colonne. C'est une DONNÉE, elle arrive par --pastille comme partout. */
  .tbl-saisie thead th.col-acte { border-bottom: 2px solid var(--pastille, var(--line)); }
  .tbl-saisie .col-acte .dot { margin-right: .35rem; }

  /* Les actions au-dessus du tableau, alignées à gauche et jamais
     superposées : elles passent à la ligne avant de se chevaucher. */
  .barre-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: .5rem;
    /* Même retrait que l'en-tête et la note du panneau : les boutons
       s'alignent sur le titre au lieu de coller à la bordure. */
    padding: 0 1.25rem;
    margin-bottom: .9rem;
  }
  .barre-actions .texte-meta { flex: 1 1 14rem; }

  /* --- Tableau des opérations bancaires ----------------------------
     `table-layout: fixed`, décision du 13/08/2026. Les largeurs étaient
     des suggestions que le navigateur arbitrait, et le libellé, réclamant
     20rem au minimum, portait le tableau à 1131 px pour environ 900 px
     disponibles : la colonne d'actions sortait du panneau, et le bouton
     « modifier » de chaque ligne devenait inatteignable.

     En largeur fixe, la somme des colonnes mesurées ne dépasse jamais le
     panneau, et c'est le libellé qui absorbe la place restante : lui seul
     peut se replier sans rien perdre, puisqu'il est du texte. Toutes les
     autres colonnes gardent la mesure de leur contenu le plus long, et le
     pas horizontal est resserré pour leur en laisser les moyens.

     `min-width` est le plancher : en deçà, on défile plutôt que de réduire
     le libellé à deux mots par ligne. */
  .tbl-operations {
    table-layout: fixed;
    min-width: 55rem;
  }
  .tbl-operations thead th,
  .tbl-operations tbody td { padding-left: .65rem; padding-right: .65rem; }

  /* La colonne des cases à cocher est la même partout où l'on supprime en
     lot : le relevé bancaire et le suivi quotidien. Elle n'est donc plus
     rattachée à un tableau en particulier. */
  .col-choix { width: 2.2rem; }

  .tbl-operations .col-date      { width: 6rem; white-space: nowrap; }
  .tbl-operations .col-libelle   { width: auto; }
  .tbl-operations .col-montant   { width: 6.75rem; white-space: nowrap; }
  .tbl-operations .col-type      { width: 4.5rem; }
  .tbl-operations .col-categorie { width: 9.5rem; }
  .tbl-operations .col-justificatif { width: 5rem; }
  .tbl-operations .actions       { width: 3.4rem; }

  /* Le libellé d'un relevé bancaire tient sur cent caractères sans un seul
     espace utile. Sans coupure, il pousse la colonne au-delà de sa mesure
     et ramène le débordement que la largeur fixe vient de supprimer. */
  .tbl-operations .col-libelle { overflow-wrap: anywhere; }

  /* La liste des catégories ne doit pas déborder de sa colonne : sans
     `max-width`, un `select` prend la largeur de sa plus longue option,
     « Rémunération du dirigeant », et rouvre le débordement. */
  .tbl-operations .col-categorie select { max-width: 100%; }

  /* Un nombre ne se coupe jamais. « 2 700,00 € » passait à la ligne sur
     ses espaces, et se lisait « 2 » au-dessus de « 700,00 € », ce qui est
     un chiffre faux pour l'œil. La règle vaut pour tous les tableaux :
     aucun n'a intérêt à replier un montant. */
  .tbl .num { white-space: nowrap; }

  /* En-tête cliquable : le filtre de la colonne s'ouvre sous son intitulé.
     Le bouton reprend le style du `th`, il ne doit pas se voir comme un
     bouton tant qu'on ne le survole pas. */
  .tete-filtre {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: inherit;
    cursor: pointer;
    transition: color .15s;
  }
  .tete-filtre:hover { color: var(--ink-1); }
  /* Une colonne filtrée se voit sans avoir à ouvrir son menu. */
  .tete-filtre.actif { color: var(--accent-texte); }
  .tete-filtre.actif::after {
    content: "";
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
  }

  /* Ligne calculée par le système : liseré dégradé animé à gauche.
     N'expose jamais de menu d'actions (cf. DESIGN_SYSTEM §3). */
  .row-sys { position: relative; background: var(--grad-soft); }
  .row-sys td:first-child { position: relative; }
  .row-sys td:first-child::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    background: var(--grad);
    background-size: 100% 300%;
    animation: flow 4s linear infinite;
  }
  @keyframes flow { to { background-position: 0 -300%; } }

  /* --- Étiquettes et pastilles ---------------------------------- */
  .chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--t-xs);
    font-weight: 600;
    padding: .22rem .65rem;
    border-radius: var(--r-full);
    border: 1px solid var(--line-strong);
    color: var(--ink-2);
    white-space: nowrap;
  }
  .chip-ok   { color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 35%, transparent); }
  .chip-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 35%, transparent); }
  .chip-bad  { color: var(--bad);  border-color: color-mix(in srgb, var(--bad) 35%, transparent); }
  .chip-off  { color: var(--ink-3); }

  /* La teinte est une DONNÉE (couleur du praticien, de la famille) : elle
     arrive par la variable --pastille, jamais par un style de présentation. */
  .dot {
    width: 8px;
    height: 8px;
    border-radius: 99px;
    flex-shrink: 0;
    display: inline-block;
    background: var(--pastille, var(--ink-3));
  }
  .dot-lg { width: 10px; height: 10px; }

  /* Rubriques par praticien (SPEC D.1 bis). Une personne salariée de
     plusieurs praticiens choisit ici qui elle consulte. La teinte est une
     donnée, celle du cabinet, et arrive par --pastille comme partout. */
  .rubriques { display: flex; flex-wrap: wrap; gap: .5rem; }
  .rubrique {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .75rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--pastille, var(--line));
    border-radius: var(--r-md);
    background: var(--surface-1);
    color: var(--ink-2);
    font: inherit;
    cursor: pointer;
  }
  .rubrique:hover { background: var(--survol); color: var(--ink-1); }
  .rubrique.on { color: var(--ink-1); border-color: var(--pastille, var(--line)); }
  .rubrique-nom { font-weight: 600; }

  /* --- Formulaires ----------------------------------------------
     Les alias `.entree form ...` couvrent les formulaires allauth,
     rendus par `form.as_p` sans classe : mêmes règles, un seul
     point de vérité. --------------------------------------------- */
  .label,
  .entree form p:not(:has(> input[type="checkbox"])) label,
  .compte form p:not(:has(> input[type="checkbox"], > input[type="radio"])) label,
  .compte p:has(> textarea) label {
    display: block;
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--ink-3);
    margin-bottom: .45rem;
  }

  .input,
  .entree form input:not([type="checkbox"]):not([type="hidden"]),
  .compte form input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  .compte textarea {
    width: 100%;
    font-size: var(--t-md);
    color: var(--ink-1);
    background: var(--surface-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    padding: .65rem .9rem;
    transition: border .2s, box-shadow .2s;
  }
  .input::placeholder,
  .entree form input::placeholder,
  .compte form input::placeholder { color: var(--ink-3); }
  .input:focus,
  .entree form input:not([type="checkbox"]):focus,
  .compte form input:not([type="checkbox"]):not([type="radio"]):focus {
    outline: none;
    border-color: var(--g-b);
    box-shadow: 0 0 0 3px var(--anneau-focus);
  }
  .input[aria-invalid="true"] {
    border-color: var(--bad);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 18%, transparent);
  }
  /* Un select natif sur fond nuit : la liste déroulée est peinte par l'OS,
     on force au moins les options pour qu'elles restent lisibles. */
  select.input option { background: var(--bg-2); color: var(--ink-1); }

  /* Un select natif ne prend pas la hauteur d'une saisie tout seul : sans
     `line-height`, il perd un à deux pixels et casse l'alignement d'une
     ligne où il côtoie des `input`. `appearance` retire en plus le relief
     que Windows dessine par-dessus le cadre du design system. */
  select.input {
    appearance: none;
    line-height: 1.5;
    padding-right: 2.2rem;
    background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                      linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
    background-position: right 1.05rem center, right .8rem center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
  }

  .champ { display: block; }
  .champ + .champ { margin-top: 1.1rem; }
  .champ-aide { font-size: var(--t-xs); color: var(--ink-3); margin-top: .35rem; }
  .champ-erreur { font-size: var(--t-xs); color: var(--bad); margin-top: .35rem; font-weight: 500; }

  .champ-inline { display: flex; align-items: center; gap: .6rem; }
  .champ-inline .label { margin-bottom: 0; }
  .champ-inline input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--g-b); }

  /* Une phrase d'acceptation n'est pas un intitulé de champ : elle se lit,
     donc elle garde sa casse, sa graisse de texte courant et sa hauteur de
     ligne. Sur deux lignes, la case remonte en tête plutôt que de flotter
     au milieu du paragraphe. */
  .champ-inline .label-phrase {
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    font-size: var(--t-sm);
    line-height: 1.45;
    color: var(--ink-2);
  }
  .champ-inline:has(.label-phrase) { align-items: flex-start; }
  .champ-inline:has(.label-phrase) input[type="checkbox"] { margin-top: .15rem; }

  /* Volet d'aide, replié par défaut. Balise `<details>` native, aucun
     JavaScript : le navigateur sait ouvrir et fermer, et la page reste
     lisible sans lui. Le chevron est un carré à deux bords, tourné d'un
     quart de tour à l'ouverture. */
  .volet { margin-top: .4rem; }
  .volet > summary {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    font-size: var(--t-xs);
    color: var(--ink-3);
    cursor: pointer;
    list-style: none;
    transition: color .2s;
  }
  .volet > summary::-webkit-details-marker { display: none; }
  .volet > summary::before {
    content: "";
    width: 5px;
    height: 5px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform .2s;
  }
  .volet[open] > summary::before { transform: rotate(45deg); }
  .volet > summary:hover { color: var(--accent-texte); }
  .volet .champ-aide { margin-top: .45rem; }
  .volet .champ-aide ul { margin: 0; padding-left: 1.1rem; }
  .volet .champ-aide li { margin-top: .2rem; }

  .grille-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }

  /* Trois cellules de même largeur : code postal, ville et pays à l'étape 1
     de l'inscription. Le pays est une liste déroulante, les deux autres des
     saisies : `.input` les met à la même hauteur, la grille au même fond. */
  .grille-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; }

  /* `.champ + .champ` est la marge de repli, pour un parent qui n'espace pas
     lui-même ses enfants. Dans une grille ou une colonne flex à `gap`, elle
     s'ajoute : la seconde cellule descend d'un cran et son champ ne s'aligne
     plus sur celui d'à côté. On la retire là où le parent fait déjà le
     travail. */
  .grille-2 > .champ + .champ,
  .grille-3 > .champ + .champ,
  .entree form > .champ + .champ,
  .modal-body > .champ + .champ,
  .panel-bloc > .champ + .champ { margin-top: 0; }

  /* Bloc de saisie répété : une ligne d'acte, un plan de traitement. Un
     filet sépare les blocs, sans le poids d'un cadre autour de chacun. */
  .ligne-saisie + .ligne-saisie {
    padding-top: 1.1rem;
    border-top: 1px solid var(--line);
  }

  .swatch {
    width: 34px;
    height: 34px;
    border-radius: 10px;
    cursor: pointer;
    border: 2px solid transparent;
    padding: 0;
    background: var(--pastille, var(--ink-3));
    transition: transform .15s var(--ease), border .15s;
  }
  .swatch:hover { transform: scale(1.12); }
  /* Teinte déjà portée par une autre ligne du cabinet (07/09/2026). Le
     contour est celui du dégradé Stralead, plein et sans halo : il se
     distingue de la sélection, qui garde son anneau blanc et son halo
     violet, et il dit avant le clic que la couleur fera doublon. */
  .swatch-prise { border-color: var(--g-b); }
  .swatch.on {
    /* Anneau blanc entre la pastille et le halo violet, dans les deux
       thèmes : sur un panneau clair il se lit comme un écart, pas comme un
       trait, et c'est le halo qui marque la sélection. */
    border-color: var(--sur-grad);
    transform: scale(1.12);
    box-shadow: 0 0 0 3px var(--anneau-swatch);
  }
  .swatches { display: flex; gap: .55rem; flex-wrap: wrap; }

  /* Bandeau d'erreurs non liées à un champ */
  .form-erreurs {
    padding: .75rem .9rem;
    border-radius: var(--r-md);
    border: 1px solid color-mix(in srgb, var(--bad) 30%, transparent);
    background: color-mix(in srgb, var(--bad) 8%, transparent);
    color: var(--bad);
    font-size: var(--t-sm);
  }
  .form-avertissement {
    padding: .75rem .9rem;
    border-radius: var(--r-md);
    border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
    background: color-mix(in srgb, var(--warn) 8%, transparent);
    color: var(--warn);
    font-size: var(--t-sm);
  }

  /* Aperçu vivant d'un formulaire (seul calcul autorisé côté Alpine) */
  .apercu {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: .8rem 1rem;
    border-radius: var(--r-md);
    background: var(--grad-soft);
    border: 1px solid var(--line-strong);
  }
  .apercu-valeur { font-family: var(--font-d); font-weight: 700; color: var(--ink-1); }

  /* --- Modales --------------------------------------------------- */
  .overlay {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: var(--voile);
    backdrop-filter: blur(6px);
    overflow-y: auto;
  }
  .modal {
    width: 100%;
    max-width: 480px;
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--bg-1);
    border: 1px solid var(--line-strong);
    box-shadow: var(--ombre-modale), 0 0 0 1px var(--anneau-marque);
  }
  .modal-lg { max-width: 620px; }
  /* Carte de confirmation : plus étroite qu'un formulaire, elle ne porte
     qu'une question et deux réponses. */
  .modal-confirm { max-width: 440px; }
  /* Une modale à rail d'onglets : sous 1000 px, les sept onglets de la
     grille d'entretien sortent du cadre et la moitié du formulaire devient
     invisible, le rail masquant sa barre de défilement. */
  .modal-xl { max-width: min(1080px, 94vw); }

  /* Le `<form>` s'intercale entre `.modal` et `.modal-body` : c'est LUI le
     véritable élément flex de la modale, et il arrivait en `display: block`,
     donc à hauteur de contenu. `.modal-body` n'était alors jamais contraint,
     son `overflow-y: auto` ne produisait aucune barre de défilement, et tout
     ce qui dépassait 90vh était rogné par l'`overflow: hidden` de `.modal` :
     champs du bas ET bouton d'enregistrement, devenus inatteignables. Les
     formulaires courts du planning ne le montraient pas, celui du Suivi
     quotidien, si. */
  .modal > form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  .modal-head {
    padding: 1.4rem 1.6rem 0;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 1rem;
    /* L'en-tête et le pied ne se compriment jamais : sans cela, un formulaire
       long les pousse hors du cadre et le bouton d'enregistrement devient
       inatteignable. */
    flex-shrink: 0;
  }
  .modal-titre { font-family: var(--font-d); font-size: var(--t-lg); font-weight: 600; }
  .modal-body {
    padding: 1.25rem 1.6rem 1.6rem;
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    overflow-y: auto;
    /* `flex: 1 1 auto` ET `min-height: 0`, les deux sont nécessaires. Un
       élément flex a une taille minimale automatique égale à son contenu :
       sans `min-height: 0` il refuse de rétrécir, `overflow-y: auto` ne
       déclenche jamais de barre de défilement, et le contenu déborde d'un
       `.modal` en `overflow: hidden`, donc disparaît. Un formulaire de six
       champs ne le montrait pas ; celui du Suivi quotidien, si. */
    flex: 1 1 auto;
    min-height: 0;
  }
  .modal-foot {
    display: flex;
    /* `wrap` depuis le 31/08/2026 : la fiche d'un membre pas encore inscrit
       porte quatre boutons, dont « Renvoyer l'invitation ». Sur une modale
       étroite, le premier sortait du cadre et se retrouvait rogné. Ils
       passent maintenant à la ligne, alignés à droite comme les autres. */
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: .7rem;
    padding: 1.1rem 1.6rem;
    border-top: 1px solid var(--line);
    background: var(--surface-0);
    flex-shrink: 0;
  }

  /* --- Carte de confirmation -------------------------------------
     Remplace la boîte `confirm()` du navigateur, peinte par le système
     et étrangère au produit : elle affiche l'URL du serveur, ignore le
     thème et ne dit pas ce qu'on est en train de confirmer. Ici, une
     icône qui annonce la gravité, une question, et deux réponses dont
     une seule est mise en avant. */
  .confirm-corps {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.5rem 1.6rem 1.3rem;
  }
  .confirm-icone {
    width: 42px;
    height: 42px;
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    background: var(--grad-soft);
    color: var(--accent-texte);
  }
  .confirm-icone.danger {
    background: color-mix(in srgb, var(--bad) 12%, transparent);
    color: var(--bad);
  }
  .confirm-titre {
    font-family: var(--font-d);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-1);
  }
  .confirm-texte {
    margin-top: .35rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
    line-height: 1.55;
  }

  /* Mot de passe redemandé avant une suppression massive. Il vit dans le
     corps de la carte, sous la question : c'est la même décision, pas une
     étape de plus. */
  .confirm-motdepasse {
    margin-top: .9rem;
    display: flex;
    flex-direction: column;
    gap: .35rem;
  }

  /* Rail d'onglets collé en tête d'une modale qui défile.

     Une grille de cinquante lignes fait défiler le corps de la modale, et le
     rail partait avec : on ne pouvait plus changer de section sans remonter
     tout le tableau. Il reste donc en place.

     Les marges négatives lui rendent les gouttières du corps de modale :
     sans elles, le contenu qui défile réapparaîtrait à gauche et à droite du
     rail, qui ne couvre que sa propre largeur.

     `top` négatif, et non zéro : le point d'accroche est le bord intérieur
     du corps de modale, sous son rembourrage haut. Collé à zéro, le rail
     laissait au-dessus de lui une bande de vingt pixels dans laquelle le
     tableau continuait de défiler à découvert. */
  .rail-colle {
    position: sticky;
    top: -1.25rem;
    z-index: 6;
    margin: -1.25rem -1.6rem 0;
    padding: 1.25rem 1.6rem .9rem;
    background: var(--bg-1);
    border-bottom: 1px solid var(--line);
  }

  /* --- Panneau de filtres, ouvert par un bouton ------------------ */
  /* Le panneau se pose sur le même mécanisme que le menu contextuel, mais
     il contient des champs : il lui faut un hôte positionné, de la place,
     et des lignes empilées plutôt que des entrées de menu. */
  .filtre { position: relative; }
  /* Sélecteur doublé, et non `.menu-filtre` seul : `.menu` est déclaré plus
     bas dans le fichier et gagnerait à égalité de spécificité, imposant sa
     largeur de menu contextuel à un panneau qui porte des champs. */
  .menu.menu-filtre {
    min-width: 260px;
    padding: .9rem;
    display: flex;
    flex-direction: column;
    gap: .8rem;
  }

  /* Panneau de filtres à dix-sept champs, celui du Suivi quotidien. Il
     défile plutôt que de dépasser l'écran, et reste sous la barre d'outils
     qui l'ouvre. */
  .menu.menu-filtre-large {
    min-width: min(660px, 88vw);
    max-height: 70vh;
    overflow-y: auto;
  }

  /* Deux champs qui n'en font qu'un : un montant minimum et un maximum, une
     date de début et une date de fin. Le « à » qui les sépare ne se
     comprime jamais, les champs se partagent le reste. */
  .paire-filtre {
    display: flex;
    align-items: center;
    gap: .5rem;
  }
  .paire-filtre .input {
    flex: 1 1 0;
    min-width: 0;
  }
  .paire-lien {
    flex-shrink: 0;
    font-size: var(--t-sm);
    color: var(--ink-3);
  }

  /* Filtre ouvert depuis un en-tête de colonne : il s'aligne sur le bord
     GAUCHE de sa colonne et se déploie vers la droite.

     `.menu` s'ancre à droite, ce qui convient à un menu d'actions posé en
     bout de ligne. Sur la première colonne d'un tableau, cet ancrage envoie
     un panneau de 260 px vers la gauche, où il sort du tableau : le conteneur
     `.table-wrap` défile horizontalement, donc il rogne, et la moitié du
     filtre de date devenait invisible. Les colonnes de fin de tableau gardent
     l'ancrage à droite, pour la raison symétrique. */
  .menu.menu-colonne {
    left: 0;
    right: auto;
  }

  /* --- Menu contextuel ⋮ ---------------------------------------- */
  .menu {
    position: absolute;
    right: 0;
    top: 100%;
    margin-top: 4px;
    z-index: 30;
    min-width: 150px;
    padding: .35rem;
    text-align: left;
    background: var(--bg-2);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    box-shadow: var(--ombre-menu);
  }
  .menu button, .menu a {
    display: flex;
    width: 100%;
    align-items: center;
    gap: .55rem;
    padding: .5rem .65rem;
    border: none;
    background: none;
    font-size: var(--t-sm);
    color: var(--ink-2);
    border-radius: var(--r-sm);
    cursor: pointer;
    text-align: left;
  }
  .menu button:hover, .menu a:hover { background: var(--survol); color: var(--ink-1); }
  .menu button.danger:hover { background: color-mix(in srgb, var(--bad) 10%, transparent); color: var(--bad); }

  /* --- État vide -------------------------------------------------- */
  .empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 3.5rem 1rem;
  }
  .empty-titre { font-weight: 600; color: var(--ink-1); }
  .empty-texte { font-size: var(--t-sm); color: var(--ink-3); margin-top: .3rem; max-width: 38ch; }

  /* --- État de chargement (HTMX) ---------------------------------- */
  .spinner {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid var(--line-strong);
    border-top-color: var(--g-c);
    animation: tourne .7s linear infinite;
  }
  @keyframes tourne { to { transform: rotate(360deg); } }

  .chargement {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .6rem;
    padding: 2.5rem 1rem;
    font-size: var(--t-sm);
    color: var(--ink-3);
  }

  /* htmx-indicator : masqué au repos, visible pendant la requête. */
  .htmx-indicator { opacity: 0; transition: opacity .2s; }
  .htmx-request .htmx-indicator,
  .htmx-request.htmx-indicator { opacity: 1; }

  /* Dans un bouton, transparent ne suffit pas : le spinner gardait sa
     largeur et le `gap` du bouton, et poussait le libellé de 26px vers la
     droite. Il ne prend sa place que pendant la requête, et le libellé
     reste centré au repos. */
  .btn > .spinner.htmx-indicator { display: none; }
  .htmx-request .btn > .spinner.htmx-indicator,
  .htmx-request.btn > .spinner.htmx-indicator { display: block; }
  /* Le bloc en cours de remplacement s'estompe : le clic ne semble pas mort. */
  .htmx-swapping { opacity: .45; transition: opacity .15s; }

  /* --- Jauge circulaire ------------------------------------------- */
  .meter { position: relative; width: 132px; height: 132px; flex-shrink: 0; }
  .meter svg { transform: rotate(-90deg); }
  .meter .track { stroke: var(--piste); }
  .meter .fill {
    stroke: url(#gradRing);
    stroke-linecap: round;
    transition: stroke-dashoffset 1s var(--ease);
    filter: drop-shadow(0 0 6px var(--halo-jauge));
  }
  .meter .center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
  }
  .meter-valeur {
    font-family: var(--font-d);
    font-size: var(--t-xl);
    font-weight: 700;
    color: var(--ink-1);
  }
  .meter-unite { font-size: var(--t-xs); color: var(--ink-3); }

  /* --- Divers ------------------------------------------------------ */
  .kpi-line { font-size: var(--t-xs); color: var(--ink-3); }
  .kpi-valeur {
    font-family: var(--font-d);
    font-size: var(--t-xl);
    font-weight: 700;
    color: var(--ink-1);
  }

  .messages { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 1.25rem; }
  .message {
    padding: .75rem 1rem;
    border-radius: var(--r-md);
    border: 1px solid var(--line-strong);
    background: var(--surface-2);
    font-size: var(--t-sm);
    color: var(--ink-2);
    animation: rise .5s var(--ease) both;
    transition: opacity .3s, max-height .45s var(--ease),
                padding .45s var(--ease), border-width .45s;
  }
  /* Sortie douce, orchestrée par app.js : les confirmations s'effacent
     seules, les erreurs et avertissements restent lisibles. */
  .message.sortie {
    opacity: 0;
    max-height: 0 !important;
    padding-top: 0;
    padding-bottom: 0;
    border-width: 0;
    overflow: hidden;
  }
  .message.success { border-color: color-mix(in srgb, var(--ok) 30%, transparent); background: color-mix(in srgb, var(--ok) 8%, transparent); color: var(--ok); }
  .message.error   { border-color: color-mix(in srgb, var(--bad) 30%, transparent); background: color-mix(in srgb, var(--bad) 8%, transparent); color: var(--bad); }
  .message.warning { border-color: color-mix(in srgb, var(--warn) 30%, transparent); background: color-mix(in srgb, var(--warn) 8%, transparent); color: var(--warn); }

  /* --- Cartes indicateur ------------------------------------------
     La brique du tableau de bord : un libellé discret, un nombre qui
     domine, une ligne d'écart. `auto-fit` plutôt qu'un nombre de
     colonnes fixe : trois cartes s'étalent, quatre se rangent, et rien
     ne casse entre les deux. */
  .cartes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 1rem;
    padding: 0 1.25rem 1.4rem;
  }
  .carte {
    padding: 1.1rem 1.15rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-1);
  }
  .carte-libelle {
    display: flex;
    align-items: center;
    gap: .45rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  .carte-valeur {
    margin-top: .55rem;
    font-family: var(--font-d);
    font-size: var(--t-xl);
    font-weight: 700;
    color: var(--ink-1);
    /* Un montant long ne doit ni déborder ni écraser la carte voisine. */
    overflow-wrap: anywhere;
  }
  .carte-unite { font-size: var(--t-md); font-weight: 500; color: var(--ink-3); }
  .carte-ecart {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-top: .5rem;
    font-size: var(--t-sm);
    font-weight: 600;
    /* Écart nul : ni avance ni retard, donc pas de couleur sémantique. */
    color: var(--ink-3);
  }
  .carte-ecart.up   { color: var(--ok); }
  /* Retard contenu : au-dessus de 80 % de l'objectif à date, l'écart se lit
     en orange. Le rouge est réservé au décrochage. */
  .carte-ecart.warn { color: var(--warn); }
  .carte-ecart.down { color: var(--bad); }
  .carte-aide { margin-top: .45rem; font-size: var(--t-xs); color: var(--ink-3); }
  /* Un montant ou un pourcentage ne se coupe jamais en deux lignes. */
  .carte-aide-chiffre { white-space: nowrap; color: var(--ink-2); }

  /* --- Réel contre prévisionnel -----------------------------------
     Deux blocs jumeaux, distingués par une teinte de bord seulement :
     le fond reste celui du panneau, sinon deux aplats colorés
     concurrenceraient le dégradé de la marque. */
  .blocs-perf {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    padding: 0 1.25rem 1.4rem;
  }
  .bloc-perf {
    padding: 1.15rem 1.2rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
  }
  .bloc-perf-titre {
    font-family: var(--font-d);
    font-size: var(--t-sm);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
  }
  .bloc-reel {
    border-color: color-mix(in srgb, var(--warn) 28%, transparent);
    background: color-mix(in srgb, var(--warn) 5%, transparent);
  }
  .bloc-reel .bloc-perf-titre { color: var(--warn); }
  .bloc-prev {
    border-color: color-mix(in srgb, var(--ok) 28%, transparent);
    background: color-mix(in srgb, var(--ok) 5%, transparent);
  }
  .bloc-prev .bloc-perf-titre { color: var(--ok); }

  /* Deux teintes de plus pour le Suivi quotidien, prises dans la palette
     assignable : quatre blocs demandent quatre repères, et le violet et le
     cyan sont les seules teintes libres qui ne concurrencent pas le dégradé. */
  .bloc-temps {
    border-color: color-mix(in srgb, var(--accent-temps) 28%, transparent);
    background: color-mix(in srgb, var(--accent-temps) 5%, transparent);
  }
  .bloc-temps .bloc-perf-titre { color: var(--accent-temps); }
  .bloc-patients {
    border-color: color-mix(in srgb, var(--accent-patients) 28%, transparent);
    background: color-mix(in srgb, var(--accent-patients) 5%, transparent);
  }
  .bloc-patients .bloc-perf-titre { color: var(--accent-patients); }

  /* Quatre blocs : deux colonnes qui se replient à une seule en dessous de
     720 px, comme `.blocs-perf` dont cette classe hérite tout le reste. */
  .blocs-4 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }

  /* Titre de section à l'intérieur d'une modale longue. Sans lui, les vingt
     champs du Suivi quotidien forment un mur indifférencié. */
  /* Le parent `.modal-body` espace déjà ses enfants avec `gap` : la marge ne
     sert qu'à détacher un peu plus un titre de la section précédente. */
  .section-modale {
    margin-top: .6rem;
    font-family: var(--font-d);
    font-size: var(--t-md);
    font-weight: 700;
    color: var(--ink-1);
  }
  .modal-body > .section-modale:first-child { margin-top: 0; }

  /* --- Pagination -------------------------------------------------- */
  .pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .9rem;
    padding: 1.25rem;
  }
  .pagination-rang { font-size: var(--t-sm); color: var(--ink-2); }
  .pagination .btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }

  /* Boutons de pagination : plus compacts que `.btn`, qui était dessiné pour
     une action de formulaire et donnait six pavés en bas de tableau. */
  .btn-page {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .4rem .7rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-1);
    color: var(--ink-2);
    font-size: var(--t-sm);
    font-weight: 500;
    cursor: pointer;
    transition: background .15s, color .15s, border-color .15s;
  }
  .btn-page:hover { background: var(--survol); color: var(--ink-1); }
  .btn-page[aria-disabled="true"] { opacity: .4; cursor: not-allowed; }

  /* Aller directement à une page : le champ tient le numéro, jamais plus
     large que trois chiffres. */
  .pagination-aller { display: flex; align-items: center; gap: .4rem; }
  .pagination-aller .input { width: 4.5rem; padding: .35rem .5rem; text-align: center; }
  .pagination-aller input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
  .pagination-aller input[type="number"]::-webkit-outer-spin-button,
  .pagination-aller input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }

  .perf-ligne {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: .7rem 0;
    border-bottom: 1px solid var(--line);
    font-size: var(--t-sm);
  }
  .perf-ligne:last-child { border-bottom: none; }
  .perf-ligne .perf-libelle { color: var(--ink-2); }
  .perf-ligne .perf-valeur { font-weight: 600; color: var(--ink-1); text-align: right; }

  /* --- Interrupteur ------------------------------------------------
     Case à cocher native repeinte : l'accessibilité clavier et le
     lecteur d'écran restent ceux du navigateur, on ne rhabille que
     l'apparence. */
  .bascule { display: inline-flex; align-items: center; gap: .6rem; cursor: pointer; }
  .bascule-libelle { font-size: var(--t-sm); color: var(--ink-2); }
  .bascule input {
    appearance: none;
    position: relative;
    width: 38px;
    height: 21px;
    margin: 0;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-full);
    background: var(--piste);
    cursor: pointer;
    transition: background .25s var(--ease);
  }
  .bascule input::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 15px;
    height: 15px;
    border-radius: var(--r-full);
    background: var(--ink-2);
    transition: transform .25s var(--ease), background .25s var(--ease);
  }
  .bascule input:checked { background: var(--grad); border-color: transparent; }
  .bascule input:checked::after { transform: translateX(17px); background: var(--sur-grad); }
  .bascule input:focus-visible { outline: 2px solid var(--g-c); outline-offset: 2px; }

  /* Variante lien : le même interrupteur, quand la bascule ne modifie rien en
     base et se contente de changer la lecture. L'état vit alors dans l'URL,
     et `aria-checked` le dit au lecteur d'écran comme à la feuille de style. */
  a.bascule { text-decoration: none; }
  .bascule-piste {
    position: relative;
    display: inline-block;
    width: 38px;
    height: 21px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-full);
    background: var(--piste);
    transition: background .25s var(--ease);
  }
  .bascule-piste::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 15px;
    height: 15px;
    border-radius: var(--r-full);
    background: var(--ink-2);
    transition: transform .25s var(--ease), background .25s var(--ease);
  }
  [aria-checked="true"] .bascule-piste { background: var(--grad); border-color: transparent; }
  [aria-checked="true"] .bascule-piste::after { transform: translateX(17px); background: var(--sur-grad); }
  a.bascule:focus-visible { outline: 2px solid var(--g-c); outline-offset: 3px; border-radius: var(--r-sm); }

  /* --- Graphiques en bâtons ----------------------------------------
     Dessin SVG calculé côté serveur (dashboard.views.graphique). Aucune
     bibliothèque tierce : le navigateur du cabinet ne joint personne. */
  .graphe { padding: 0 1.25rem .5rem; }
  .graphe svg { display: block; width: 100%; height: auto; overflow: visible; }

  .graphe-grille { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 4; }
  .graphe-axe, .graphe-mois {
    fill: var(--ink-3);
    font-family: var(--font-b);
    font-size: 13px;
  }
  .graphe-mois { fill: var(--ink-2); }
  .graphe-jours {
    fill: var(--ink-3);
    font-family: var(--font-b);
    font-size: 11px;
  }
  .graphe-titre-axe {
    fill: var(--ink-2);
    font-family: var(--font-b);
    font-size: 13px;
    font-weight: 600;
  }

  /* Violet pour le réel, bleu pour le prévisionnel (SPEC C.5). Les deux
     teintes sont les bornes du dégradé de la marque, jamais des couleurs
     inventées. */
  .graphe-barre.reel { fill: var(--g-c); }
  .graphe-barre.prev { fill: var(--g-a); }
  .graphe-barre { transition: opacity .15s; }
  .graphe svg:hover .graphe-barre { opacity: .55; }
  .graphe svg .graphe-barre:hover { opacity: 1; }

  .graphe-legende {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 1.5rem;
    padding: 0 1.25rem 1.4rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  .graphe-cle { display: inline-flex; align-items: center; gap: .5rem; }
  .graphe-pastille {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    flex-shrink: 0;
  }
  .graphe-pastille.reel { background: var(--g-c); }
  .graphe-pastille.prev { background: var(--g-a); }

  /* Variante en courbes : le bilan compare quatre années, pas douze mois.
     Des bâtons y feraient perdre la seule chose qui compte, la pente. */
  .graphe-ligne {
    fill: none;
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
  }
  /* Panneau 6 du tableau de bord, en lecture cumulée. Mêmes teintes que les
     bâtons de la lecture mensuelle : basculer l'interrupteur change le
     dessin, jamais la signification des couleurs. */
  .graphe-ligne.reel { stroke: var(--g-c); }
  .graphe-ligne.prev { stroke: var(--g-a); }
  .graphe-point.reel { fill: var(--g-c); }
  .graphe-point.prev { fill: var(--g-a); }
  .graphe-point { transition: r .15s; }
  .graphe svg .graphe-point:hover { r: 5.5; }

  .graphe-ligne.ca { stroke: var(--g-c); }
  .graphe-ligne.benefice { stroke: var(--g-a); }
  .graphe-ligne.remuneration { stroke: var(--pal-5); }
  .graphe-point.ca { fill: var(--g-c); }
  .graphe-point.benefice { fill: var(--g-a); }
  .graphe-point.remuneration { fill: var(--pal-5); }
  .graphe-pastille.ca { background: var(--g-c); }
  .graphe-pastille.benefice { background: var(--g-a); }
  .graphe-pastille.remuneration { background: var(--pal-5); }

  /* --- Barre de filtres (Précomptabilité) ---------------------------
     Une grille qui se remplit toute seule : ajouter un filtre demain ne
     demandera pas de retoucher le gabarit. */
  .filtres {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 1rem;
    align-items: end;
    padding: 0 1.25rem 1.4rem;
  }
  .filtres .champ + .champ { margin-top: 0; }
  .filtres-action { display: flex; align-items: end; }

  /* --- Camembert des charges ----------------------------------------
     Anneau dessiné côté serveur (expenses.views.RepartitionMixin) : une
     part = un arc posé sur le même cercle par stroke-dasharray. */
  .donut {
    display: grid;
    grid-template-columns: minmax(200px, 320px) 1fr;
    gap: 2rem;
    align-items: center;
    padding: 0 1.25rem 1.4rem;
  }
  .donut-dessin { display: flex; justify-content: center; }

  /* Deux camemberts côte à côte, chacun dans une demi-largeur : le dessin ne
     tient plus à côté de sa légende, il passe donc au-dessus. C'est le cas de
     l'écran Correspondants (SPEC C.4 quater), dont le panneau 2 en aligne
     deux dans une `.grille-2`. */
  .donut-etroit {
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 0 0 1rem;
  }
  .donut-etroit .donut-dessin svg { max-width: 200px; }

  /* Deux camemberts jumeaux (SPEC C.4 quater, panneau 2). Les colonnes se
     calent en haut : sans cela, celle qui porte la légende la plus courte
     s'étire sur la hauteur de l'autre, et son dessin, centré dans une cellule
     trop haute, descend sous celui d'à côté. */
  .camemberts { align-items: start; padding: 0 1.25rem 1.4rem; }

  /* Le titre se pose sur l'axe de son dessin, et la légende garde la largeur
     du camembert : les trois éléments d'une carte partagent un même axe. */
  .camembert .eyebrow { text-align: center; margin-bottom: .8rem; }
  .camembert .donut-legende { margin: 0 auto; max-width: 34rem; }
  .donut-dessin svg { width: 100%; max-width: 260px; height: auto; }
  .donut-part { transition: opacity .15s; }
  .donut svg:hover .donut-part { opacity: .55; }
  .donut svg .donut-part:hover { opacity: 1; }

  .donut-legende {
    background: var(--surface-1);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    padding: .5rem 1.1rem;
  }
  .donut-ligne {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .55rem 0;
    border-bottom: 1px dashed var(--line);
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  .donut-ligne:last-child { border-bottom: none; }
  .donut-cle { display: inline-flex; align-items: center; gap: .6rem; }
  .donut-pastille {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    flex-shrink: 0;
  }
  .donut-montant { color: var(--ink-1); white-space: nowrap; }

  /* Charges totales et solde : les deux lignes qu'on lit en premier. */
  .donut-total { border-top: 1px solid var(--line-strong); border-bottom: none; }
  .donut-total .donut-cle,
  .donut-total .donut-montant { font-weight: 600; color: var(--ink-1); }
  .solde-negatif { color: var(--bad); }

  /* --- Champ posé dans une cellule de tableau ------------------------ */
  .input-cellule {
    padding: .35rem .5rem;
    font-size: var(--t-sm);
    min-width: 150px;
  }

  /* Sept colonnes modifiables tiennent dans les 967 px disponibles seulement
     si chaque champ est taillé pour ce qu'il reçoit : une date, trois
     lettres, un montant. Le `min-width` de 150 px ci-dessus vaut pour une
     cellule isolée, il déborde dès qu'une ligne entière se saisit.

     Corrigé le 30/08/2026, la colonne État du devis n'affichant plus que
     « En att… » sous sa propre flèche. Quatre causes, quatre mesures.

     Un `input[type="date"]` ne descend pas sous sa largeur minimale
     intrinsèque, 130 px avec l'icône de calendrier telle que le navigateur
     la dessine : la largeur déclarée ici était sans effet, et les trois
     colonnes de dates gardaient une place que le tableau reprenait
     ailleurs. L'icône resserrée et le champ moins bourré ramènent ce
     minimum à 121 px, et la largeur redevient celle qu'on écrit.

     Le `select`, lui, se laisse écraser sans rien dire : c'était la
     variable d'ajustement de toute la ligne. Il a désormais un plancher.

     Ces quatre règles sont préfixées par `.tbl-saisie`, et il le faut :
     `.tbl-saisie .input` plus haut pose `width: 100%` et `min-width: 0`
     pour la grille de précomptabilité, et sa spécificité l'emportait sur
     les largeurs écrites ici. Elles ne s'appliquaient donc à rien.

     Enfin `select.input` réserve 2.2rem à droite pour sa flèche, que le
     `padding` de `.input-cellule` ramenait à .5rem : le libellé passait
     dessous. La marge est rendue, ajustée à la taille d'une cellule.

     Le resserrement de l'icône est propre à Chrome et Edge. Ailleurs, une
     date reprend ses 130 px : le tableau dépasse alors de quelques pixels
     et `.table-wrap` défile, mais aucun champ n'est plus rogné. */
  .tbl-saisie .input-cellule-date   { min-width: 0; width: 7.6rem; padding-left: .3rem; padding-right: .3rem; }
  .tbl-saisie .input-cellule-date::-webkit-calendar-picker-indicator { width: 12px; margin-left: 2px; padding: 0; }
  .tbl-saisie .input-cellule-nom    { min-width: 0; width: 4.1rem; text-transform: uppercase; }
  .tbl-saisie .input-cellule-etat   { min-width: 6.6rem; width: 6.6rem; padding-right: 1.5rem; }
  .tbl-saisie .input-cellule-montant { min-width: 0; width: 5.6rem; text-align: right; }

  /* Un tableau entièrement saisissable respire moins qu'un tableau de
     lecture : le pas horizontal se resserre pour rendre aux champs ce que
     les marges prenaient.

     `vertical-align: top` aligne les champs entre eux. La cellule du patient
     porte l'âge sous les deux champs, elle est donc plus haute que les
     autres ; centrées, celles-ci descendaient d'une demi-ligne et la rangée
     de champs ondulait. */
  .tbl-saisie thead th, .tbl-saisie tbody td { padding: .5rem .45rem; }
  .tbl-saisie tbody td { vertical-align: top; }

  .cellule-patient { display: inline-flex; gap: .35rem; }

  .cellule-montant {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: .3rem;
  }
  .cellule-suffixe { color: var(--ink-3); font-size: var(--t-sm); }

  /* --- Import d'un relevé bancaire ----------------------------------
     Le champ de fichier vit hors de la zone rechargée par HTMX : le
     remplacer viderait la sélection du navigateur. D'où les deux
     `.modal-body` qui se suivent, dont on rapproche les bords. */
  /* Le choix du fichier ne défile pas : il reste sous les yeux, et c'est la
     suite qui bouge. */
  .import-tete { padding-bottom: .35rem; flex-shrink: 0; }
  .import-suite { padding-top: .35rem; }

  /* Le bloc rechargé par HTMX porte les étapes 2 et 3 ET le pied de modale.
     Il s'intercale entre le `<form>` et le `.modal-body` de la suite, et
     arrivait donc en `display: block`, à hauteur de contenu : le corps
     n'était jamais contraint, son `overflow-y: auto` ne produisait aucune
     barre de défilement, et le mappage débordait d'une modale en
     `overflow: hidden`. Champs du bas et compte rendu devenaient
     inatteignables : la carte paraissait figée. Même correction que pour
     `.modal > form`, et pour la même raison. */
  .import-etapes {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
  }

  .depot {
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-md);
    padding: 1.1rem;
    background: var(--surface-0);
  }
  .depot input[type="file"] {
    width: 100%;
    margin-top: .5rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  /* Le champ de fichier tant que la banque n'est pas choisie : visible, pour
     que le praticien voie où il va, mais manifestement hors service. */
  .depot input[type="file"]:disabled { opacity: .45; cursor: not-allowed; }
  .depot input[type="file"]:disabled::file-selector-button {
    cursor: not-allowed;
    filter: grayscale(1);
  }
  /* Deuxième intitulé du dépôt : la banque d'abord, le fichier ensuite. */
  .depot .label-espace { margin-top: 1rem; }
  .depot input[type="file"]::file-selector-button {
    font-family: inherit;
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--sur-grad);
    background: var(--grad);
    border: none;
    border-radius: var(--r-full);
    padding: .45rem 1rem;
    margin-right: .8rem;
    cursor: pointer;
  }

  /* Le compte rendu de l'étape 3 : quatre chiffres, lus d'un coup d'œil. */
  .import-rapport {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: .8rem;
    margin-bottom: 1rem;
  }
  .import-chiffre {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    padding: .8rem .9rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-1);
  }
  .import-valeur {
    font-family: var(--font-d);
    font-size: var(--t-xl);
    font-weight: 700;
    color: var(--ink-1);
  }
  .import-libelle { font-size: var(--t-xs); color: var(--ink-3); }

  /* La banque reconnue, annoncée en clair : c'est ce qui rend l'automatisme
     vérifiable. Le praticien voit ce que l'application a compris, au lieu
     d'un résultat tombé sans explication. */
  .import-reconnu {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
    margin-bottom: 1rem;
  }

  /* Le mappage des colonnes, devenu un recours et non une étape (SPEC C.6,
     13/08/2026). Replié tant qu'une banque est reconnue, ouvert d'office
     sinon : c'est le seul cas où le praticien a une décision à prendre. */
  .import-repli {
    margin-top: 1.1rem;
    padding-top: .9rem;
    border-top: 1px solid var(--line);
  }
  .import-repli > summary {
    display: flex;
    align-items: center;
    gap: .45rem;
    cursor: pointer;
    font-size: var(--t-sm);
    color: var(--ink-3);
    list-style: none;
  }
  .import-repli > summary::-webkit-details-marker { display: none; }
  .import-repli > summary:hover { color: var(--ink-2); }
  /* Le chevron suit l'état du bloc, sans image ni police d'icônes. */
  .import-repli > summary::before {
    content: "";
    width: .42rem;
    height: .42rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform .15s ease;
  }
  .import-repli[open] > summary::before { transform: rotate(45deg); }
  .import-repli > .champ-aide { margin-top: .6rem; }
  .import-repli > .grille-2 { margin-top: .8rem; }

  /* --- Choix du thème (Réglages) ------------------------------------
     Chaque vignette porte `data-theme` : elle se peint donc avec les
     tokens de SON thème, pas avec ceux de la page. Aucune couleur n'est
     écrite ici, la miniature ne fait que réemployer --bg-0, --bg-1,
     --ink-1 et le dégradé de la marque. */
  .themes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 1rem;
    padding: 0 1.25rem 1.4rem;
  }

  .theme-choix {
    display: flex;
    flex-direction: column;
    gap: .7rem;
    padding: .8rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-1);
    cursor: pointer;
    text-align: left;
    transition: border-color .15s, background .15s, transform .15s var(--ease);
  }
  .theme-choix:hover { border-color: var(--line-survol); background: var(--survol); }
  .theme-choix:active { transform: scale(.99); }
  .theme-choix[aria-pressed="true"] {
    border-color: transparent;
    box-shadow: 0 0 0 2px var(--g-b);
    background: var(--grad-soft);
  }

  /* La miniature : une barre latérale, un titre, deux lignes de texte. */
  .theme-apercu {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 5px;
    height: 76px;
    padding: 5px;
    border-radius: var(--r-sm);
    background: var(--bg-0);
    border: 1px solid var(--line);
    overflow: hidden;
  }
  .theme-apercu-nav {
    border-radius: 4px;
    background: var(--nav-fond);
    border: 1px solid var(--line);
  }
  .theme-apercu-corps {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    padding: 0 7px;
    border-radius: 4px;
    background: var(--bg-1);
    border: 1px solid var(--line);
  }
  .theme-apercu-titre {
    height: 7px;
    width: 62%;
    border-radius: var(--r-full);
    background: var(--grad);
  }
  .theme-apercu-ligne {
    height: 5px;
    border-radius: var(--r-full);
    background: var(--ink-3);
    opacity: .5;
  }
  .theme-apercu-ligne.courte { width: 55%; }

  .theme-pied { display: flex; align-items: center; justify-content: space-between; gap: .6rem; }
  .theme-nom { font-size: var(--t-sm); font-weight: 600; color: var(--ink-1); }
  /* --ink-2 et non --ink-3 : posé sur --surface-1, le gris de méta tombe à
     4.27:1, sous le plancher de 4.5:1 pour un texte de 11 px. */
  .theme-etat { font-size: var(--t-xs); color: var(--ink-2); }
  .theme-choix[aria-pressed="true"] .theme-etat { color: var(--nav-actif-encre); font-weight: 600; }

  /* --- Cartouche de périmètre (écran Statistiques) -----------------
     L'écran ne montre que des moyennes : le premier bloc doit donc dire,
     avant tout chiffre, sur quelle zone on est. D'où un cartouche à part,
     posé au-dessus des cartes, avec le bouton qui change de zone. */
  .zone-entete {
    padding: 1.25rem 1.25rem 1rem;
    margin: 1.25rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--grad-soft);
  }
  .zone-perimetre { display: flex; align-items: flex-start; gap: .85rem; }
  .zone-icone { color: var(--accent-texte); flex-shrink: 0; line-height: 0; }
  .zone-titre { font-family: var(--font-d); font-size: var(--t-lg); font-weight: 600; }
  .zone-texte { margin-top: .25rem; font-size: var(--t-sm); color: var(--ink-2); }
  /* Le menu déroulant se positionne sur ce bloc, pas sur le panneau. */
  .zone-menu { position: relative; flex-shrink: 0; }
  .zone-adresse {
    display: flex;
    align-items: center;
    gap: .55rem;
    margin-top: 1rem;
    padding-top: .9rem;
    border-top: 1px solid var(--line);
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  .zone-adresse svg { color: var(--ink-3); flex-shrink: 0; }

  /* Entrée de menu fermée : affichée, jamais masquée, comme les entrées de
     navigation à construire. Le praticien doit voir le périmètre qui lui
     manque, et pourquoi. */
  .menu button:disabled { opacity: .45; cursor: not-allowed; }
  .menu button:disabled:hover { background: none; color: var(--ink-2); }
  .menu [aria-current="true"] { background: var(--survol); color: var(--ink-1); }

  /* --- Bandeau de zone --------------------------------------------
     Le seul aplat de dégradé plein du produit hors bouton : il sépare le
     choix du périmètre des chiffres qui en découlent, et rappelle sur quoi
     ils portent. */
  .bandeau-zone {
    margin: 0 1.25rem 1.25rem;
    padding: .9rem 1.1rem;
    border-radius: var(--r-md);
    background: var(--grad);
    color: var(--sur-grad);
    text-align: center;
  }
  .bandeau-zone-titre { font-family: var(--font-d); font-size: var(--t-md); font-weight: 600; }
  .bandeau-zone-note { margin-top: .25rem; font-size: var(--t-sm); opacity: .88; }

  /* Blocs d'information empilés : trois notices de lecture, pas trois
     indicateurs. Les mettre côte à côte les ferait passer pour des chiffres. */
  .blocs-empiles { grid-template-columns: 1fr; }
  .bloc-info { text-align: center; }
  .bloc-info-titre {
    font-family: var(--font-d);
    font-size: var(--t-md);
    font-weight: 600;
    color: var(--ink-1);
    margin-bottom: .35rem;
  }
}

/* ============================================================
   UTILITAIRES — le strict minimum, pour éviter les style="" répétés
   ============================================================ */
@layer utilities {
  .flex        { display: flex; }
  .flex-col    { display: flex; flex-direction: column; }
  .items-center{ align-items: center; }
  .justify-between { justify-content: space-between; }
  /* Un bouton seul, poussé à droite : « Supprimer mon compte » (C.10). */
  .justify-end { justify-content: flex-end; }
  .gap-xs { gap: .35rem; }
  .gap-sm { gap: .7rem; }
  .gap-md { gap: 1rem; }
  .gap-lg { gap: 1.75rem; }
  .wrap   { flex-wrap: wrap; }
  /* Posée le 31/08/2026 pour la note sous un tableau : elle courait le long
     du bord gauche et se lisait comme une ligne du tableau, alors qu'elle
     parle de l'ensemble. */
  .texte-centre { text-align: center; }
  .grow   { flex: 1; min-width: 0; }
  /* Tableau des tâches de Collaboration. La case Statut tenait à peine ses
     libellés, « En cours » y passait à la ligne : elle a désormais une
     largeur propre. Le commentaire s'écrit dans la colonne depuis le
     30/08/2026, il lui faut la place d'une phrase. */
  .input-statut { min-width: 8.5rem; }
  .input-commentaire { min-width: 12rem; }
  /* Le tableau des tâches (D.2) : neuf colonnes sur un seul écran, sans
     défilement (22/09/2026). Les champs de réponse se resserrent, le message
     garde sa largeur de lecture. */
  .tbl-taches .input-statut { min-width: 5.5rem; }
  .tbl-taches .cellule-message { min-width: 9.5rem; }
  /* Neuf colonnes : le pas horizontal se resserre encore, sinon la ligne
     du praticien, qui porte le menu Statut, déborde sous 1366 px. */
  .tbl-taches thead th, .tbl-taches tbody td { padding-left: .6rem; padding-right: .6rem; }
  /* La cellule Commentaire : trois lignes coupées comme le message. Pour
     qui répond, la cellule entière est un bouton qui ouvre la carte de
     saisie ; il se lit comme du texte, pas comme un lien. */
  .tbl-taches .cellule-commentaire { min-width: 7rem; }
  .tbl-taches .btn-commentaire {
    width: 100%;
    justify-content: flex-start;
    align-items: flex-start;
    text-align: left;
    white-space: normal;
    font-weight: 500;
    font-size: var(--t-xs);
    padding: .4rem .55rem;
    gap: .4rem;
  }
  .tbl-taches .btn-commentaire svg { flex: none; margin-top: .15rem; color: var(--g-b); }
  .tbl-taches .btn-commentaire .texte-coupe { min-width: 0; }
  .tbl-taches .cellule-commentaire .texte-coupe {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Les intitulés courts restent sur une ligne, « De qui ? » se coupait en
     trois ; seul le plus long a le droit de passer à la ligne. */
  .tbl-taches thead th { white-space: nowrap; }
  .tbl-taches thead .th-souple { white-space: normal; }
  /* Les deux listes de mois du tableau de bord. Sans largeur propre, le
     select se tasse sur la place que lui laisse l'en-tête du panneau et
     coupe « Décembre » en « Décembr », flèche comprise. Neuf lettres et
     demie, c'est « Septembre » plus le chevron. */
  .input-mois { min-width: 10.5rem; }

  .mt-sm  { margin-top: .5rem; }
  .mt-md  { margin-top: 1.25rem; }
  .mt-lg  { margin-top: 1.75rem; }
  .mb-md  { margin-bottom: 1.25rem; }
  .mb-lg  { margin-bottom: 1.75rem; }
  .texte-fort  { font-weight: 600; color: var(--ink-1); }
  .texte-doux  { color: var(--ink-2); }
  .texte-meta  { font-size: var(--t-xs); color: var(--ink-3); }
  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}

/* ============================================================
   PAGE D'IMPRESSION — export PDF par le navigateur

   Aucune bibliothèque PDF côté serveur : la page est rendue par Django,
   puis enregistrée en PDF par le navigateur. Ce qui ne doit pas figurer
   sur le papier porte `.sans-impression`.
   ============================================================ */
@layer composants {
  .impression {
    max-width: 900px;
    margin: 0 auto;
    padding: 2.5rem 1.5rem;
  }
  .impression-tete {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
  }
}

@media print {
  .sans-impression { display: none !important; }
  .impression { padding: 0; max-width: none; }
  .glass { border: none; background: none; backdrop-filter: none; }
  /* Rien ne bouge sur du papier, et un dégradé animé s'imprime en image :
     le liseré des lignes de section, animé à l'écran, pesait à lui seul la
     moitié du PDF de la grille d'évaluation. Aplat, et plus d'animation. */
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .row-sys { background: none; }
  .row-sys td:first-child::before { background: var(--g-b); }
  /* Une ligne coupée en deux par un saut de page devient illisible. */
  .tbl tbody tr { break-inside: avoid; }
  .tbl thead { display: table-header-group; }
}

/* ============================================================
   RESPONSIVE — plancher : 380 px
   ============================================================ */
@media (max-width: 900px) {
  .shell { grid-template-columns: minmax(0, 1fr); }

  .nav {
    position: static;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: .75rem;
    overflow-x: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
    padding: .75rem 1rem;
  }
  .nav-groupe { flex-direction: row; }
  .nav-titre, .nav-cabinet-meta, .nav-cabinet-fonction, .nav-pied { display: none; }
  .nav-cabinet { padding: .4rem .6rem; white-space: nowrap; }
  .nav-lien { white-space: nowrap; }
  .nav-lien.on { box-shadow: inset 0 -2px 0 0 var(--g-b); }
}

@media (max-width: 720px) {
  .page { padding: 1.5rem 1.25rem 3rem; }
  .panel-head { flex-direction: column; align-items: flex-start; }
  .module-head { align-items: flex-start; }
  .tbl thead th, .tbl tbody td { padding: .8rem .9rem; }
  /* Réel et prévisionnel l'un sous l'autre : côte à côte sur mobile,
     chaque montant passerait à la ligne au milieu d'un nombre. */
  .blocs-perf { grid-template-columns: 1fr; }
  .cartes, .blocs-perf { padding: 0 1rem 1.25rem; }
  /* L'anneau passe au-dessus de sa légende : côte à côte, les montants
     se couperaient au milieu d'un nombre. */
  .donut { grid-template-columns: 1fr; gap: 1.25rem; }
  .donut, .filtres { padding: 0 1rem 1.25rem; }
}

@media (max-width: 420px) {
  .page { padding: 1.25rem 1rem 3rem; }
  .grille-2, .grille-3 { grid-template-columns: 1fr; }
  .modal-head { padding: 1.1rem 1.1rem 0; }
  .modal-body { padding: 1rem 1.1rem 1.25rem; }
  .modal-foot { padding: .9rem 1.1rem; flex-direction: column-reverse; }
  .modal-foot .btn { width: 100%; }
  .titre-module { font-size: var(--t-xl); }
}

/* Écran qui prend toute la place de la page, l'écran Coach aujourd'hui : la
   page perd ses marges, la colonne sa largeur maximale, et le panneau reçoit
   toute la hauteur disponible. Les deux classes se posent depuis le gabarit,
   par les blocs prévus dans base.html.

   Écrit ICI, hors couche et après les requetes média ci-dessus : celles-ci
   redéfinissent le remplissage de `.page` sur petit écran et sont elles-mêmes
   hors couche. Une règle hors couche bat toute règle en couche, quelle que
   soit sa spécificité ; placées dans `components`, ces deux-là restaient sans
   effet sous 720 px.

   La hauteur se prend par emboitement de colonnes flex, jamais en `100vh` :
   sur mobile la navigation devient une barre en tête, et une hauteur de
   fenêtre entière la ferait déborder d'autant. */
.page.page-pleine {
  padding: 0;
  display: flex;
  flex-direction: column;
}
.page-inner.page-inner-pleine {
  max-width: none;
  margin: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* ============================================================
   COACH — page d'attente (dashboard/coach.html)

   Le module n'existe pas encore : l'écran met en scène une
   présence, pas une fonctionnalité. Tout est dessiné avec les
   tokens des deux thèmes ; aucune couleur en dur, le mouvement
   est neutralisé par le bloc prefers-reduced-motion ci-dessous.
   ============================================================ */
@layer components {

  .coach-scene {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* L'orbe et les deux mots au milieu de la hauteur reçue, la signature
       posée au pied : elle est en absolu, elle ne déplace donc pas le
       centre de ce qu'elle accompagne. */
    justify-content: center;
    text-align: center;
    padding: 4.5rem 2rem 3.5rem;
    min-height: 480px;
    /* Toute la hauteur de la page quand la coquille la lui donne. */
    flex: 1;
    border-radius: 0;
    border-left: none;
    border-right: none;
  }

  /* Halo qui dérive lentement derrière la scène : la pièce sombre
     dans laquelle quelque chose travaille. */
  .coach-scene::before {
    content: "";
    position: absolute;
    inset: -40%;
    z-index: -1;
    background: conic-gradient(from 0deg,
                transparent,
                color-mix(in srgb, var(--g-a) 10%, transparent) 25%,
                transparent 50%,
                color-mix(in srgb, var(--g-c) 9%, transparent) 75%,
                transparent);
    filter: blur(60px);
    animation: coach-derive 26s linear infinite;
  }

  /* Trame de points, comme l'aurore, mais concentrée sur le centre. */
  .coach-scene::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: radial-gradient(var(--aurora-points) 1px, transparent 1px);
    background-size: 22px 22px;
    mask-image: radial-gradient(ellipse 60% 55% at 50% 40%, #000 20%, transparent 72%);
    -webkit-mask-image: radial-gradient(ellipse 60% 55% at 50% 40%, #000 20%, transparent 72%);
  }

  /* --- L'orbe et ses orbites ------------------------------------ */
  .coach-presence {
    position: relative;
    width: 300px;
    height: 300px;
    display: grid;
    place-items: center;
    margin-bottom: .5rem;
  }

  .coach-orbe {
    width: 128px;
    height: 128px;
    border-radius: 50%;
    background: var(--grad);
    box-shadow: var(--ombre-grad-forte), var(--glow);
    animation: coach-pouls 5s var(--ease) infinite alternate;
    position: relative;
    overflow: hidden;
  }
  /* Reflet : la sphère prend du volume sans une couleur de plus. */
  .coach-orbe::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: radial-gradient(circle at 32% 26%,
                color-mix(in srgb, var(--sur-grad) 55%, transparent),
                transparent 55%);
  }
  /* Voile interne qui tourne : l'orbe semble réfléchir. */
  .coach-orbe::after {
    content: "";
    position: absolute;
    inset: -30%;
    background: conic-gradient(from 0deg,
                transparent,
                color-mix(in srgb, var(--sur-grad) 28%, transparent) 18%,
                transparent 40%);
    animation: coach-rotation 7s linear infinite;
  }

  .coach-anneau {
    position: absolute;
    border-radius: 50%;
    border: 1px solid color-mix(in srgb, var(--g-b) 30%, transparent);
  }
  /* Le satellite : un point qui parcourt l'anneau. */
  .coach-anneau::before {
    content: "";
    position: absolute;
    top: -3px;
    left: 50%;
    width: 6px;
    height: 6px;
    margin-left: -3px;
    border-radius: 50%;
    background: var(--g-c);
    box-shadow: 0 0 10px 2px color-mix(in srgb, var(--g-c) 55%, transparent);
  }
  .coach-anneau-1 {
    width: 206px;
    height: 206px;
    animation: coach-rotation 14s linear infinite;
  }
  .coach-anneau-2 {
    width: 286px;
    height: 286px;
    border-color: color-mix(in srgb, var(--g-a) 22%, transparent);
    animation: coach-rotation 30s linear infinite reverse;
  }
  .coach-anneau-2::before { background: var(--g-a); box-shadow: 0 0 10px 2px color-mix(in srgb, var(--g-a) 55%, transparent); }

  /* --- Le texte -------------------------------------------------- */
  .coach-soon {
    font-family: var(--font-d);
    font-weight: 700;
    font-size: clamp(1.5rem, 4.5vw, 2.4rem);
    letter-spacing: .32em;
    text-indent: .32em;   /* compense l'espacement de la dernière lettre */
    text-transform: uppercase;
    background: var(--grad);
    background-size: 220% auto;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: coach-brille 6s linear infinite;
  }

  /* La signature du module, au pied de la scène. `margin-top: auto` la
     pousse au bas du panneau quelle qu'en soit la hauteur : la scène est
     une colonne flex, l'orbe et les deux mots restent au centre.

     Police d'affichage, lettres espacées et dégradé de marque, porté par
     `.grad-text` dans le gabarit : c'est la même écriture que COMING SOON
     au-dessus, en très petit et sans l'animation, qui ferait deux mouvements
     concurrents dans le même panneau. */
  .coach-ai {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 2.4rem;
    font-family: var(--font-d);
    font-size: var(--t-sm);
    font-weight: 600;
    letter-spacing: .3em;
    text-indent: .3em;
    opacity: .85;
  }

  /* --- L'entrée de navigation, en couleur de marque -------------- */
  .nav-lien-coach {
    color: var(--accent-texte);
    background: var(--grad-soft);
    border: 1px solid color-mix(in srgb, var(--g-b) 28%, transparent);
  }
  .nav-lien-coach:hover {
    color: var(--accent-texte);
    background: var(--grad-soft);
    box-shadow: 0 0 14px color-mix(in srgb, var(--g-b) 25%, transparent);
  }
  .nav-lien-coach.on { box-shadow: inset 2px 0 0 0 var(--g-b), 0 0 14px color-mix(in srgb, var(--g-b) 25%, transparent); }

  @keyframes coach-derive    { to { transform: rotate(360deg); } }
  @keyframes coach-rotation  { to { transform: rotate(360deg); } }
  @keyframes coach-pouls {
    from { box-shadow: var(--ombre-grad), var(--glow); transform: scale(1); }
    to   { box-shadow: var(--ombre-grad-forte), var(--glow); transform: scale(1.05); }
  }
  @keyframes coach-brille    { to { background-position: -220% center; } }
}

@media (max-width: 720px) {
  .coach-scene { min-height: 420px; padding: 3.5rem 1.25rem 2.75rem; }
  .coach-presence { width: 240px; height: 240px; }
  .coach-anneau-1 { width: 168px; height: 168px; }
  .coach-anneau-2 { width: 232px; height: 232px; }
  .coach-orbe { width: 104px; height: 104px; }
}

/* ============================================================
   ENTRÉE — connexion et pages de compte (allauth)

   Une carte de verre centrée sous l'aurore : la première
   impression du produit. Le gabarit est allauth/layouts/
   entrance.html ; les champs reçoivent .label et .input par les
   éléments surchargés, le reste se style ici par portée.
   ============================================================ */
@layer components {

  .entree {
    position: relative;
    z-index: 1;
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 2.5rem 1.25rem;
  }

  .entree-carte {
    width: 100%;
    max-width: 400px;
    padding: 2.5rem 2.25rem 2.25rem;
  }

  /* La bascule de thème se pose au-dessus de la carte, calée sur son bord
     droit : dedans, elle entrerait en concurrence avec l'orbe et la marque. */
  .entree-bascule {
    width: 100%;
    max-width: 400px;
    display: flex;
    justify-content: flex-end;
    margin-bottom: .5rem;
  }

  .entree-marque {
    display: block;
    text-align: center;
    font-family: var(--font-d);
    font-weight: 700;
    font-size: var(--t-md);
    letter-spacing: .22em;
    text-indent: .22em;
    text-transform: uppercase;
  }

  /* L'orbe du Coach, en petit : la même présence, posée sur la porte. */
  .entree-orbe {
    display: block;
    margin: 0 auto 1.4rem;
    width: 58px;
    height: 58px;
  }

  /* Et en miniature dans les états vides (components/etat_vide.html).
     Écrite après .coach-orbe : à spécificité égale, c'est l'ordre qui
     tranche, la taille miniature doit gagner. */
  .empty-orbe {
    width: 46px;
    height: 46px;
    margin-bottom: .9rem;
  }

  .entree h1 {
    text-align: center;
    font-size: var(--t-xl);
    margin-top: 1.1rem;
  }

  /* Les paragraphes d'allauth : intro, renvois. Jamais de sélecteur
     d'élément hors de cette portée. */
  .entree-carte > p,
  .entree form > p:not(:has(input)) {
    text-align: center;
    font-size: var(--t-sm);
    color: var(--ink-2);
    margin-top: .5rem;
  }
  .entree a { color: var(--accent-texte); font-weight: 600; }
  /* Les pages d'erreur posent un bouton dégradé dans la carte : la règle
     ci-dessus, plus spécifique que .btn-grad, lui volerait sa couleur. */
  .entree a.btn-grad { color: var(--sur-grad); }

  /* L'annonce de l'essai, sur la création de compte. La durée porte le
     dégradé de marque dans la police de titre, et l'objet la suit en petites
     capitales : c'est le nombre qui se retient, pas la phrase. La pilule les
     tient ensemble sans le poids d'un encadré, la carte de verre en portant
     déjà un. */
  /* Portée `.entree-carte >` et non `.essai` seul : la règle des
     paragraphes de la carte, deux lignes plus haut, est plus spécifique et
     reprendrait la marge et la couleur. */
  .entree-carte > .essai {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    width: fit-content;
    margin: 1.15rem auto 0;
    padding: .5rem 1rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-full);
    background: var(--surface-1);
  }
  .essai-duree {
    font-family: var(--font-d);
    font-size: var(--t-lg);
    font-weight: 700;
    letter-spacing: -.01em;
  }
  .essai-objet {
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--ink-3);
  }
  .entree-carte > .essai-note {
    margin-top: .7rem;
    color: var(--ink-3);
    line-height: 1.5;
  }

  .entree form {
    display: flex;
    flex-direction: column;
    gap: .95rem;
    margin-top: 1.4rem;
  }
  .entree form p {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    margin: 0;
  }
  .entree form p:has(> input[type="checkbox"]) {
    flex-direction: row;
    align-items: center;
    gap: .5rem;
    font-size: var(--t-sm);
    color: var(--ink-2);
  }
  .entree form button { width: 100%; margin-top: .4rem; }

  /* Erreurs de champ : la liste brute de Django, apprivoisée. */
  .entree .errorlist,
  .compte .errorlist {
    list-style: none;
    margin: 0;
    padding: .55rem .8rem;
    border-radius: var(--r-sm);
    border: 1px solid color-mix(in srgb, var(--bad) 30%, transparent);
    background: color-mix(in srgb, var(--bad) 8%, transparent);
    color: var(--bad);
    font-size: var(--t-sm);
  }

  /* « Mot de passe oublié ? » : l'aide du champ, discrète, à droite. */
  .entree .helptext {
    font-size: var(--t-xs);
    text-align: right;
  }
  .entree .helptext a { color: var(--ink-3); font-weight: 500; }
  .entree .helptext a:hover { color: var(--accent-texte); }

  /* Repli discret sous la connexion : récupérer un compte supprimé (C.10).
     Fermé par défaut, il ne pèse qu'une ligne en gris. */
  .entree-repli {
    margin-top: 1.1rem;
    font-size: var(--t-xs);
    color: var(--ink-3);
    text-align: center;
  }
  .entree-repli summary { cursor: pointer; font-weight: 500; }
  .entree-repli summary:hover { color: var(--accent-texte); }
  .entree-repli p { margin-top: .5rem; line-height: 1.5; }
  .entree-repli a { color: var(--accent-texte); font-weight: 500; }

  .entree-pied {
    margin-top: 1.25rem;
    text-align: center;
    font-size: var(--t-xs);
    color: var(--ink-3);
  }
}

/* ============================================================
   COMPTE — pages allauth d'un utilisateur connecté

   Le gabarit est allauth/layouts/manage.html, raccroché à la
   coquille : ici, seulement la portée .compte qui apprivoise le
   balisage rendu par les éléments allauth (h1, form.as_p, radios
   de la liste d'adresses). Les alias .label et .input sont
   étendus dans la section Formulaires.
   ============================================================ */
@layer components {

  .compte {
    max-width: 640px;
    padding: 1.9rem 1.75rem 2rem;
  }

  /* Le h1 du contenu est le titre de la page : même traitement que
     .titre-module en dégradé, à l'échelle d'un panneau. */
  .compte h1 {
    font-family: var(--font-d);
    font-size: var(--t-xl);
    font-weight: 700;
    letter-spacing: -.01em;
    background: var(--grad);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    margin-bottom: .4rem;
  }
  .compte h2 {
    font-size: var(--t-lg);
    margin: 1.9rem 0 .4rem;
  }

  .compte p {
    font-size: var(--t-sm);
    color: var(--ink-2);
    margin-bottom: .9rem;
  }
  .compte a { color: var(--accent-texte); font-weight: 600; }
  /* Même piège que dans `.entree` : la règle ci-dessus est plus spécifique
     que `.btn-grad` et lui volait sa couleur. Les boutons d'allauth sont des
     liens dès qu'ils portent une adresse, et « Télécharger les codes »
     s'affichait en bleu sombre sur un dégradé bleu sombre, illisible. */
  .compte a.btn-grad { color: var(--sur-grad); }
  .compte a.btn-ghost { color: var(--ink-2); }
  .compte a.btn-ghost:hover { color: var(--ink-1); }
  .compte a.btn-danger { color: var(--bad); }

  /* Allauth range ses actions dans une liste. Sans cette règle, la double
     authentification affichait ses boutons à la suite de puces, décalés vers
     la droite : une liste à puces de boutons n'est pas une liste. */
  .compte ul:not(.errorlist) {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    align-items: center;
  }

  .compte form { margin-top: 1.1rem; }
  .compte form > p { margin-bottom: .95rem; }

  /* La liste d'adresses : une ligne par radio, l'adresse et ses chips. */
  .compte form p:has(> input[type="radio"]) {
    display: flex;
    align-items: center;
    gap: .55rem;
    margin-bottom: .55rem;
  }
  .compte form p:has(> input[type="radio"]) label {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    color: var(--ink-1);
  }
  .compte input[type="radio"] { accent-color: var(--g-b); }

  /* Les actions se suivent sur une ligne, à la taille de leur libellé.
     Sans le `form` : la page des codes de récupération pose ses deux boutons
     hors de tout formulaire, et ils se touchaient. */
  .compte .btn { margin: .5rem .5rem 0 0; }

  /* La zone de texte garde la hauteur qu'allauth demande par son attribut
     `rows`, et ne se redimensionne qu'en hauteur : elargie, elle sortirait
     du panneau. Aucune police a chasse fixe n'est imposee, le design system
     n'en declare pas et ce n'est pas ici qu'on en invente une. */
  .compte textarea { resize: vertical; }

  .compte .helptext {
    display: block;
    font-size: var(--t-xs);
    color: var(--ink-3);
    margin-top: .3rem;
  }
}

/* ============================================================
   GRAPHIQUES DU TABLEAU DE BORD — dégradés et entrée animée

   Les dégradés sont définis une fois dans _graphiques.html ; les
   couleurs des arrêts vivent ici, en tokens. Les animations sont
   portées par la classe .graphe-anime : les SVG du bilan et des
   statistiques, qui ne l'ont pas, ne bougent pas.
   ============================================================ */
@layer components {

  /* Arrêts de dégradé : la couleur par série, le dosage par position. */
  .graphe-stop-reel { stop-color: var(--g-c); }
  .graphe-stop-prev { stop-color: var(--g-a); }
  .graphe-stop-haut    { stop-opacity: .2; }
  .graphe-stop-bas     { stop-opacity: 0; }
  .graphe-stop-plein   { stop-opacity: 1; }
  .graphe-stop-attenue { stop-opacity: .55; }

  /* L'aire sous une courbe cumulée. Le repli `var(--g-…)` couvre le cas
     d'un dégradé introuvable : sans lui, la forme disparaîtrait. */
  .graphe-aire { stroke: none; animation: graphe-apparait .8s var(--ease) .2s both; }
  .graphe-aire.reel { fill: url(#graphe-aire-reel) var(--g-c); }
  .graphe-aire.prev { fill: url(#graphe-aire-prev) var(--g-a); }

  /* Barres en dégradé vertical, qui poussent depuis la ligne de base. */
  .graphe-anime .graphe-barre.reel { fill: url(#graphe-barres-reel) var(--g-c); }
  .graphe-anime .graphe-barre.prev { fill: url(#graphe-barres-prev) var(--g-a); }
  .graphe-anime .graphe-barre {
    transform-box: fill-box;
    transform-origin: center bottom;
    animation: graphe-pousse .55s var(--ease) both;
  }

  /* Les courbes se tracent, puis leurs points se posent. */
  .graphe-anime .graphe-ligne {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: graphe-trace .9s var(--ease) .15s forwards;
  }
  .graphe-anime .graphe-point { animation: graphe-apparait .35s var(--ease) .8s both; }

  @keyframes graphe-pousse   { from { transform: scaleY(0); } }
  @keyframes graphe-trace    { to { stroke-dashoffset: 0; } }
  @keyframes graphe-apparait { from { opacity: 0; } }
}

/* Sans animation, une courbe en pointillés d'attente resterait
   invisible : on la rend entière d'emblée. */
@media (prefers-reduced-motion: reduce) {
  .graphe-anime .graphe-ligne { stroke-dasharray: none; stroke-dashoffset: 0; }
}

/* ============================================================
   BARRE DE PROGRESSION — requêtes HTMX

   Un filet dégradé qui parcourt le haut de l'écran tant qu'une
   requête est en vol. Montrée par app.js après un court délai :
   une requête rapide ne provoque aucun clignotement. Au-dessus
   de tout, même d'une modale (l'overlay est à 50).
   ============================================================ */
@layer components {

  .progression {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    z-index: 60;
    pointer-events: none;
    overflow: hidden;
    opacity: 0;
    transition: opacity .2s;
  }
  .progression::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--grad);
    transform: translateX(-100%);
  }
  .progression.on { opacity: 1; }
  .progression.on::before { animation: progression-course 1.1s var(--ease) infinite; }

  @keyframes progression-course {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
  }
}

/* Non négociable : neutralise TOUTES les animations et transitions. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
