/* ============================================================
   POLICES — hebergees ici, JAMAIS depuis Google Fonts
   /!\ L'espace client est AUTHENTIFIE : l'adresse IP envoyee a Google a
   chaque visite serait une donnee nominative, et cette pratique a deja ete
   sanctionnee en Europe. Six ecrans les chargeaient depuis Google
   (connexion, inscription, mots de passe oublies, demande, souscription) —
   rapatriees le 2026-08-16.
   Geist et JetBrains Mono sont sous licence OFL : l'hebergement local est
   permis. Seuls les sous-ensembles latin et latin-ext sont embarques (308 Ko) ;
   les alphabets cyrillique, grec et vietnamien de Google sont ecartes.
   ============================================================ */
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/static/fonts/Geist-300-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/static/fonts/Geist-300-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/Geist-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/Geist-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/Geist-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/Geist-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/Geist-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/static/fonts/Geist-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/static/fonts/Geist-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/static/fonts/Geist-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/JetBrainsMono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/JetBrainsMono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/JetBrainsMono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/static/fonts/JetBrainsMono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   TVNI — Charte graphique v3 (light only, vanilla CSS)
   Source de vérité unique pour tous les tokens design.
   ============================================================ */

/* ============================================================
   :root — Design tokens
   ============================================================ */
:root {
  /* Surfaces */
  /* ======================================================================
     SOMBRE — le seul thème. Décision du fondateur, 2026-08-16.
     Palette reprise de la vitrine (`landing/index.html`) : les deux
     partageaient déjà `#4A3B7A`, l'application n'en était que l'expression
     claire. Elle rejoint la sombre.
     /!\ Le mode clair est ABANDONNÉ. Ses valeurs sont dans l'historique git —
     ne pas les laisser traîner en commentaire ici.
     ====================================================================== */
  --background:          #0A0B10;
  --card:                #14161F;
  --muted:               #1A1D27;
  --border:              rgba(255,255,255,0.07);
  /* Liseré plus affirmé, pour ce qui doit se détacher du fond : boutons
     secondaires, champs au survol, cartes posées sur une surface déjà sombre.
     Vient des écrans d'authentification, qui portaient leur propre vocabulaire
     (`--border-2`) — rapatrié ici le 2026-08-16. */
  --border-strong:       rgba(255,255,255,0.12);
  /* Haut du degrade du bouton principal, et lueur qui le fait flotter. Viennent
     des ecrans d'authentification, ou ce bouton etait le plus abouti de
     l'application — promu ici le 2026-08-16 pour que tous les ecrans en heritent. */
  --accent-hi:           #C8BEFF;
  --accent-glow:         rgba(139,127,224,0.30);
  --accent-glow-strong:  rgba(139,127,224,0.45);
  /* Survol (repris de la vitrine, 2026-08-16) : un liseré d'accent tres discret
     sur une carte qu'on souleve, et un voile de la meme teinte sur un bloc qui
     s'eclaire sans bouger. Trois valeurs, pas une : le liseré d'une carte doit
     s'effacer devant celui d'un bloc directement survolé. */
  --accent-ring:         rgba(180,168,255,0.12);
  --accent-ring-strong:  rgba(180,168,255,0.25);
  --accent-wash:         rgba(180,168,255,0.04);
  --ring:                #B4A8FF;

  /* Texte */
  --foreground:          #F5F4F0;
  --muted-foreground:    rgba(245,244,240,0.62);
  --disabled-foreground: rgba(245,244,240,0.40);
  --on-primary:          #0A0B10;
  --on-accent:           #14161F;

  /* Marque
     /!\ L'accent CHANGE de valeur par rapport au thème clair (#4A3B7A) : un
     violet foncé sur fond noir ne se lit pas. C'est le RÔLE qui est stable,
     pas la valeur — c'est exactement à ça que servent ces variables. */
  --primary:             #8B7FE0;
  --accent:              #B4A8FF;

  /* Sémantique */
  --success:             #4ADE80;
  --error:               #F87171;
  --warning:             #FBBF24;
  --warning-text:        #FCD34D;
  --info:                #7DD3FC;

  /* Trading */
  --profit:              #4ADE80;
  --loss:                #F87171;
  --neutral:             rgba(245,244,240,0.55);

  /* Charts */
  --chart-1:             #B4A8FF;
  --chart-2:             #8B7FE0;
  --chart-3:             #7DD3FC;
  --chart-4:             #4ADE80;
  --chart-5:             rgba(245,244,240,0.55);

  /* Typographie */
  --font-sans:           'Geist', -apple-system, BlinkMacSystemFont, sans-serif;
  /* /!\ `--font-mono` n'est PLUS une police a chasse fixe (2026-08-24, demande du
     fondateur : « je n'aime pas la police des titres de KPI, mets la meme que
     l'autre »). Le nom reste parce qu'il est cite ~120 fois, dans la charte ET
     dans les gabarits : le repointer ICI change toute l'application d'un geste —
     etiquettes, chiffres, badges compris. L'alignement des colonnes ne tenait
     deja pas a la chasse fixe mais a `font-variant-numeric: tabular-nums`, pose
     partout ou il compte. Pour du VRAI monospace (cle, code, chemin), il reste
     `--font-code`. */
  --font-mono:           'Geist', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-code:           'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ============================================================
     L'ECHELLE DE TEXTE : dix crans, et un seul endroit pour les regler
     ============================================================
     Posee le 2026-09-12, choix du fondateur (« je veux vraiment agrandir la
     police sans zoomer », option « texte courant a 16 px » parmi trois montrees
     cote a cote sur un fragment d'ecran reconstitue).

     /!\ CE QUE CETTE ECHELLE REMPLACE. L'application ecrivait ses tailles a la
     main : 972 declarations `font-size: Npx` dans 69 fichiers, pour 32 valeurs
     distinctes dont NEUF tassees entre 9 et 15,5 px — et ces neuf-la portaient
     90 % du texte. Entre 12 px et 12,5 px l'oeil ne voit rien, mais ce sont deux
     reglages a tenir, et c'est ce tassement qui rendait tout agrandissement
     impossible a viser. Les dix crans ci-dessous les rangent : il n'existe plus
     de 12,5 px a cote d'un 13 px.

     /!\ POURQUOI EN `rem` ET PAS EN `px`. Un cran vaut une part de la taille de
     reference du NAVIGATEUR (1rem = 16 px par defaut, et aucune regle de ce
     depot ne pose de `font-size` sur `html` ou `:root` — ne pas en poser, ce
     serait reprendre au client ce qu'on vient de lui rendre). Consequence :
     un client qui regle son navigateur en gros caracteres est enfin ECOUTE.
     Le zoom, lui, l'ignorait completement.

     /!\ CE N'EST PAS UN RETOUR DU ZOOM, et la difference est le coeur du
     chantier : le zoom grossissait le texte ET ses boites parce qu'il ne savait
     pas faire autrement. Ici les boites suivent leur texte parce qu'elles sont
     exprimees en `em` — voir la section « LES BOITES QUI SUIVENT LEUR TEXTE ».
     `--echelle` reste a 1 et n'a pas a remonter. */

  /* Les DIX jetons d'avant, rebranches sur les crans. Ils sont cites 103 fois
     dans la charte et les gabarits : les repointer ICI fait grandir tout ce qui
     passait deja par eux, sans toucher a un seul gabarit.
     /!\ Ils ne sont PAS deprecies et ne partiront pas : ils disent le ROLE
     (« une legende », « une cellule de donnee »), la ou un cran ne dit que la
     taille. Le role est ce qu'on veut lire dans un gabarit.
     /!\ Les GRANDS ne bougent pas — h4, display-3, kpi, display-2, display-1
     gardent leur taille. La plainte n'a jamais porte sur les titres, et les
     grossir couterait des lignes a l'ecran sans rien rendre plus lisible. */

  --leading-tight:       1.0;
  --leading-snug:        1.2;
  --leading-normal:      1.5;
  --leading-relaxed:     1.6;

  --tracking-wordmark:   0.28em;
  --tracking-eyebrow:    0.22em;
  --tracking-mono-data:  0.04em;
  --tracking-tight:      -0.01em;

  /* Le CADRE : la largeur d'une page, et la colonne d'explication d'un encadre
     de reglage. Deux valeurs, jamais reecrites dans un gabarit : voir la section
     « Le CADRE » plus bas pour le pourquoi. */
  /* /!\ **1150 depuis le 2026-09-12** (fondateur, apres comparaison sur le
     tableau de bord). 1280 etait la place disponible a droite du menu sur son
     ecran de 1536 points : la page y remplissait l'ecran d'un bord a l'autre.
     Avec l'echelle a 1,08, une ligne de 1280 devient une ligne de 1382 points
     reels, et une phrase de cette longueur se relit mal. Le cadre cede, la
     lisibilite gagne. Les deux valeurs se sont choisies ENSEMBLE, on ne touche
     pas l'une sans regarder l'autre. */
  /* MAQUETTE — VARIANTE 4, PLEIN ECRAN PLAFONNE (2026-09-17). UNE SEULE
     largeur, portee par `.wrap` et `.cadre` : tout l'espace client la recoit
     d'un coup, aucun ecran ne reste en arriere a 1150.
     /!\ **2000 N'EST PAS UN CHIFFRE ROND, C'EST LE POSTE DU FONDATEUR.** Sous
     ce plafond (1440, 1920), la page occupe la fenetre et replier la barre
     elargit VRAIMENT le contenu. Au-dessus (2560), elle s'arrete et se centre :
     les memes proportions qu'a 1920, des marges symetriques, et aucune des
     rangees orphelines que la variante 3 fabriquait. */
  --cadre:               2000px;
  --cadre-large:         2000px;
  --explication:         300px;

  /* L'ECHELLE de toute l'application. Voir la section « L'ECHELLE » plus bas :
     c'est le seul bouton pour grossir ou reduire l'ensemble, et il n'en existait
     aucun avant le 2026-08-18. */
  /* Pose a 1.2 le matin du 2026-08-18 (« il faut qu'on agrandisse la police, on ne
     voit pas grand chose »), ramene a 1 l'apres-midi (« dezoome l'appli »), arrete
     a 1.1 dans la foulee (« agrandis tout de 10 % au lieu de 20 »). Un seul nombre,
     trois essais, aucune ligne de code touchee entre les deux.

     /!\ POURQUOI un zoom et pas une taille de police, malgre la demande litterale
     « uniquement la taille des textes ». Essaye le 2026-08-18 : l'application ecrit
     ses tailles EN DUR, 796 fois dans 63 fichiers, sans variable qui les commande.
     Les grossir demande de reecrire les 796 — ce qui a ete fait, puis defait le
     jour meme. Et cela laisse le texte grossir DANS des boites restees petites :
     les etiquettes en capitales sont a 10 px dans des pastilles de 22.

     Le zoom fait grossir le texte ET ce qui le contient, dans la meme proportion.
     C'est la seule facon, ici, d'agrandir sans rien casser.

     /!\ REVISION DU 2026-09-12 : « ici » a cesse d'etre vrai, et c'etait le but.
     Ce paragraphe decrivait un manque, pas une preference — le zoom avait ete
     choisi FAUTE DE MIEUX, parce qu'aucune variable ne commandait les tailles.
     Ce mieux existe maintenant : voir « L'ECHELLE DE TEXTE » dans `:root`, dix
     crans en `rem`, et les boites qui suivent leur texte en `em`.

     /!\ CE QUI EN DECOULE POUR CE NOMBRE : il reste a 1 et il n'a plus de
     raison de bouger. Le remonter pour « regler » une police jugee petite
     rouvrirait par la fenetre ce que ce chantier ferme, et la police se regle
     desormais par `--text-base`, un cran a la fois.
     /!\ NE PAS LE SUPPRIMER pour autant : `tests/test_echelle_hauteurs.py`
     epingle mot pour mot `body { zoom: var(--echelle); }` et la derivation de
     `--fenetre`, qui protege les hauteurs calees sur la fenetre. Le retirer est
     une decision du fondateur, pas un nettoyage. */
  /* /!\ **1,08 depuis le 2026-09-12**, et ce chiffre a une histoire courte : 1,1
     jusqu'au 2026-09-11 (« on peut dezoomer l'application et la remettre a
     100% »), puis 1 la meme journee, puis 1,08 le lendemain apres que le
     fondateur a compare quatre reglages sur le tableau de bord. Le dezoom avait
     rendu l'application plus petite qu'il ne le voulait ; 1,08 rend le confort
     d'avant sans revenir au 1,1 juge trop gros.

     /!\ IL SE REGLE AU CENTIEME, sans rien casser : c'est tout l'interet de ce
     mecanisme, explique ci-dessus. Les tailles ecrites en dur dans les 63
     fichiers suivent, et les boites avec elles. */
  /* /!\ **RETOUR A 1 le 2026-09-12**, et c'est une DECISION, pas un reglage :
     le fondateur veut agrandir la POLICE sans zoomer l'application (« je veux
     vraiment agrandir la police sans zoomer »). Le zoom grossit le texte ET les
     boites ; lui veut le premier sans le second.

     /!\ CE QUE CELA COUTE, MESURE LE JOUR MEME, pour que personne ne reprenne ce
     chantier en croyant qu'il est court : **948 tailles ecrites en dur dans 68
     fichiers**, dont 144 a 12px, 141 a 13px, 122 a 12,5px. Et 298 hauteurs ou
     rembourrages en dur dans la seule charte, qui devront suivre — sinon le
     texte grossit dans des boites restees petites, ce qui est exactement le
     defaut qui avait fait defaire l'essai du 2026-08-18.

     /!\ NE PAS REMONTER CETTE VALEUR pour « regler » une police jugee petite :
     ce serait rouvrir le zoom par la fenetre. Le levier est desormais la
     typographie, pas l'echelle. */
  --echelle:             1;
  /* La hauteur reellement disponible, une fois l'echelle appliquee. TOUTE hauteur
     calee sur la fenetre doit passer par elle : `100vh` seul deborde de l'ecran
     du facteur d'echelle. */
  --fenetre:             calc(100vh / var(--echelle));

  /* Spacing (grille 4) */
  --space-1:             4px;
  --space-2:             8px;
  --space-3:             12px;
  --space-4:             16px;
  --space-6:             24px;
  --space-8:             32px;
  --space-12:            48px;
  --space-16:            64px;

  /* Radius (plafond 12) */
  --radius-none:         0;
  --radius-sm:           4px;
  --radius-md:           6px;
  --radius-lg:           10px;
  /* Grande carte posée sur le fond — l'arrondi de la vitrine. Rapatrie le
     `--r-xl` que les écrans d'authentification déclaraient chacun de leur côté.
     /!\ L'échelle existante (4/6/10) n'est PAS modifiée : seuls deux écrans
     employaient cet arrondi, et le changer aurait adouci tous les coins de
     l'application sans que personne l'ait demandé. */
  --radius-xl:           22px;
  --radius-pill:         999px;

  /* Shadows (teintées slate) */
  --shadow-xs:           0 1px 2px rgb(28 35 51 / 0.04);
  --shadow-sm:           0 1px 3px rgb(28 35 51 / 0.06), 0 1px 2px rgb(28 35 51 / 0.04);
  --shadow-md:           0 4px 8px rgb(28 35 51 / 0.08), 0 2px 4px rgb(28 35 51 / 0.04);
  --shadow-lg:           0 8px 16px rgb(28 35 51 / 0.10), 0 4px 6px rgb(28 35 51 / 0.04);
  --shadow-drawer:       -8px 0 24px rgb(28 35 51 / 0.10);

  /* Motion */
  --motion-fast:         100ms;
  --motion-base:         150ms;
  --motion-slow:         200ms;
  --motion-slower:       300ms;
  --ease-out:            cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out:         cubic-bezier(0.4, 0, 0.2, 1);
  --ease-in:             cubic-bezier(0.4, 0, 1, 1);

  /* Z-index */
  --z-dropdown:          100;
  --z-sticky:            200;
  --z-drawer:            300;
  --z-modal:             400;
  --z-toast:             500;
}

/* ============================================================
   LA MOLETTE DU TEXTE (28/10/2026) : les dix crans et leurs alias, ici et
   nulle part ailleurs
   ============================================================
   Le fondateur, après Rich Circles (« réduire la police de l'app comme on a
   fait pour ReachCircles ») : chaque cran se multiplie par `--echelle-texte`,
   et toute taille encore écrite en dur aussi (`calc(Npx * var(--echelle-texte))`).
   À 1, rien ne change au pixel près ; l'ESPACE ÉQUIPE et l'ESPACE CLIENT la
   posent à 13/15 (le texte courant de 15 à 13 px), comme Rich Circles (le
   client aussi : décision du fondateur, le même jour, qui remplace pour la
   taille le « texte courant à 16 px » du 12/09). Les pages publiques
   (connexion, paiement) la prennent aussi depuis le 31/10/2026, et la vitrine
   pose la sienne (`landing/`), à la même valeur.
   /!\ POURQUOI UN BLOC À PART, déclaré aussi sur le corps des deux espaces : une
   variable se calcule là où elle est déclarée. Déclarés seulement sur `:root`,
   les crans garderaient la molette de la racine ; redéclarés sur le corps de
   page, ils prennent la sienne.
   /!\ PAS AU TÉLÉPHONE (moins de 900 px) : sous 16 px, l'iPhone zoome sur un
   champ qu'on touche ; Rich Circles garde ses tailles mobiles pour la même raison.
   /!\ Ce n'est pas un retour du zoom (voir « L'ECHELLE DE TEXTE » plus haut) :
   les boîtes suivent leur texte parce qu'elles sont en `em`. */
:root { --echelle-texte: 1; }
@media (min-width: 900px) { body.espace-equipe, body.espace-client, body.espace-public { --echelle-texte: 0.8667; } }
/* 31/10/2026 (fondateur : « les pages de connexion sont trop grosses ») : les pages
   publiques (`espace-public`) prennent la même molette que l'espace. */
:root, body.espace-equipe, body.espace-client, body.espace-public {
  --text-2xs:            calc(0.6875rem * var(--echelle-texte));  /* 11px — capitales espacees, mentions */
  --text-xs:             calc(0.75rem * var(--echelle-texte));    /* 12px — legendes sous un chiffre */
  --text-sm:             calc(0.8125rem * var(--echelle-texte));  /* 13px — cellules de tableau, pastilles */
  --text-base:           calc(0.9375rem * var(--echelle-texte));  /* 15px — LE TEXTE COURANT, le cran choisi */
  --text-md:             calc(1.0625rem * var(--echelle-texte));  /* 17px — intitules de bloc, champs */
  --text-lg:             calc(1.1875rem * var(--echelle-texte));  /* 19px — titres de carte */
  --text-xl:             calc(1.375rem * var(--echelle-texte));   /* 22px — titres de page */
  --text-2xl:            calc(1.75rem * var(--echelle-texte));    /* 28px — les grands chiffres */
  --text-3xl:            calc(2.125rem * var(--echelle-texte));   /* 34px — KPI, accueil */
  --text-4xl:            calc(2.8125rem * var(--echelle-texte));  /* 45px — le seul titre de vitrine */
  --text-eyebrow:        var(--text-2xs);   /* 10px -> 12px */
  --text-caption:        var(--text-xs);    /* 12px -> 13px */
  --text-body-sm:        var(--text-sm);    /* 13px -> 14px */
  --text-data-cell:      var(--text-sm);    /* 13px -> 14px */
  --text-body:           var(--text-base);  /* 14px -> 16px, le cran choisi */
  --text-h4:             var(--text-md);    /* 18px, inchange */
  --text-display-3:      var(--text-xl);    /* 24px, inchange */
  --text-kpi:            var(--text-3xl);   /* 36px, inchange */
  --text-display-2:      var(--text-3xl);   /* 36px, inchange */
  --text-display-1:      var(--text-4xl);   /* 48px, inchange */
}

/* ============================================================
   `--fenetre` SUR UN TELEPHONE (2026-09-20, demande TVNI-93)
   ============================================================
   /!\ AJOUT, pas correction : la declaration d'origine reste au-dessus, mot
   pour mot, et `tests/test_echelle_hauteurs.py` continue de l'epingler. Ce
   bloc la REDEFINIT seulement la ou le navigateur sait faire mieux.

   /!\ Ce que `100vh` vaut sur un telephone, et que personne ne voit au bureau :
   la HAUTEUR BARRE D'ADRESSE MASQUEE, c'est-a-dire la plus grande des deux.
   Tant que la barre est visible - c'est-a-dire a l'ouverture de la page, et
   des qu'on remonte - l'ecran est plus court que `100vh` d'environ 60 a 90
   points. Tout ce qui est cale sur la fenetre depasse donc par le BAS : le
   pied d'une fenetre modale et son bouton de validation passent sous la barre,
   et rien ne le signale.

   /!\ `svh` ET PAS `dvh`, et c'est le choix qui compte ici. `dvh` suit la barre
   d'adresse pendant le defilement : les hauteurs se recalculent a chaque geste,
   et une fenetre modale ouverte sursaute. `svh` est la hauteur la plus PETITE
   des deux, donc une constante : rien ne saute, et rien ne passe jamais sous la
   barre. Ce qu'on paie est une bande de 60 a 90 points sous un panneau pleine
   hauteur quand la barre se retire - visible, sans consequence.

   /!\ Decision prise SANS le fondateur (carnet du chantier). La reouvrir, c'est
   passer a `dvh` et accepter le sursaut, ou revenir a `vh` et accepter le pied
   coupe : il n'y a pas de troisieme valeur.

   /!\ Sur un ordinateur `svh` vaut exactement `vh` : ce bloc n'y change rien.

   /!\ La condition teste `top` et NON `height`, et ce n'est pas un caprice :
   `test_echelle_hauteurs.py` interdit, dans TOUT ce fichier, une ligne qui
   nomme une propriete de hauteur suivie d'une unite en `vh` - or `svh` se
   termine par ces deux lettres. La condition ecrite avec la propriete de
   hauteur faisait donc rougir le test qui protege cette variable, et ce
   commentaire lui-meme doit eviter d'ecrire les deux cote a cote.
   `top: 1svh` pose exactement la meme question au navigateur :
   « sais-tu lire cette unite ? ».

   /!\ Et ce `@supports` n'est PAS decoratif, meme si tous les navigateurs
   courants savent lire `svh` depuis 2022 : la valeur d'une propriete
   personnalisee n'est pas verifiee a la declaration. Un navigateur qui ignore
   `svh` accepterait quand meme cette ligne, puis rendrait la hauteur invalide
   a l'usage - donc AUCUNE hauteur du tout. Le garde evite ca. */
@supports (top: 1svh) {
  :root { --fenetre: calc(100svh / var(--echelle)); }
}

/* ============================================================
   UN SEUL THÈME — le sombre
   /!\ La consigne « le projet est light only, ne pas ajouter de
   prefers-color-scheme: dark » qui occupait ce bloc a été RÉVOQUÉE le
   2026-08-16, à la demande du fondateur. Ne pas la rétablir.
   Il n'y a plus de bascule ni de second thème : les valeurs sombres sont
   dans `:root` ci-dessus, point. Un `[data-theme]` serait du décor.
   ============================================================ */

/* ============================================================
   Reset minimal + base body
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* /!\ Ce que le NAVIGATEUR dessine lui-meme ne suit pas nos variables : une
     case a cocher non cochee, le menu deroulant d'un `<select>`, le calendrier
     d'un champ date, les fleches d'un champ nombre s'ouvraient en BLANC au
     milieu d'une page noire. Une seule ligne les aligne tous. Ajoutee le
     2026-08-16, en constatant des cases blanches sur la fiche d'un trader ;
     c'est le complement de la decision « le sombre est le seul theme », pas un
     changement de style — sans elle, la moitie des controles ignore le theme. */
  color-scheme: dark;
}

/* ============================================================
   L'ECHELLE : le seul bouton pour grossir toute l'application (2026-08-18)
   ============================================================
   Demande du fondateur : « il faut qu'on agrandisse la police de l'application,
   on ne voit pas grand chose. »

   /!\ Ce bouton n'existait PAS, et c'est pour ca que rien n'avait jamais bouge.
   L'echelle de tailles declaree dans `:root` (`--text-body: 14px` et les dix
   autres) est DECORATIVE : l'application ecrit ses tailles en dur, environ 900
   fois entre cette feuille et les gabarits (175 fois `13px`, 130 fois `11px`,
   129 fois `12px`, 113 fois `12.5px`). Monter `--text-body` ne deplacerait
   presque rien a l'ecran. Meme piege que `.wrap`, qui n'avait aucune regle.

   /!\ REVISION DU 2026-09-12 : le mot DECORATIVE n'est plus vrai, et ce
   paragraphe se lit maintenant comme l'etat de depart du chantier, pas comme
   l'etat courant. Les dix jetons sont rebranches sur dix crans en `rem`
   (« L'ECHELLE DE TEXTE » dans `:root`) : monter `--text-base` deplace
   desormais tout ce qui passe par eux. Ce qui RESTE vrai, et qui est le travail
   du chantier : les tailles ecrites en dur ne bougent pas toutes seules. Elles
   se reprennent ecran par ecran, jamais par un remplacement automatique — une
   partie d'entre elles sont des choix deliberes (les capitales espacees, qui
   restent un cran sous le texte courant) qu'un `sed` effacerait avec le reste.

   /!\ `zoom` et pas une taille de police, et c'est le point : le texte seul
   grossirait DANS des boites restees petites. Les etiquettes en capitales sont a
   10 px dans des pastilles de 22 : elles en deborderaient. Ici les cadres, les
   boutons, les icones et les espaces suivent, dans la meme proportion.

   /!\ TOUTE hauteur calee sur la fenetre doit passer par `--fenetre`. Un
   `100vh` brut est multiplie par l'echelle comme le reste : la barre laterale
   depassait du bas de l'ecran et « Se deconnecter » passait sous la ligne de
   flottaison. Defaut vu sur la maquette a +20 %, avant livraison.

   /!\ Les points de bascule (`@media (max-width: 1023px)`) ne connaissent PAS
   cette echelle : ils se declenchent toujours a la largeur reelle. Entre 1024 et
   ~1230 px de large, la mise en page de bureau est donc un peu a l'etroit avant
   de basculer. Assume : les clients travaillent sur le poste qui fait tourner
   NinjaTrader, en grand. Si le cas devient reel, multiplier les bornes par
   l'echelle est le geste, pas un rustine locale. */
body { zoom: var(--echelle); }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-normal);
  color: var(--foreground);
  background: var(--background);
  font-feature-settings: "cv11", "ss01";
}

img, svg, video { display: block; max-width: 100%; }

/* /!\ `hidden` doit gagner, toujours. La regle du navigateur est
   `[hidden] { display: none }` a specificite ZERO : la moindre classe qui pose
   un `display` (`.champ` en flex, `.champs` en grille) la bat, et l'element
   marque cache reste A L'ECRAN. Le defaut se voit rarement en developpement —
   il apparait sur le premier bloc replie. */
[hidden] { display: none !important; }

/* ============================================================
   Le CADRE : la largeur d'une page, et son centrage (2026-08-18)
   ============================================================
   Le conteneur de TOUTE page de l'espace authentifie. Il portait jusqu'ici
   une largeur ecrite en dur dans chaque gabarit (`style="max-width:900px"`,
   17 fois, sept valeurs differentes) et AUCUNE regle : `.wrap` n'existait
   nulle part dans cette feuille.

   /!\ C'est le defaut signale par le fondateur le 2026-08-18 : un bloc plus
   etroit que la place disponible se colle A GAUCHE. Tant que la fenetre est
   petite, la colonne remplit l'ecran et personne ne le voit ; en plein ecran,
   tout se tasse a gauche et il reste 400 px de vide a droite. Ce n'etait donc
   pas un defaut des ecrans refaits : c'etait le socle, sur les 50 gabarits.

   /!\ Le centrage seul aurait cree un second defaut : chaque page ayant sa
   propre largeur, le titre et le fil d'Ariane se seraient DEPLACES a chaque
   changement de page. D'ou une seule et meme largeur partout. Ce qui doit
   rester etroit ne retrecit pas le cadre : c'est l'interieur d'un encadre de
   reglage qui se reorganise, voir la regle `--reglages` juste en dessous.

   Pourquoi 1280 : c'est la place disponible a droite du menu sur un ecran de
   1536 points (le poste du fondateur), au point pres. La page y remplit donc
   l'ecran d'un bord a l'autre ; au-dela elle s'arrete et se centre, pour
   qu'une ligne reste lisible d'un seul regard.

   /!\ `.wrap` est le nom historique, porte par les 50 gabarits ; `.cadre` est
   le nom du vocabulaire courant. Les deux designent la meme chose et vivent
   sur la meme regle : un gabarit refait ecrit `.cadre`, les autres gardent
   `.wrap` jusqu'a leur tour. Ne pas renommer les 50 d'un coup : c'est la
   migration ecran par ecran tranchee le 2026-08-16. */
.cadre, .wrap {
  width: 100%;
  max-width: var(--cadre);
  margin-inline: auto;
}

/* ── PLEIN ECRAN PLAFONNE : ce que la variante 4 corrige ────────────────────
   La variante 3 (aucun plafond) plaisait a 1920 et se trouait a 2560. Le
   plafond a 2000 garde le premier et supprime le second : sous 2000, rien ne
   change par rapport a elle ; au-dessus, la page se centre au lieu de s'etirer.

   Les deux garde-fous qui l'accompagnent, et qui sont le VRAI travail :
   - **la ligne de reglage ne divorce plus sa reponse de sa question** (voir
     `.reglage-ligne` plus bas). C'etait la seule objection de fond au plein
     ecran, mesuree a l'ecran : ~1 700 points de vide entre « Ne pas entrer
     trop pres du prix » et l'interrupteur qui l'eteint ;
   - **la grille des traders s'arrete a quatre colonnes** (voir le gabarit) :
     au-dela, huit traders se rangeaient en six colonnes plus une rangee
     orpheline de deux, ce que le fondateur appelle un trou.

   ── Le CADRE ELARGI : reste pour memoire (MAQUETTE, 2026-09-17) ────────────
   /!\ Dans cette variante, `--cadre-large` VAUT `--cadre` : la classe ne fait
   plus rien, elle est conservee pour que les variantes 2 et 4 se comparent
   sans toucher aux gabarits.
   /!\ **1150 EST UNE LARGEUR DE LECTURE, ET ELLE S'APPLIQUAIT A TOUT.** Le
   fondateur, sur un ecran de ~2000 points : « l'espace central est mal
   exploite, et replier le volet en cree davantage ». Mesure a l'ecran, a
   2560 px : `.main` fait 2360 points, le contenu 1150, et replier la barre
   fait passer `.main` a 2496 sans elargir le contenu d'un seul point -- les
   136 points gagnes partent dans les marges.

   La reponse de cette variante : une page qui MONTRE (grille de cartes,
   liste, tableau, messagerie) monte a 1700 ; une page qui SE LIT (texte,
   reglages) garde sa largeur de lecture. Deux largeurs, pas cinquante.

   /!\ **CE N'EST PAS UN RETOUR AU DEFAUT DE 2026-08-18** : le contenu reste
   centre, et le titre comme le fil d'Ariane ne se deplacent qu'entre une page
   de collection et une page de lecture -- jamais entre deux pages du meme
   genre. */
.cadre--large, .wrap--large { max-width: var(--cadre-large); }

/* Les pages de REGLAGE : le titre de chaque encadre et la phrase qui l'explique
   passent A GAUCHE, sur une colonne etroite ; les champs et les boutons prennent
   toute la droite.

   /!\ Ceci REMPLACE une premiere reponse, du meme jour, qui bornait tout le
   contenu d'une page de reglage a une colonne de 820. Elle reglait le vide de la
   page mais en laissait un autre a droite des champs, que le fondateur a
   immediatement signale : « pareil pour les pages de detail ».

   /!\ La solution evidente (laisser le formulaire s'etirer sur le cadre) a ete
   ESSAYEE et jetee : `.champs` est en `auto-fit`, donc deux champs dans une boite
   de 1280 deviennent deux champs de 590 points pour saisir un nom. Plus large et
   moins bon. C'est la colonne d'explication qui absorbe la largeur en trop, pas
   les saisies.

   /!\ Portee `--reglages` et pas `.encadre` tout court : l'encadre sert aussi aux
   pages de consultation (« Mes trades », le detail d'une analyse), ou il n'y a
   ni champ ni explication a mettre en regard.

   /!\ Flottant, PAS une grille a deux colonnes, et c'est mesure. En grille, le
   titre occupe la ligne 1 et l'explication la ligne 2 ; une ligne 1 haute a
   droite (un bandeau d'alerte, une paire de saisies) etire la ligne et
   l'explication DECROCHE de son titre, jusqu'a 110 points de vide entre les deux.
   Un flottant ignore les lignes : l'explication reste collee sous son titre quoi
   qu'il arrive a droite.
   /!\ `display: flow-root` est ce qui contient les flottants sans `overflow`
   (qui, lui, couperait les bulles d'aide au survol). */
.cadre--reglages .encadre,
.wrap--reglages  .encadre { display: flow-root; }
.cadre--reglages .encadre > .encadre-titre,
.wrap--reglages  .encadre > .encadre-titre,
.cadre--reglages .encadre > .encadre-sous,
.wrap--reglages  .encadre > .encadre-sous {
  float: left; clear: left;
  width: var(--explication);
  margin-bottom: 0;
}
.cadre--reglages .encadre > *:not(.encadre-titre):not(.encadre-sous),
.wrap--reglages  .encadre > *:not(.encadre-titre):not(.encadre-sous) {
  margin-left: calc(var(--explication) + 56px);
}
/* L'air que `.encadre-titre + .champs` posait sous le titre n'a plus lieu d'etre :
   les champs ne sont plus SOUS le titre, ils sont A COTE. */
.cadre--reglages .encadre > .encadre-titre + .champs,
.wrap--reglages  .encadre > .encadre-titre + .champs,
.cadre--reglages .encadre > .encadre-titre + .champ,
.wrap--reglages  .encadre > .encadre-titre + .champ { margin-top: 0; }

/* Sous 1024 px, la barre laterale disparait et l'ecran n'a plus de largeur a
   donner : les deux colonnes se remettent l'une sous l'autre. Meme borne que le
   reste de l'application, une seule requete media. */
@media (max-width: 1023px) {
  .cadre--reglages .encadre > .encadre-titre,
  .wrap--reglages  .encadre > .encadre-titre,
  .cadre--reglages .encadre > .encadre-sous,
  .wrap--reglages  .encadre > .encadre-sous { float: none; width: auto; }
  .cadre--reglages .encadre > .encadre-sous,
  .wrap--reglages  .encadre > .encadre-sous { margin-bottom: 20px; }
  .cadre--reglages .encadre > *:not(.encadre-titre):not(.encadre-sous),
  .wrap--reglages  .encadre > *:not(.encadre-titre):not(.encadre-sous) { margin-left: 0; }
}

/* /!\ **MAQUETTE, VARIANTE 4 : UN ENCADRE DE REGLAGE A LA TAILLE DE CE QU'IL
   PORTE.** Rapprocher la reponse de sa question (voir `.reglage-ligne`) reglait
   la lecture mais laissait un second defaut, vu a l'ecran a 2560 : la carte
   faisait 1 758 points pour 1 040 de contenu, et ses quatre dixiemes de droite
   etaient un aplat vide dans un liseré. Une boite a moitie pleine se lit comme
   un chargement rate.

   /!\ **62ch + 356, ET LES DEUX NOMBRES SE LISENT** : `62ch` est la colonne de
   la question (la meme borne que `.reglage-quoi`), 356 est ce qu'il faut pour
   l'ecart de 20, la plus large des listes (300) et le remplissage de 36. Un
   nombre rond aurait derive au premier cran de police ; celui-ci suit `ch`.

   /!\ **IL NE MORD QUE SUR LES GRANDS ECRANS** : a 1440, la colonne de reglages
   fait deja moins que ce plafond, et rien ne bouge. C'est voulu -- cette borne
   repare le plein ecran, elle ne redessine pas l'ecran d'un portable. */
.cadre--reglages .encadre,
.wrap--reglages  .encadre { max-width: calc(62ch + 356px); }

/* L'espacement entre deux encadres qui s'empilent. Il vivait dans le `<style>` de
   la fiche d'un trader, seul gabarit a en empiler ; il est monte ici le
   2026-08-18, quand cinq pages de reglage de plus sont passees a ce vocabulaire.
   Meme valeur, pour ne rien deplacer sur l'ecran qui l'avait. */
.encadre + .encadre { margin-top: 16px; }

/* ============================================================
   Typographie — classes utilitaires
   ============================================================ */
.t-display-1 { font-size: var(--text-display-1); font-weight: 300; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--primary); }
.t-display-2 { font-size: var(--text-display-2); font-weight: 300; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--foreground); }
.t-display-3 { font-size: var(--text-display-3); font-weight: 400; line-height: var(--leading-snug); color: var(--foreground); }
.t-h4 { font-size: var(--text-h4); font-weight: 500; line-height: var(--leading-snug); color: var(--foreground); }
.t-body { font-size: var(--text-body); line-height: var(--leading-normal); }
.t-body-sm { font-size: var(--text-body-sm); line-height: var(--leading-normal); color: var(--muted-foreground); }
.t-caption { font-size: var(--text-caption); line-height: var(--leading-normal); color: var(--muted-foreground); }
.t-eyebrow { font-family: var(--font-mono); font-size: var(--text-eyebrow); font-weight: 500; letter-spacing: var(--tracking-eyebrow); text-transform: uppercase; color: var(--muted-foreground); }
.t-kpi { font-family: var(--font-sans); font-size: var(--text-kpi); font-weight: 500; line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); color: var(--primary); font-variant-numeric: tabular-nums; }
.t-mono { font-family: var(--font-mono); font-size: var(--text-data-cell); letter-spacing: var(--tracking-mono-data); font-variant-numeric: tabular-nums; }
.t-wordmark { font-family: var(--font-sans); font-weight: 400; letter-spacing: var(--tracking-wordmark); text-transform: uppercase; color: var(--primary); }
.num { font-variant-numeric: tabular-nums; text-align: right; }

/* ============================================================
   Focus visible — règle universelle
   ============================================================ */
*:focus { outline: none; }
*:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: inherit;
}

/* ============================================================
   prefers-reduced-motion
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   Sélection texte + scrollbar
   ============================================================ */
::selection { background: rgb(74 59 122 / 0.20); color: var(--foreground); }

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgb(91 101 119 / 0.3); border-radius: var(--radius-pill); }
::-webkit-scrollbar-thumb:hover { background: rgb(91 101 119 / 0.5); }

/* ============================================================
   Composants — primitives
   ============================================================ */

/* ── LES BOUTONS — direction « console » (2026-08-23) ─────────────────────────
   Choisie par le fondateur sur maquette, contre la pilule brillante d'avant. Ce
   qui change, et pourquoi :

   /!\ LE COIN N'EST PLUS UNE PILULE. Elle venait de la page de vente et n'avait
   aucun cousin dans l'application : cartes, tiroirs et tableaux sont arrondis a
   4, 6 ou 10 px. Le bouton rejoint `--radius-md` (6 px), de loin le coin le plus
   employe de la charte — 33 declarations, contre 13 pour 8 px et 12 pour 10.

   /!\ Reste UN ecart connu : `.saisie` est a 8 px en dur. Un bouton pose a cote
   d'un champ montre donc encore 2 px de difference. A aligner, mais pas en
   passant : ce sont tous les formulaires de l'application.

   /!\ PLUS RIEN NE SAUTE AU SURVOL. `translateY(-1px)` s'appliquait a TOUS les
   boutons, fantomes et icones compris : quand tout bouge, le mouvement ne
   signale plus rien. Il ne reste que l'enfonce au clic, qui, lui, repond a un
   geste. Le survol se dit par la surface.

   /!\ Une PASTILLE reste une pilule (`.filter-chip`, `.seg`, les badges). Ce
   n'est pas une incoherence : une pastille se lit comme une etiquette, un
   bouton comme une touche. */
/* ── LA TAILLE, revue le 2026-08-23 ──────────────────────────────────────────
   Demande du fondateur, sur maquette : « tous les boutons dans la dimension du
   bouton Inviter un proche ». C'est 30 px de haut, 12 px de texte, 6 px de coin.

   /!\ CE N'ETAIT PAS QU'UN CHOIX DE GOUT : la coquille de l'application
   (`admin_base.html`) redeclarait `.btn { height: 38px }`, ce qui ecrasait la
   charte ET rendait `.btn-sm` INOPERANT dans tout l'espace authentifie. Un
   ecran qui voulait un petit bouton n'en obtenait pas, et personne ne savait
   pourquoi. La declaration de la coquille a ete SUPPRIMEE : la taille se regle
   ici, en un seul endroit. Ne pas la remettre.

   /!\ Les trois ecrans d'authentification (connexion, mot de passe oublie,
   reinitialisation) gardent leur propre `.btn`, plus grand : ce sont des pages
   autonomes ou le bouton fait toute la largeur d'un formulaire centre. Les
   reduire a 30 px les rendrait maigres. C'est une exception ASSUMEE, et la
   seule. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5em;
  height: 2.5em; padding: 0 1.167em; box-sizing: border-box;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 500; line-height: 1;
  border: 1px solid transparent; border-radius: var(--radius-md);
  cursor: pointer; text-decoration: none;
  transition: background var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out),
              color var(--motion-base) var(--ease-out),
              transform var(--motion-fast) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
  white-space: nowrap;
}
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn:active:not(:disabled) { transform: translateY(1px); }

/* L'etiquette d'un champ : petite, en chasse fixe, en capitales espacees.
   C'est la seule rescapee de la grille `.spec-grid` posee ici le matin du
   2026-08-16 : « Mes traders » l'a remplacee le soir meme par deux groupes
   (par trade / par jour), et plus aucun ecran ne s'en servait. Une primitive
   sans usage est du code mort, meme jeune — elle reviendra le jour ou un ecran
   en aura vraiment besoin. L'etiquette, elle, sert toujours. */
.spec-label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--muted-foreground);
  margin-bottom: 4px;
}

/* LE GESTE PRINCIPAL — un aplat violet, sans degrade ni halo (2026-08-23).
   /!\ Le fond était `#4A3B7A` CODÉ EN DUR — l'accent de l'ancien thème clair —
   tandis que la couleur du texte, elle, suivait `var(--on-accent)`. Au passage
   au sombre, le texte est devenu foncé et s'est retrouvé illisible sur ce
   violet foncé figé. C'est LE défaut type que le mode sombre révèle : une
   moitié du composant suit le vocabulaire, l'autre non — et rien ne le signale
   tant qu'un seul thème existe. Le texte reste donc sur `--on-accent`.

   /!\ LE HALO EST PARTI. `0 8px 24px` de violet sous chaque bouton principal :
   sur un ecran qui n'en porte qu'un, il flotte ; sur un tiroir qui en aligne
   trois, l'ecran devient brumeux. Le bouton se voit maintenant parce qu'il est
   le seul objet plein et colore de la page, pas parce qu'il brille. Rien
   d'autre dans l'application n'a besoin d'une lueur pour se faire remarquer.

   /!\ LE DEGRADE AUSSI. Il obligeait a rattraper l'enfonce par une seconde
   declaration de degrade — le raccourci `background` effacait le premier, et le
   texte sombre se retrouvait sur un violet sombre pendant le clic (defaut vecu
   le 2026-08-16). Un aplat n'a pas ce piege : les trois etats sont trois
   valeurs de la meme rampe, `--accent-hi` > `--accent` > `--primary`. */
/* LA VOIX FORTE — un aplat violet, et le SEUL de l'écran.
   /!\ RÉSERVÉE AUX GESTES QUI ENGAGENT DE L'ARGENT : souscrire, passer à N
   traders facturés, mettre à jour la carte d'un impayé. Nulle part ailleurs.
   C'est le § B de la maquette du 2026-08-23, choisie par le fondateur : tout
   le reste des boutons s'est calmé, et si la page de vente se calmait aussi,
   l'oeil n'aurait plus aucun point d'ancrage là où ça compte.
   /!\ Deux `.btn-fort` sur un même écran, c'est un de trop : la règle ne tient
   que tant qu'il n'y en a qu'un. */
.btn-fort {
  background: var(--accent);
  color: var(--on-accent);
  border: 1px solid var(--accent);
  font-weight: 600;
}
.btn-fort:hover:not(:disabled) { background: var(--accent-hi); border-color: var(--accent-hi); }
.btn-fort:active:not(:disabled) { background: var(--primary); border-color: var(--primary); }

/* LE GESTE PRINCIPAL D'UN ÉCRAN — l'habit du bouton « Inviter un proche ».
   Le fondateur l'a désigné comme la référence : violet lavé, liseré violet,
   libellé clair. Il se distingue d'un geste neutre sans peser comme un aplat. */
.btn-primary {
  background: rgba(167,139,250,0.18);
  color: #C9BCFF;
  border: 1px solid rgba(167,139,250,0.30);
}
.btn-primary:hover:not(:disabled) {
  background: rgba(167,139,250,0.28);
  border-color: rgba(167,139,250,0.50);
  color: #FFFFFF;
}
.btn-primary:active:not(:disabled) { background: rgba(167,139,250,0.34); }

/* LE GESTE D'À CÔTÉ — une surface, un liseré, aucune teinte. */
.btn-secondary { background: var(--card); color: var(--foreground); border: 1px solid rgba(255,255,255,0.14); }
.btn-secondary:hover:not(:disabled) { background: var(--muted); border-color: rgba(255,255,255,0.24); }

/* LE GESTE SECONDAIRE.
   /!\ Le liseré passe de `--border` (7 % de blanc) à `--border-strong` (12 %) :
   à 7 % le bouton se devinait plus qu'il ne se voyait, et il n'existait aucun
   niveau entre lui et le bouton plein. C'est le manque que la maquette du
   2026-08-23 nommait en quatrieme point.

   /!\ Il ne VIRE PLUS A L'ACCENT au survol. Un fantome qui devient violet se
   lit comme un second bouton principal ; deux gestes de meme couleur cote a
   cote annulent la hierarchie qu'on vient de poser. Seule la surface change. */
.btn-ghost {
  background: transparent;
  color: var(--foreground);
  border: 1px solid rgba(255,255,255,0.14);
}
.btn-ghost:hover:not(:disabled) {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.24);
}

/* LE GESTE DESTRUCTEUR — un contour teinté, jamais un aplat rouge.
   /!\ CE N'EST PAS UN CHOIX DE GOUT. Du blanc sur `--error` (#F87171) donne un
   contraste de 2,8 pour 1, la ou la norme en demande 4,5 : le libelle du seul
   bouton qu'on ne veut PAS cliquer par erreur etait le moins lisible de
   l'application. Le rouge passe donc au texte, sur un fond transparent.

   /!\ L'affirmative d'une confirmation reste `.btn-primary` (cf.
   `confirmation.js`) : ce bouton-ci n'est jamais le dernier rempart, il ouvre
   la fenetre qui l'est. Il n'a donc pas besoin du poids d'un aplat. */
.btn-danger {
  background: transparent;
  color: var(--error);
  border-color: rgba(248,113,113,0.35);
}
.btn-danger:hover:not(:disabled) {
  background: rgba(248,113,113,0.10);
  border-color: rgba(248,113,113,0.60);
}

/* Variantes "soft" tintées — actions secondaires (drawers : Supprimer, Archiver,
   Annuler, Réactiver...). À utiliser avec .btn pour le gabarit. Radius 8px. */
/* /!\ Le `border-radius: 8px` codé en dur a sauté le 2026-08-23 : ces deux
   variantes se posaient a cote d'un `.btn` en pilule et devaient donc corriger
   sa forme. Le `.btn` etant passe a `--radius-md`, elles n'ont plus rien a
   redresser — et un coin de 8 px a cote d'un coin de 6 se voit. */
.btn-danger-ghost {
  background: rgba(159,63,78,.07); color: var(--error);
  border: 1px solid rgba(159,63,78,.28);
}
.btn-danger-ghost:hover:not(:disabled) {
  background: rgba(159,63,78,.13); border-color: rgba(159,63,78,.48);
}
.btn-success-ghost {
  background: rgba(63,110,92,.10); color: var(--success);
  border: 1px solid rgba(63,110,92,.30);
}
.btn-success-ghost:hover:not(:disabled) {
  background: rgba(63,110,92,.16); border-color: rgba(63,110,92,.50);
}

/* /!\ `display: inline-flex` est INDISPENSABLE ici, pas decoratif. La regle
   demandait un centrage (`justify-content`) sans jamais poser de contexte flex :
   elle ne marchait que sur les boutons qui portaient AUSSI `.btn` (lui en flex).
   Un `.btn-icon` employe seul — l'oeil de « Mon profil », les icones de cle de
   « Mes traders » — collait son icone contre le bord gauche, a 1 px au lieu de
   11,5 dans un cadre de 38. Mesure du 2026-08-16. */
/* /!\ Le rond est devenu un carre adouci le 2026-08-23, avec le reste des
   boutons : une icone en pastille ronde posee a cote d'un bouton a coins droits
   se lit comme un autre composant. Et l'icone au repos est ATTENUEE
   (`--muted-foreground`) : elle ne reprend la couleur du texte qu'au survol,
   pour que trois icones de barre d'outils ne pesent pas autant que le libelle
   qu'elles accompagnent. */
.btn-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  background: transparent; color: var(--muted-foreground);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
}
.btn-icon:hover:not(:disabled) { background: var(--muted); color: var(--foreground); }

/* Compact variant used in tight UI (drawer headers, table actions) */
/* La taille réduite — pour une action posée DANS une ligne ou une carte, à
   côté d'un texte de 12 px. Elle était inopérante depuis que la coquille
   imposait 38 px à tout ; elle redevient une vraie taille. */
.btn-sm { height: 2.261em; padding: 0 0.87em; font-size: var(--text-xs); gap: 0.435em; }

/* ── Une ACTION DE LISTE : du texte, pas une pilule (2026-08-17) ──
   Sur une carte de resume ou une rangee, trois `.btn-ghost` cote a cote pesent plus lourd
   que le contenu qu'ils accompagnent : trois cadres arrondis, trois bordures, et l'oeil ne
   sait plus ou regarder. Le fondateur, en designant ceux de « Mes traders » : « je n'aime
   pas du tout ».

   /!\ Ne PAS combiner avec `.btn` : celui-ci porte une bordure, une pilule et un
   soulevement au survol, exactement ce qu'on retire ici. `.action` se suffit.

   /!\ La cible tactile reste correcte : le padding vertical donne 30 px de hauteur, et le
   trait de survol n'est pas la seule marque du survol (la couleur change aussi), pour qui
   ne distingue pas les nuances.

   /!\ Une action DESTRUCTRICE prend `.action--rouge`, jamais un `style` en ligne : la
   couleur d'un danger appartient a la charte, pas a l'ecran. */
.action {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 0;
  background: transparent; border: 0;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 500; line-height: 1.2;
  color: var(--muted-foreground);
  text-decoration: none; cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: color var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out);
}
.action:hover, .action:focus-visible { color: var(--foreground); border-bottom-color: var(--accent); }
.action svg { width: 13px; height: 13px; opacity: 0.75; }
.action:hover svg { opacity: 1; }
/* L'action qui COMPTE sur la rangee : elle garde la couleur du texte au repos, les autres
   sont atténuées. Une hiérarchie sans une seule bordure. */
.action--forte { color: var(--foreground); }
.action--forte:hover, .action--forte:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }
.action--rouge { color: var(--error); }
.action--rouge:hover, .action--rouge:focus-visible { color: var(--error); border-bottom-color: var(--error); }

@media (max-width: 768px) {
  .btn-icon { width: 44px; height: 44px; }
}

/* Selecteur segmente — un choix EXCLUSIF parmi peu d'options, toutes visibles.
   Ne pas confondre avec `.filter-chip`, qui retire un filtre deja pose : ici on
   bascule entre des vues, il y en a toujours exactement une d'active.

   /!\ Chaque segment porte son EFFECTIF (« Actifs 2 ») : sans lui on choisit a
   l'aveugle, et un segment vide se decouvre en cliquant dessus. Meme regle que
   les onglets de mode de « Mes trades ».

   /!\ Ne l'afficher que lorsqu'il y a quelque chose a departager. Un selecteur
   pose au-dessus d'un seul element est un piege deja rencontre le 2026-08-15
   (le selecteur de version, inatteignable a un seul trader). */
.seg {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px;
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
.seg > * {
  appearance: none; background: none; border: 0; cursor: pointer;
  padding: 6px 14px;
  font-family: inherit; font-size: var(--text-base); font-weight: 500;
  color: var(--muted-foreground);
  border-radius: var(--radius-pill);
  text-decoration: none; white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
}
.seg > *:hover { color: var(--foreground); }
/* Deux facons de dire « celui-la ». `aria-selected` quand le segment change une VUE
   sans quitter la page (un filtre, un mode d'affichage) ; `aria-current="page"`
   quand ce sont des LIENS vers d'autres pages, ou l'un est la page ouverte. Le
   second n'existait pas, et un selecteur de pages restait donc sans surbrillance :
   il paraissait casse. */
.seg > [aria-selected="true"],
.seg > [aria-current="page"] {
  background: var(--card);
  color: var(--foreground);
  box-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
/* Un segment PRESENT mais indisponible ICI (2026-08-21). Il garde sa place et son
   libelle, et dit « ca existe, pas a cet endroit ».

   /!\ Grise plutot que RETIRE, et c'est la lecon du cul-de-sac du 2026-08-17 : un
   controle qui disparait avec ce qu'il commandait laisse sans moyen de revenir, sinon
   en editant l'adresse a la main. Retirer le segment ferait en outre sauter la largeur
   du selecteur d'un cran a l'autre.

   /!\ Un segment grise sans motif se lit comme une panne : l'ecran qui l'emploie DOIT
   dire pourquoi, a cote. Le CSS ne peut pas porter cette phrase, mais il peut le
   rappeler ici. */
.seg > [aria-disabled="true"] {
  cursor: default; color: var(--disabled-foreground);
}
.seg > [aria-disabled="true"]:hover { color: var(--disabled-foreground); }

.seg .n { color: var(--muted-foreground); font-weight: 400; margin-left: 6px; }
.seg > [aria-selected="true"] .n,
.seg > [aria-current="page"] .n { color: var(--accent); }


/* ============================================================
   Les JETONS : des filtres qu'on allume et qu'on eteint (2026-08-18)
   ============================================================
   Une rangee de petits filtres independants, chacun a deux etats. Promu depuis
   la page News, ou les huit devises etaient une TROISIEME famille de pastilles
   posee a cote de deux autres, aucune des trois n'etant celle du reste de
   l'application.

   Ce que `.seg` ne sait pas faire, et qui justifie une brique de plus :
   un segment retient TOUJOURS un choix (il faut donc y ajouter un « Tous »
   artificiel pour pouvoir ne rien filtrer), et il en retient UN SEUL. Un jeton
   s'eteint tout seul, et rien n'empeche d'en allumer plusieurs si l'ecran qui
   s'en sert le permet.

   /!\ L'etat passe par `aria-pressed`, jamais par une classe. C'est l'attribut
   qu'un bouton a deux etats doit porter, il est annonce au lecteur d'ecran, et
   il evite le piege vecu ici : le JS posait une classe `.on` que le vocabulaire
   commun ne connait pas, donc les filtres restaient eteints a l'oeil.

   /!\ Un jeton est un FILTRE, pas une etiquette. Ce qui ne se clique pas est un
   `.badge-*`. */
.jetons { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.jeton {
  appearance: none; cursor: pointer;
  padding: 4px 11px;
  font-family: var(--font-mono); font-size: var(--text-caption); font-weight: 500;
  color: var(--muted-foreground);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out),
              background var(--motion-fast) var(--ease-out);
}
.jeton:hover { color: var(--foreground); border-color: var(--muted-foreground); }
.jeton[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
  background: rgb(74 59 122 / 0.14);
}
.jeton:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* ============================================================
   Les FILTRES : une barre de selecteurs etiquetes (2026-08-17)
   ============================================================
   Plusieurs filtres cote a cote, chacun avec son etiquette : « Periode [ce mois-ci] »,
   « Mode (segments) ». La barre passe a la ligne toute seule.

   /!\ Chaque filtre porte son ETIQUETTE. Une rangee de selecteurs nus oblige a
   deviner ce que chacun filtre, et un filtre qu'on ne comprend pas ne se touche pas.

   /!\ Un filtre a une seule valeur possible ne s'affiche pas. C'est le piege du
   2026-08-15 : un selecteur de version pose au-dessus d'une seule version etait
   inatteignable, donc l'ecran paraissait casse. La regle vaut ici aussi.

   /!\ La barre de filtres n'est PAS un formulaire de saisie : ses controles sont
   compacts (`.liste--compact`). A la taille d'un champ de reglage, cinq filtres
   coutaient plus de hauteur que la bande de chiffres qu'ils gouvernent. */
.filtres { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; }
.filtre { display: flex; align-items: center; gap: 8px; min-width: 0; }
.filtre-l {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground); white-space: nowrap;
}
/* La liste deroulante en version barre de filtres : meme dessin, moitie moins haute.

   /!\ DEUX selecteurs, et il faut les deux. `liste.js` remplace l'affichage du
   `<select>` par un `.liste-bouton` et masque le natif a 1x1 px (il reste dans le
   formulaire, il porte la valeur). Ne styler que `select.saisie` habillait donc un
   element invisible : mesure au banc, le filtre rendait un controle de 1 px de haut
   sur 1 px de large. Le natif garde quand meme sa regle, c'est lui qui s'affiche si le
   script ne se charge pas. */
/* `.liste.liste--compact` et pas `.liste--compact` seul : `.liste { display: block }`,
   declaree plus bas, gagnait a poids egal, et la liste compacte prenait toute la
   ligne hors d'une rangee flex (2026-09-17). */
.liste.liste--compact { display: inline-block; vertical-align: middle; }
.liste--compact > select.saisie,
.liste--compact > .liste-bouton {
  width: auto; padding: 6px 13px 6px 11px;
  font-size: var(--text-base); border-radius: var(--radius-pill);
}
.liste--compact > select.saisie { padding-right: 32px; }
.liste--compact > .liste-bouton { gap: 8px; }
.liste--compact::after { right: 13px; width: 6px; height: 6px; margin-top: -4px; }
/* /!\ **LA REGLE DE LA BARRE DE FILTRES EST DEVENUE LA REGLE GENERALE** (2026-09-15) :
   « le panneau deplie ne peut pas suivre la largeur d'un bouton compact » etait vrai
   de TOUT champ etroit, pas seulement d'un filtre. Elle vit desormais sur
   `.liste-panneau`, et il n'y a plus rien a redire ici. */

/* ============================================================
   Verre — la surface de la vitrine, portee dans l'application (2026-08-16)
   ============================================================
   Ce qui fait le verre : un degrade diagonal tres clair par-dessus un fond
   sombre translucide, un REFLET d'un pixel en haut de la surface, et un LISERE
   en degrade qui s'allume dans l'angle superieur gauche et s'eteint en bas.

   /!\ SANS `backdrop-filter`, et c'est un choix mesure, pas un oubli. La vitrine
   elle-meme avertit qu'il « recompose sa zone a CHAQUE frame de defilement » et
   n'en pose que deux dans toute la page. Ici le fond de l'application est un
   APLAT (`--background`) : il n'y a rien derriere a flouter, le filtre ne
   changerait donc RIEN a l'image tout en coutant une recomposition par carte, sur
   une liste qui peut en compter dix. Le jour ou un fond texture apparait, c'est
   ici qu'on ajoute `backdrop-filter: blur(32px) saturate(165%)`.

   /!\ La bordure est TRANSPARENTE et le liseré est peint par `::before` : un
   `border-color` classique donnerait un trait d'epaisseur uniforme, alors que
   tout l'effet tient dans son degrade. Ne pas rajouter de `border` par-dessus. */
.verre {
  position: relative;
  background:
    linear-gradient(160deg, rgba(255,255,255,0.07) 0%,
                            rgba(255,255,255,0.02) 42%,
                            rgba(255,255,255,0.035) 100%),
    /* /!\ 0,55 -> 0,97 le 2026-09-11 (fondateur : « le fond est transparent, du
       coup on voit trop le texte de la page derrière »). Le voile de fond
       (`::backdrop`, 0,72) ne suffisait pas : c'est le PANNEAU lui-même qui
       laissait lire la page au travers, et un formulaire posé dessus devenait
       illisible. Le verre garde son reflet et son liseré — il perd seulement sa
       transparence. */
    rgba(18, 20, 29, 0.97);
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  box-shadow:
    0 34px 90px -24px rgba(0,0,0,0.68),
    inset 0 1px 0 0 rgba(255,255,255,0.14);
}
.verre::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(140deg, rgba(255,255,255,0.28) 0%,
                                      rgba(180,168,255,0.16) 38%,
                                      rgba(255,255,255,0.04) 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}

/* Une zone DANS une surface de verre. Pas de bordure : un cadre dans un cadre
   fabrique l'empilement de boites que la refonte cherche a supprimer. Elle se
   distingue par un voile et un filet en haut, rien de plus. */
.verre-zone {
  position: relative;
  background: rgba(255,255,255,0.028);
  border: 0;
  border-radius: var(--radius-md);
}

/* ============================================================
   Survol — les deux idiomes de la vitrine, repris tels quels (2026-08-16)
   ============================================================
   La vitrine en a DEUX, et ils ne disent pas la meme chose :

   `--survol`      la CARTE se souleve, prend un liseré violet et une ombre
                   portee. Elle se detache du fond : « cet ensemble est un objet ».
   `--survol-bloc` le BLOC INTERNE se teinte, sans bouger. Il reste dans la carte :
                   « cette zone est vivante », sans concurrencer la carte.

   /!\ OPT-IN, jamais sur `.panel` tout court. Une liste d'administration ou un
   tableau dense ou chaque ligne se souleverait au passage de la souris devient
   illisible. Le mouvement se merite : on le pose sur ce qu'on manipule.

   /!\ Le bloc `prefers-reduced-motion` plus haut ramene deja les durees a 0,01 ms
   pour qui demande moins d'animation — le deplacement reste, mais instantane. */
.panel--survol {
  transition: transform 320ms var(--ease-out),
              border-color 320ms var(--ease-out),
              box-shadow 320ms var(--ease-out);
}
.panel--survol:hover {
  transform: translateY(-4px);
  border-color: var(--border-strong);
  box-shadow: 0 24px 60px -20px rgba(0,0,0,0.6),
              0 0 0 1px var(--accent-ring);
}
/* Sur une surface de verre, le liseré est peint par `::before` : ecraser
   `border-color` n'aurait aucun effet, et l'ombre doit garder le reflet interne
   sans quoi il disparait au survol. */
.verre.panel--survol:hover {
  box-shadow: 0 40px 100px -24px rgba(0,0,0,0.72),
              0 0 0 1px var(--accent-ring),
              inset 0 1px 0 0 rgba(255,255,255,0.20);
}
.verre.panel--survol:hover::before {
  background: linear-gradient(140deg, rgba(255,255,255,0.38) 0%,
                                      rgba(180,168,255,0.30) 38%,
                                      rgba(255,255,255,0.06) 70%);
}
.bloc--survol {
  transition: border-color 240ms var(--ease-out),
              background 240ms var(--ease-out);
}
.bloc--survol:hover {
  border-color: var(--accent-ring-strong);
  background: var(--accent-wash);
}

/* Une valeur qu'on recopie ailleurs — une cle, un identifiant, une commande.
   Le bouton REMPLACE la selection a la souris : sur une chaine de 40 caracteres en
   chasse fixe, un double-clic n'attrape qu'un morceau et l'utilisateur ne le voit
   pas. Le retour ecrit (« Copie ») est obligatoire : sans lui, rien ne distingue un
   clic reussi d'un clic rate, et l'utilisateur recolle l'ancienne valeur.

   /!\ `navigator.clipboard` n'existe QUE sur une origine sure (https ou localhost).
   En clair sur une IP, il est `undefined` : le script le teste et laisse alors le
   texte selectionnable plutot que d'echouer en silence. */
.copiable {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px;
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.copiable code {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--font-mono); font-size: var(--text-body-sm);
  color: var(--foreground);
  overflow-wrap: anywhere;
}
.copiable .copier {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 6px;
  appearance: none; cursor: pointer;
  padding: 6px 12px;
  font-family: inherit; font-size: var(--text-sm); font-weight: 500;
  color: var(--foreground);
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  transition: border-color var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
}
.copiable .copier:hover { border-color: var(--accent); color: var(--accent); }
.copiable .copier[data-fait="oui"] { color: var(--success); border-color: var(--success); }

/* ============================================================
   L'ENCADRE et le CHAMP : le modele du formulaire de connexion (2026-08-16)
   ============================================================
   L'ecran de connexion etait le seul endroit ou une boite et un champ avaient
   ete dessines ; partout ailleurs regnait `.panel` > `.drawer-section`, herite
   du theme clair (bordures et ombres bleu-gris sur une page devenue noire) et
   qui empile DEUX cadres pour une seule idee. Ces deux primitives sont sa
   promotion : une boite, un champ, definis une fois.

   /!\ Ce qu'on promeut est le MODELE, pas le fichier. `login.html` garde sa
   carte a lui (`.login-card`) : elle est plein ecran, seule, avec sa lueur
   haute. `.encadre` est la meme surface reglee pour une SUITE de boites qui
   s'empilent dans une page.

   /!\ Migration ECRAN PAR ECRAN, decision du fondateur du 2026-08-16. Les
   ~470 champs `.fi`/`.fl` du reste de l'application ne bougent pas : ils
   changeront quand leur ecran sera refait et regarde. Les deux vocabulaires
   cohabitent donc pour un temps, c'est voulu et pas un oubli. */
.encadre, .carte {
  position: relative;
  padding: 26px 28px;
  background:
    radial-gradient(600px 300px at 50% -10%, rgba(139,127,224,0.15), transparent 60%),
    linear-gradient(180deg, rgba(26,29,39,0.92) 0%, rgba(20,22,31,0.92) 100%);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow:
    0 30px 80px -20px rgba(0,0,0,0.6),
    0 0 0 1px rgba(255,255,255,0.04) inset;
}

/* ============================================================
   La CARTE : meme surface que l'encadre, mais VIVANTE (2026-08-17)
   ============================================================
   Demande du fondateur : les cartes de « Mes traders » prennent le style des
   encadres, « mais par contre pour les cards elle doit etre plus dynamique,
   c'est-a-dire qu'au survol il y a une petite animation avec une surbrillance
   violette. Ce sera un modele a part qu'on reutilisera pour toutes les pages
   avec des cards. »

   La difference entre les deux dit quelque chose de vrai :
     `.encadre`  une boite qui CONTIENT des reglages. On la lit, on la remplit.
                 Elle ne bouge pas : dix encadres qui se soulevent au passage de
                 la souris, sur un formulaire de trente champs, rendent l'ecran
                 nerveux et illisible.
     `.carte`    un OBJET de la liste, qu'on manipule (ouvrir, activer, dupliquer).
                 Elle se souleve et s'allume : le survol dit « celle-ci ».

   /!\ La surbrillance est peinte par `::before` AU-DESSUS du fond, et le contenu
   passe au-dessus d'elle par `.carte > *`. Premiere tentative, ratee : `::before`
   en `z-index: -1` avec `isolation: isolate` sur la carte. Sur le papier le
   negatif passe entre le fond et le contenu ; en pratique il s'est retrouve
   DERRIERE le fond de la carte, qui est opaque a 92 % — donc invisible. Verifie
   en peignant la couche en rouge vif : rien ne se voyait. Ne pas y revenir.

   /!\ `.carte > *` prend donc `position: relative`, ce qui fait de chaque enfant
   direct un bloc de reference pour ce qu'il contient en absolu. Rien a signaler
   sur les cartes actuelles ; a garder en tete si une carte doit un jour porter un
   panneau flottant qui depasse d'elle.

   /!\ Le bloc `prefers-reduced-motion` plus haut ramene les durees a 0,01 ms
   pour qui demande moins d'animation. La lueur reste, elle devient instantanee.

   /!\ Ne pas empiler `.carte` et `.panel--survol` : deux mouvements de survol sur
   le meme element se combattent. `.panel--survol` reste pour les surfaces qui ne
   sont pas des cartes. */
.carte {
  /* La marge horizontale de la carte, pour que le filet d'une `.carte-zone` aille
     d'un bord a l'autre. Elle vaut le padding declare plus haut ; un ecran qui
     resserre ce padding (une liste de dix cartes) redeclare les deux ensemble. */
  --carte-marge: 28px;
  transition: transform 320ms var(--ease-out),
              border-color 320ms var(--ease-out),
              box-shadow 320ms var(--ease-out);
}
.carte::before {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background:
    radial-gradient(900px 300px at 50% -8%, rgba(139,127,224,0.34), transparent 62%),
    linear-gradient(180deg, rgba(180,168,255,0.05) 0%, transparent 45%);
  opacity: 0;
  transition: opacity 320ms var(--ease-out);
  pointer-events: none;
}
.carte > * { position: relative; }
.carte:hover {
  transform: translateY(-3px);
  border-color: var(--accent-ring-strong);
  box-shadow:
    0 38px 96px -24px rgba(0,0,0,0.72),
    0 0 0 1px var(--accent-ring-strong),
    0 0 48px -10px var(--accent-glow-strong),
    inset 0 1px 0 0 rgba(200,190,255,0.28);
}
.carte:hover::before { opacity: 1; }
/* Le clavier doit voir la meme chose que la souris, sinon le survol est un
   privilege de la souris. Plus discret : on ne souleve pas la carte, le
   deplacement pendant une tabulation ferait sauter la page. */
.carte:focus-within {
  border-color: var(--accent-ring-strong);
  box-shadow: 0 30px 80px -20px rgba(0,0,0,0.6),
              0 0 0 1px var(--accent-ring),
              inset 0 1px 0 0 rgba(200,190,255,0.20);
}
.carte:focus-within::before { opacity: 0.55; }
/* ── AU TELEPHONE, la surface rend sa marge au contenu (02/10/2026) ──
   26 × 28 points de marge interieure, c'est le bon souffle sur un ecran de
   bureau ; sur 375 points, un encadre dans la page en mangeait 56 de large, et
   un encadre dans un encadre plus de cent : les listes (le journal, les
   positions) se lisaient dans une colonne de 240 points, en mots coupes.
   Le bureau ne bouge pas. `--carte-marge` suit, pour les filets de `.carte-zone`. */
@media (max-width: 720px) {
  .encadre, .carte { padding: 18px 16px; }
  .carte { --carte-marge: 16px; }
  .encadre .encadre, .carte .encadre { padding: 14px 12px; }
}
/* Le titre d'une carte : plus present que celui d'un encadre, parce qu'il NOMME
   l'objet (« Scalp ES matin ») au lieu d'annoncer une rubrique. */
/* ── Une carte ENTIEREMENT cliquable (2026-08-17) ──
   Demande du fondateur : « lorsqu'on clique sur la carte, ça affiche le détail ». Elle se
   soulevait deja au survol ; il ne lui manquait que d'etre cliquable.

   Le contrat tient en deux classes :
     `.carte-lien`    le VRAI lien (le titre, en general). Son `::after` s'etend sur toute
                      la carte et devient la surface cliquable.
     `.carte-dessus`  tout ce qui doit rester cliquable PAR-DESSUS : un autre lien, un
                      bouton, un formulaire.

   /!\ Pas de `<a>` autour de la carte, et pas de `onclick` sur un `<div>`. Un lien qui
   enveloppe d'autres liens est du HTML invalide, et un `div` cliquable est invisible au
   clavier comme au lecteur d'ecran. Ici il y a UN lien, il porte un nom, et la tabulation
   l'atteint.

   /!\ Tout element interactif de la carte DOIT porter `.carte-dessus`. Un element
   positionne avec `z-index: 1` passe au-dessus de tous les `z-index: auto`, quel que soit
   l'ordre du code : oublier la classe rend le bouton inerte, et c'est le genre de panne
   qu'on ne voit qu'en essayant de cliquer.

   /!\ Contrepartie assumee : la selection du texte a la souris devient difficile sur la
   zone couverte. Sur une carte de resume, on clique bien plus souvent qu'on ne copie. */
/* /!\ Le lien doit etre un ENFANT DIRECT de la carte, et il reste `position: static`.
   `.carte > *` pose `position: relative` sur tous les enfants directs (c'est ce qui les
   fait passer au-dessus de la lueur) : un lien positionne devient le bloc conteneur de son
   propre `::after`, et la surface ne couvre alors que le lien. Premiere tentative faite
   sur le titre : la zone cliquable s'arretait au nom du trader, et seule une sonde
   `elementFromPoint` l'a montre. */
.carte > .carte-lien { position: static; }
.carte-lien { text-decoration: none; color: inherit; }
.carte-lien::after {
  content: ""; position: absolute; inset: 0;
  border-radius: inherit; z-index: 1;
}
/* Le lien porte un texte pour lecteur d'ecran, donc pas de boite visible : l'anneau de
   focus se pose sur la SURFACE, ce qui entoure la carte entiere au clavier. */
.carte-lien:focus-visible { outline: none; }
.carte-lien:focus-visible::after {
  outline: 2px solid var(--ring); outline-offset: -3px;
}
.carte-dessus { position: relative; z-index: 2; }

.carte-titre {
  margin: 0 0 4px;
  font-family: var(--font-sans);
  font-size: var(--text-xl); font-weight: 600;
  letter-spacing: -0.02em; line-height: 1.2;
  color: var(--foreground);
}
.cartes { display: flex; flex-direction: column; gap: 14px; }

/* ============================================================
   La MISE EN ROUTE (2026-08-28) : cinq marches, chacune cochee sur un fait
   observe par le serveur, jamais a la main.
   ============================================================
   /!\ La puce dit un ETAT, pas une identite : pleine et de la couleur du
   succes quand la marche est faite, creuse (juste un contour) sinon. Aucune
   couleur codee en dur, tout vient des variables de la charte. */
.mise-en-route-sous {
  margin: 0 0 16px;
  color: var(--muted-foreground);
  font-size: var(--text-body-sm);
}
.marches { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.marche { display: flex; align-items: flex-start; gap: 12px; }
.marche-puce {
  flex: 0 0 auto;
  width: 14px; height: 14px;
  margin-top: 3px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--border-strong);
  background: transparent;
  transition: background 220ms var(--ease-out), border-color 220ms var(--ease-out);
}
.marche--faite .marche-puce {
  background: var(--success);
  border-color: var(--success);
}
.marche-corps { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.marche-titre {
  font-weight: 600;
  color: var(--foreground);
}
.marche--faite .marche-titre {
  color: var(--muted-foreground);
  text-decoration: line-through;
  text-decoration-color: var(--border-strong);
}
.marche-phrase { color: var(--muted-foreground); font-size: var(--text-body-sm); }

/* ============================================================
   LA MISE EN ROUTE (refaite le 2026-09-14)
   ============================================================
   Demande du fondateur : « je n'aime pas du tout, vraiment pas. Mets-toi du
   point de vue de l'utilisateur (...) il faut que tout soit user-friendly. »

   /!\ **CE QUI CLOCHAIT ETAIT UNE CONTRAINTE DE PLACE, PAS UNE COULEUR.** Le
   bandeau etait une BANDE : il devait loger une jauge, deux phrases et trois
   commandes sur quelques dizaines de points de haut. Le texte se coupait en deux
   lignes serrees, la jauge etait un trait gris de trois points, et « Voir les 6
   etapes », « Continuer » et « Je m'en passe » se disputaient le coin droit.
   Aucune retouche de couleur ne repare une boite trop petite.

   /!\ **UN SEUL COMPOSANT, DEUX TAILLES** : une CARTE sur les deux pages
   d'arrivee (`/user`, `/user/traders`), une LIGNE partout ailleurs. La carte a
   la place de respirer parce qu'elle ne vit que la ; la ligne est courte parce
   qu'elle ne repete rien, elle renvoie a la carte.

   /!\ Aucune regle `display` n'est posee sur un element que le gabarit cache par
   l'attribut `hidden` : la charte porte ce piege ailleurs, on ne le rejoue pas.
   Ici rien ne se replie : les six etapes sont visibles d'un coup, et c'est
   justement ce qui remplace le bouton « Voir les 6 etapes ». */

.mer-carte {
  margin: 0 0 26px;
  padding: 20px 24px 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 14px);
  /* Un fond a peine tiede : la carte doit se distinguer du fond de page sans
     crier, puisqu'elle disparaitra pour de bon a la derniere etape. */
  background:
    linear-gradient(180deg, var(--accent-wash, rgba(124,108,246,.07)), transparent 140px),
    var(--card, rgb(18, 20, 29));
}
.mer-carte-titre {
  margin: 0 0 4px;
  font-family: var(--font-sans); font-size: var(--text-2xl, 1.45rem);
  font-weight: 600; letter-spacing: -0.02em;
}
.mer-carte-sous {
  margin: 0; max-width: 62ch;
  font-size: var(--text-body-sm); line-height: 1.55;
  color: var(--muted-foreground);
}

/* La progression, LUE D'UN COUP : un segment par etape. Un trait continu ne
   disait ni combien il en restait, ni combien il y en avait. */
.mer-jauge { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.mer-jauge-segments { display: flex; gap: 4px; }
.mer-jauge-segments i {
  display: block; width: 26px; height: 5px; border-radius: 3px;
  background: var(--border);
}
.mer-jauge-segments i[data-faite] { background: var(--primary, rgb(124,108,246)); }
.mer-jauge b {
  font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.01em;
  white-space: nowrap;
}
.mer-jauge b span { color: var(--muted-foreground); font-weight: 500; }

/* ── Les six etapes ─────────────────────────────────────────────────────── */
.mer-etapes { list-style: none; margin: 16px 0 0; padding: 0; }
.mer-etape {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 9px 0;
  border-top: 1px solid var(--border-subtle, rgba(255,255,255,.06));
}
.mer-etape:first-child { border-top: 0; }

/* La pastille porte le numero, puis la coche. C'est elle qui fait lire la liste
   comme un chemin plutot que comme des puces. */
.mer-puce {
  flex: 0 0 auto; width: 24px; height: 24px; margin-top: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 1px solid var(--border);
  font-size: var(--text-xs, .75rem); font-weight: 600;
  color: var(--muted-foreground);
}
.mer-puce svg { width: 14px; height: 14px; }
.mer-etape--faite .mer-puce {
  border-color: transparent;
  background: var(--success-wash, rgba(52, 199, 123, .16));
  color: var(--success, rgb(52, 199, 123));
}
.mer-etape--courante .mer-puce {
  border-color: transparent;
  background: var(--primary, rgb(124,108,246)); color: var(--on-primary, #fff);
}

.mer-etape-corps { flex: 1 1 auto; min-width: 0; display: flex;
                   flex-direction: column; gap: 3px; }
.mer-etape-titre { font-size: var(--text-body); color: var(--muted-foreground); }
.mer-etape--courante .mer-etape-titre {
  color: var(--foreground); font-weight: 600;
}
/* /!\ Ce qui est FAIT se barre et s'efface : c'est la recompense, et elle doit se
   voir sans disputer l'attention a ce qui reste. */
.mer-etape--faite .mer-etape-titre {
  color: var(--disabled-foreground, rgba(255,255,255,.38));
  text-decoration: line-through;
  text-decoration-color: var(--border-strong, rgba(255,255,255,.22));
}
.mer-etape-btn { white-space: nowrap; }
/* Le rendez-vous PRIS, en une ligne dans la carte (TVNI-242, 24/09/2026) : il
   n'est plus une etape, il reste un rappel discret, avec sa seule commande.
   « Je m'en passe » est parti avec l'etape. */
.mer-rdv {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 16px 0 0; padding: 10px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: rgba(255,255,255,.02);
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.mer-rdv b { color: var(--foreground); font-weight: 500; }
.mer-rdv-visage {
  flex: 0 0 26px; width: 26px; height: 26px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: calc(10px * var(--echelle-texte)); font-weight: 600; letter-spacing: .02em; color: #14101F;
  background: linear-gradient(145deg, #D9D2FF, #9C8FF0);
}
.mer-rdv-texte { flex: 1 1 240px; min-width: 0; }
.mer-rdv-gerer {
  margin-left: auto; white-space: nowrap; color: var(--accent);
  text-decoration: none; transition: color 160ms var(--ease-out);
}
.mer-rdv-gerer:hover { color: var(--accent-hi); text-decoration: underline;
  text-underline-offset: 3px; }

/* ── La ligne, sur toutes les autres pages ──────────────────────────────── */
.mer-ligne {
  display: flex; align-items: center; gap: 14px;
  margin: 0 0 22px; padding: 11px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card, rgb(18, 20, 29));
  color: inherit; text-decoration: none;
  transition: border-color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.mer-ligne:hover {
  border-color: var(--border-strong); background: var(--accent-wash);
}
.mer-jauge--mince .mer-jauge-segments i { width: 16px; height: 4px; }
.mer-ligne-texte {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mer-ligne-texte b { color: var(--foreground); font-weight: 600; }
.mer-ligne-fin {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-body-sm); font-weight: 600;
  color: var(--primary, rgb(124,108,246)); white-space: nowrap;
}
.mer-ligne-fin svg { width: 12px; height: 12px; }

/* ============================================================
   LA PRISE DE RENDEZ-VOUS, EN FENETRE (2026-09-14)
   ============================================================
   Demande du fondateur : « la fenetre de rdv je veux que ce soit une popup pas
   une page entiere ».

   /!\⚠️ **`dialog.rdv-fenetre.verre`, ET LES TROIS MORCEAUX COMPTENT.** Le
   guide et la pop-up d'accueil s'en tirent avec `dialog.<classe>` parce que
   leurs feuilles sont chargees APRES `charte.css` : a specificite egale, c'est
   l'ordre de source qui tranche, et elles passent en dernier. Ce bloc-ci vit
   DANS `charte.css`, AU-DESSUS de `dialog.verre` (plus bas dans le meme
   fichier) : a egalite, c'est `.verre` qui gagnait, et la fenetre s'ouvrait a
   460 px avec le calendrier tasse sur une colonne. Mesure a l'ecran. On monte
   donc d'une classe, ce qui rend la regle independante de l'ordre.

   /!\ **LE CONTENU EST UNE IFRAME**, donc la fenetre doit lui donner une
   HAUTEUR FERME : une iframe n'a pas de hauteur naturelle, et sans cette regle
   elle se rend a 150 px par defaut, ce qui coupe le calendrier a la deuxieme
   ligne. */
dialog.rdv-fenetre.verre {
  width: min(980px, 94vw);
  max-width: none;
  /* /!\ **`var(--fenetre)` ET SURTOUT PAS `92vh`.** La charte pose un
     `zoom: 1.1` sur `body` : une hauteur exprimee en `vh` brut est calculee sur
     la fenetre REELLE puis agrandie de 10 %, donc elle deborde de l'ecran
     d'exactement ce facteur. `--fenetre` vaut `calc(100vh / var(--echelle))` et
     corrige ca. `tests/test_echelle_hauteurs.py` balaie cette feuille, et il a
     attrape celle-ci. */
  height: min(760px, calc(var(--fenetre) - 80px));
  max-height: none;
  padding: 0; border: 0; overflow: hidden;
  /* /!\ La hauteur ci-dessus est le REPLI, pas la regle : `rdv-fenetre.js`
     pose `data-ajustee` des que la page servie a dit sa hauteur, et la fenetre
     epouse alors son contenu (2026-09-16). Sans JavaScript, on garde la
     hauteur figee -- et la page, elle, s'etire pour la remplir (cf. le
     `min-height` de `rdv_client.html`), donc pas de bande vide non plus. */
  color: var(--foreground);
  /* Opaque, comme le guide : le verre est dessine pour trois lignes de
     confirmation, pas pour un calendrier. */
  background: var(--card, rgb(18, 20, 29));
}
dialog.rdv-fenetre.verre[open] { display: flex; flex-direction: column; }
dialog.rdv-fenetre.verre::backdrop { background: rgba(5, 6, 10, 0.78); }

.rdv-fenetre-tete {
  flex: none;
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 16px 20px 13px;
  border-bottom: 1px solid var(--border);
}
.rdv-fenetre-mots { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.rdv-fenetre-mots b {
  font-family: var(--font-sans); font-size: var(--text-lg); font-weight: 600;
  letter-spacing: -0.015em;
}
.rdv-fenetre-mots span {
  font-size: var(--text-body-sm); line-height: 1.5; color: var(--muted-foreground);
}
.rdv-fenetre-fermer {
  flex: none; background: none; border: 0; cursor: pointer;
  font-size: var(--text-2xl); line-height: 1; padding: 0 4px;
  color: var(--muted-foreground);
}
.rdv-fenetre-fermer:hover { color: var(--foreground); }
.rdv-fenetre-fermer:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.rdv-fenetre-cadre { flex: 1; min-height: 0; width: 100%; border: 0; display: block; }

/* La fenetre qui a recu la hauteur de sa page : elle la suit, jusqu'au plafond
   que le script applique lui-meme sur le cadre.

   /!\ **`fit-content`, ET SURTOUT PAS `auto`.** Mesure a l'ecran : avec `auto`,
   la fenetre s'etirait sur TOUTE la hauteur de l'ecran (900 px sur un ecran de
   900) alors que son cadre faisait 233. La raison est dans la feuille du
   navigateur : `dialog` y vaut `height: fit-content` avec `inset-block: 0 0` et
   `margin: auto`. Ecrire `auto` par-dessus rend les deux bords opposes
   contraignants, et la boite remplit l'espace entre eux -- exactement le
   contraire de ce qu'on demandait. On REND donc la valeur du navigateur au lieu
   de la remplacer.

   /!\ **ET ON REMET UN `max-height`**, que le bloc du dessus avait mis a `none`
   parce que la hauteur y etait figee : sans lui, une page longue sur un petit
   ecran ferait sortir la fenetre de l'ecran. Le script borne deja le cadre ; les
   deux gardes sont voulues, l'une ne depend pas de JavaScript. */
dialog.rdv-fenetre.verre[data-ajustee] {
  height: fit-content;
  max-height: calc(var(--fenetre) - 80px);
}
dialog.rdv-fenetre.verre[data-ajustee] .rdv-fenetre-cadre { flex: none; }

/* ── L'ecran etroit ────────────────────────────────────────────────────────
   /!\ Sous cette largeur, le bouton de l'etape courante ne tient plus a cote de
   son titre : il passe dessous, aligne sur le texte et non sur la pastille. */
@media (max-width: 760px) {
  .mer-carte { padding: 18px 16px 6px; }
  .mer-etape { flex-wrap: wrap; }
  .mer-etape-gestes { width: 100%; padding-left: 38px; margin-top: 8px; }
  .mer-jauge b { font-size: var(--text-body); }
  /* ⚠️ **`white-space: normal` NE SUFFISAIT PAS, et le resultat etait illisible**
     (releve a 390 px le 2026-09-16). La ligne ne passe pas a la ligne : la jauge
     (96 px) et « Reprendre ma mise en route » (195 px, insecable) mangeaient les
     298 px disponibles, et le texte se repliait dans les CINQ points restants :
     une lettre par ligne, sur cent trente points de haut. Autoriser le retour a
     la ligne sans donner de place au texte ne fait que choisir la forme du
     degat. */
  .mer-ligne { flex-wrap: wrap; row-gap: 8px; }
  /* Le texte reste a cote de la jauge, avec de quoi ecrire : une base de 120 px
     tient (96 + 14 + 120 = 230), et `1 1` lui rend tout ce qui traine. */
  .mer-ligne-texte { white-space: normal; flex: 1 1 120px; }
  /* Le lien prend sa propre ligne plutot que d'ecraser la phrase qu'il conclut. */
  .mer-ligne-fin { flex: 1 0 100%; }
}

/* La GRILLE de cartes : elles se posent cote a cote et reviennent a la ligne toutes
   seules (2026-08-18, demande du fondateur sur « Mes traders »).

   /!\ Les cartes d'une meme rangee ont la MEME HAUTEUR, et c'est ce qui separe une
   grille rangee d'un escalier. C'est `align-items: stretch` (le defaut d'une grille)
   qui le donne ; encore faut-il que la carte, elle, sache occuper la hauteur qu'on
   lui offre : d'ou la colonne flexible ci-dessous. Un contenu plus court laisse alors
   son vide en BAS de la carte, jamais entre deux blocs.

   /!\ La largeur d'une colonne se regle en un seul endroit, `--carte-min`, et jamais
   dans un gabarit : c'est la lecon du 2026-08-18 (dix-sept largeurs ecrites en dur).
   `auto-fill` et non `auto-fit` : `auto-fit` etire une carte unique sur tout le cadre,
   ce qui donne une carte de 1280 de large quand un client n'a qu'un trader. */
.cartes--grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--carte-min, 320px), 1fr));
  /* /!\ `1fr` sur les rangees implicites, et non `auto` : sans lui, seules les cartes
     d'une MEME rangee ont la meme hauteur, et la deuxieme rangee est plus courte que la
     premiere. Les boutons d'une carte du bas ne tombent alors pas a la meme hauteur que
     ceux d'une carte du haut. Avec `1fr`, toutes les rangees prennent la hauteur de la
     plus grande et TOUTES les cartes de la grille sont identiques. */
  grid-auto-rows: 1fr;
  gap: 14px;
}
/* La carte devient une colonne : ce qui doit tomber en bas (les actions) porte
   `margin-top: auto` et s'y colle, quelle que soit la hauteur de la rangee. */
.cartes--grille > .carte {
  display: flex; flex-direction: column; margin-bottom: 0;
}
.carte-bas { margin-top: auto; }

/* Les ZONES d'une carte, separees par un filet (2026-08-18).

   Demande du fondateur, apres avoir vu la grille : « l'interieur manque de relief ».
   Il ne manquait pas de relief, il manquait de STRUCTURE : tout etait du texte gris
   aligne a gauche, a la meme taille, sauf le titre. Une carte se lit mieux en trois
   temps : ce que c'est, ce que ça produit, ce qu'on peut en faire.

   /!\ Le filet est pose en `border-top` sur la zone SUIVANTE, jamais en `<hr>` : un
   element vide de plus dans le flux serait lu a voix haute par un lecteur d'ecran
   sans rien signifier.

   /!\ Il deborde du padding de la carte (marges negatives) pour aller d'un bord a
   l'autre. Un filet qui s'arrete avant les bords ressemble a un soulignement rate. */
.carte-zone {
  border-top: 1px solid var(--border);
  margin-left: calc(-1 * var(--carte-marge, 28px));
  margin-right: calc(-1 * var(--carte-marge, 28px));
  padding: 12px var(--carte-marge, 28px) 0;
}


/* ============================================================
   Les MESURES : etiquette AU-DESSUS, valeur en dessous (2026-08-18)
   ============================================================
   Deux ou trois chiffres poses cote a cote dans une carte. Plus leger que `.chiffre`,
   qui est une TUILE (fond, bordure, lisere, survol) : une tuile posee dans une carte
   fait une boite dans une boite, et deux boites imbriquees se disputent le regard.

   /!\ L'etiquette est AU-DESSUS de la valeur, jamais apres. La carte ecrivait
   « rien analyse  aujourd'hui » : on lit la valeur avant de savoir de quoi elle
   parle, et deux mesures empilees ne ressemblent plus a une paire. Au-dessus, l'oeil
   descend d'une colonne a l'autre et compare.

   /!\ Les colonnes sont de largeur EGALE (`1fr`). Deux mesures qui repondent a deux
   questions de meme rang doivent avoir le meme poids ; la premiere qui s'elargirait
   parce que sa valeur est plus longue ferait croire a une hierarchie qui n'existe pas. */
/* ── CE QU'UN TRADER FAIT EN CE MOMENT (2026-08-21) ─────────────────────────
   La zone qui a remplace les chiffres de performance sur la carte d'un trader.

   /!\ La COULEUR porte l'etat, et c'est tout l'interet : dix cartes se lisent d'un
   regard sans lire un mot. Bleu en position, orange ordre posé, gris au repos.

   /!\ Ce sont les couleurs SEMANTIQUES (`--info`, `--warning`), pas l'accent de
   marque : elles disent un etat, pas une identite. Et jamais `--success`/`--error`
   ici -- une position ouverte n'est ni gagnante ni perdante, le serveur ne recoit
   aucun prix en direct et l'ignore.

   /!\ La pastille double la couleur du texte au lieu de la remplacer : une teinte
   seule est illisible pour huit hommes sur cent, et le mot reste le porteur. */
/* Le bloc de CLE POSE SUR UNE CARTE (2026-08-21). Le meme composant qu'ailleurs,
   resserre pour tenir dans une colonne de 320 px.

   /!\ L'etiquette « Cle du plugin » n'est pas SUPPRIMEE, elle est rendue invisible a
   l'oeil et gardee pour les lecteurs d'ecran. Elle occupait 110 px de la premiere
   ligne, ce qui renvoyait les trois boutons a la ligne suivante et faisait grandir
   chaque carte d'une rangee -- soit dix rangees pour dix traders, exactement le
   « ce sera indigestible » qui avait fait retirer ce bloc des cartes le 2026-08-17.
   Sur la carte, le contexte et les trois icones disent deja de quoi il s'agit. */
/* ⚠️ **`.trader-carte` N'EXISTE PLUS** (refonte « l'équipe », 2026-09-15) : la carte
   d'un trader est `.tr`. Ces règles la suivent plutôt que de rester à viser une classe
   morte — un style sans balise finit par être recopié ailleurs « parce qu'il existe ».
   ⚠️ **L'ÉTIQUETTE RESTE MASQUÉE**, comme sur l'ancienne carte, et pour la raison
   d'alors : elle prend la largeur d'un bouton et renvoie les trois icônes à la ligne,
   ce qui fait grandir chaque carte d'une rangée. Ce qu'elle disait est désormais écrit
   EN TOUTES LETTRES sous le bloc (« Collez cette clé dans le plugin NinjaTrader »),
   là où quelqu'un qui installe son plugin pour la première fois le lira vraiment.
   Elle reste dans le balisage pour les lecteurs d'écran. */
.tr .trader-cle .spec-label {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.tr .trader-cle { padding: 8px 10px; gap: 8px; margin: 0; }
/* Le masque ne revient JAMAIS a la ligne : coupe, il se lit comme deux cles. */
.tr .trader-cle code { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; overflow-wrap: normal; }
/* Les trois icones tiennent sur la MEME ligne que le code : a la ligne, elles font
   grandir chaque carte d'une rangee. */
.tr .trader-cle .btn-icon { width: 30px; height: 30px; flex: none; }

.vivant { display: flex; align-items: flex-start; gap: 10px; }
/* Le resultat du JOUR, pousse au bout de la ligne d'etat (2026-08-21). Il tient la
   parce que la carte fait 460 px ; sous 400, la grille passe a une colonne et la
   ligne a de la place de reste. Aligne a droite : un montant se lit par sa fin. */
.vivant-jour { margin-left: auto; text-align: right; flex: none; }
.vivant-pastille {
  width: 8px; height: 8px; border-radius: 50%; flex: none; margin-top: 6px;
  background: var(--disabled-foreground);
}
.vivant-txt { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.vivant-titre { font-size: var(--text-md); font-weight: 500; color: var(--muted-foreground); }
.vivant-note {
  font-family: var(--font-mono); font-size: var(--text-caption);
  color: var(--muted-foreground);
}
.vivant--position .vivant-pastille {
  background: var(--info); box-shadow: 0 0 0 3px rgba(125,211,252,0.16);
}
.vivant--position .vivant-titre { color: var(--info); }
.vivant--ordre .vivant-pastille {
  background: var(--warning); box-shadow: 0 0 0 3px rgba(251,191,36,0.16);
}
.vivant--ordre .vivant-titre { color: var(--warning); }
/* Au repos et a l'arret : rien ne se detache. Une carte calme ne doit pas attirer
   l'oeil autant qu'une carte qui porte de l'argent en risque. */
.vivant--rien .vivant-titre,
.vivant--arret .vivant-titre { font-weight: 400; }

.mesures { display: grid; grid-template-columns: repeat(var(--mesures, 2), 1fr); gap: 14px; }
.mesure { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.mesure-l {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.mesure-v {
  font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--foreground);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Une mesure qui n'a pas de nombre a montrer (« hors seance ») : le mot prend la
   place du chiffre, mais pas son poids. Il DIT quelque chose, il ne se compare pas. */
.mesure-v--mot {
  font-family: var(--font-sans); font-size: var(--text-body);
  font-weight: 500; color: var(--muted-foreground);
}
.mesure-n { font-size: var(--text-caption); color: var(--muted-foreground); }
/* Une mesure cliquable garde le texte du dessus et prend la teinte au survol. */
a.mesure { text-decoration: none; border-radius: var(--radius-md);
  margin: -4px -6px; padding: 4px 6px;
  transition: background var(--motion-base) var(--ease-out); }
a.mesure:hover, a.mesure:focus-visible { background: var(--accent-wash); }

/* ============================================================
   Le SOMMAIRE : naviguer dans une page longue (2026-08-17)
   ============================================================
   Demande du fondateur : « c'est assez chiant de devoir tout defiler pour acceder
   aux differentes sections ». La fiche d'un trader en compte une dizaine.

   Une barre qui reste en haut, une puce par section, celle qu'on lit s'allume.
   `sommaire.js` s'occupe de la surbrillance.

   /!\ Pourquoi PAS des onglets DANS le formulaire : il est UN tout, valide d'un
   bloc, et un refus portant sur un champ range dans un onglet ferme serait
   invisible. Le client lirait « refuse » sans voir quoi. Le formulaire reste donc
   entier sur sa page, et le sommaire n'y deplace rien : il fait defiler.

   /!\ A ne pas confondre avec le decoupage en PAGES du 2026-08-17 (Reglages /
   Regles / Gerer). Ce qui est parti sur une autre page n'appartenait pas au
   formulaire : ni la cle du plugin, ni les regles, ni l'arret ne s'enregistrent
   avec lui, ils agissent tout de suite. L'objection ci-dessus tient toujours, elle
   ne visait que le fait de CACHER une partie d'un meme formulaire.

   /!\ `top` vaut la hauteur de la barre d'application (56 px), qui est elle-meme
   collante. Sans cela le sommaire se glisserait DESSOUS et disparaitrait.

   /!\ `backdrop-filter` est ici justifie, a l'inverse de `.verre` : du contenu
   defile REELLEMENT derriere cette barre. Une seule zone a recomposer, pas dix
   cartes.

   /!\ Les sections cibles ont besoin de `scroll-margin-top` : sans lui, l'ancre
   les amene sous les deux barres collantes, et on atterrit sur un titre cache. */
.sommaire {
  position: sticky; top: 56px; z-index: 40;
  display: flex; align-items: center; gap: 2px;
  margin: 0 0 18px;
  padding: 5px;
  overflow-x: auto;
  background: rgba(16, 18, 26, 0.88);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  scrollbar-width: none;
}
.sommaire::-webkit-scrollbar { height: 0; }
.sommaire > a {
  flex: 0 0 auto;
  padding: 6px 13px;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 500;
  line-height: 1;
  color: var(--muted-foreground);
  text-decoration: none;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-out),
              color var(--motion-fast) var(--ease-out);
}
.sommaire > a:hover { color: var(--foreground); background: rgba(255,255,255,0.05); }
.sommaire > a[aria-current="true"] {
  background: var(--card);
  color: var(--accent);
  box-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
/* 56 de barre d'application + 46 de sommaire + une respiration. */
[data-section] { scroll-margin-top: 116px; }

/* La CHAINE : les etapes d'un ordre, de gauche a droite (2026-08-18).
   ============================================================
   Choix du fondateur parmi trois maquettes (frise verticale, chaine, tableau) :
   « j'aime bien le B, avec toutes les informations utiles ».

   /!\ Ce qu'elle dit et qu'une liste ne disait pas : que c'est une SUITE. Chaque
   etape pointe vers la suivante, et la derniere se teinte du resultat. Les rangees
   d'avant alignaient trois blocs sans lien visible entre eux, et le montant partait
   a l'extreme droite, a mille pixels de l'etape qui l'avait produit.

   /!\ Sa limite est REELLE et il faut la connaitre avant d'y mettre autre chose :
   au-dela de quatre ou cinq etapes elle se replie sur deux lignes, et le detail de
   chaque etape doit rester court. Un ordre en compte trois dans le cas courant
   (pose, rempli, sorti), deux s'il est annule, un s'il attend encore.

   /!\ La fleche est un CARRE tourne a 45 degres, pas un caractere : elle doit
   prendre le fond et la bordure de l'etape qu'elle prolonge, y compris quand celle-ci
   est teintee. Un « > » textuel ne saurait pas suivre. */
.chaine { display: flex; align-items: stretch; flex-wrap: wrap; }
.chaine-etape {
  flex: 1 1 190px; min-width: 0; position: relative;
  padding: 14px 18px 14px 22px;
  background: rgba(255,255,255,0.02);
  border: 1px solid var(--border); border-left: 0;
}
.chaine-etape:first-child {
  border-left: 1px solid var(--border);
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  padding-left: 18px;
}
.chaine-etape:last-child { border-radius: 0 var(--radius-lg) var(--radius-lg) 0; }
.chaine-etape + .chaine-etape::before {
  content: ""; position: absolute; left: -7px; top: 50%;
  width: 12px; height: 12px; margin-top: -6px;
  background: rgba(255,255,255,0.02);
  border-top: 1px solid var(--border); border-right: 1px solid var(--border);
  transform: rotate(45deg);
}
.chaine-rang {
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 500;
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.chaine-quoi {
  margin-top: 4px;
  font-family: var(--font-sans); font-size: var(--text-md); font-weight: 600;
  color: var(--foreground);
}
.chaine-motif { font-weight: 400; color: var(--muted-foreground); }
.chaine-heure {
  margin-top: 3px;
  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--muted-foreground);
}
.chaine-detail {
  margin-top: 7px; font-size: var(--text-sm); line-height: 1.5;
  color: var(--disabled-foreground);
}
.chaine-montant {
  margin-top: 7px;
  font-family: var(--font-mono); font-size: var(--text-md); font-weight: 600;
}
/* L'etape qui SOLDE prend la teinte de son resultat — et sa fleche avec elle, sinon
   le raccord se voit. */
.chaine-etape--gain { background: rgba(60,190,130,0.06); border-color: rgba(60,190,130,0.30); }
.chaine-etape--gain::before { background: rgba(60,190,130,0.06); }
.chaine-etape--perte { background: rgba(214,90,90,0.06); border-color: rgba(214,90,90,0.30); }
.chaine-etape--perte::before { background: rgba(214,90,90,0.06); }
/* Sous 640 px les etapes s'empilent : la fleche laterale n'a plus de sens, les coins
   se remettent d'aplomb. */
@media (max-width: 640px) {
  .chaine-etape,
  .chaine-etape:first-child,
  .chaine-etape:last-child {
    flex: 1 1 100%; border-left: 1px solid var(--border); border-radius: var(--radius-lg);
  }
  .chaine-etape + .chaine-etape { margin-top: 8px; }
  .chaine-etape + .chaine-etape::before { display: none; }
}

/* Le RELEVE D'UN TRADE (recette du 24/09/2026) : « Ce que ça a donné ».
   ============================================================
   /!\ **LA BANDE `.verdict` EST PARTIE** (montant en gros a gauche, deux tuiles a
   droite) : le fondateur a demande le style du relevé de la synthèse du journal,
   `.releve` plus bas, avec tous les chiffres du trade. On ne garde ici que ce que
   la fiche ajoute : son espace avant le bandeau du rejeu, et un MOT lisible (le
   type d'ordre, le modèle) qui n'est pas un « - » grisé. */
.releve--trade { margin-bottom: 16px; }
.releve dd.releve-mot--clair { color: var(--foreground); }
/* La POSITION EN TROP refermée (24/09/2026) : un relevé à part, sous celui du trade,
   bordé de l'ocre de `.badge-warning` : son résultat n'est pas celui du trade. */
.releve--excedent {
  padding: 12px 14px; border-radius: var(--radius-md);
  border: 1px solid rgb(251 191 36 / 0.35);
}
.releve--excedent .releve-titre { margin: 0 0 10px; }
.releve--excedent > .t-caption { margin: 10px 0 0; }

/* ══ LE DÉTAIL D'UNE ANALYSE, REPENSÉ (fondateur, 27/09/2026) ═══════════════════
   « Je ne la trouve pas du tout user-friendly. » Trois changements, un motif chacun :
   - les chiffres de « Ce que ça a donné » se GROUPENT par sens (l'ordre, la
     position, l'analyse) : trois `.releve-groupe`, une colonne chacun, côte à côte
     au bureau et empilés au téléphone, sans media query (`auto-fit`) ;
   - le déroulé sort de son repli : les rangées du journal, un liseré par fait ;
   - « Ce qu'il avait sous les yeux » montre les DONNÉES (`.yeux-*`), jamais le texte
     envoyé au modèle : une bande de repères (`.bande`, la charte), puis des cartes
     plates, le cadre de `.bloc-plat` en plus serré. */
.rejeu-ligne {
  margin: 0 0 16px; font-size: var(--text-body-sm); line-height: 1.6;
  color: var(--muted-foreground);
}
.rejeu-ligne > .badge { margin-right: 8px; }
.reglage-groupe > p.rejeu-ligne { max-width: none; }
.releve--groupes {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 18px 34px;
}
.releve--groupes dl { columns: auto; }
.releve-groupe { min-width: 0; }
.releve-groupe > .releve-titre, .deroule > .releve-titre {
  margin: 0 0 2px; font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .09em; text-transform: uppercase; color: var(--muted-foreground);
}

/* Le DÉROULÉ : les rangées du journal (`.rangees` / `.rangee`, et le liseré des
   tickets, `.ticket--*`), pas une frise à elle — la charte l'interdit depuis le
   2026-09-15 (« NE PAS REFABRIQUER `.frise-*` »), et la même page portait déjà la
   même liste sous une autre forme. Visible, plus replié. */
.deroule { margin: 26px 0 0; }
.deroule > .rangees { margin-top: 6px; }
.deroule-note { margin: 8px 0 0; }

/* ── Ce qu'il avait sous les yeux ── */
.yeux-bande { margin: 6px 0 18px; }
.yeux-bande .yeux-mot { font-family: var(--font-sans); }
.yeux-carte {
  min-width: 0; margin: 0 0 14px; padding: 14px 16px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.yeux-tete {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 4px 14px; flex-wrap: wrap;
}
.yeux-titre { margin: 0; font-size: var(--text-base); font-weight: 600; color: var(--foreground); }
.yeux-note { margin: 2px 0 0; }
.yeux-defile {
  margin-top: 10px; max-height: 340px; overflow: auto;
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.yeux-defile--court { max-height: 250px; }
.yeux-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.yeux-table th {
  position: sticky; top: 0; z-index: 1; background: var(--card);
  padding: 8px 10px; text-align: right; white-space: nowrap;
  font-size: var(--text-2xs); font-weight: 500; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted-foreground);
  border-bottom: 1px solid var(--border);
}
.yeux-table td {
  padding: 6px 10px; text-align: right; white-space: nowrap;
  font-family: var(--font-mono); font-size: var(--text-sm); font-variant-numeric: tabular-nums;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.yeux-table th:first-child, .yeux-table td:first-child { text-align: left; }
.yeux-table tbody tr:last-child td { border-bottom: 0; }
/* La bougie la plus RÉCENTE, celle de la décision, en haut et marquée. */
.yeux-table tbody tr:first-child td { background: rgba(180,168,255,0.07); }
.yeux-table .yeux-large { text-align: left; white-space: normal; min-width: 16em; font-size: var(--text-xs); }
.yeux-h { color: var(--muted-foreground); }
.yeux-hausse { color: var(--success); }
.yeux-baisse { color: var(--error); }
.yeux-grille {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  align-items: start; gap: 14px; margin: 0 0 14px;
}
.yeux-grille > .yeux-carte { margin: 0; }
.yeux-valeurs { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 6px 24px; margin: 10px 0 0; }
.yeux-valeurs > div { display: flex; flex-direction: column; }
.yeux-grand {
  font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 500;
  font-variant-numeric: tabular-nums; color: var(--foreground);
}
.yeux-depli { margin-top: 10px; }
.yeux-depli > summary { padding: 8px 2px 0; }
.yeux-liste { margin: 8px 0 0; padding-left: 18px; font-size: var(--text-body-sm); color: var(--foreground); }
.yeux-liste li + li { margin-top: 3px; }
.yeux-manques {
  display: flex; align-items: baseline; gap: 6px 10px; flex-wrap: wrap;
  margin: 0 0 14px; font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.yeux-annonces { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.yeux-annonces li {
  display: grid; grid-template-columns: 3.4em auto minmax(0, 1fr);
  gap: 2px 10px; align-items: baseline;
}
.yeux-annonces li .t-caption { grid-column: 3; }
.yeux-annonces .yeux-h { font-family: var(--font-mono); font-size: var(--text-sm); }
.yeux-annonce-nom { color: var(--foreground); font-size: var(--text-body-sm); }
.yeux-barres { display: flex; flex-direction: column; gap: 2px; padding: 6px 0; }
.yeux-barre {
  display: grid; grid-template-columns: 5.4em minmax(0, 1fr) 4.4em;
  gap: 10px; align-items: center; padding: 0 8px;
  font-family: var(--font-mono); font-size: var(--text-xs); font-variant-numeric: tabular-nums;
}
.yeux-barre-prix { text-align: right; color: var(--muted-foreground); }
.yeux-barre-q { text-align: right; color: var(--foreground); }
.yeux-barre-piste { height: 10px; border-radius: 3px; background: rgba(255,255,255,0.03); overflow: hidden; }
.yeux-barre-piste > i { display: block; height: 100%; border-radius: 3px; background: rgba(180,168,255,0.30); }
.yeux-barre--valeur .yeux-barre-piste > i { background: rgba(180,168,255,0.55); }
.yeux-barre--poc .yeux-barre-piste > i { background: var(--accent); }
.yeux-barre--poc .yeux-barre-prix { color: var(--accent); font-weight: 600; }
.yeux-barre--vente .yeux-barre-piste > i { background: rgb(248 113 113 / 0.5); }
.yeux-barre--achat .yeux-barre-piste > i { background: rgb(74 222 128 / 0.45); }
.yeux-barre-milieu {
  margin: 4px 0; padding: 4px 0; text-align: center;
  border-top: 1px dashed var(--border); border-bottom: 1px dashed var(--border);
  font-size: var(--text-2xs); color: var(--muted-foreground);
}
.yeux-seances { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; margin-top: 10px; }
.yeux-seance { min-width: 0; }
.yeux-seance-titre { margin: 0; font-weight: 500; color: var(--foreground); }
.yeux-reperes {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 4px 0 0;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--foreground);
}
.yeux-reperes b { margin-right: 4px; font-family: var(--font-sans); font-weight: 500; color: var(--muted-foreground); }
.yeux-seance .yeux-defile { border: 0; margin-top: 6px; }

/* ── LE GRAPHIQUE D'UN TRADE, ET L'APERCU DE SES COULEURS ────────────────────
   /!\ **L'ECHELLE DE PRIX VERTICALE A ETE PURGEE LE 2026-09-15**, avec ses dix-huit
   classes `.ech-*` et `performance.echelle_de_prix` : le graphique porte le plan a son
   HEURE, avec les bougies autour, et deux dessins du meme trade c'etait un de trop.
   Ne pas la remettre : ce qu'elle disait -- les DISTANCES entre le stop, l'entree et
   l'objectif -- se lit mieux sur le graphique, qui montre en plus ce que le marche a
   fait entre les deux.

   /!\ Le cours du jour et la plus-value LATENTE s'affichaient sur elle. Ils ont ete
   reportes sur le graphique le meme jour : ils avaient disparu de l'ecran sans que rien
   ne le dise, et c'est la purge qui l'a montre (`tests/test_latent_sur_le_dessin.py`).

   /!\ **L'APERCU SVG DE QUATRE BOUGIES A ETE PURGE LE 2026-09-15**, avec ses classes
   `.ch-*` et `.chandelles` et l'onglet « Graphique » du profil qui l'hebergeait
   (fondateur : « retire la page des reglages des graphiques du profil »). Il vivait
   dans `user_profile.html` et dessinait a la main ce que le canevas peint : c'est comme
   ca qu'il s'est retrouve NOIR SUR NOIR sans que rien ne crie, en septembre.
   **L'apercu est desormais le VRAI graphique**, repeint par `graphique-trade.js` dans
   la fenetre de reglages -- un seul peintre, donc plus de second dessin a tenir
   d'accord. Ne pas les remettre : les variables `--ch-*`, elles, restent, et c'est le
   canevas qui les lit. */

/* LE GRAPHIQUE D'UN TRADE — le cadre, la barre de lecture, la toile.
   /!\ Le DESSIN vit dans `static/js/graphique-trade.js` : il se fait dans le navigateur
   depuis le 2026-09-15, parce qu'un graphique ou l'on NAVIGUE ne peut pas etre rendu
   d'avance — seul le navigateur sait quelle fenetre le client regarde.
   Ce bloc ne porte donc que le cadre et les teintes ; les couleurs elles-memes sont
   posees sur l'element par le serveur, depuis le profil du client. */
.graphique-trade { position: relative; padding: 0; overflow: hidden; }
/* Les teintes que le SCRIPT lit sur le cadre, en plus des huit du client : elles
   viennent de la charte, pas d'un defaut ecrit en JavaScript.
   /!\ **DEUX SELECTEURS, UNE SEULE DECLARATION** : l'apercu de la fenetre de reglages
   est peint par le MEME script, qui lit ces memes variables sur LUI. Les redeclarer
   pour lui, ce serait deux jeux de teintes a tenir d'accord -- et l'apercu qui ment. */
.graphique-trade, .rg-apercu {
  --ch-texte: var(--foreground);
  --ch-discret: var(--muted-foreground);
  --ch-grille: rgba(255,255,255,0.055);
  --ch-fond: var(--card);
  --ch-alerte: var(--warning);
}
.gt-toile { display: block; width: 100%; height: 480px; cursor: crosshair; touch-action: none; }
.gt-toile.ch-glisse { cursor: grabbing; }
/* La barre du haut : LE PLAN a gauche, le zoom et le recadrage a droite.
   /!\ **LE PLAN Y EST DEPUIS LE 2026-09-15**, quand il a quitte les bougies (fondateur :
   « je n'aime pas l'affichage et le texte sur le graphique »). Une SEULE barre, pas deux
   empilees : le dessin perdait trente points de hauteur pour redire ce que celle-ci
   disait deja.
   /!\ `flex-wrap` n'est pas un detail : a trois jetons plus le cours plus trois boutons,
   une barre qui ne se replie pas pousse les boutons hors du cadre sur un telephone. */
.gt-barre {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 8px 12px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.gt-plan { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
/* B5 (23/09/2026) : LES ONGLETS d'une analyse, un par serie lue. /!\ Ils passent a
   la ligne au telephone : jamais de defilement horizontal (plan § 8). */
.gt-onglets {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.gt-onglet {
  font: inherit; font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 4px 11px; border-radius: 999px; cursor: pointer;
  color: var(--muted-foreground); background: transparent;
  border: 1px solid var(--border);
}
.gt-onglet:hover { color: var(--foreground); }
.gt-onglet--actif {
  color: var(--foreground); background: rgba(255,255,255,0.06);
  border-color: var(--foreground);
}
.gt-dit { margin-top: 8px; }
/* LE JETON d'un niveau : une pastille, sa couleur en point, son prix en gras.
   /!\ La COULEUR vient du profil du client, comme le trait qu'elle designe -- la
   feuille ne connait que la recette (`var(--ch-stop)` & consorts, posees sur le cadre
   par le gabarit). Un vert et un rouge ecrits ici divergeraient du dessin le jour ou il
   change ses teintes. */
.gt-jeton {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 4px 11px 4px 9px; border-radius: 999px;
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 500;
  line-height: 1.5; color: var(--muted-foreground);
  background: rgba(255,255,255,0.035); border: 1px solid var(--border);
}
.gt-jeton b { color: var(--foreground); font-weight: 600; font-variant-numeric: tabular-nums; }
.gt-jeton .gt-sec { font-weight: 400; font-variant-numeric: tabular-nums; opacity: 0.8; }
.gt-jeton .gt-pt { align-self: center; width: 7px; height: 7px; border-radius: 99px; }
.gt-jeton--entree   .gt-pt { background: rgba(var(--ch-entree), 1); }
.gt-jeton--stop     .gt-pt { background: rgba(var(--ch-stop), 1); }
.gt-jeton--objectif .gt-pt { background: rgba(var(--ch-objectif), 1); }
.gt-jeton--cours    .gt-pt { background: var(--foreground); }
/* /!\ SUR UN TELEPHONE, CHAQUE JETON PREND SA LIGNE, et quatre lignes de bandeau
   poussaient le dessin d'autant. On resserre plutot que de cacher : ce qui est ecrit la
   -- la distance en ticks, ce que le niveau coute en dollars -- est justement ce qui a
   quitte le dessin, et le masquer sur telephone le perdrait pour de bon. */
@media (max-width: 640px) {
  .gt-jeton { padding: 3px 9px 3px 7px; font-size: calc(11px * var(--echelle-texte)); gap: 5px; }
  .gt-jeton .gt-pt { width: 6px; height: 6px; }
  .gt-plan { gap: 5px; }
  .gt-barre { padding: 7px 10px; gap: 7px 10px; }
}
/* LA LEGENDE : les valeurs de la bougie survolee, POSEES SUR LE DESSIN.
   /!\ Elle vivait dans la barre du haut, hors du cadre ; le fondateur ne l'y voyait
   pas bouger (2026-09-15 : « en haut a gauche les valeurs OHLCV »). C'est le meme
   texte, deplace la ou le regard suit deja le repere.
   /!\ `pointer-events: none` n'est pas un detail : sans lui, la legende volerait au
   dessin le glissement et la molette des qu'on passe dessus. */
.gt-scene { position: relative; }
.gt-legende {
  position: absolute; top: 7px; left: 12px; z-index: 2; pointer-events: none;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px;
  max-width: calc(100% - 80px); padding: 5px 10px; border-radius: 8px;
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.4;
  color: var(--muted-foreground);
  /* /!\ LE FOND N'EST PAS DE LA DECORATION : le haut du dessin porte deja l'etiquette
     de l'objectif, et sans lui les deux textes se superposaient — illisibles tous les
     deux. Le repli sert les navigateurs sans `color-mix`. */
  background: var(--card);
  background: color-mix(in srgb, var(--card) 86%, transparent);
  backdrop-filter: blur(3px);
}
.gt-legende b { color: var(--foreground); font-weight: 600; letter-spacing: 0.02em; }
.gt-legende i { font-style: normal; opacity: 0.75; margin-right: 4px; }
/* La valeur prend la couleur de la bougie — celles du profil du client, posees en
   ligne par le script : la feuille ne peut pas savoir si la bougie monte ou descend. */
.gt-legende em { font-style: normal; font-weight: 600; }
/* LE GESTE de la bougie survolee (« entree 10 », « sorties partielles 4 »).
   /!\ Il s'ecrivait A MEME LE DESSIN jusqu'au 2026-09-15 ; il se lit ici depuis que le
   dessin ne porte plus de texte. La couleur est posee en ligne par le script : elle
   depend du geste, que la feuille ne connait pas. */
.gt-legende .gt-geste { font-weight: 600; }
.gt-legende .gt-geste::before { content: '●'; margin-right: 5px; font-size: 0.7em; vertical-align: 1px; }
.gt-gestes { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.gt-bouton {
  min-width: 28px; padding: 4px 9px; cursor: pointer; line-height: 1.2;
  font-size: var(--text-sm); color: var(--muted-foreground);
  background-color: rgba(255,255,255,0.035);
  border: 1px solid var(--border-strong); border-radius: 6px;
}
.gt-bouton:hover { color: var(--foreground); border-color: var(--ring); }
.gt-recadrer { font-size: var(--text-xs); }
.gt-aide {
  margin: 0; padding: 6px 12px 10px; color: var(--muted-foreground);
  border-top: 1px solid var(--border);
}
/* AUDIT DE LA SÉANCE (22/10/2026) : « Ses horaires » / « Toute la journée » en mots, et
   la légende À SYMBOLES sous le dessin, les mêmes formes que le script peint
   (`graphique-trade.js`, marques de la séance). Le mode d'emploi est derrière « ? ». */
.gt-vues { display: flex; gap: 4px; margin-right: auto; }
.gt-symboles {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px;
  padding: 6px 12px 10px; color: var(--muted-foreground); border-top: 1px solid var(--border);
}
.gt-symboles > span { display: inline-flex; align-items: center; gap: 6px; }
.gt-symboles .gt-aide {
  margin-left: auto; padding: 0; border: 1px solid var(--border-strong); border-radius: 50%;
  width: 18px; height: 18px; justify-content: center; cursor: help; font-weight: 600;
}
.gt-sym { display: inline-block; width: 10px; height: 10px; flex: none; }
/* Les teintes sont CELLES QUE LE CLIENT RÈGLE (« Vos trades sur la séance »). */
.gt-sym--achat { width: 0; height: 0; border: 5px solid transparent; border-bottom: 9px solid rgb(var(--ch-trade-achat)); border-top: 0; }
.gt-sym--vente { width: 0; height: 0; border: 5px solid transparent; border-top: 9px solid rgb(var(--ch-trade-vente)); border-bottom: 0; }
.gt-sym--gain { border-radius: 50%; background: rgb(var(--ch-sortie-gain)); box-shadow: 0 0 0 1.5px rgba(245,244,240,.9); }
.gt-sym--perte { border-radius: 50%; background: rgb(var(--ch-sortie-perte)); box-shadow: 0 0 0 1.5px rgba(245,244,240,.9); }
.gt-sym--lu { width: 4px; height: 4px; border-radius: 50%; background: var(--muted-foreground); opacity: .6; }
.gt-sym--horaires { width: 16px; height: 3px; border-radius: 2px; background: rgba(var(--ch-horaires), var(--ch-horaires-o)); }
.gt-aide kbd, .aide-texte kbd {
  display: inline-block; min-width: 1.5em; padding: 0 5px; text-align: center;
  font: 600 10.5px/1.6 var(--font-mono); color: var(--foreground);
  background: rgba(255,255,255,0.05); border: 1px solid var(--border-strong); border-radius: 4px;
}

/* ══ LA PAGE D'UNE ANALYSE, POUR CELUI QUI L'OUVRE TOUS LES JOURS (09/10/2026) ═════
   Fondateur : « mets-toi vraiment à la place d'un client qui regarde sa page tous les
   jours. Il faut que ce soit clair, que ce soit un plaisir d'y naviguer. » Le
   graphique passe en tête et grandit ; dessous, quatre onglets ; on dessine dessus, et
   on peut le prendre en plein écran.
   /!\ Les dessins sont peints par `graphique-trade.js` ; cette feuille ne porte que la
   barre, la palette, le champ de texte et l'atelier plein écran. */
.an-graphique { margin: 0 0 20px; }
.an-graphique-dit {
  margin: 0 0 10px; max-width: 95ch;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.an-voisines { display: inline-flex; gap: 6px; margin-right: 6px; }
.an-voisines [aria-disabled="true"] { opacity: .35; cursor: default; }
/* /!\ `[hidden]` ne gagne pas contre une classe qui pose `display` (`.yeux-grille`,
   `.yeux-carte`) : sans cette ligne, un panneau « caché » restait affiché. */
[data-an-panneau][hidden], [data-yeux-panneau][hidden], .gt-palette[hidden] { display: none !important; }
.an-onglets { margin: 6px 0 22px; }
/* Dans un onglet, une partie est seule à l'écran : sa pastille numérotée ne compte
   plus rien, et la phrase d'introduction reprend sa marge. */
.an-panneaux .reglage-groupe { margin-bottom: 28px; }
/* Le journal de séance (21/10/2026) : les analyses d'une séance, une ligne chacune,
   toute la ligne cliquable (la page d'une séance a le même style qu'une analyse). */
.se-analyses { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border); }
.se-analyses li { border-bottom: 1px solid var(--border); }
.se-analyse {
  display: flex; align-items: center; gap: 14px; padding: 12px 6px;
  color: var(--foreground); text-decoration: none; font-size: var(--text-body-sm);
}
.se-analyse:hover, .se-analyse:focus-visible { background: var(--muted); }
.se-heure { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 46px; }
.se-doux { color: var(--muted-foreground); }
.se-ouvrir { margin-left: auto; color: var(--muted-foreground); display: inline-flex; }
/* La note d'une analyse (30/10/2026) : son icône sous la ligne, alignée sur le sens
   (le retrait de la ligne, plus la colonne de l'heure, plus l'écart). */
.se-analyses .jp-ligne { padding: 0 6px 8px calc(6px + 46px + 14px); margin-top: -6px; }
.seance-faits { margin: 0; padding-left: 18px; font-size: var(--text-body-sm); }
.seance-faits li { margin: 0 0 8px; }
.an-panneaux .reglage-groupe > h2::before { display: none; }
.an-panneaux .reglage-groupe > h2 + p { margin-left: 0; }

/* LES GESTES de la barre : des icônes carrées, la même taille que l'écrou. */
.gt-bouton { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.gt-bouton > svg { display: block; }
.gt-bouton[aria-pressed="true"] {
  color: var(--foreground); border-color: var(--accent);
  background-color: rgba(180,168,255,0.16);
}
.gt-sep { width: 1px; height: 18px; background: var(--border-strong); margin: 0 3px; flex: 0 0 auto; }
.gt-si-plein { display: none; }
.gt-atelier--plein .gt-si-normal { display: none; }
.gt-atelier--plein .gt-si-plein { display: inline-flex; }
.gt-toile:focus { outline: none; }
.gt-toile:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
.gt-toile[data-outil]:not([data-outil="curseur"]) { cursor: crosshair; }
.gt-toile[data-outil="gomme"] { cursor: cell; }
.gt-toile.gt-sur-dessin { cursor: move; }

/* LA PALETTE DE DESSIN : les outils, cinq teintes, annuler, tout effacer, et l'état de
   l'enregistrement à droite. /!\ `flex-wrap` : au téléphone elle passe sur deux lignes
   plutôt que de pousser des outils hors du cadre. */
.gt-palette {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 6px 12px; border-bottom: 1px solid var(--border);
  background: rgba(180,168,255,0.04);
}
.gt-outils, .gt-teintes { display: inline-flex; align-items: center; gap: 4px; }
.gt-outil {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  color: var(--muted-foreground); background: transparent;
  border: 1px solid transparent; border-radius: 7px;
}
.gt-outil:hover { color: var(--foreground); background: rgba(255,255,255,0.05); }
.gt-outil[aria-pressed="true"] {
  color: var(--foreground); background: rgba(180,168,255,0.18); border-color: var(--accent);
}
.gt-teinte {
  width: 18px; height: 18px; padding: 0; cursor: pointer; border-radius: 50%;
  border: 2px solid var(--card); box-shadow: 0 0 0 1px var(--border-strong);
}
.gt-teinte[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--foreground); }
.gt-teinte--ambre { background: #f59e0b; }
.gt-teinte--bleu  { background: #60a5fa; }
.gt-teinte--rose  { background: #f472b6; }
.gt-teinte--vert  { background: #34d399; }
.gt-teinte--blanc { background: #f5f4f0; }
.gt-etat { margin-left: auto; white-space: nowrap; }
.gt-etat[data-etat="echec"] { color: var(--warning); }
/* Le champ d'un TEXTE, posé à l'endroit cliqué, au-dessus du dessin. */
.gt-saisie {
  position: absolute; z-index: 4; width: 220px; max-width: calc(100% - 80px);
  padding: 5px 8px; resize: none; overflow: hidden;
  font: 600 12.5px/1.4 var(--font-mono); color: var(--foreground);
  background: var(--card); border: 1px solid var(--accent); border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.35);
}

/* L'ATELIER : sur la page, il n'est que le cadre ; en PLEIN ÉCRAN, le cadre prend tout
   et le raisonnement se lit à côté (`.gt-volet`), masquable. */
.gt-atelier { position: relative; }
.gt-volet, .gt-volet-ouvrir { display: none; }
html.gt-plein-ouvert { overflow: hidden; }
/* /!\ Le contenu de la coquille vit dans un parent `z-index: 1` : sans cette ligne,
   l'atelier « par-dessus tout » passait SOUS le menu de gauche et la barre du haut
   (vu à l'écran, 09/10/2026). Le temps du plein écran, ses parents cessent d'empiler. */
html.gt-plein-ouvert *:has(> * .gt-atelier--plein),
html.gt-plein-ouvert *:has(> .gt-atelier--plein) { z-index: auto !important; }
.gt-atelier--plein {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 12px;
  padding: 12px; background: var(--background);
}
.gt-atelier--plein.gt-atelier--sans-volet { grid-template-columns: minmax(0, 1fr); }
.gt-atelier--plein > .graphique-trade {
  display: flex; flex-direction: column; min-height: 0; height: 100%; margin: 0;
}
.gt-atelier--plein .gt-scene { flex: 1 1 auto; min-height: 0; }
.gt-atelier--plein .gt-toile { height: 100%; }
.gt-atelier--plein .gt-volet {
  display: flex; flex-direction: column; min-height: 0;
  padding: 14px 16px; background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.gt-atelier--plein.gt-atelier--sans-volet .gt-volet { display: none; }
.gt-volet-tete { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.gt-volet-titre { margin: 0; font-weight: 600; color: var(--foreground); }
.gt-volet-texte {
  margin: 10px 0 0; overflow: auto; white-space: pre-wrap;
  font-size: var(--text-body-sm); line-height: 1.65; color: var(--foreground);
}
.gt-atelier--plein .gt-volet-ouvrir:not([hidden]) {
  display: inline-flex; position: absolute; right: 22px; bottom: 54px; z-index: 5;
}
@media (max-width: 720px) {
  .gt-atelier--plein {
    grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto;
    padding: 6px; gap: 6px;
  }
  .gt-atelier--plein .gt-volet { max-height: calc(var(--fenetre) * 0.3); }
  .gt-atelier--plein .gt-aide { display: none; }
}

/* LES DONNÉES REÇUES, une source à la fois ; une ligne de bougie mène au graphique. */
.yeux-onglets { margin: 2px 0 14px; }
.yeux-carte--etroite { max-width: 620px; }
.yeux-table tr[data-t] { cursor: pointer; }
.yeux-table tr[data-t]:hover td,
.yeux-table tr[data-t]:focus-visible td { background: rgba(180,168,255,0.12); }
.yeux-table tr[data-t]:focus-visible { outline: none; }

/* L'ECROU, dans la barre du graphique : la porte des reglages (2026-09-15). Meme
   bouton que le zoom, avec une icone a la place d'un signe -- deux habillages pour des
   boutons voisins, ca se voit tout de suite. */
.gt-bouton--ecrou { display: inline-flex; align-items: center; justify-content: center;
  padding: 4px 7px; }

/* ════ LA FENETRE DE REGLAGES DU GRAPHIQUE (2026-09-15) ══════════════════════
   Elle REMPLACE l'onglet « Graphique » du profil (fondateur : « retire la page des
   reglages des graphiques du profil et mets une icone a gauche avec un ecrou »).

   /!\ **ELLE SE DEROULE DEDANS, PAS DERRIERE** : `max-height` + un corps en
   `overflow-y: auto`, pour que la tete et surtout le PIED -- Appliquer, Annuler,
   Reinitialiser -- restent visibles sans chercher. Un `dialog` qui deborde de l'ecran
   cache ses propres boutons, et c'est le geste qu'on vient faire qu'on ne trouve plus. */
/* /!\ **`--fenetre`, ET JAMAIS `vh` BRUT** : la charte pose `zoom: var(--echelle)` sur
   le corps, donc un enfant qui demande `92vh` obtient 92 % de la hauteur DIVISEE par
   l'echelle -- il deborde de l'ecran, et ce sont ses boutons du bas qu'on perd.
   `--fenetre` vaut `calc(100vh / var(--echelle))`. Un test l'epingle
   (`test_echelle_hauteurs.py`), et il a attrape celle-ci. */
.rg-fenetre {
  width: min(760px, 94vw); max-height: calc(var(--fenetre) * 0.92);
  padding: 0; border: 0;
  color: var(--foreground); background: var(--card);
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  box-shadow: 0 24px 60px rgba(0,0,0,0.6);
}
.rg-fenetre::backdrop { background: rgba(0,0,0,0.62); backdrop-filter: blur(2px); }
.rg-fenetre form {
  display: flex; flex-direction: column; max-height: calc(var(--fenetre) * 0.92);
}
.rg-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 20px; border-bottom: 1px solid var(--border); flex: 0 0 auto;
}
.rg-tete h2 { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.rg-croix {
  display: inline-flex; padding: 6px; cursor: pointer; line-height: 0;
  color: var(--muted-foreground); background: none;
  border: 1px solid transparent; border-radius: 8px;
}
.rg-croix:hover { color: var(--foreground); border-color: var(--border-strong); }
/* /!\ **AUCUN REMBOURRAGE EN HAUT, et c'est l'apercu collant qui l'impose** : un
   `padding-top` sur le conteneur laisse une bande au-dessus de l'element colle, et les
   lignes de reglage defilaient DANS cette bande -- du texte coupe qui flotte au-dessus
   de l'apercu. L'air du haut est donc porte par la marge de l'apercu lui-meme. */
.rg-corps { padding: 0 20px 4px; overflow-y: auto; flex: 1 1 auto; }
/* L'APERCU reste SOUS LES YEUX pendant qu'on deroule les couleurs : c'est lui qu'on
   regarde en reglant, et le faire partir en haut de page viderait la fenetre de sa
   raison d'etre. */
.rg-apercu {
  position: sticky; top: 0; z-index: 3; margin: 18px 0 4px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-md); overflow: hidden;
}
.rg-apercu-toile { display: block; width: 100%; height: 190px; }
.rg-quoi { margin: 12px 0 18px; font-size: var(--text-sm); color: var(--muted-foreground); }
.rg-groupe { margin: 0 0 20px; }
.rg-groupe h3 {
  margin: 0 0 10px; font-size: var(--text-eyebrow); font-weight: 600;
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.rg-pied {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; padding: 14px 20px; flex: 0 0 auto;
  border-top: 1px solid var(--border); background: var(--card);
}
.rg-pied-droite { display: flex; gap: 10px; }
@media (max-width: 640px) {
  .rg-fenetre { width: 100vw; max-height: var(--fenetre); border-radius: 0; border: 0; }
  .rg-fenetre form { max-height: var(--fenetre); }
  .rg-corps { padding: 0 14px 4px; }
  .rg-apercu { margin-top: 14px; }
  .rg-tete, .rg-pied { padding: 12px 14px; }
  .rg-apercu-toile { height: 150px; }
  /* /!\ L'empilement de la ligne de reglage sur telephone est DEJA dans la charte
     (media 720px, `.reglage-ligne`) : le redire ici en ferait une seconde regle, et
     c'est celle qu'on oublierait le jour ou l'autre bouge. */
}

/* LA FENETRE DE PARTAGE d'un trade (2026-09-15). */
.partage {
  width: min(680px, 92vw); padding: 22px; color: var(--foreground);
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: 0 24px 60px rgba(0,0,0,0.6);
}
.partage::backdrop { background: rgba(0,0,0,0.6); }
.partage h2 { margin: 0 0 14px; font-size: var(--text-lg); }
.partage-fermer { position: absolute; top: 14px; right: 14px; margin: 0; }
.partage-apercu {
  display: block; width: 100%; border-radius: var(--radius-md);
  border: 1px solid var(--border);
}
.partage-gestes { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 10px; }
.partage-mot { margin: 0 0 14px; font-size: var(--text-sm); color: var(--muted-foreground); }
.partage-reseaux { display: flex; flex-wrap: wrap; gap: 8px; }
.partage-reseaux a { text-decoration: none; }

/* LA FENETRE DE PARTAGE, EN DEUX TEMPS (2026-09-16, arbitrage 3 du fondateur).

   /!\ **ELLE EST DANS LA CHARTE, PAS DANS UN GABARIT.** Le style de l'ancienne
   fenetre « Publier dans la communaute » vivait dans `user_trade_detail.html`,
   au motif que « rien de ce qu'elle dessine ne sert ailleurs » -- vrai tant
   qu'un seul ecran la portait. Le journal filtre ouvre desormais la MEME
   fenetre : recopie, ce bloc aurait montre deux fenetres differentes pour un
   seul geste. Meme motif que la carte d'analyse partagee, descendue ici le meme
   jour pour la meme raison.

   /!\ **PLUS D'ETAPE « OU » DEPUIS LE 24/09/2026** (TVNI-242) : la fenetre
   s'ouvre sur l'apercu, et la communaute est une destination parmi les reseaux.
   Les grandes cibles de l'etape partie (`.pa-cible`) servent desormais les TROIS
   SORTES de destination de la communaute (`.pa-sorte`) : un salon public, un
   message prive, un groupe. */
.pa-sortes {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
  margin-bottom: 12px;
}
/* Chaque sorte dit CE QU'ELLE FAIT, pas seulement son nom. */
.pa-sorte {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 10px 12px; color: var(--foreground);
  background: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius-md); font: inherit;
  transition: border-color var(--motion-fast) var(--ease-out);
}
.pa-sorte:hover, .pa-sorte:focus-visible {
  border-color: var(--accent-ring-strong); outline: none;
}
.pa-sorte[aria-selected="true"] {
  border-color: var(--accent-ring-strong); background: var(--accent-ring);
}
/* /!\ **UNE DESTINATION IMPOSSIBLE RESTE LISIBLE.** Elle n'est pas cliquable,
   mais sa phrase explique pourquoi -- c'est tout ce qu'elle a a faire. Un
   `opacity` trop bas la rendrait illisible et reviendrait a la cacher, ce que
   ce bloc existe precisement pour eviter. */
.pa-sorte[disabled] { cursor: default; opacity: 0.72; }
.pa-sorte[disabled]:hover { border-color: var(--border); }
.pa-sorte[disabled] span { color: var(--warning-text); }
.pa-sorte b { display: block; font-size: var(--text-sm); font-weight: 600; }
.pa-sorte span {
  display: block; margin-top: 3px;
  font-size: var(--text-xs); line-height: 1.4; color: var(--muted-foreground);
}
.pa-dest-titre {
  margin: 14px 0 8px; font-size: var(--text-xs); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted-foreground);
}
/* « Communaute TVNI », en tete des reseaux : la seule destination ou TVNI
   publie lui-meme. Elle porte l'accent de la charte, les reseaux non. */
.pa-communaute-bouton {
  color: var(--foreground); border-color: var(--accent-ring-strong);
  background: var(--accent-ring);
}
.pa-communaute-bouton[disabled] { opacity: .6; cursor: default; }
.pa-destinations .partage-mot { margin: 8px 0 0; }
.pa-destinations [data-partage-refus] { margin-top: 8px; }
.pa-lieu--prive span::before {
  content: "@"; font-size: var(--text-xs); opacity: .8;
}
/* UN JOURNAL SANS TRADE (TVNI-242) : une phrase, et rien a emporter. */
.partage[data-partage-vide] { width: min(460px, 92vw); }
.pa-vide { margin: 4px 0 0; padding: 26px 4px 8px; text-align: center; }
.pa-vide b { display: block; font-size: var(--text-base); font-weight: 600; }
.pa-vide span { display: block; margin-top: 6px; font-size: var(--text-sm); color: var(--muted-foreground); }
.pa-vide-gestes { justify-content: center; }
@media (max-width: 560px) {
  .pa-sortes { grid-template-columns: minmax(0, 1fr); }
}
.pa-cases { list-style: none; margin: 0 0 4px; padding: 0; }
.pa-case { margin: 0 0 9px; }
.pa-case label {
  display: flex; gap: 9px; align-items: flex-start;
  font-size: var(--text-sm); cursor: pointer;
}
.pa-case input { margin-top: 3px; flex: none; }
/* /!\ L'aide est DANS l'etiquette de la case, pas a cote : c'est au moment de
   cocher qu'elle compte, pas trois lignes plus bas. */
.pa-case em {
  display: block; margin-top: 2px; font-style: normal;
  font-size: var(--text-xs); color: var(--muted-foreground);
}
/* /!\ **CE QUI EST SENSIBLE SE VOIT AVANT D'ETRE COCHE.** L'argent et le
   raisonnement du modele partent decoches ; leur aide passe en teinte
   d'avertissement pour que la difference se lise au coup d'oeil, et pas
   seulement dans la case qui n'est pas remplie. */
.pa-case--sensible em { color: var(--warning-text); }
.pa-raison {
  margin: 4px 0 0; padding: 9px 11px; max-height: 160px; overflow-y: auto;
  background: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--text-sm); line-height: 1.55; color: var(--muted-foreground);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
/* OÙ PUBLIER dans la communauté (2026-09-17) : des puces, pas une liste
   déroulante (un `select` ne s'habille pas). */
.pa-lieux { border: 0; margin: 12px 0 0; padding: 0; min-width: 0; }
.pa-lieux legend { padding: 0; margin-bottom: 6px; font-size: var(--text-sm); color: var(--muted-foreground); }
.pa-lieux-choix { display: flex; flex-wrap: wrap; gap: 6px; }
.pa-lieu { position: relative; cursor: pointer; }
.pa-lieu input { position: absolute; opacity: 0; pointer-events: none; }
.pa-lieu span {
  display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 12px;
  border-radius: var(--radius-pill); background: var(--muted); border: 1px solid var(--border);
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.pa-lieu--groupe span::before {
  content: ""; width: 9px; height: 10px; flex: none;
  border: 1.5px solid currentColor; border-radius: 2px; border-top-width: 4px; opacity: .8;
}
.pa-lieu input:checked + span {
  background: var(--accent-ring); border-color: var(--accent-ring-strong); color: var(--foreground);
}
.pa-lieu input:focus-visible + span { outline: 2px solid var(--ring); outline-offset: 2px; }
/* LES LIEUX SONT REMONTES AU-DESSUS DES CASES (2026-09-20). La fenetre demande
   d'abord OU puis QUOI, et le salon est un « ou » ; la marge suit le
   demenagement — en derniere position, le bloc n'avait besoin que d'un blanc
   AU-DESSUS. /!\ Regle AJOUTEE, `.pa-lieux` n'est pas touchee : deux chantiers
   travaillaient cette feuille le meme jour. */
.pa-quoi .pa-lieux { margin: 0 0 16px; }

.pa-mot { display: block; margin-top: 14px; }
.pa-mot span {
  display: block; margin-bottom: 6px;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.pa-mot em { font-style: normal; font-size: var(--text-xs); }
.pa-mot textarea {
  width: 100%; box-sizing: border-box; resize: vertical;
  background: var(--muted); color: var(--foreground);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 9px 11px; font: inherit; font-size: var(--text-base); line-height: 1.5;
}
.pa-mot textarea:focus-visible {
  outline: none; border-color: var(--accent-ring-strong);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

/* LA FENETRE A DEUX COLONNES (24/09/2026, TVNI-241 point 5) : les cases a gauche,
   l'image a droite, EN DIRECT. Le premier temps (« ou ») garde la fenetre etroite ;
   le second l'elargit (`.partage--large`, pose par `partage.js`).
   /!\ Sous 860 px, les colonnes S'EMPILENT : l'apercu passe AU-DESSUS des cases,
   parce que c'est lui qu'on vient voir, et les gestes restent sous l'apercu. */
.partage.partage--large { width: min(1120px, 96vw); }
.pa-grille {
  display: grid; grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 24px; align-items: start;
}
.pa-choix { min-width: 0; }
.pa-bloc { border: 0; margin: 0 0 14px; padding: 0; min-width: 0; }
.pa-bloc legend {
  padding: 0; margin-bottom: 8px; font-size: var(--text-xs); font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted-foreground);
}
.pa-bloc-dit { margin: 2px 0 0; font-size: var(--text-xs); color: var(--muted-foreground); }
.pa-bloc-dit--alerte { color: var(--warning-text); }
/* Un graphique a cocher : son titre, et dessous ses indicateurs. */
.pa-case--graphique label {
  padding: 8px 10px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--muted);
}
.pa-case--graphique label:has(input:checked) { border-color: var(--accent-ring-strong); }
.pa-rendu { min-width: 0; position: sticky; top: 0; }
.pa-apercu-cadre {
  display: flex; flex-direction: column; gap: 10px; align-items: stretch;
  max-height: min(calc(var(--fenetre) * .62), 640px); overflow-y: auto; padding: 10px;
  background: var(--background); border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition: opacity var(--motion-fast) var(--ease-out);
}
.pa-apercu-cadre--calcul { opacity: .7; }
.pa-apercu-cadre .partage-apercu { border: 0; }
.pa-jointes { display: flex; flex-direction: column; gap: 10px; }
.pa-apercu-vide {
  margin: 0; padding: 36px 12px; text-align: center;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.pa-rendu .partage-gestes { margin: 12px 0 8px; }
/* 30/09/2026 : ce que le partage montre du trader, dit sous l'aperçu. */
.pa-trader-visible { margin: 8px 0 0; font-size: var(--text-xs); color: var(--muted-foreground); }
.pa-rendu .partage-gestes [data-partage-retour] { margin-right: auto; }
@media (max-width: 860px) {
  .partage.partage--large { width: 100vw; max-width: 100vw; max-height: var(--fenetre);
                            border-radius: 0; padding: 16px; }
  .pa-grille { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  .pa-rendu { position: static; order: -1; }
  .pa-apercu-cadre { max-height: calc(var(--fenetre) * .52); padding: 6px; }
}

/* TROIS FACONS DE CHOISIR UNE TEINTE, sur une ligne de reglage : la pastille (qui
   ouvre notre nuancier), le code hexadecimal, et les puces de la palette.

   /!\ **TOUT EST A NOUS, et c'est la raison d'etre de ce bloc** (fondateur, 2026-09-14,
   capture a l'appui) : `input type="color"` et `select` ouvrent des fenetres dessinees
   par le NAVIGATEUR -- fond gris, champs R/G/B en anglais, aucune de nos teintes. Aucune
   feuille de style ne les atteint. On les a donc remplaces, pas rhabilles. */
.choix-teinte { display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
                position: relative; }
.choix-teinte-haut { display: flex; gap: 8px; align-items: center; }
/* La pastille qui OUVRE le nuancier : elle montre la teinte en cours. */
.choix-ouvre {
  width: 64px; height: 34px; padding: 0; cursor: pointer;
  background: var(--teinte); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
.choix-ouvre:hover, .choix-ouvre[aria-expanded="true"] {
  box-shadow: 0 0 0 2px var(--background), 0 0 0 3px var(--ring);
}
.choix-hex {
  width: 92px; padding: 7px 9px; text-align: center; text-transform: lowercase;
  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--foreground);
  background-color: rgba(255,255,255,0.035);
  border: 1px solid var(--border-strong); border-radius: 8px; outline: none;
}
.choix-hex:focus { border-color: var(--ring); }
/* /!\ Le code INCOMPLET n'est pas une faute : on le signale sans rien effacer ni
   corriger pendant la frappe. « #22 » est une couleur en cours d'ecriture. */
.choix-hex--faux { border-color: var(--warning); }

/* LE NUANCIER -- le notre. Il flotte sous la pastille, comme les autres fenetres de
   l'application. */
.nuancier {
  position: absolute; top: 42px; right: 0; z-index: 30; width: 268px;
  display: flex; flex-direction: column; gap: 10px; padding: 12px;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: 0 18px 40px rgba(0,0,0,0.55);
}
/* Le carre saturation / luminosite : la teinte pure en fond, du blanc en travers, du
   noir vers le bas. Trois couches, aucune image. */
.nuancier-sv {
  position: relative; height: 132px; border-radius: var(--radius-md);
  background: var(--fond); cursor: crosshair; touch-action: none; overflow: hidden;
}
.nuancier-sv-blanc, .nuancier-sv-noir { position: absolute; inset: 0; }
.nuancier-sv-blanc { background: linear-gradient(to right, #fff, rgba(255,255,255,0)); }
.nuancier-sv-noir  { background: linear-gradient(to top, #000, rgba(0,0,0,0)); }
.nuancier-viseur {
  position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px;
  border: 2px solid #fff; border-radius: 50%; background: var(--teinte);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.5); pointer-events: none;
}
/* Le curseur des TEINTES : l'arc-en-ciel, dans un `range` habille. */
.nuancier-teintes {
  -webkit-appearance: none; appearance: none; width: 100%; height: 12px;
  border-radius: 6px; cursor: pointer; outline: none;
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
                              #00f 67%, #f0f 83%, #f00 100%);
}
.nuancier-teintes::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 18px; border-radius: 4px;
  background: var(--foreground); border: 1px solid rgba(0,0,0,0.6); cursor: pointer;
}
.nuancier-teintes::-moz-range-thumb {
  width: 14px; height: 18px; border-radius: 4px;
  background: var(--foreground); border: 1px solid rgba(0,0,0,0.6); cursor: pointer;
}

/* LE TYPE DE TRAIT : trois boutons qui DESSINENT le trait.
   /!\ Dessine plutot que nomme : « pointilles » se comprend mieux vu qu'ecrit -- et
   une liste deroulante ouvrirait, la aussi, une fenetre du navigateur. */
.choix-trait { display: flex; gap: 4px; }
.trait-choix {
  width: 46px; height: 26px; padding: 0 4px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted-foreground);
  background-color: rgba(255,255,255,0.035);
  border: 1px solid var(--border-strong); border-radius: 8px;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.trait-choix svg { width: 100%; height: 8px; }
.trait-choix:hover { color: var(--foreground); }
.trait-choix[aria-pressed="true"] {
  color: var(--accent); border-color: var(--accent);
  background-color: rgba(180,168,255,0.10);
}

/* L'OPACITE, a cote de la teinte : un curseur et son chiffre. */
.choix-opacite { display: flex; align-items: center; gap: 8px; }
.choix-opacite input[type=range] { width: 150px; accent-color: var(--accent); cursor: pointer; }
.choix-opacite-v {
  min-width: 42px; text-align: right;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted-foreground);
}
.palette-couleurs { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-start; }
.puce-couleur {
  width: 22px; height: 22px; padding: 0; cursor: pointer;
  background: var(--puce); border: 1px solid rgba(255,255,255,0.18);
  border-radius: 6px; transition: transform var(--motion-fast) var(--ease-out),
                                  box-shadow var(--motion-fast) var(--ease-out);
}
.puce-couleur:hover { transform: scale(1.12); }
/* /!\ `puce-couleur` et NON `pastille` : la charte porte deja une `.pastille`
   (l'etiquette cliquable des filtres), declaree 900 lignes plus bas -- elle gagnait
   donc toute egalite de specificite et repeignait chaque puce en violet a 6 %. Vu a
   l'ecran, et tranche par le style CALCULE : la variable etait juste, le fond ne
   l'etait pas.
   La teinte EN COURS se marque : sans ce repere, le client ne sait plus laquelle il a
   choisie des qu'il en essaie une seconde. */
.puce-couleur--choisie {
  border-color: var(--foreground);
  box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--foreground);
}

/* /!\ Le selecteur natif est minuscule et colle au bord : on lui donne la taille d'un
   geste, pas celle d'une case a cocher. */
.choix-couleur {
  width: 64px; height: 34px; padding: 3px; cursor: pointer;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
}
.choix-couleur::-webkit-color-swatch-wrapper { padding: 0; }
.choix-couleur::-webkit-color-swatch { border: none; border-radius: 4px; }
.choix-couleur::-moz-color-swatch { border: none; border-radius: 4px; }

/* ============================================================
   `.depli` : UN BLOC QU'ON OUVRE ET QU'ON FERME (2026-09-15)
   ------------------------------------------------------------
   Une barre cliquable, et ce qu'elle cache en dessous. `<details>` natif : il s'ouvre,
   se ferme et se prend au clavier sans une ligne de script.

   /!\ Il est NÉ sur le tableau de bord (« Le fil de ses décisions », sous chaque
   trader), et le détail d'un trade a demandé le même le jour suivant. Il vit donc dans
   la charte plutôt qu'en deux exemplaires : deux replis recopiés se ressemblent le jour
   où on les écrit, et plus jamais ensuite.

   /!\ Le triangle est en `::before` sur le `summary`, et le marqueur natif est retiré :
   Safari le dessine ailleurs que Firefox, et deux flèches valent mieux qu'aucune.
   ============================================================ */
.depli { border-top: 1px solid var(--border); }
.depli > summary {
  list-style: none; cursor: pointer; padding: 10px 2px;
  font-size: var(--text-body-sm); color: var(--accent);
}
.depli > summary::-webkit-details-marker { display: none; }
.depli > summary::before { content: "\25B8\00a0\00a0"; }
.depli[open] > summary::before { content: "\25BE\00a0\00a0"; }
.depli-dedans { padding: 2px 0 10px; }

/* ⚠️ **LA FRISE N'A PLUS DE STYLES À ELLE** (2026-09-15). Elle en avait deux vies :
   une grille `auto-fit` de cartes, puis des rangées flex écrites pour l'occasion. Le
   fondateur a tranché la troisième : « je trouve ça trop gros, inspire-toi de ce qu'on
   a fait sur le journal ».

   Le journal ne s'est rien inventé : il pose `.rangees` / `.rangee` + `.rangee-rang`
   (l'heure), `.rangee-corps` et `.rangee-argent`, la grille à trois colonnes de la
   charte, avec `.jr-heure` pour la chasse fixe. La frise dit exactement la même chose
   -- quand, quoi, combien -- donc elle prend exactement les mêmes classes.

   ⚠️ **NE PAS REFABRIQUER `.frise-*`.** Deux listes de la même chose finissent par ne
   plus se ressembler : c'est ce qui s'était produit ici, et l'écart se voyait sur la
   même page à un écran d'intervalle. `.jr-*` reste le préfixe du JOURNAL, et il est
   réutilisé tel quel, pas recopié.

   ⚠️ **PLUS DE TEINTE VERTE OU ROUGE SUR LA RANGÉE** : le journal ne colore que le
   MONTANT (`montant-gain` / `montant-perte`). Un fond coloré sur toute la largeur est
   ce qui faisait « trop gros ». */

/* Le graphique en BARRES EMPILEES de « Mes analyses » (2026-08-18).
   ============================================================
   Maquette « A », choisie par le fondateur parmi trois (empilees, groupees,
   divergentes). Une barre par periode : sa HAUTEUR dit combien le trader a analyse,
   ses trois segments disent ce qu'il en a fait.

   /!\ Pourquoi les empilees : l'abstention est la decision LA PLUS FREQUENTE (six
   analyses sur dix sur les donnees reelles du jour). Empilee, elle garde son poids
   visuel et le volume analyse se lit d'un coup ; groupee, elle devient une barre grise
   parmi trois et le total disparait.

   /!\ Chaque barre est un LIEN, comme sa rangee : on clique ce qu'on regarde. C'est
   aussi pour ca qu'elle porte `data-bulle` -- le meme mecanisme d'infobulle que les
   rangees de trades, pas un second. */
/* ⚠️ UNE BARRE A UNE LARGEUR BORNÉE (2026-09-22) : `flex: 1 1 0` étirait une période
   UNIQUE sur toute la largeur du cadre — un bloc vert/rouge/gris de 1 100 px qui ne se
   lisait plus comme une barre. Au plus 56 px, et les barres se RÉPARTISSENT à
   intervalles réguliers quand elles sont peu nombreuses ; nombreuses, elles se
   resserrent comme avant. */
.barres {
  display: flex; align-items: flex-end; justify-content: space-around; gap: 8px;
  height: 160px; padding: 0 2px; margin-bottom: 26px;
}
.barre {
  flex: 0 1 56px; min-width: 0; max-width: 56px; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  position: relative; text-decoration: none;
  border-radius: 4px 4px 0 0;
  transition: opacity var(--motion-fast) var(--ease-out);
}
/* /!\ L'ensemble palit et la barre visee reste pleine : teinter la seule barre survolee
   ne se voit pas sur une colonne de 6 px de large. */
.barres:hover .barre { opacity: .45; }
.barres .barre:hover { opacity: 1; }
.barre-pile {
  width: 100%; display: flex; flex-direction: column; justify-content: flex-end;
  border-radius: 3px 3px 0 0; overflow: hidden;
  /* Une periode VIDE garde sa colonne : un trait suffit a dire qu'elle existe et qu'il
     ne s'y est rien passe. La faire disparaitre resserrerait les autres. */
  min-height: 2px;
}
.barre-seg { display: block; width: 100%; }
.barre-seg--achat { background: var(--success); }
.barre-seg--vente { background: var(--error); }
.barre-seg--abst  { background: rgba(255,255,255,0.13); }
/* A5b : un ordre REFUSÉ n'est pas une abstention ; la teinte d'avertissement de la
   frise (le refus y est déjà jaune), jamais le gris des abstentions. */
.barre-seg--refus { background: var(--warning); }
.barre-quand {
  position: absolute; bottom: -20px; left: 0; right: 0; text-align: center;
  font-size: var(--text-2xs); color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Trois barres au plus : il y a de la place autour, la date s'écrit en entier
   (« septembre 2026 » se coupait en « septembr… » sous une barre de 56 px). */
.barre:first-child:nth-last-child(-n+3) .barre-quand,
.barre:first-child:nth-last-child(-n+3) ~ .barre .barre-quand { left: -34px; right: -34px; }
.barres-axe {
  display: flex; justify-content: space-between;
  font-size: var(--text-xs); color: var(--muted-foreground); margin-bottom: 6px;
}
.barres-legende {
  display: flex; gap: 18px; align-items: center; flex-wrap: wrap;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
/* /!\ « puce » et non « pastille » : le second appartient aux FILTRES de « Mes trades »
   (le mot du fondateur, valide sur maquette). Les deux ont coexiste une heure le
   2026-08-18, et les pastilles de filtre heritaient des 10 px de ce point de legende --
   elles se chevauchaient toutes. Deux objets differents ne partagent pas un nom. */
.puce {
  display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  margin-right: 6px; vertical-align: -1px;
}
.puce--achat { background: var(--success); }
.puce--vente { background: var(--error); }
.puce--abst  { background: rgba(255,255,255,0.16); }
.puce--refus { background: var(--warning); }
/* Sous 640 px les etiquettes de date ne tiennent plus sous des colonnes de 8 px : on les
   retire plutot que de les laisser se chevaucher. L'infobulle les porte toujours. */
@media (max-width: 640px) {
  .barres { gap: 4px; }
  .barre-quand { display: none; }
  /* …sauf quand il y a de la place : trois barres au plus. */
  .barre:first-child:nth-last-child(-n+3) .barre-quand,
  .barre:first-child:nth-last-child(-n+3) ~ .barre .barre-quand { display: block; }
}

/* La PAGINATION d'une longue liste (2026-08-18).
   /!\ Elle porte son ETAT en toutes lettres (« analyses 201 a 400 sur 1 894 »), et
   pas seulement des fleches. Un « suivant » seul ne dit ni ou l'on est, ni combien
   il reste : le client ne sait pas s'il a tout vu, ce qui est exactement le defaut
   que la pagination vient corriger. */
.pagination {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  flex-wrap: wrap;
  margin-top: 18px; padding-top: 16px;
  border-top: 1px solid var(--border);
}
.pagination-etat { font-size: var(--text-base); color: var(--muted-foreground); }
.pagination-etat strong { color: var(--foreground); font-weight: 600; }

/* La RANGEE D'AJOUT : ce qui ouvre le formulaire d'un element de liste
   (2026-08-18). Remarque du fondateur sur « Ajouter une regle » : « je trouve le
   bouton pas du tout esthetique ».

   /!\ Il avait raison, et pas seulement sur le dessin : un bouton PLEIN, de la
   couleur d'accent, pose au-dessus de la liste, criait plus fort que les regles
   elles-memes. C'est l'action la plus rare de l'ecran (on ecrit une regle une
   fois, on relit la liste souvent) et elle portait l'accent le plus vif.

   /!\ Il descend donc EN BOUT DE LISTE, en pointille : c'est la ligne suivante
   qu'on n'a pas encore ecrite. Le geste se lit sans qu'aucune couleur ne le
   reclame, et il est la ou l'oeil finit sa lecture. */
.rangee-ajout {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; margin-top: 10px; padding: 13px 16px;
  appearance: none; background: none; cursor: pointer;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  font-family: inherit; font-size: var(--text-base); font-weight: 500;
  color: var(--muted-foreground);
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out),
              background var(--motion-fast) var(--ease-out);
}
.rangee-ajout:hover, .rangee-ajout:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-wash);
}

/* ============================================================
   Les GESTES d'un en-tete, et leur menu (2026-08-18)
   ============================================================
   Demande du fondateur : arreter un trader, le dupliquer, voir ses trades « en un
   clic », « dans l'en-tete, pour que ce soit user-friendly ». Une page « Gerer »
   entiere a disparu au profit de cette rangee.

   /!\ `align-items: flex-start` : l'en-tete peut tenir sur deux lignes de titre,
   les gestes restent en haut, alignes sur la premiere.

   /!\ `flex-wrap` : cinq boutons plus un menu ne tiennent pas a cote d'un titre sur
   un ecran etroit. Ils passent a la ligne plutot que de comprimer le titre. */
.e-gestes {
  /* /!\ `center` depuis le 2026-09-11 : la rangee ne porte plus que des boutons
     de meme hauteur. La CLE du plugin y est entree (un bloc, plus haut qu'un
     bouton), et avec `flex-start` les boutons se collaient a son bord SUPERIEUR
     au lieu de s'aligner sur son milieu. Vu a l'ecran. */
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 8px; flex-shrink: 0;
}
/* LES TROIS ENTREES D'UN TRADER (29/09/2026 ; 30/09/2026, fondateur : « au meme format
   que les autres ») : trois `cle-icone`, comme la cle et le plugin. La page ouverte
   porte la couleur d'accent. */
.cle-icone.e-entree { text-decoration: none; }
.cle-icone.e-entree--ici { border-color: var(--accent-ring-strong); color: var(--accent);
  background: var(--accent-wash); }
/* La cle est un bloc d'ecran ramene dans une barre d'outils : elle y perd sa
   hauteur de carte, pas ses trois gestes. */
.e-gestes .trader-cle { padding: 5px 10px; margin: 0; }
.e-gestes .trader-cle .spec-label { font-size: var(--text-2xs); }

/* L'ETAT, dans le titre. C'est la premiere question qu'on se pose en ouvrant la
   fiche d'un trader — est-ce qu'il tourne ? — et elle n'etait ecrite nulle part.
   /!\ La pastille est GRISE par defaut et verte seulement si le trader est actif :
   un point colore qui ne dit rien ne sert a rien. */
.titre-avec-etat { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.etat-pastille {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 13px 5px 11px;
  background: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 400;
  color: var(--muted-foreground);
}
.etat-pastille b { color: var(--foreground); font-weight: 600; }
.etat-pastille > span + span::before {
  content: "·"; margin-right: 8px; color: var(--muted-foreground);
}
.etat-point {
  width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%;
  background: var(--disabled-foreground);
}
.etat-pastille--on .etat-point { background: var(--success); }
/* LES ÉTATS D'UN CONTRAT (programme, 11/10/2026) : en ATTENTE (à signer, en
   préavis) en ambre, ROMPU (faute grave) en rouge ; gris reste « terminé », vert
   « en vigueur » (`--on`). La couleur n'est jamais seule : le mot est dans la
   pastille. `--vive` teinte aussi le liseré, pour les pages qui la mettent en
   avant (Mon programme, le contrat à signer). */
.etat-pastille--attente .etat-point { background: var(--warning); }
.etat-pastille--rompu .etat-point { background: var(--error); }
.etat-pastille--vive.etat-pastille--on { border-color: color-mix(in srgb, var(--success) 38%, transparent); }
.etat-pastille--vive.etat-pastille--attente { border-color: color-mix(in srgb, var(--warning) 40%, transparent); }
.etat-pastille--vive.etat-pastille--rompu { border-color: color-mix(in srgb, var(--error) 40%, transparent); }
/* AU TELEPHONE, la pastille passe a la ligne ENTRE ses mentions, jamais dedans
   (02/10/2026) : « Client depuis le / 02/10/2026 » se cassait en deux, coince a
   cote de « Français ». */
@media (max-width: 720px) {
  .etat-pastille { flex-wrap: wrap; row-gap: 2px; column-gap: 12px; max-width: 100%; }
  .etat-pastille > * { white-space: nowrap; }
  /* Le point median ouvrirait la seconde ligne : l'ecart suffit a separer. */
  .etat-pastille > span + span::before { content: none; }
}

/* ══ LE BADGE DE VERSION, DEVENU LE DECLENCHEUR DU MENU (2026-09-13) ═════════
   Demande du fondateur : la liste des versions s'ouvre en cliquant le badge a gauche,
   et la pastille de droite disparait — elle disait la meme chose au meme moment.

   /!\ **IL DOIT RESSEMBLER AU RESTE DU BADGE, PAS A UN BOUTON.** Ce qui l'entoure
   (« Actif », « ES ») est du texte ; un bouton dessine au milieu ferait lire le badge
   comme une barre d'outils. Il ne se signale qu'au survol et a la mise au point. */
.lentille--badge { display: inline-flex; }
.lentille-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 5px; margin: -1px -2px; border-radius: var(--radius-sm);
  color: var(--muted-foreground); cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.lentille-badge:hover { background: rgba(255, 255, 255, .07); color: var(--foreground); }
.lentille-badge:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
/* /!\ Le chevron est DISCRET au repos : c'est la seule chose qui distingue ce
   morceau du texte voisin, et un chevron plein ferait un bouton. */
.lentille-badge > .chev { font-size: .7em; opacity: .55; }
.lentille-badge:hover > .chev { opacity: 1; }
/* L'explication, DANS le panneau et non dans une bulle au survol : on la lit au
   moment ou l'on choisit. /!\ La bulle s'ouvrait au survol, le menu au clic, et la
   souris est forcement sur le badge au moment du clic : elle se posait par-dessus la
   liste des versions. */
.v-quoi {
  margin: 2px 10px 8px; max-width: 34ch;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
  line-height: var(--leading-relaxed);
}
/* Sous la lentille, le badge prend la couleur de l'avertissement : on ne regarde pas
   la version en service, et ca doit se voir sans lire. */
.lentille-badge--archive { color: var(--warning-text); }
/* /!\ Le panneau s'ouvre SOUS le badge, aligne a gauche : ancre a droite comme celui
   de la rangee de gestes, il sortait de l'ecran a gauche du titre. */
.lentille--badge > .cmd-liste { left: 0; right: auto; }
/* /!\ Le separateur « · » du badge ne doit PAS se poser avant le menu : il est deja
   rendu par la regle generique `span + span`, et le `<details>` n'est pas un `span`.
   On le remet a la main pour que « Actif · v1 · ES » garde ses deux points. */
.etat-pastille > details.lentille::before {
  content: "·"; margin-right: 8px; color: var(--muted-foreground);
}

/* Le MENU des gestes rares. Un `<details>` : il s'ouvre, se ferme, se prend au
   clavier et s'annonce, sans une ligne de script. `menu.js` ne fait que le refermer
   quand on clique ailleurs — ce que `<details>` ne sait pas faire seul.

   /!\ Ce qui vit ici n'y est pas par manque de place : ce sont les gestes qui NE SE
   DEFONT PAS. Un bouton « Supprimer » aligne a cote de « Dupliquer » est un accident
   qui attend son heure. */
.menu { position: relative; }
/* La fleche que le navigateur ajoute a tout `<summary>` n'a pas sa place sur un
   bouton d'icone : elle doublerait les trois points. */
.menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::marker { content: ""; }
.menu-panneau {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  min-width: 220px;
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px;
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 44px -16px rgba(0,0,0,0.7);
}
.menu-item {
  appearance: none; background: none; border: 0; cursor: pointer;
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 9px 11px;
  font-family: inherit; font-size: var(--text-base); font-weight: 500; text-align: left;
  color: var(--foreground);
  border-radius: var(--radius-md);
  transition: background var(--motion-fast) var(--ease-out);
}
.menu-item:hover, .menu-item:focus-visible { background: var(--muted); }
/* /!\ SANS CETTE LIGNE, `hidden` NE CACHE RIEN ICI. Le `display: flex` juste
   au-dessus est une regle d'AUTEUR, et elle bat le `display: none` que l'agent
   utilisateur applique a `[hidden]`. Piege deja paye dans ce depot. C'est ce
   qui permet a `rappel-guide.js` de ne laisser qu'une entree d'onglet dans le
   menu du point d'interrogation ; ne pas la retirer en croyant nettoyer. */
.menu-item[hidden] { display: none; }
.menu-item--danger { color: var(--error); }
/* Une ligne de menu qui n'est plus un GESTE mais un ETAT (« demande, en
   attente », vague 4 C) : meme place, meme hauteur, mais elle ne repond pas au
   survol -- un element qui s'eclaire sous la souris promet un clic. */
.menu-item--inerte { color: var(--muted-foreground); cursor: default; }
.menu-item--inerte:hover { background: none; color: var(--muted-foreground); }
/* /!\ Le trait n'est pas decoratif : il separe ce qui se defait de ce qui ne se
   defait pas. Sous lui, arreter et supprimer. */
.menu-trait { height: 1px; background: var(--border); margin: 5px 4px; }
a.menu-item { text-decoration: none; }
/* L'HOTE d'un menu ouvert (TVNI-280, 26/09/2026) : `menu.js` pose cette classe
   sur les ancetres positionnes du menu, le temps qu'il est ouvert. Une carte
   atténuée (`opacity`) forme un calque à part : sans elle, le panneau passait sous
   la carte suivante, à demi transparent. Une seule règle pour tous les menus : pas
   de rustine écran par écran. */
.menu-hote { z-index: 5; opacity: 1 !important; }
/* Le titre d'un GROUPE d'entrees, quand le menu en porte deux sortes (le rappel
   du guide, dans la barre du haut : les ecrans a rejouer, puis le guide ecrit).

   /!\ UN NOM NEUF, et c'est delibere. La tentation etait de reprendre
   `.menu-item` en le neutralisant, ou l'un des `.popover-label` d'a cote : une
   classe partagee par deux objets differents a deja coute deux fois ici, avec
   des boutons ecrases sans qu'aucun test ne le voie. Ce titre n'est pas
   cliquable, il n'a donc rien a partager avec une entree. */
.menu-titre {
  margin: 2px 4px 4px; padding: 0 7px;
  font-size: var(--text-xs); font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--muted-foreground);
}

/* ============================================================
   Les ONGLETS : une page longue coupee en vues (2026-08-18)
   ============================================================
   Demande du fondateur : « je trouve la page Mes Trades tres lourde avec les
   trois parties ». Le sommaire FAISAIT DEFILER une page de dix mille pixels ;
   les onglets n'en affichent qu'un tiers a la fois.

   /!\ Ce n'est PAS un composant de plus : c'est `.seg`, le selecteur segmente
   deja en place partout (« Tous 3 · Actifs 2 · Desactives 1 » sur la liste des
   traders), avec le seul ajout dont un onglet a besoin. Le fondateur l'a tranche
   le 2026-08-18 en montrant ce selecteur : « mets juste les onglets sur ce
   gabarit-la ». Un premier jet avait duplique `.seg` sous le nom `.onglets`, avec
   ses propres teintes — deux composants pour un geste, qui auraient diverge.

   Ce que le modificateur ajoute, et rien de plus :
     - il ne deborde pas (neuf onglets sur un ecran etroit defilent a plat) ;
     - il peut suivre le lecteur (`--collant`), pour une page dont un onglet fait
       dix mille pixels.

   /!\ Deux facons de dire « celui-la », et `.seg` les porte deja toutes deux :
   `aria-current="page"` quand l'onglet est une ADRESSE (« Mes trades », ou chaque
   onglet est une page), `aria-selected` quand il bascule SANS quitter la page (les
   parties des reglages d'un trader, qui sont UN formulaire et ne peuvent pas se
   recharger sans perdre ce qui est saisi).

   /!\ `type="button"` est obligatoire sur la seconde forme : un bouton dans un
   formulaire l'ENVOIE par defaut, et cliquer un onglet enregistrerait le trader. */
.seg--onglets {
  /* /!\ `inline-flex`, comme `.seg` : la barre EPOUSE ses onglets au lieu de
     s'etirer sur toute la largeur. C'est ce que le fondateur a montre en pointant
     le selecteur « Tous 3 · Actifs 2 · Desactives 1 ». Etiree, une barre de trois
     onglets laissait huit cents points de pilule vide a droite.
     `max-width` + `overflow-x` gardent le cas inverse : neuf onglets sur un ecran
     etroit defilent a plat plutot que de deborder. */
  display: inline-flex;
  vertical-align: top;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  margin: 0 0 18px;
}
.seg--onglets::-webkit-scrollbar { height: 0; }
.seg--onglets > * { flex: 0 0 auto; }
/* /!\ **UN ONGLET QUI PORTE UNE ICONE DEVIENT UNE RANGEE** (2026-09-14, demande du
   fondateur). Sans contexte flex, le SVG se pose sur la ligne de base du texte et
   descend sous lui ; et `gap` ne separe que des ELEMENTS, d'ou le `<span>` autour du
   libelle dans le gabarit — un noeud de texte nu n'aurait rien a espacer.
   /!\ La regle vise `:has(svg)` : les barres `.seg` SANS icone — les filtres
   « Tous / Actifs / Desactives », les etats des consignes — gardent exactement le
   rendu qu'elles avaient. Une barre de filtres n'est pas une barre d'onglets. */
.seg--onglets > *:has(> svg) { display: inline-flex; align-items: center; }
/* L'icone accompagne le mot, elle ne se lit pas a sa place : plus pale au repos, au
   ton du libelle quand l'onglet est choisi ou survole. */
/* /!\ Marge, et non `gap` : cf. la note de `.t-onglets a > svg`. Le libelle reste un
   noeud de texte NU, que les releves de tests savent lire. */
.seg--onglets > * > svg {
  flex: 0 0 auto; margin-right: 7px; opacity: .62; transition: opacity .14s;
}
.seg--onglets > *:hover > svg,
.seg--onglets > *[aria-selected="true"] > svg,
.seg--onglets > *[aria-current="page"] > svg { opacity: 1; }
/* /!\ `top` vaut la hauteur de la barre d'application (56 px), qui est elle-meme
   collante. Sans cela la barre d'onglets se glisserait DESSOUS et disparaitrait. */
.seg--collant { position: sticky; top: 56px; z-index: 40; }

/* ============================================================
   Le PERIMETRE : la barre de filtres qui se replie (2026-08-18)
   ============================================================
   Meme demande. Les cinq filtres qui gouvernent toute la page occupaient deux
   rangees en permanence, sur chacun des trois onglets.

   Elle se replie sur UNE ligne, et cette ligne continue de DIRE le perimetre.

   /!\ Le RESUME n'est pas decoratif, il est la condition du repli. Replier une
   barre de filtres sans rien laisser voir transforme tous les chiffres du dessous
   en chiffres sans leur cadre -- « +412 $ » sur quoi, sur quelle periode, sur quel
   trader ? C'est precisement ce que cette page combat depuis le 2026-08-12. Le
   repli cache les COMMANDES, jamais l'etat.

   /!\ L'en-tete est un vrai `<button>`, pas un `<div>` cliquable : il se prend au
   clavier, il s'annonce, et `aria-expanded` dit dans quel etat il est.

   /!\ Elle n'est PAS collante, a l'inverse des onglets. Deux barres collantes
   l'une sur l'autre mangent 110 px de hauteur en permanence -- soit ce qu'on
   vient de gagner en repliant. C'est la barre d'onglets qui suit le lecteur ;
   celle-ci se consulte quand on la cherche. */
.perimetre { margin: 0 0 12px; }
/* La barre et sa remise a zero sur la meme ligne. /!\ Le lien est DEHORS du bouton de
   repli : un `<a>` dans un `<button>` est du HTML invalide, et le clic partirait au
   repli plutot qu'au lien. */
/* /!\ FINDING C, ronde 2 de revue (2026-08-20) : `flex-wrap` n'attendait plus le
   passage sous 640px pour être nécessaire. Depuis que la rangée porte aussi le
   déclencheur des vues et, quand une vue ouverte est retouchée, trois actions en
   clair, leur largeur cumulée peut dépasser celle de la rangée sur un écran large
   aussi. Sans un wrap, le seul enfant à `min-width:0` (`.perimetre-tete`, juste en
   dessous) absorbait tout le manque -- jusqu'à l'écraser à zéro. Le résumé du
   périmètre EST la condition du repli (cf. le commentaire du gabarit) : le perdre
   revient à refermer la barre sans rien laisser voir, au moment où le client vient
   justement de la toucher. */
.perimetre-rangee { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* /!\ `flex: 1 1 240px` et pas la `width: 100%` de la regle de base (2026-08-21,
   demande du fondateur : « les vues sur la meme ligne que le filtre pour economiser
   de la hauteur »). Une `width: 100%` sur un enfant de flex lui donne une taille de
   base egale a TOUTE la rangee : le bouton de repli reclamait donc la ligne entiere,
   et le declencheur des vues -- pose avant lui depuis le 2026-08-20 -- se retrouvait
   REJETE SUR SA PROPRE LIGNE par le `flex-wrap` ci-dessus. Ce n'etait pas une mise en
   page choisie, c'etait un effet de bord : deux rangees la ou une suffit, sur chacun
   des trois onglets.

   /!\ La base reste 240px et le plancher aussi : le resume du perimetre garde de quoi
   se lire (cf. le test `test_le_perimetre_tete_garde_un_plancher_de_largeur`), et
   quand la rangee « vue modifiee » porte ses trois actions, c'est le `flex-wrap` qui
   recoupe la ligne -- jamais l'ecrasement du resume. */
.perimetre-rangee .perimetre-tete { flex: 1 1 240px; min-width: 240px; }
.perimetre-raz {
  /* /!\ C'est un `<button>` dans un `<form>` depuis 2026-08-20 (tache 9), plus un
     `<a>` : `appearance`, `cursor` et `font-family` remettent a plat ce qu'un bouton
     natif imposerait sinon (curseur fleche, police systeme), que les regles
     suivantes -- pensees pour un lien -- ne couvraient pas. */
  appearance: none; cursor: pointer; font-family: inherit;
  flex: 0 0 auto; padding: 8px 14px;
  /* Coin à `--radius-md` comme `.cmd-choix` : voir sa clause. */
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--muted-foreground);
  font-size: var(--text-sm); white-space: nowrap; text-decoration: none;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.perimetre-raz:hover { color: var(--foreground); border-color: var(--border-strong); }
@media (max-width: 640px) {
  .perimetre-rangee { flex-wrap: wrap; }
  .perimetre-raz { flex: 1 1 100%; text-align: center; }
}
.perimetre-tete {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 14px;
  background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  cursor: pointer; text-align: left;
  font: inherit; color: var(--foreground);
  transition: border-color var(--motion-fast) var(--ease-out);
}
.perimetre-tete:hover { border-color: var(--border-strong); }
.perimetre-tete .chev {
  flex: 0 0 auto; color: var(--muted-foreground);
  transition: transform var(--motion-base) var(--ease-out);
}
.perimetre[data-replie="non"] .perimetre-tete .chev { transform: rotate(180deg); }
.perimetre-l {
  flex: 0 0 auto;
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.perimetre-resume {
  flex: 1 1 auto; min-width: 0;
  font-size: var(--text-base); color: var(--foreground);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Les faits du resume, separes par un point median pose en CSS : le gabarit ecrit
   des `<span>`, jamais des separateurs -- un fait absent ne doit pas laisser un
   « · » orphelin. */
.perimetre-resume > span + span::before {
  content: "\00b7"; margin: 0 7px; color: var(--muted-foreground);
}

/* ============================================================
   L'ACCES RAPIDE : Trader et Periode, hors des pastilles (2026-08-21)
   ============================================================
   Demande du fondateur : « les filtres Trader et Periode, tu les laisses en acces
   rapide, pas besoin de mettre un filtre, pour pouvoir switcher rapidement ».

   /!\ Les periodes sont A PLAT, pas dans un menu, et c'est tout l'objet de la
   demande. Un raccourci qu'il faut deplier avant de cliquer coute deux gestes,
   soit exactement ce qu'on venait supprimer. Le trader garde son menu : les noms
   sont longs, et leur nombre n'est pas borne.

   /!\ Ces deux rangees sont DEHORS de `.perimetre` : elles ne se replient pas avec
   lui. Une commande d'acces rapide qui disparait au repli n'est plus rapide. */
.rapide {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 8px;
}
.rapide-l {
  flex: 0 0 auto;
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
/* Le libelle d'une seconde commande sur la meme rangee (la version) respire un peu
   plus a gauche, sinon il se colle au selecteur qui le precede. */
.rapide-l--suite { margin-left: 10px; }
.rapide-pastilles { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Le declencheur d'un menu d'acces rapide (trader, version). */
.rapide-choix {
  list-style: none;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px;
  background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  font-size: var(--text-sm); color: var(--foreground);
  cursor: pointer; white-space: nowrap;
}
.rapide-choix::-webkit-details-marker { display: none; }
.rapide-choix::marker { content: ""; }
.rapide-choix:hover { border-color: var(--border-strong); }
.rapide-choix .chev { font-size: var(--text-2xs); color: var(--muted-foreground); }
.rapide-liste { left: 0; right: auto; min-width: 240px; }
.rapide-liste .menu-item[aria-current="page"] {
  background: rgba(180,168,255,0.14); font-weight: 600;
}
/* L'effectif, pousse a droite de la ligne : sans lui on choisit a l'aveugle -- meme
   raison que sur la bascule Reel/Rejeu et sur les onglets. */
.rapide-n {
  margin-left: auto; padding-left: 12px;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted-foreground);
}
/* Un raccourci de periode. */
.rapide-p {
  padding: 5px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--card); color: var(--muted-foreground);
  font-size: var(--text-sm); white-space: nowrap; text-decoration: none;
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.rapide-p:hover { color: var(--foreground); border-color: var(--border-strong); }
.rapide-p[aria-current="page"] {
  background: rgba(180,168,255,0.14); border-color: rgba(180,168,255,0.5);
  color: var(--foreground); font-weight: 600;
}
/* /!\ « Personnalise » est un `<span>`, pas un lien : il n'a pas de bornes a lui,
   ce sont les deux dates saisies. Le trait discontinu le dit avant le clic. */
.rapide-p--perso { border-style: dashed; cursor: default; }
.rapide-perso {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-left: 6px; font-size: var(--text-sm); color: var(--muted-foreground);
}
.rapide-perso input[type="date"],
.rapide-perso input.champ-maison-texte {
  font-family: var(--font-mono); font-size: var(--text-sm); padding: 4px 8px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--card); color: var(--foreground);
}
@media (max-width: 640px) {
  .rapide-l { flex: 1 1 100%; }
}

/* ============================================================
   LA BARRE DE COMMANDES : une seule ligne (2026-08-21)
   ============================================================
   Demande du fondateur, apres avoir juge le haut de page illisible : quatre rangees
   de commandes s'y empilaient sans hierarchie (le bandeau repliable et son corps, la
   rangee Trader, la rangee Periode), plus la bascule Reel/Rejeu seule dans son coin.

   Elle porte, DANS CET ORDRE : les vues, les QUATRE FIXES (execution, trader, compte,
   periode), les deux boutons d'ajout, puis les filtres et tris ajoutes.

   /!\ LES DEUX BOUTONS D'AJOUT NE BOUGENT JAMAIS. C'est la seule raison qui a fait
   preferer cet agencement au « tout au fil de la barre » : un bouton qui se deplace a
   chaque ajout se cherche des yeux a chaque fois, alors qu'un bouton immobile finit
   par se cliquer sans regarder.

   /!\ L'ordre est tenu par `order` et non par le gabarit. Les pastilles viennent d'un
   partiel qui rend d'abord les filtres PUIS leur bouton, puis les tris PUIS le leur :
   le reecrire aurait casse `pastilles.js`, qui s'attache a sa structure (ajout,
   reglage, retrait, glisser-deposer). `display: contents` fait participer ses rangees
   a la ligne, et `order` remet les boutons devant. */
.cmdbar {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 10px 12px; margin: 0 0 12px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card);
}
/* /!\ `display: contents` : le partiel des pastilles n'est plus une boite, ses enfants
   deviennent des elements de CETTE ligne. Sans cela il formerait un bloc a lui seul et
   on retomberait sur une seconde rangee -- exactement ce qu'on vient de supprimer. */
.cmdbar .pastilles, .cmdbar .pastilles-rangee { display: contents; }
/* Les libelles « Filtres » et « Tri » du partiel : le mot « Filtre » est deja sur le
   bouton d'ajout, et il etait ecrit DEUX FOIS sur l'ancienne page. */
.cmdbar .pastilles-l { display: none; }
.cmdbar .pastilles-ajouter { order: 1; }
.cmdbar .pastille { order: 2; }
/* Les deux phrases du partiel (une pastille ecartee, l'origine de l'etat) prennent
   une ligne entiere SOUS la barre : ce sont des avertissements, pas des commandes. */
.cmdbar .pastilles > p { order: 3; flex: 1 1 100%; margin: 4px 0 0; }
.cmdbar .vue-actions { order: 4; }
/* /!\ **IL SUIT LES FILTRES, IL NE PART PLUS À DROITE** (fondateur, 2026-09-12 :
   « le Tout remettre à zéro, il faut qu'il soit juste à côté du bouton filtre et
   que selon le nombre de filtres il s'ajuste »).

   Il était en `order: 4`, derrière `.vue-actions` qui porte un `margin-left: auto` :
   cette marge absorbe tout l'espace libre de la rangée, et les deux partaient donc
   se coller au bord droit — à dix centimètres du « + Filtre » qu'ils défont. En
   `order: 3`, il se range immédiatement après la dernière pastille (order 2) et
   avance ou recule tout seul avec elles.

   /!\ `margin-left: 0` EXPLICITE : `.vue-actions` le précède et sa marge
   automatique déborderait sinon sur lui. */
.cmdbar .cmd-raz { order: 3; margin-left: 0; }
/* /!\ LES QUATRE FIXES NE SE RENDENT PAS DEUX FOIS. Trader, version, compte et
   periode ont chacun leur commande dans la barre depuis le 2026-08-21 ; leur pastille
   les doublerait, et deux commandes pour une seule chose finissent toujours par ne
   plus dire pareil.

   /!\ `display: none` et SURTOUT PAS un retrait du gabarit : `pastilles.js` reconstruit
   l'etat depuis le DOM (`etatCourant()` lit `[data-pastille]`). Une pastille absente du
   DOM disparaitrait de l'etat au premier geste sur une AUTRE pastille -- ajouter un
   filtre effacerait le trader choisi, en silence. Elle reste donc la, invisible. */
.cmdbar .pastille[data-champ="trader"],
.cmdbar .pastille[data-champ="version"],
.cmdbar .pastille[data-champ="compte"],
.cmdbar .pastille[data-champ="periode"] { display: none; }
.cmd-sep {
  order: 0; flex: 0 0 auto; width: 1px; height: 20px; background: var(--border);
  margin: 0 2px;
}
/* La BASCULE Reel/Rejeu, dans la barre depuis le 2026-08-21. Elle se CLIQUE (demande
   du fondateur : « comme c'etait le cas, pour que ce soit rapide »), elle ne se deplie
   pas -- et elle n'a toujours que DEUX positions. */
.basc {
  order: 0; flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--background);
}
/* /!\ **`a` ET `button`, depuis le 2026-09-11.** Une bascule faite de LIENS navigue
   (le serveur refiltre) ; faite de BOUTONS elle filtre sur place. C'est le meme objet
   pour qui la regarde, et les deux formes ont leur emploi : la page Consignes filtre
   cote navigateur parce que l'ordre de ses lignes est leur priorite de lecture, et
   qu'une liste tronquee par le serveur n'aurait plus de rang calculable.
   /!\ Sans cette regle, les boutons gardaient le dessin NATIF du navigateur : trois
   pilules grises toutes pareilles, dont aucune ne se distinguait comme choisie. Vu a
   la capture, pas devine. */
.basc a, .basc button {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: var(--radius-pill);
  font-size: var(--text-sm); color: var(--muted-foreground); white-space: nowrap;
  text-decoration: none;
  background: none; border: 0; font-family: inherit; cursor: pointer;
}
.basc a:hover, .basc button:hover { color: var(--foreground); }
.basc a .n, .basc button .n {
  font-family: var(--font-mono); font-size: var(--text-xs); opacity: .85;
}
.basc a[aria-current="page"], .basc button[aria-current="page"] {
  background: rgba(180,168,255,0.16); color: var(--foreground); font-weight: 600;
}
/* Le declencheur d'une commande fixe (trader, compte, periode). */
/* /!\ COIN A `--radius-md`, PLUS LA PILULE (2026-09-11, arbitrage du fondateur).
   C'est la suite directe de la maquette « B » du 2026-08-23, qui avait carré les
   boutons mais laissé les pastilles et sélecteurs en pilule ; le carnet
   `NOTES-guide-et-boutons.md` notait l'incohérence comme « reste ouvert, à
   trancher avec le fondateur ». Il vient de la trancher pour la barre de
   filtres : les boutons y sont rectangulaires à bords arrondis.

   /!\ La barre était DÉJÀ à 6 px pour l'essentiel — `.pastille` et `.cmd-loupe`.
   Seuls `.cmd-choix` et `.perimetre-raz` restaient ronds : c'étaient EUX les
   intrus, pas l'inverse. */
.cmd-choix {
  order: 0; list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--background);
  font-size: var(--text-sm); color: var(--foreground);
  cursor: pointer; white-space: nowrap;
}
/* /!\ **`.cmd-cherche` A ETE RETIREE le 2026-09-11**, avec le champ de recherche
   toujours visible de la barre Contacts. Cette recherche-la n'a pas disparu — elle
   cherche toujours dans TOUTE la base, ce qui est la bonne facon sur une liste
   paginee — elle s'est repliee derriere une loupe, en forme R3, comme partout
   ailleurs. Elle porte donc desormais `.recherche-champ`, la brique commune.
   Une meme intention ne doit pas avoir deux formes selon l'ecran. */

.cmd-choix::-webkit-details-marker { display: none; }
.cmd-choix::marker { content: ""; }
.cmd-choix:hover { border-color: var(--border-strong); }
.cmd-choix .k { color: var(--muted-foreground); }
.cmd-choix .vv { color: var(--muted-foreground); }
.cmd-choix .chev { font-size: var(--text-2xs); color: var(--muted-foreground); }
/* /!\ Pose = teinte de marque. C'est ce qui distingue d'un coup d'oeil les commandes
   qui MORDENT de celles qui laissent tout passer. */
.cmd-choix[data-pose="oui"] {
  border-color: rgba(180,168,255,0.5); background: rgba(180,168,255,0.14);
}
.cmd-choix[data-pose="oui"] .v { font-weight: 600; }
/* La TEINTE DE MARQUE demandée à la main (2026-09-02 : « en reprenant le style violet
   transparent »), pour une commande qui n'a pas d'état « posé » à annoncer : le menu
   « Actions » de la ligne du titre.

   /!\ Les MÊMES valeurs que `[data-pose="oui"]` juste au-dessus, et non des valeurs
   approchantes : deux violets à deux pourcents près sur le même écran se lisent comme
   deux composants différents. */
.cmd-choix--marque {
  border-color: rgba(180,168,255,0.5); background: rgba(180,168,255,0.14);
  font-weight: 600;
}
.cmd-choix--marque:hover { background: rgba(180,168,255,0.20); }

/* Le menu d'actions de la ligne du titre. /!\ `position: relative` porté par le
   conteneur et non par le `<details>` : son panneau est en absolu, et sans ancêtre
   positionné il se serait accroché à la page, donc décalé dès qu'on défile. */
/* Les DEUX gestes de la ligne du titre : le fantome, puis le plein. */
.sec-actions-menu {
  position: relative; margin-left: auto; flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 8px;
}
/* /!\ Un `<summary>` porte `display: list-item` par defaut, et la puce reapparait
   des qu'on lui donne une classe de bouton : `.btn` ne la retire pas, il ne s'en
   occupe pas. Sans ces trois lignes, « Actions » s'affichait avec un triangle noir
   collé à son icone. */
.cmd-summary { list-style: none; cursor: pointer; }
.cmd-summary::-webkit-details-marker { display: none; }
.cmd-summary::marker { content: ""; }

/* ── LE PIED D'UNE LISTE PAGINEE (2026-09-02, lot 6) ──────────────────────────
   /!\ Le COMPTE d'abord, les pages ensuite. Une liste paginee sans son total laisse
   croire que la page EST la liste. */
.pied-liste {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  padding: 12px 16px;
  border-top: 1px solid var(--border);
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.pied-liste strong { color: var(--foreground); font-weight: 600; }
.pied-par-page { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
.pied-pages, .pied-par-page { display: inline-flex; align-items: center; gap: 4px; }
.pied-pages a, .pied-par-page a {
  padding: 3px 9px; border-radius: var(--radius-pill);
  border: 1px solid transparent; color: var(--muted-foreground);
  text-decoration: none; font-variant-numeric: tabular-nums;
}
.pied-pages a:hover, .pied-par-page a:hover {
  border-color: var(--border-strong); color: var(--foreground);
}
/* /!\ La page COURANTE porte la teinte de marque, comme une commande posee : sans
   elle, on ne sait pas ou l'on est et on reclique la meme page. */
.pied-pages a[aria-current="page"], .pied-par-page a[aria-current="page"] {
  border-color: rgba(180,168,255,0.5); background: rgba(180,168,255,0.14);
  color: var(--foreground); font-weight: 600;
}

/* L'en-tete d'une colonne TRIABLE. /!\ Aucune couleur d'accent : c'est un en-tete,
   pas un bouton, et dix-huit accents cote a cote ne designeraient plus rien. */
.tri-en-tete {
  color: inherit; text-decoration: none;
  display: inline-flex; align-items: center; gap: 4px;
}
.tri-en-tete:hover { color: var(--foreground); }
.tri-sens { color: var(--accent); font-size: var(--text-xs); }
/* /!\ Le panneau s'ouvre vers la GAUCHE. `.cmd-liste` pose `left: 0` parce que ses
   menus vivent au DEBUT d'une barre ; ancre au bord droit de l'ecran, il sortait de
   la fenetre et les intitules etaient coupes en deux. Vu a l'ecran le 2026-09-02. */
.sec-actions-menu .cmd-liste { left: auto; right: 0; }

/* /!\ `--fenetre` et JAMAIS `70vh` : `100vh` ignore le `zoom` du corps, donc a
   l'echelle 1.2 la liste se croirait autorisee a 20 % de plus que la hauteur reelle et
   deborderait de l'ecran. Un test du depot epingle cette regle, et il a attrape
   celle-ci le jour meme ou elle a ete ecrite. */
.cmd-liste {
  left: 0; right: auto; min-width: 250px;
  max-height: calc(var(--fenetre) * 0.7); overflow-y: auto;
}
.cmd-liste .menu-item[aria-current="page"] {
  background: rgba(180,168,255,0.14); font-weight: 600;
}

/* ⚠️ CE PANNEAU S'OUVRAIT HORS DE L'ÉCRAN SUR UN TÉLÉPHONE (2026-09-20,
   TVNI-93). Mesuré sur `/user/traders/1/analyses` à 390 points : la pastille
   de version (« v1 ▾ ») est tout à droite de la ligne de titre, à l'abscisse
   322 ; le panneau s'ouvre à SON bord gauche et réclame 250 points, donc de
   322 à 572. Sur un écran de 390, on ne voyait que les deux premiers
   caractères de chaque version, et il n'existait aucun geste pour ramener le
   reste : un menu qu'on ouvre et qui n'affiche rien.

   ⚠️ Le remède n'est pas de le rétrécir — à 68 points de large il ne dirait
   rien non plus — mais de l'ANCRER À DROITE : son bord droit rejoint celui de
   son bouton, et ses 250 points partent alors vers l'intérieur de l'écran.
   C'est déjà ce que fait `.sec-actions-menu` plus bas, pour la même raison.

   ⚠️ La règle est bornée au téléphone : sur un écran large, l'ancrage à gauche
   est le bon, et c'est un choix documenté juste au-dessus.

   ⚠️⚠️ ET ELLE S'ARRÊTE À 360 POINTS, CE QUI N'EST PAS UN CAPRICE DE BORNE.
   Sous cette largeur, la ligne de titre se replie et la pastille remonte vers
   la GAUCHE : son bord droit tombe alors à l'abscisse 125, et un panneau de
   250 points ancré à droite sortirait par la gauche (mesuré : -125 à 125).
   L'ancrage à gauche, lui, n'y perd que 19 points. Aucun des deux ancrages ne
   tient à 320 : ce qu'il faudrait là est un panneau pleine largeur sous
   l'en-tête, donc une refonte de ce menu, pas un réglage. Tant qu'elle n'est
   pas faite, on garde à 320 le moins mauvais des deux — c'est écrit dans
   `docs/notes/NOTES-mobile-public-et-client.md`, avec la mesure. */
@media (max-width: 720px) {
  .lentille--badge > .cmd-liste {
    position: fixed; left: 16px; right: 16px; bottom: 16px; top: auto;
    min-width: 0; width: auto;
    max-height: calc(var(--fenetre) * 0.6);
    z-index: var(--z-modal);
  }
}
/* L'INTITULE d'un panneau de commande (« Sur quel trader », « Quand »).
   /!\ Il etait ECRIT dans les gabarits depuis le 2026-08-21 et n'avait AUCUNE
   regle : `.v-titre` n'existait que sous `.vue-liste`, si bien que ces titres se
   rendaient en texte courant, a la meme graisse que les entrees qu'ils coiffent.
   Releve le 2026-08-23 en montant les memes menus sur « Parrainage » et « Mes
   gains ». Meme habit que dans la liste des vues : c'est le meme role. */
.cmd-liste .v-titre {
  display: block; padding: 4px 11px 6px;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--disabled-foreground);
}
.cmd-n {
  margin-left: auto; padding-left: 12px;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted-foreground);
}
/* Une VERSION, sous le trader courant : en retrait, reliee par un filet, pour qu'on
   voie qu'elle appartient a la ligne du dessus et non a la liste. */
.menu-version {
  position: relative;
  display: flex; align-items: center; gap: 9px;
  padding: 6px 11px 6px 26px; margin-left: 4px;
  border-radius: var(--radius-md);
  font-size: var(--text-sm); color: var(--muted-foreground); text-decoration: none;
}
.menu-version::before {
  content: ""; position: absolute; left: 12px; top: 0; bottom: 0;
  width: 1px; background: var(--border);
}
.menu-version:hover { background: var(--muted); color: var(--foreground); }
.menu-version[aria-current="page"] {
  background: rgba(180,168,255,0.10); color: var(--foreground); font-weight: 600;
}
.cmd-note {
  margin: 6px 4px 2px; padding: 0 7px;
  font-size: var(--text-xs); line-height: 1.5; color: var(--muted-foreground);
}
/* /!\ Dans une barre, elle passe A LA LIGNE et se range APRES les outils : sans
   `order`, elle retombe a 0 et se glisse entre deux filtres. Meme piege que la ligne
   de recherche, meme remede. `15` la laisse devant celle-ci (20), qui reste derniere :
   une note qui explique pourquoi un geste est refuse doit se lire avant le champ. */
.cmdbar .cmd-note { order: 15; flex-basis: 100%; }
.cmd-perso {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 4px 11px 6px;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.cmd-perso input[type="date"],
.cmd-perso input.champ-maison-texte {
  font-family: var(--font-mono); font-size: var(--text-sm); padding: 4px 7px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--background); color: var(--foreground);
}
@media (max-width: 640px) {
  .cmd-sep { display: none; }
}

/* /!\ `.partager-icone`, le partage du journal (TVNI-121, 2026-09-21). Il avait un
   jumeau, l'icône de l'export, retirée le 27/09/2026 (l'export légal vit dans le
   profil). Le `font: inherit` et `line-height: 0` habillent le `<button>`. */
.partager-icone {
  list-style: none;
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; width: 34px; height: 34px; padding: 0;
  background: var(--card);
  border: 1px solid var(--border); border-radius: 50%;
  color: var(--muted-foreground); cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.partager-icone:hover, .partager-icone:focus-visible {
  color: var(--accent); border-color: var(--accent-ring-strong);
}
.partager-icone svg { width: 16px; height: 16px; }
/* /!\ `order: 5`, comme « Analyser » et l'export qui l'entourent : sans lui, il
   retombe à 0 et passe DEVANT « Analyser », loin de l'export dont il est le jumeau. */
.partager-icone { font: inherit; line-height: 0; order: 5; }
/* La BARRE A PASTILLES (2026-08-18).
   ============================================================
   Chaque filtre est une pastille : une poignee pour la deplacer, un libelle, sa valeur,
   une croix pour la retirer. Les trois predefinis (trader, periode, execution) n'ont
   AUCUN statut a part -- ils se retirent comme les autres, et c'est la demande.

   /!\ La poignee et la croix restent VISIBLES, pas revelees au survol. Une commande qui
   n'apparait qu'au survol n'existe pas au doigt, et le fondateur consulte aussi sur
   tablette. C'est le defaut qu'on a corrige sur `.filter-chip`, dont la croix etait a
   `opacity: 0`.

   /!\ La pastille est un `<span>` avec `tabindex`, pas un `<button>` : elle CONTIENT un
   bouton (la croix), et un bouton dans un bouton est du HTML invalide -- le clic partirait
   au mauvais endroit. Meme piege que la rangee d'un trade, le meme jour. */
.pastilles { display: flex; flex-direction: column; gap: 10px; }
.pastilles-rangee { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.pastilles-l {
  flex: 0 0 auto; min-width: 52px;
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.pastille {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 6px 5px 8px;
  background: rgba(180,168,255,0.06);
  border: 1px solid rgba(180,168,255,0.22);
  border-radius: var(--radius-md);
  font-size: var(--text-sm); line-height: 1.4; cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.pastille:hover { background: rgba(180,168,255,0.12); border-color: rgba(180,168,255,0.45); }
.pastille:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.pastille-poignee {
  color: var(--disabled-foreground); cursor: grab;
  letter-spacing: -2px; font-size: var(--text-sm);
}
.pastille-champ { font-weight: 600; color: var(--foreground); }
.pastille-op { color: var(--muted-foreground); font-size: var(--text-xs); }
.pastille-valeur { color: var(--accent); font-weight: 500; }
.pastille-retirer {
  width: 1.286em; height: 1.286em; padding: 0; border: 0; border-radius: 3px;
  background: transparent; color: var(--disabled-foreground);
  font-size: var(--text-md); line-height: 1; cursor: pointer;
}
.pastille-retirer:hover { background: rgba(214,90,90,0.12); color: var(--error); }
/* /!\ LA PASTILLE DE TRI A VECU MOINS DE TROIS HEURES (2026-08-21). Elle venait de
   gagner sa teinte neutre et son mot « TRI », sur remarque du fondateur (« on ne
   distingue pas trop les filtres des tris ») ; il a ensuite retire le tri
   ENTIEREMENT le meme soir. Le style part avec lui : une regle qui ne peint plus rien
   est un piege pour qui la lira ensuite. */

/* La pastille qu'on deplace, et le trou ou elle atterrira. */
.pastille--attrapee { opacity: .4; }
.pastille--cible { outline: 2px dashed var(--accent); outline-offset: 2px; }
.pastilles-ajouter { white-space: nowrap; }

/* Le PANNEAU de reglage d'une pastille (2026-08-18).
   /!\ Il n'avait AUCUN style : `.pop-l` et `.pop-pied` venaient de la maquette et
   n'existaient pas dans la charte. Resultat vu a l'ecran par le fondateur -- « Retirer »
   sous « Appliquer », des champs deux fois trop hauts, des libelles sans hierarchie.
   Une maquette qui invente des classes doit les apporter avec elle. */
.panneau-pastille { min-width: 240px; max-width: 320px; }
/* Le titre d'une famille dans le selecteur de proprietes. /!\ Il est pose dans la liste
   des boutons : sans `display:block` il s'alignerait sur eux et se lirait comme une
   entree cliquable qui ne repond pas. */
.filter-popover .picker-groupe {
  display: block; padding: 9px 8px 4px;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--disabled-foreground);
}
.filter-popover .picker-groupe:first-child { padding-top: 2px; }
/* Une propriete DEJA posee dans la barre. /!\ Elle reste cliquable -- deux bornes sur
   le meme prix s'ecrivent en deux pastilles -- donc elle est ATTENUEE, jamais grisee :
   un bouton grise annonce qu'il ne repond pas. Le point remplace le texte « deja pose »,
   retire le 2026-08-18 : il doublait la longueur de la ligne pour redire ce qu'un coup
   d'oeil a la barre montre deja. */
.filter-popover .picker-item[data-deja] {
  /* /!\ `flex` ici SEULEMENT, pas sur `.picker-item` : cette classe sert aussi les
     selecteurs d'administration, et passer la base en flex les toucherait tous. */
  display: flex; align-items: center; gap: 8px;
  color: var(--muted-foreground);
}
.filter-popover .picker-item[data-deja]::after {
  content: ""; width: 5px; height: 5px; margin-left: auto; flex: 0 0 auto;
  border-radius: 50%; background: var(--accent); opacity: 0.75;
}
.filter-popover .picker-item[data-deja]:hover { color: var(--foreground); }
/* Ce qu'une pastille PAS ENCORE REGLEE dit d'elle-meme. /!\ En italique et attenue :
   ce n'est pas une valeur, c'est ce qu'elle attend. Une pastille qui afficherait « a
   regler » du meme ton que « 200 » se lirait comme une valeur nommee « a regler ». */
.pastille-valeur--attente { color: var(--muted-foreground); font-style: italic; }
.panneau-pastille .pop-l {
  display: block;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground); margin: 0 0 5px;
}
.panneau-pastille .pop-l + * { margin-bottom: 10px; }
.panneau-pastille .saisie--compacte {
  width: 100%; padding: 7px 10px; font-size: var(--text-base); border-radius: var(--radius-md);
}
/* Les deux commandes sur UNE ligne, separees : c'est leur chevauchement qui a fait
   dire au fondateur que ce n'etait « vraiment pas esthetique ». */
.panneau-pastille .pop-pied {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border);
}
/* /!\ « Retirer » reste discret et ROUGE au survol seulement : c'est un geste
   destructeur pose a cote du geste courant, et il ne doit pas attirer le clic. */
.panneau-pastille .pop-pied .action { color: var(--muted-foreground); }
.panneau-pastille .pop-pied .action:hover { color: var(--error); }

/* Les RANGEES de la barre de filtres (2026-08-18). Trois familles, trois rangees :
   le perimetre (change les chiffres), la selection (les recalcule sur une partie), les
   outils de liste (n'en changent aucun). Un trait les separe, discret : ce n'est pas
   une frontiere, c'est un souffle. */
.filtres--rangee { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }

/* Le TIROIR des filtres rares (compte, modele). `<details>` natif : il s'ouvre sans
   JavaScript, et le gabarit l'ouvre d'office quand un des filtres qu'il cache est pose
   -- sinon un client verrait ses chiffres resserres sans voir par quoi. */
.tiroir { display: inline-block; }
.tiroir-tete {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: transparent;
  font-size: var(--text-base); color: var(--muted-foreground);
  cursor: pointer; list-style: none; white-space: nowrap;
}
.tiroir-tete::-webkit-details-marker { display: none; }
.tiroir-tete:hover { color: var(--foreground); border-color: var(--border-strong); }
.tiroir[open] .tiroir-tete { color: var(--foreground); border-color: var(--border-strong); }
.tiroir-corps { margin-top: 10px; }

/* Les COLONNES PROPOSEES du filtre avance (2026-08-18, demande du fondateur).
   /!\ Le bouton seul n'annoncait rien de ce qu'il ouvre : il fallait cliquer pour
   decouvrir qu'on peut filtrer sur le prix d'entree ou l'amplitude. Les pastilles sont
   un RACCOURCI vers les sept colonnes, pas un remplacement du panneau -- lui seul porte
   la recherche de colonne et les groupes de conditions. */
.colonnes-proposees { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* /!\ Les pastilles portent une BORDURE. Sans elle, `.action` les rend en texte nu :
   sept libelles alignes se lisaient comme une phrase (« Instrument Contrats Prix
   d'entree… »), et rien ne disait qu'on peut cliquer dedans. */
.action--colonne {
  font-size: var(--text-sm); padding: 5px 11px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  color: var(--muted-foreground); background: transparent;
}
.action--colonne:hover { color: var(--foreground); border-color: var(--border-strong); }
.filtre--avance { align-items: flex-start; }
@media (max-width: 720px) {
  .filtre--avance { flex-direction: column; gap: 6px; }
}

/* La VUE, dans la tete de la barre de filtres. Elle prend la place du libelle
   « Mes filtres » : la vue EST les filtres, la nommer deux fois serait du bruit. */
.vue-bouton {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px 5px 11px;
  background: rgba(180,168,255,0.09);
  border: 1px solid rgba(180,168,255,0.30);
  border-radius: var(--radius-pill);
  font-size: var(--text-base); font-weight: 600; color: var(--foreground);
  cursor: pointer; white-space: nowrap;
}
.vue-bouton:hover { background: rgba(180,168,255,0.16); border-color: rgba(180,168,255,0.5); }
.vue-bouton .chev { font-size: var(--text-2xs); color: var(--muted-foreground); }
/* La PASTILLE « modifiee » : un point, pas un mot en majuscules. Elle dit qu'il y
   a quelque chose a enregistrer, elle n'alarme pas. */
/* /!\ TEXTE vs POINT (petit point de la revue finale, 2026-08-20) : le texte prend
   `--warning-text`, le point prend `--warning`, comme `.barre-enregistrement
   [data-modifie="oui"] .etat` un peu plus bas dans ce fichier -- la meme convention.
   Portait avant un repli `#E0B341` MORT (`--warning` est toujours defini dans
   `:root`, il n'y a pas d'autre theme) et d'une teinte DIFFERENTE de `--warning`
   lui-meme (`#FBBF24`) : un repli qui ne sert jamais mais aurait menti sur la
   couleur s'il avait jamais servi. */
/* ══ LA CARTE-POSITION : « LE POSTE DE PILOTAGE » (refonte-cartes, 27/09/2026) ═══════
   Décision du fondateur : « OK pour les deux, variante 1 ». UNE carte pour une position
   ouverte, sur « Mes traders » (en tête), dans le journal d'un trader et sur le détail
   d'un trade (`partials/carte_position.html`). Elle remplace la bande « positions
   encore ouvertes » du journal, le bloc « En position » des cartes de trader et
   l'encadré jaune du détail : trois dessins pour la même chose, devenus un.
   Ce qu'elle rapporte POUR L'INSTANT (avec l'âge de son prix, jamais ajouté au
   réalisé), où en est le cours entre le stop et l'objectif, et le geste qui la ferme.
   /!\ Le bord VERT dit « ça vit » ; JAUNE, un incident ; VIOLET, le rejeu. */
/* Deux cartes côte à côte dès qu'il y a la place : sans graphique, une carte sur toute
   la largeur d'un écran de 1280 laissait sa moitié droite vide. */
.postes { display: grid; gap: 12px; margin: 0 0 16px;
  grid-template-columns: repeat(auto-fill, minmax(min(460px, 100%), 1fr)); align-items: start; }
.poste {
  position: relative; display: flex; flex-direction: column; min-width: 0;
  background: linear-gradient(180deg, #171A25, var(--card));
  border: 1px solid rgba(74,222,128,.28); border-radius: 14px; overflow: hidden;
  box-shadow: 0 24px 60px -30px rgba(0,0,0,.8);
}
.poste--incident { border-color: rgba(251,191,36,.45); }
.poste--rejeu { border-color: rgba(180,168,255,.35); }
.poste-corps { padding: 16px 20px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.poste-qui { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
.poste-nom { font-weight: 600; font-size: var(--text-md); color: var(--foreground);
  text-decoration: none; overflow-wrap: anywhere; }
.poste-nom:hover { text-decoration: underline; }
.poste-tete { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; }
.poste-etat { display: inline-flex; align-items: center; gap: 7px; font-size: var(--text-2xs);
  letter-spacing: .14em; text-transform: uppercase; font-weight: 600; color: var(--success); }
.poste-etat i { width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: none; }
.poste-etat--vif i { animation: poste-pouls 2s ease-out infinite; }
.poste-etat--attention { color: var(--warning-text); }
@keyframes poste-pouls { 0% { box-shadow: 0 0 0 0 rgba(74,222,128,.55); }
                         70% { box-shadow: 0 0 0 8px rgba(74,222,128,0); }
                         100% { box-shadow: 0 0 0 0 rgba(74,222,128,0); } }
@media (prefers-reduced-motion: reduce) { .poste-etat i { animation: none; } }
.poste-quoi { font-size: var(--text-sm); color: var(--muted-foreground); }
.poste-quoi b { color: var(--foreground); font-weight: 600; }
.sens { font-weight: 600; }
.sens--achat { color: var(--success); }
.sens--vente { color: var(--error); }
/* LE COMPTE : le seul élément qui dit si l'argent est réel ou simulé (2026-08-23). */
.poste-compte { font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1;
  letter-spacing: var(--tracking-mono-data); color: var(--muted-foreground);
  background: var(--muted); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 4px 7px; white-space: nowrap; }
.poste-latent { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.poste-latent [data-direct-valeur], .poste-latent-v {
  font-size: 2.2rem; font-weight: 600; letter-spacing: -.02em; line-height: 1;
  font-variant-numeric: tabular-nums; }
.poste-latent-s { font-size: var(--text-sm); color: var(--muted-foreground);
  font-variant-numeric: tabular-nums; line-height: 1.5; }
.poste-latent-s b { color: var(--foreground); font-weight: 600; }
.poste-latent-s i { font-style: normal; }
.poste-latent--fige .poste-latent-v { color: var(--disabled-foreground); }
.poste-attente { margin: 0; font-size: var(--text-sm); color: var(--muted-foreground); }
/* LE MINI-GRAPHIQUE (22/10/2026) : les dernières bougies et les niveaux du trade, sur
   la carte d'une position et la case d'un ordre (`mini-graphique.js`). Un appui ouvre
   le détail. Couleurs de la charte : hausse/baisse = gain/perte, entrée = violet. */
.mini { position: relative; height: 150px; border-radius: var(--radius-md);
  background: rgba(255,255,255,.02); border: 1px solid var(--border); cursor: pointer;
  overflow: hidden; transition: border-color .15s ease; }
.mini:hover, .mini:focus-visible { border-color: var(--border-strong); outline: none; }
.mini[hidden] { display: none; }
.mini--ordre { margin-top: 10px; height: 130px; }
.mini-dessin { display: block; }
.mini-zone-gain { fill: rgba(74,222,128,.07); }
.mini-zone-perte { fill: rgba(248,113,113,.07); }
.mini-decision { stroke: rgba(180,168,255,.35); stroke-width: 1; stroke-dasharray: 2 3; }
.mini-bougie line { stroke-width: 1; }
/* Les teintes du CLIENT (`--ch-*`, réglées dans la fenêtre du graphique), comme le
   grand graphique ; les défauts de la charte quand elles manquent. */
.mini-hausse line { stroke: rgba(var(--ch-meche-hausse, 74,222,128), var(--ch-meche-hausse-o, 1)); }
.mini-hausse rect { stroke: none; fill: rgba(var(--ch-corps-hausse, 74,222,128), var(--ch-corps-hausse-o, 1)); }
.mini-baisse line { stroke: rgba(var(--ch-meche-baisse, 248,113,113), var(--ch-meche-baisse-o, 1)); }
.mini-baisse rect { stroke: none; fill: rgba(var(--ch-corps-baisse, 248,113,113), var(--ch-corps-baisse-o, 1)); }
.mini-en-cours { opacity: .65; }
.mini-niveau { stroke-width: 1; }
.mini-niveau--entree { stroke: rgb(var(--ch-entree, 167,139,250)); }
.mini-niveau--ordre { stroke: rgb(var(--ch-entree, 167,139,250)); stroke-dasharray: 5 4; }
.mini-niveau--stop { stroke: rgb(var(--ch-stop, 248,113,113)); stroke-dasharray: 3 3; opacity: .85; }
.mini-niveau--objectif { stroke: rgb(var(--ch-objectif, 74,222,128)); stroke-dasharray: 3 3; opacity: .85; }
.mini-cours-trait { stroke: var(--foreground); stroke-width: 1; stroke-dasharray: 1 3; opacity: .6; }
.mini-etiq text { font-family: var(--font-mono); font-size: calc(10.5px * var(--echelle-texte)); font-weight: 600;
  font-variant-numeric: tabular-nums; }
.mini-etiq--entree rect, .mini-etiq--ordre rect { fill: rgba(180,168,255,.18); }
.mini-etiq--entree text, .mini-etiq--ordre text { fill: var(--accent); }
.mini-etiq--stop rect { fill: rgba(248,113,113,.16); }
.mini-etiq--stop text { fill: #FCA5A5; }
.mini-etiq--objectif rect { fill: rgba(74,222,128,.14); }
.mini-etiq--objectif text { fill: #86EFAC; }
.mini-etiq--cours rect { fill: var(--foreground); }
.mini-etiq--cours text { fill: var(--background); }
@media (max-width: 720px) { .mini { height: 128px; } .mini--ordre { height: 116px; } }
/* LA RÈGLE : le stop à gauche, l'objectif à droite, quel que soit le sens. */
.poste-regle { min-width: 0; }
.regle { position: relative; height: 40px; }
.regle-piste { position: absolute; left: 0; right: 0; top: 12px; height: 6px; border-radius: 3px;
  background: linear-gradient(90deg, rgba(248,113,113,.6), rgba(255,255,255,.08) 50%, rgba(74,222,128,.6)); }
.regle-repere { position: absolute; top: 6px; width: 2px; height: 18px; background: var(--accent);
  transform: translateX(-1px); }
.regle-prix { position: absolute; top: 3px; width: 14px; height: 24px; border-radius: 4px;
  background: var(--foreground); box-shadow: 0 0 0 3px rgba(245,244,240,.15);
  transform: translateX(-7px); transition: left .6s ease-out; }
.regle-entree { position: absolute; top: 26px; transform: translateX(-50%); font-size: var(--text-2xs);
  color: var(--accent); white-space: nowrap; }
.regle-bouts { display: flex; justify-content: space-between; gap: 12px; font-size: var(--text-xs);
  color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.regle-bouts b { display: block; font-size: var(--text-sm); color: var(--foreground); font-weight: 600; }
.regle-bouts .d { text-align: right; }
.poste-gestes { display: flex; gap: 8px 10px; flex-wrap: wrap; align-items: flex-start; }
.poste-gestes .fermer-pos { margin-top: 0; }
.btn-fermer { border: 1px solid rgba(248,113,113,.5); color: #FCA5A5; background: rgba(248,113,113,.08); }
.btn-fermer:hover:not(:disabled) { background: rgba(248,113,113,.16); color: #fff; }
.poste-limite { margin: 0; font-size: var(--text-xs); color: var(--muted-foreground); line-height: 1.55;
  padding-top: 10px; border-top: 1px dashed var(--border-strong); }
.poste-limite b { color: var(--foreground); font-weight: 600; }
/* L'INCIDENT : le problème, puis ce qu'on fait, numéroté (`suivi_position`). */
.poste-incident { padding: 14px 20px; background: rgba(251,191,36,.08);
  border-bottom: 1px solid rgba(251,191,36,.3); font-size: var(--text-sm);
  color: var(--muted-foreground); line-height: 1.55; }
.poste-incident strong { display: block; color: var(--warning-text); font-size: var(--text-md);
  margin-bottom: 2px; }
.poste-incident .incident-etapes { color: var(--foreground); }
.poste-incident .incident-etapes li::marker { color: var(--warning-text); font-weight: 600; }
/* Le titre de la section, sur le journal et le détail (« Mes traders » a le sien). */
/* Sur le détail d'un trade : UNE carte, sur la largeur de la colonne de lecture. */
.postes--seul { grid-template-columns: minmax(0, 720px); }
.postes-tete { display: flex; align-items: baseline; gap: 4px 12px; flex-wrap: wrap; margin: 0 0 10px; }
.postes-tete h2 { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.postes-tete span { font-size: var(--text-body-sm); color: var(--muted-foreground); }
.jr-positions { margin: 0 0 18px; }
.postes-ailleurs { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 0;
  font-size: var(--text-sm); color: var(--muted-foreground); }
.postes-ailleurs svg { color: var(--info); }
.postes-ailleurs a { color: var(--accent); }
/* LES AUTRES POSITIONS : une liste qui s'ouvre et qui DÉFILE (jamais tronquée). */
.postes-autres { margin: 0 0 16px; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card); }
.postes-autres > summary { list-style: none; cursor: pointer; display: flex; align-items: baseline;
  gap: 6px 10px; flex-wrap: wrap; padding: 11px 14px; }
.postes-autres > summary::-webkit-details-marker { display: none; }
.postes-autres-geste { margin-left: auto; font-size: var(--text-sm); color: var(--muted-foreground); }
.postes-autres[open] > summary { border-bottom: 1px solid var(--border); }
.postes-autres-liste { list-style: none; margin: 0; padding: 0; max-height: 360px; overflow-y: auto; }
.poste-ligne { display: flex; align-items: center; gap: 6px 12px; flex-wrap: wrap; padding: 8px 14px;
  border-top: 1px solid var(--border); font-size: var(--text-sm); }
.poste-ligne:first-child { border-top: 0; }
.poste-ligne-quoi { color: var(--foreground); text-decoration: none; }
.poste-ligne-quoi:hover b { text-decoration: underline; }
.poste-ligne-latent b { font-family: var(--font-mono); font-weight: 600; }
.poste-ligne-latent i { font-style: normal; font-size: var(--text-caption); color: var(--muted-foreground); }
.poste-ligne-geste { margin-left: auto; }
.poste-ligne-geste .fermer-pos { margin-top: 0; justify-content: flex-end; }
.poste-ligne-geste .fermer-pos-pourquoi { text-align: right; }
@media (max-width: 720px) {
  .poste-corps { padding: 14px; }
  .poste-incident { padding: 12px 14px; }
  .poste-latent [data-direct-valeur], .poste-latent-v { font-size: 1.9rem; }
  .poste-gestes > * { flex: 1 1 auto; }
  .poste-gestes .btn { justify-content: center; }
}

/* ── TVNI-271 (26/09/2026) : FERMER LA POSITION, ET L'INCIDENT ─────────────────────
   Le geste de fermeture (réel, ou comptable en repli), son suivi en direct, et le
   bandeau d'une position à vérifier avec ses étapes. Mêmes tokens que le reste de la
   charte ; aucune couleur écrite en dur. */
.fermer-pos { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
              margin-top: 10px; }
.fermer-pos form { margin: 0; }
.fermer-pos-pourquoi { margin: 0; flex-basis: 100%; font-size: var(--text-sm);
                       color: var(--muted-foreground); }
.fermer-pos-suivi { flex-basis: 100%; font-size: var(--text-sm); }
.fermer-pos-suivi p { margin: 0; }
.fermer-pos-ok { color: var(--success, var(--foreground)); }
.fermer-pos-echec { color: var(--warning, var(--foreground)); }
/* TVNI-282 : « Annuler l'ordre » sur le détail d'un trade dont l'ordre attend. */
.ordre-geste { margin: 0 0 16px; max-width: 720px; }
/* Réexamen de l'ordre en attente (18/10/2026) : les relectures d'un ordre, sous lui. */
.reexamens { margin: 0 0 var(--space-6); max-width: 720px; }
.reexamens-titre { display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-3); font-size: var(--text-body); font-weight: 600;
  color: var(--foreground); }
.reexamens-liste { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--space-2); }
.reexamen { border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4); }
.reexamen--retirer { border-color: color-mix(in srgb, var(--warning) 45%, transparent); }
.reexamen-tete { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  font-size: var(--text-body-sm); color: var(--muted-foreground); }
.reexamen-heure { color: var(--foreground); font-variant-numeric: tabular-nums; }
.reexamen-cout { margin-left: auto; font-variant-numeric: tabular-nums; }
.reexamen-texte { margin: var(--space-2) 0 0; font-size: var(--text-body-sm);
  color: var(--foreground); line-height: 1.5; }
.reexamen-effet { margin: var(--space-2) 0 0; font-size: var(--text-body-sm);
  color: var(--muted-foreground); }
.fermer-pos-etapes { display: flex; flex-wrap: wrap; gap: 4px 14px; margin: 0; padding: 0;
                     list-style: none; color: var(--muted-foreground); }
.fermer-pos-etapes li::before { content: "○ "; }
.fermer-pos-etapes li.fait { color: var(--foreground); }
.fermer-pos-etapes li.fait::before { content: "● "; }
.fermer-pos-etapes li.en-cours { color: var(--foreground); font-weight: 600; }
.fermer-pos-etapes li.en-cours::before { content: "◐ "; }
.incident-position { margin: 0 0 16px; }
.incident-etapes { margin: 8px 0 0; padding-left: 20px; }
.incident-etapes li { margin: 2px 0; }

/* Le POINT de vue RETOUCHEE (2026-08-22). Il remplace trois boutons en toutes lettres
   dans la barre : le fondateur les trouvait trop encombrants, et l'etat qu'ils
   annoncaient tient dans six pixels. Les gestes eux-memes sont en tete du menu.
   /!\ `::after` et pas `::before` : le chevron ferme le bouton, le point doit rester
   colle au NOM de la vue, sinon il se lit comme une puce du chevron. */
.vue-bouton[data-modifiee] { border-color: rgba(251, 191, 36, 0.45); }
.vue-bouton[data-modifiee] .chev { order: 2; }
.vue-bouton[data-modifiee]::after {
  content: ""; order: 1;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--warning); flex: 0 0 auto;
}

/* La COCHE du menu a selection multiple (le filtre Compte, 2026-08-22).
   /!\ Largeur FIXE, meme vide : sans elle, les comptes coches et non coches
   s'aligneraient sur deux colonnes differentes et la liste se lirait en escalier. */
.cmd-liste .coche {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; flex: 0 0 14px; color: var(--accent);
}

/* Une rangee de SAISIE dans un menu : champ + bouton sur UNE ligne (2026-08-22).
   Remplace un pave de trois hauteurs de ligne (titre, champ, bouton pleine largeur)
   pour les gestes renommer et creer une vue. */
.menu-item--saisie {
  display: flex; flex-direction: row; align-items: center; gap: 6px;
}
.menu-item--saisie .saisie { flex: 1 1 auto; min-width: 0; }
.menu-item--saisie .btn { flex: 0 0 auto; padding: 4px 8px; }
.menu-item--saisie .menu-item-pico {
  display: inline-flex; flex: 0 0 auto; color: var(--muted-foreground);
}

.vue-modifiee {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--text-xs); color: var(--warning-text); font-weight: 500;
}
.vue-modifiee::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--warning);
}
.vue-actions { display: inline-flex; align-items: center; gap: 14px; margin-left: auto; }
/* La liste des vues, ouverte. Reprend `.menu-panneau`, en plus large et ancree a
   GAUCHE : elle descend d'un bouton pose a gauche, pas d'un bouton d'icone.

   /!\ PAS de `position: static` ici (corrige le 2026-08-20, tache 9) : cette regle
   se charge APRES `.menu-panneau` dans la feuille, donc un `position: static` posait
   ici ecrasait son `position: absolute` -- le menu poussait alors toute la page vers
   le bas au lieu de se superposer. Cette valeur venait d'une maquette figee ou elle
   etait une commodite pour montrer tous les etats sur une page qui ne defilait pas ;
   recopiee au caractere pres dans la charte, elle est devenue un bug a l'ecran. */
.vue-liste {
  left: 0; right: auto;
  width: 360px; min-width: 0; margin: 8px 0 0 26px;
}
.vue-liste .menu-item[aria-selected="true"] { background: var(--muted); }
.vue-liste .v-titre {
  display: block; padding: 4px 11px 6px;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--disabled-foreground);
}
.vue-liste .menu-item .v-def {
  margin-left: auto; font-size: var(--text-2xs); font-family: var(--font-mono);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--accent); font-weight: 400;
}

.perimetre-corps {
  margin-top: 8px; padding: 14px 16px;
  background: var(--card);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.perimetre[data-replie="oui"] .perimetre-corps { display: none; }
/* /!\ Sans JavaScript, rien ne replie : la barre reste ouverte et la page marche.
   Le script pose `data-replie` des qu'il tourne. */

/* ============================================================
   Une page de reglages EN ONGLETS (2026-08-18)
   ============================================================
   Remarque du fondateur, le jour ou la fiche d'un trader est passee en onglets :
   « chaque onglet a un encadre et l'encadre est disproportionne ».

   Il avait raison, et pour deux raisons qui se cumulent :

   /!\ 1. UNE BOITE DANS UNE BOITE. Tant que les sept parties se suivaient sur une
   page, le cadre les SEPARAIT — c'etait son travail. Seule sous une barre
   d'onglets, la partie n'a plus rien dont se separer : le cadre ne fait plus que
   redessiner un contour a l'interieur d'un contour.

   /!\ 2. LE VIDE A GAUCHE. La colonne d'explication fait 300 points ; quatre
   parties sur huit n'y mettaient qu'un titre de trois mots. Empilees, elles se
   compensaient ; seule a l'ecran, « Identite et marche » donnait 250 points a
   moitie vides face a deux champs. Le cadre le RENDAIT VISIBLE, il ne le causait
   pas — d'ou les quatre explications ecrites le meme jour dans le gabarit. Retirer
   le cadre sans les ecrire n'aurait fait que cacher le trou.

   /!\ Portee `--onglets` et pas `--reglages` : les sept AUTRES pages de reglages de
   l'application (profil, facturation, admin…) empilent toujours leurs parties et
   ont toujours besoin du cadre qui les separe. */
.wrap--reglages.wrap--onglets .encadre[data-section] {
  padding: 6px 0 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* Sans cadre, c'est l'espace qui doit tenir la partie a distance de la barre
   d'onglets, sinon le premier champ lui colle dessous. */
.wrap--reglages.wrap--onglets .encadre[data-section] > .encadre-titre { margin-top: 0; }

/* /!\ La colonne d'explication a gauche est ANNULEE dans une page a onglets. C'est
   elle que le fondateur a rejetee le 2026-08-18 : « quand on clique sur un reglage,
   le texte a gauche et les champs a droite, ca cree un desequilibre ». Elle vaut
   toujours pour les sept autres pages de reglages, qui empilent leurs parties — ici
   chaque partie occupe seule l'ecran, et un titre de trois mots face a deux champs
   laissait une moitie d'ecran vide. */
/* /!\ Les DEUX classes du conteneur sont nommees, et ce n'est pas du zele : la
   regle a defaire (`.wrap--reglages .encadre > *:not(…):not(…)`) compte quatre
   classes, une de plus qu'une version courte de celle-ci. Elle aurait gagne, et le
   contenu serait reste decale de 356 points vers la droite — avec une colonne vide
   a gauche, soit exactement le defaut qu'on corrige. */
.wrap--reglages.wrap--onglets .encadre[data-section] > .encadre-titre,
.wrap--reglages.wrap--onglets .encadre[data-section] > .encadre-sous {
  float: none; width: auto; max-width: none;
}
.wrap--reglages.wrap--onglets .encadre[data-section] > *:not(.encadre-titre):not(.encadre-sous),
.wrap--reglages.wrap--onglets .encadre[data-section] > * { margin-left: 0; }
/* /!\ `.encadre + .encadre { margin-top: 16px }` SÉPARE deux encadrés qui se
   suivent — c'est son travail quand ils sont empilés. Dans une page à onglets, un
   seul est visible : cette marge ne separe plus rien, elle DÉCALE. Et comme elle ne
   s'applique qu'aux sections precedees d'une autre dans le DOM, l'ecart sous la
   barre d'onglets valait 20 px ou 37 px SELON LA POSITION de l'onglet — remarque du
   fondateur le 2026-08-18 : « les espaces entre l'onglet et le texte different d'un
   onglet a l'autre ». Une seule source pour cet ecart : la marge basse de la barre. */
.wrap--onglets [data-section] + [data-section],
.wrap--onglets [data-section] { margin-top: 0; }

/* ============================================================
   Les SOUS-GROUPES d'un onglet (2026-08-18)
   ============================================================
   Maquette « onglet C », choisie par le fondateur. Les champs se rangent par
   QUESTION — « ou je sors », « combien je mets », « ou j'entre » — et chaque
   groupe porte son titre et sa phrase courte.

   /!\ Ce que ca resout, et que ni le chapeau ni les champs nus ne resolvaient :
   « Strategie » compte quatorze reglages. En liste, c'est un rouleau ; en grille
   nue, c'est quatorze etiquettes sans hierarchie. Groupes, ce sont quatre
   questions.

   /!\ Et une partie a DEUX champs ne fait qu'un groupe : la grille se remplit
   d'elle-meme, rien n'est jamais a moitie vide. C'est le defaut d'origine.

   /!\ Le groupe est plus discret que `.encadre` — bordure simple, fond a peine
   marque, pas d'ombre. Il structure, il ne se met pas en avant : sinon on aurait
   remplace une boite dans une boite par quatre. */
/* Le CHAPEAU d'un onglet : la phrase qui vaut pour tous ses groupes, quand il y en
   a une.

   /!\ PLEINE LARGEUR, sur decision du fondateur (2026-08-18) : « les textes en
   en-tete sur certains onglets ne prennent pas toute la largeur alors que certains
   la prennent, fais qu'ils la prennent tous ». Le premier jet la bornait a 74
   caracteres, par souci de confort de lecture ; ce qui se voyait a l'ecran, c'est
   que les onglets ne se ressemblaient pas — « Regles » et « Notifications », dont
   l'intro n'est pas un `.chapeau`, couraient sur toute la largeur, les six autres
   s'arretaient au tiers. Une regle de confort qui produit une incoherence visible
   coute plus qu'elle ne rapporte. */
.chapeau { margin: 0 0 20px; font-size: var(--text-base); line-height: 1.65;
           color: var(--muted-foreground); }
.chapeau strong { color: var(--foreground); font-weight: 500; }
.chapeau code { font-family: var(--font-code); font-size: .92em; }
/* /!\ Un lien dans un chapeau DOIT se voir. `admin_base.html` pose
   `a { color: inherit; text-decoration: none }` sur toute l'application : sans
   cette règle, un lien posé dans un chapeau prend le gris du texte et devient
   invisible — défaut relevé à l'écran le 2026-08-22 sur « Modifier mes valeurs
   de départ », que rien ne distinguait de la phrase qui le portait. */
.chapeau a { color: var(--accent); text-decoration: none; }
.chapeau a:hover { text-decoration: underline; }

/* /!\ Le nombre de colonnes est DECIDE, grille par grille, et n'est plus laisse a
   `auto-fit`. Avec `auto-fit`, c'est la LARGEUR DE LA FENETRE qui choisissait :
   la grille de quatre cartes de « Entrer et sortir » rendait 3 + 1, la quatrieme
   seule sur sa ligne avec un grand vide a droite, et cet orphelin apparaissait ou
   disparaissait selon l'ecran du client. Trois colonnes est le DEFAUT ; une grille
   qui n'a pas de quoi les remplir declare `.groupes--2` ou `.groupes--1`. C'est le
   gabarit qui sait combien de cartes il pose, pas la feuille de style. */
.groupes {
  display: grid; gap: 16px;
  /* /!\ `minmax(0, 1fr)` et JAMAIS `1fr` : sans le zero, une carte qui porte un
     tableau ou un champ large refuse de retrecir et deborde de la grille. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* /!\ `stretch` et non `start` : avec `start`, les cartes d'une meme ligne
     gardent chacune sa hauteur et leurs bords bas font un escalier. C'est le
     reproche numero un du fondateur, « il y a des encadres qui ne sont pas
     alignes ». Etirees, elles se terminent toutes sur la meme ligne. */
  align-items: stretch;
}
.groupes--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.groupes--1 { grid-template-columns: minmax(0, 1fr); }

/* /!\ Les bornes tiennent compte du `zoom: 1.1` du corps ET de la barre laterale :
   une fenetre de 1280 points reels ne laisse que ~884 points a la grille
   (1280 / 1,1 - 200 de barre - 80 de marges), pas 1280. Trois colonnes n'y
   tiendraient qu'a ~295 points chacune, sous le confort de 330 points de la
   maquette d'origine. D'ou la bascule a 1400 et non a 1100.
   /!\ Chaque bloc REDECLARE `.groupes--1` : les deux selecteurs ont la meme
   specificite, et sans ce rappel la regle du media, ecrite plus bas, gagnerait et
   rendrait deux colonnes a une grille qui n'a qu'une carte. */
@media (max-width: 1400px) {
  .groupes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .groupes--1 { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 760px) {
  .groupes, .groupes--2, .groupes--1 { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   LES REGLAGES D'UN TRADER N'ONT QU'UNE COLONNE (2026-09-12)
   ------------------------------------------------------------
   Demande du fondateur, mot pour mot : « je mettrai plutot les blocs les uns en
   dessous des autres, toujours pas cote a cote, parce que ca cree des espaces
   vides quand deux blocs ne font pas la meme taille ».

   /!\ **C'EST LE CONTRAIRE DU REMEDE DE 2026-08-29**, et c'est assume. A
   l'epoque, deux cartes d'une meme rangee gardaient chacune sa hauteur et leurs
   bords bas faisaient un escalier ; on les a etirees (`align-items: stretch`)
   pour qu'elles finissent sur la meme ligne. Mais etirer ne remplit pas : la
   carte la plus courte gagne un grand vide sous son dernier champ, et c'est CE
   vide-la que le fondateur voit. Deux cartes cote a cote ne peuvent pas etre a
   la fois alignees en bas et remplies ; empilees, la question ne se pose plus.

   /!\ **LA REGLE VISE LA PAGE, PAS LES CARTES.** Elle porte sur le conteneur
   `.wrap--reglages` : une grille ajoutee demain y tombe aussi, sans que personne
   ait a y penser. Corriger a la place les cinq `.groupes--2` du gabarit aurait
   laisse la sixieme revenir a deux colonnes en silence — c'est exactement le
   genre d'oubli que ce depot paie a repetition.

   /!\ Elle ne touche PAS les autres ecrans : le profil garde sa grille a deux
   colonnes, personne ne s'en est plaint.
   ============================================================ */
.wrap--reglages .groupes,
.wrap--reglages .groupes--2,
.wrap--reglages .groupes--1 { grid-template-columns: minmax(0, 1fr); }

/* /!\ **ET LES CHAMPS CESSENT DE S'ETIRER AVEC LA CARTE.** Empiler les cartes les
   elargit a toute la page, et `auto-fit, minmax(150px, 1fr)` etirait chaque champ
   d'autant : mesure du jour, le champ « validite, en minutes » faisait **866 px de
   large pour y taper `5`**, ses voisins 425, et deux autres 98. C'est la seconde
   moitie de la remarque du fondateur — « les espaces entre les paragraphes et les
   champs ne sont pas toujours justes ».

   /!\ Une borne HAUTE a la cellule, pas une largeur fixe : les listes deroulantes
   de cet ecran portent des phrases entieres (« Toujours a la distance reglee
   ci-dessus (par defaut) ») et se tronqueraient a 240 px. Le nombre, lui, est borne
   a part, plus court : un champ ou l'on tape deux caracteres n'a aucune raison
   d'etre plus large qu'une liste.

   /!\ `justify-content: start` : sans lui, `auto-fit` REPARTIT l'espace restant
   entre les colonnes, et deux champs d'une meme rangee se retrouvent separes par
   un fosse au milieu de la carte. */
/* /!\ **`1fr`, PAS une borne haute sur la CELLULE.** Premiere version : des
   cellules bornees a 520 px. Effet mesure a l'ecran — dans une carte de 630 px,
   une seule cellule tenait, et « Perte maximale du jour » / « Gain maximal du
   jour », deux champs courts qui allaient cote a cote, se sont EMPILES : la page
   « Combien il risque » est passee de 2 015 a 2 608 px. Le fondateur demandait
   d'empiler les BLOCS, pas les champs.
   On borne donc le CHAMP (plus bas), jamais sa cellule : les cellules continuent
   de se partager la largeur, et le champ s'aligne a gauche dans la sienne. */
.wrap--reglages .groupe .champs {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
/* /!\ **LES CHAMPS D'UNE MEME RANGEE COMMENCENT A LA MEME HAUTEUR.** Vu a l'ecran
   sur « En nombre » : « Nombre de trades maximum » tient sur une ligne, ses deux
   voisines (« … gagnants maximum », « … perdants maximum ») sur deux — les trois
   cases se retrouvaient a trois hauteurs differentes, en escalier. L'etiquette
   prend donc la place disponible et pousse sa case en bas de la cellule ; les
   cellules d'une rangee ayant deja la meme hauteur, les cases s'alignent. */
.wrap--reglages .groupe .champs > .champ { display: flex; flex-direction: column; }
.wrap--reglages .groupe .champs > .champ > .etiquette { flex: 1 0 auto; }

/* /!\ **LA LONGUEUR DE LIGNE EST BORNEE, ET C'EST LA CONTREPARTIE DE L'EMPILEMENT.**
   Une carte empilee prend toute la page : ~1 050 px a 1 280 de fenetre. Les
   paragraphes s'y etalaient sur 110 caracteres et plus, la ou ils en faisaient 60
   quand les cartes etaient cote a cote. C'est ce que le fondateur voit quand il dit
   « ce n'est pas tres esthetique » : rien ne se chevauche, la LIGNE est trop longue,
   l'oeil perd le debut de la suivante.

   /!\ En `ch` et non en px : l'unite suit la police. Le levier de confort de ce
   depot est desormais la TYPOGRAPHIE (cf. le commentaire de `--echelle`) — une
   borne en pixels se serait desaccordee au premier cran de police.

   /!\ Ca borne aussi l'endroit ou l'infobulle atterrit : sur une ligne pleine
   largeur, le petit rond se retrouvait SEUL sur la ligne suivante, sous un
   paragraphe, comme un point tombe du texte. */
.wrap--reglages .s-quoi,
.wrap--reglages .groupe-sous,
.wrap--reglages .saisie-note,
.wrap--reglages .groupe-intertitre-quoi { max-width: 72ch; }
/* /!\ **TOUS les champs, pas seulement ceux d'une `.champs`.** La moitie des
   `.champ` de cet ecran sont des enfants DIRECTS de la carte (les listes du stop et
   de l'objectif, par exemple) : bornes par la grille seulement, elles gardaient
   leurs 866 px pendant que leurs voisines tombaient a 240. `textarea` est exclu —
   une zone de texte a besoin de toute la largeur. */
/* 520 et non 420 : mesure a l'ecran, « Toujours a la distance reglee ci-dessus
   (par defaut) » se tronquait a 420. Une borne qui coupe le libelle d'une option
   est pire que pas de borne — on ne sait plus ce qu'on a choisi. */
.wrap--reglages .groupe .saisie:not(textarea) { max-width: 520px; }
.wrap--reglages .groupe .saisie[type="number"] { max-width: 240px; }
/* /!\ MÊME SURFACE QUE `.encadre` (fondateur, 2026-09-11 : « pour les encadrés
   utilise le même style que pour les encadrés sur les pages quand il n'y a pas
   d'infos »). C'était un aplat blanc à 2 % dans un liseré faible, là où le reste
   de l'application emploie le fond dégradé, le liseré fort et le halo violet.

   /!\ `.encadre` et NON `.carte`, et la charte dit pourquoi quelques centaines
   de lignes plus haut : la carte se soulève au survol parce qu'elle est un OBJET
   qu'on manipule ; l'encadré contient des RÉGLAGES qu'on lit et qu'on remplit,
   et dix boîtes qui bougent sous la souris rendent un formulaire illisible. Un
   `.groupe` est un formulaire. Il prend donc la surface, pas l'animation.

   /!\ Le rembourrage reste PLUS SERRÉ que celui d'un encadré (18/20 contre
   26/28) : trois `.groupe` tiennent côte à côte dans une grille, un encadré
   occupe la largeur. */
.groupe {
  position: relative;
  padding: 18px 20px 20px;
  background:
    radial-gradient(600px 300px at 50% -10%, rgba(139,127,224,0.15), transparent 60%),
    linear-gradient(180deg, rgba(26,29,39,0.92) 0%, rgba(20,22,31,0.92) 100%);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow:
    0 30px 80px -20px rgba(0,0,0,0.6),
    0 0 0 1px rgba(255,255,255,0.04) inset;
}
.groupe-titre {
  font-family: var(--font-sans); font-size: var(--text-md); font-weight: 600;
  color: var(--foreground); margin: 0 0 4px;
}
.groupe-sous {
  margin: 0 0 16px;
  font-size: var(--text-sm); line-height: 1.55; color: var(--muted-foreground);
}
.groupe-sous:last-child { margin-bottom: 0; }
.groupe-sous strong { color: var(--foreground); font-weight: 500; }
/* Une phrase de carte qui n'OUVRE pas la carte (2026-08-29).

   `.groupe-sous` n'a qu'une marge BASSE, et c'est juste : elle suit presque
   toujours le titre, qui pose deja l'air au-dessus. L'application en pose
   TRENTE-SEPT (`abonnement.html` 3, `user_profile.html` 13,
   `user_trader_edit.html` 21, compte du 2026-08-29). Trente-cinq suivent bien
   leur titre. Les DEUX autres :

     - le pave de « Site public » suit une CASE A COCHER : rien ne l'en ecartait,
       il lui collait, et c'est ce modificateur qui l'en ecarte ;
     - le lien vers la console d'Anthropic (`user_trader_edit.html`) suit une
       autre `.groupe-sous`, et se RAPPROCHE d'elle expres, par un
       `margin-top:-8px` pose a la main : la phrase et son lien se lisent comme
       un seul bloc. Defaut inverse, remede inverse ; ce modificateur-ci ne la
       concerne pas.

   /!\ La marge de `.groupe-sous` n'est PAS touchee : la changer deplacerait les
   trente-six autres, dont aucune n'a de defaut. C'est un modificateur qui porte
   le cas particulier, et il porte un nom a lui : une classe partagee par deux
   usages est le piege le plus cher de ce depot.

   /!\ IL NE SE POSE QUE SUR UNE `.groupe-sous`. `.case` est en `inline-flex` et
   n'a aucune marge, donc l'ecran des reglages ecarte deux de ses cases par un
   `style=` pose a la main (`user_trader_edit.html`, `margin-top:16px` et
   `margin-bottom:16px`). Ce ne sont PAS des candidats a `--suite` : le poser sur
   une `.case` serait mettre le modificateur d'un objet sur un AUTRE objet, la
   classe partagee que le paragraphe ci-dessus interdit, et sur la seconde ce
   serait de toute facon sans effet, `--suite` n'ayant qu'un `margin-top`. Ils
   restent en ligne parce qu'ils sont DEUX, uniques, a deux valeurs opposees :
   une classe par emploi ne serait que du bruit. Le jour ou l'un des deux se
   repete, le remede est un modificateur de LEUR objet (`.case--…`), jamais
   celui-ci. */
.groupe-sous--suite { margin-top: 16px; }
/* Un groupe qui ne porte qu'un ou deux champs les empile : ses colonnes sont deja
   celles de la grille des groupes. */
.groupe .champs {
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px;
}
/* Un groupe LARGE, pour ce qui ne se coupe pas en deux (un selecteur suivi de ses
   deux montants deduits).
   /!\ Il prend toute la ligne, donc il se pose en FIN de grille SEULEMENT. Pose au
   milieu, il coupe le flux : les cartes qui le suivent repartent d'une ligne neuve
   et rouvrent plus bas l'orphelin qu'on vient de fermer. */
.groupe--large { grid-column: 1 / -1; }
.groupe--large .champs { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* UNE LIGNE DE L'EDITEUR DES DONNEES : une seance, un graphique, une lecture.
   /!\ ELLE PORTE AUSSI `.champs`, QUI EST UNE GRILLE a colonnes de 240 px minimum —
   et c'est ce qui la cassait : le petit bouton « x » occupait une cellule entiere et
   s'etalait en BARRE GRISE sur sa propre ligne, sous chaque graphique. Vu en regardant
   l'ecran, pas en lisant le HTML. Une ligne d'objets se lit de gauche a droite : c'est
   une rangee souple, pas une grille.
   /!\ `align-items: flex-end` pour que les champs s'alignent par le BAS : ceux qui
   portent un libelle au-dessus sont plus hauts que les autres. */
.champs.ligne-objet {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
}
.champs.ligne-objet .saisie { width: auto; min-width: 0; }
.champs.ligne-objet > .liste { flex: 0 0 auto; }

/* Le bouton qui RETIRE une ligne : discret, carre, au bout de sa rangee. */
.oter-ligne {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid var(--bord, rgba(255, 255, 255, .12));
  background: transparent;
  color: var(--texte-doux, #8b95a7);
  cursor: pointer;
  line-height: 1;
}
.oter-ligne:hover { color: var(--danger, #ff6b6b); border-color: currentColor; }

/* Le bouton qui AJOUTE une ligne : un lien d'action, pas un pave. */
.ajouter-ligne {
  margin-top: 6px;
  padding: 6px 12px;
  border-radius: 8px;
  border: 1px dashed var(--bord, rgba(255, 255, 255, .18));
  background: transparent;
  color: var(--texte-doux, #8b95a7);
  cursor: pointer;
  font-size: var(--text-base);
}
.ajouter-ligne:hover { color: var(--texte, #e6e9ef); border-style: solid; }

/* Un REGLAGE DE LECTURE dans l'editeur des donnees : son libelle au-dessus de son
   champ, compact, pour qu'une ligne en porte plusieurs sans se casser.
   /!\ La classe `.mini` etait deja posee dans le gabarit et n'existait NULLE PART
   ici — elle habillait donc deux champs, en apparence par hasard. Depuis le
   2026-09-05 chaque reglage du catalogue a son champ, donc une ligne peut en porter
   six : sans regle, ils s'alignaient bout a bout sans libelle lisible.
   /!\ `[hidden]` porte `display:none !important` plus haut dans ce fichier, donc un
   reglage que le type ne porte pas disparait vraiment — c'est ce qui evite qu'une
   ligne de lecture affiche vingt et un champs. */
.mini {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--text-sm);
  color: var(--texte-doux, #8b95a7);
  white-space: nowrap;
}

/* ============================================================
   La BARRE D'ENREGISTREMENT (2026-08-17)
   ============================================================
   Demande du fondateur : « c'est un peu flou, le fait d'enregistrer les parametres
   a un endroit ». Le bouton etait au milieu d'une page de six cents lignes de
   champs, et on continuait de defiler apres lui. Il suit maintenant le lecteur.

   /!\ `position: fixed`, PAS `sticky`, et c'est mesure. Ici c'est la FENETRE qui
   defile (`.main` n'a ni hauteur ni `overflow`), et `.main` porte un
   `padding-bottom` de 64 px : une barre collante cesserait de coller 64 px avant le
   bas de page et se mettrait a defiler avec le contenu sur le dernier ecran.

   /!\ Elle est posee DANS le formulaire, malgre les apparences. Un element fixe se
   voit au meme endroit ou qu'il soit dans le document, et y etre lui donne deux
   choses gratuitement : son bouton envoie le formulaire sans attribut `form`, et le
   champ « motif » qu'elle porte part avec le reste.

   /!\ Le decalage a gauche suit la barre laterale (200 px), qui disparait sous
   1024 px comme partout ailleurs dans l'application. Meme borne, une seule requete
   media.

   /!\ La fenetre de confirmation (`dialog.verre`) vit dans la couche superieure du
   navigateur : elle passe au-dessus sans qu'on ait a arbitrer un `z-index`.

   /!\ La barre doit rester DANS `.wrap`, jamais enfant direct de `.main`.
   `admin_base.html` pose `.main > * { position: relative }` avec la MEME specificite
   que la regle ci-dessous, dans un `<style>` charge APRES cette feuille : en enfant
   direct de `.main`, la barre redeviendrait `relative` et se contenterait de flotter
   en bas du flux. Le defaut ne se verrait meme pas sur une page courte.
   Bonus de ce placement : `.wrap` porte `z-index: 1`, donc un contexte
   d'empilement ; la barre passe automatiquement SOUS la barre d'application et sous
   le menu lateral mobile, quel que soit son propre `z-index`.

   /!\ Le jour ou quelqu'un pose `transform`, `filter`, `backdrop-filter` ou
   `contain` sur `.main` ou `.wrap`, la barre s'ancrera a ce conteneur au lieu de
   l'ecran et cessera d'etre fixe. Aucun test HTML n'attrape cette casse.

   /!\ Sur un telephone, ouvrir le clavier virtuel pour saisir le motif peut masquer
   la barre : le navigateur ne recalcule le `fixed` qu'en fin de geste. Le motif est
   facultatif, l'enregistrement ne depend pas de lui. Les clients travaillent sur le
   poste Windows qui fait tourner NinjaTrader ; si le tactile devient un usage reel,
   c'est ici qu'il faudra sortir le motif de la barre. */
.barre-enregistrement {
  position: fixed;
  left: 200px; right: 0; bottom: 0;
  z-index: 45;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 40px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  background: rgba(14, 16, 24, 0.94);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border-top: 1px solid var(--border-strong);
  box-shadow: 0 -16px 44px -18px rgba(0,0,0,0.75);
}
@media (max-width: 1023px) {
  .barre-enregistrement { left: 0; padding: 10px 16px; }
}
/* La barre ne se montre QUE s'il y a quelque chose a enregistrer (2026-08-18,
   demande du fondateur). Elle occupait 132 points en bas de chaque ecran de
   reglages, en permanence, pour dire « tout est enregistre » — c'est-a-dire pour
   ne rien demander.

   /!\ `data-cachable` est pose par `enregistrer.js`, JAMAIS ecrit dans le gabarit.
   Sans JavaScript, rien ne detecte une modification : la barre doit alors rester
   visible en permanence, sinon le formulaire n'a plus de bouton pour l'envoyer.
   C'est le meme raisonnement que le repli du perimetre de « Mes trades » et que
   les onglets d'un formulaire — le script RETIRE, il ne donne jamais l'acces. */
.barre-enregistrement[data-cachable] { display: none; }
.barre-enregistrement[data-cachable][data-modifie="oui"] { display: flex; }
/* La reserve sous la page : sans elle, la barre recouvre le dernier champ. Elle est
   posee sur `.main` et non sur la barre, parce que c'est la PAGE qui doit rendre de
   la place, pas la barre qui doit en prendre moins.
   /!\ Elle suit la barre : reserver 132 points sous une barre absente laisserait un
   trou permanent au bas de chaque page de reglages — on aurait deplace le probleme
   au lieu de le resoudre. */
body:has(.barre-enregistrement:not([data-cachable])) .main,
body:has(.barre-enregistrement[data-modifie="oui"]) .main { padding-bottom: 132px; }
/* Les bulles flottantes (l'aide, « Analyser ») REMONTENT au-dessus de la barre tant
   qu'elle est montree (25/09/2026). Capture du fondateur : son « Enregistrer » etait
   cache derriere elles, on n'en lisait que « …egistrer ».
   /!\ `body:has(...)` gagne sur `.chat-lanceur` et `.kb-pastille` (chat.css,
   clavier-bulle.css) par la specificite, pas par l'ordre des feuilles. */
body:has(.barre-enregistrement:not([data-cachable])) .chat-lanceur,
body:has(.barre-enregistrement[data-modifie="oui"]) .chat-lanceur,
body:has(.barre-enregistrement:not([data-cachable])) .kb-pastille,
body:has(.barre-enregistrement[data-modifie="oui"]) .kb-pastille { bottom: 96px; }
body:has(.barre-enregistrement:not([data-cachable])) .kb-panneau,
body:has(.barre-enregistrement[data-modifie="oui"]) .kb-panneau { bottom: 164px; }
@media (max-width: 720px) {
  body:has(.barre-enregistrement:not([data-cachable])) .chat-lanceur,
  body:has(.barre-enregistrement[data-modifie="oui"]) .chat-lanceur,
  body:has(.barre-enregistrement:not([data-cachable])) .kb-pastille,
  body:has(.barre-enregistrement[data-modifie="oui"]) .kb-pastille { bottom: 132px; }
}

/* L'etat. Muet quand rien n'a change, ocre des qu'un champ bouge : c'est le meme
   ocre que partout ailleurs pour « attention, lis ca » (`--warning-text`). */
.barre-enregistrement .etat {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--text-base); color: var(--muted-foreground);
  margin-right: auto;
}
.barre-enregistrement .etat::before {
  content: ""; width: 7px; height: 7px; flex: 0 0 auto;
  border-radius: 50%; background: var(--disabled-foreground);
  transition: background var(--motion-base) var(--ease-out);
}
.barre-enregistrement[data-modifie="oui"] .etat { color: var(--warning-text); }
.barre-enregistrement[data-modifie="oui"] .etat::before { background: var(--warning); }
/* Ce qui n'a de sens qu'une fois quelque chose modifie : le motif du changement, et
   le renoncement. Les montrer en permanence, c'est proposer d'annuler ce qu'on n'a
   pas fait. */
.barre-enregistrement .si-modifie { display: none; }
.barre-enregistrement[data-modifie="oui"] .si-modifie { display: inline-flex; }
.barre-enregistrement[data-modifie="oui"] .sinon-modifie { display: none; }
/* Le motif est une PHRASE : chasse proportionnelle, comme les listes et la
   recherche. En chasse fixe, son texte d'invite depassait deja du champ. */
.barre-enregistrement .saisie {
  max-width: 280px;
  font-family: var(--font-sans); font-size: var(--text-base);
  padding: 10px 14px;
}

/* ============================================================
   Les RANGEES : une liste d'elements courts, qu'on parcourt (2026-08-17)
   ============================================================
   Pour ce qui s'accumule et se relit vite : des regles, des versions, des
   activites. Une rangee tient sur une ou deux lignes, porte son RANG quand
   l'ordre compte, et garde ses actions a droite.

   /!\ Les actions restent VISIBLES. L'habitude de les cacher jusqu'au survol
   (cf. `.data-table .row-actions` dans cette meme feuille) les rend introuvables
   au doigt : sur un ecran tactile, il n'y a pas de survol. Elles sont donc
   discretes, pas cachees.

   /!\ Le texte est limite a deux lignes. Une regle peut faire un paragraphe ;
   vingt paragraphes empiles rendent la liste impossible a parcourir, ce qui est
   precisement le probleme qu'on vient corriger. Le texte entier s'affiche au
   depliage. */
/* ══ LES SIX TEINTES DE REPERAGE ══════════════════════════════════════════════════
   La couleur par laquelle l'oeil retrouve QUELQU'UN ou QUELQUE CHOSE dans une liste,
   avant d'avoir lu son nom. Nees le 2026-09-15 pour les cartes de filleuls
   (`.fil-t1..6`, declarees dans `user_parrainage.html`), remontees ici le 2026-09-16
   quand l'avatar d'un trader a eu besoin des memes.

   /!\ **ELLES NE SE DECLARENT QU'ICI.** Recopier six couleurs dans un second gabarit,
   c'est se garantir que le jour ou l'une bouge, deux ecrans voisins montreront deux
   violets differents pour la meme personne. `user_parrainage.html` a donc PERDU ses
   `.fil-t*` le meme jour : ses cartes portent `.teinte-N` comme tout le monde, aux
   memes valeurs, au caractere pres.

   /!\ Elles teintent SANS remplir : voile a ~18 %, texte clair par-dessus. C'est le
   principe du theme sombre de ce depot (cf. `.drawer-flash-*`) -- un aplat clair
   resterait un trou blanc au milieu d'une page noire.

   /!\ Le NUMERO se deduit du nom quand personne n'a choisi (`avatar.teinte`,
   `filtres_parrainage.teinte`, meme regle, sur le nom SANS ACCENT) : deux homonymes
   ont la meme couleur, et personne n'en change parce qu'on a retrie la liste. */
.teinte-1 { background: rgba(180,168,255,0.20); color: #C9BEFF; }
.teinte-2 { background: rgba(120,200,180,0.18); color: #8FD6C2; }
.teinte-3 { background: rgba(230,170,120,0.18); color: #EEBC8C; }
.teinte-4 { background: rgba(140,180,240,0.18); color: #A9C9F7; }
.teinte-5 { background: rgba(220,150,190,0.18); color: #EBA9CE; }
.teinte-6 { background: rgba(200,200,140,0.18); color: #DCDCA0; }

/* ══ LE VERDICT D'UNE VERSION : la coche et la croix (2026-09-16) ════════════════
   Demande du fondateur : « il faudrait que, sur les versions, je puisse faire un
   systeme de check ou de croix pour tout simplement dire : OK, cette version, je la
   valide. Cette version, je ne la valide pas. »

   /!\ **ELLE VIT DANS LA CHARTE PARCE QU'ELLE SERT A DEUX ENDROITS** : la liste des
   versions, et le menu qui les propose sur les quatre ecrans d'un trader. Declaree
   dans un gabarit, la seconde se serait affichee SANS COULEUR -- un rond gris a cote
   d'un rond vert, sur deux ecrans voisins, pour le meme fait.

   /!\ La couleur ne suffit JAMAIS seule : partout ou cette marque parait, un badge
   ecrit ou un `title` dit le mot. Un verdict rendu par la seule teinte est muet pour
   qui ne la distingue pas. */
.v-marque {
  display: inline-grid; place-items: center; flex: none;
  width: 17px; height: 17px; border-radius: 50%;
}
.v-marque svg { width: 11px; height: 11px; }
.v-marque--ok  { background: rgb(74 222 128 / .16);  color: var(--success); }
.v-marque--non { background: rgb(248 113 113 / .16); color: var(--error); }

/* ══ L'AVATAR D'UN TRADER (2026-09-16, demande du fondateur) ══════════════════════
   « Un icone qui soit dans le style de l'application et qui sera en fait sa photo de
   profil pour le reperer facilement quand on a plusieurs traders. »

   /!\ **UN CARRE ARRONDI, ET PAS UN ROND** -- la difference est le sens. Un rond,
   dans cette application, c'est une PERSONNE (la pastille d'un filleul, l'avatar d'un
   contact). Un trader n'en est pas une : c'est une machine que le client a reglee, et
   lui donner la forme reservee aux humains brouillerait la seule distinction que ces
   ecrans doivent tenir. Meme palette, forme differente.

   /!\ **LA MEME BOITE PORTE UN EMOJI OU UNE IMAGE** (second jet, 2026-09-16 au soir).
   La teinte reste dans les deux cas : c'est le carre qui les porte tous les deux, et
   c'est ce que le fondateur a demande (« qui se mettra sous forme d'icone, avec la
   couleur de fond »). Une image transparente pose donc sur sa couleur, et une image
   opaque la recouvre — les deux sont justes. */
.trd-avatar {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 38px; height: 38px; border-radius: var(--radius-md);
  border: 1px solid rgba(255,255,255,0.10);
  overflow: hidden;                      /* l'image suit le rayon du carre */
}
/* /!\ `object-fit: cover` : le serveur ne redimensionne RIEN (pas de Pillow dans ce
   depot, cf. `avatar_fichier`), donc c'est le navigateur qui cadre. Sans lui, une
   image rectangulaire s'etirerait -- un visage ovale sur chaque carte. */
.trd-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* L'emoji occupe la boite sans la deborder. `line-height: 1` parce qu'un emoji porte
   des metriques a lui, et l'interligne du corps de page le decalait vers le bas. */
.trd-emoji { font-size: calc(21px * var(--echelle-texte)); line-height: 1; }
/* Le meme, au format d'un en-tete de page : il accompagne un titre, il ne le porte pas. */
.trd-avatar--sm { width: 30px; height: 30px; border-radius: var(--radius-sm); }
.trd-avatar--sm .trd-emoji { font-size: calc(17px * var(--echelle-texte)); }
/* LE PORTRAIT (30/09/2026, visages uniques) : sur la carte de « Mes traders », le visage
   se reconnaît à sa taille, rond, cerné de la couleur du trader quand la carte la
   porte (`--tr-teinte`). */
.trd-avatar--portrait { width: 64px; height: 64px; border-radius: 50%;
  border: 2px solid var(--tr-teinte, rgba(255,255,255,0.18)); }
.trd-avatar--portrait .trd-emoji { font-size: calc(32px * var(--echelle-texte)); }
/* « Trader IA » à côté de chaque visage : un visage presque photo ne doit pas laisser
   croire qu'une personne trade pour le client. */
.trd-ia { display: inline-block; margin-left: 8px; padding: 1px 6px; border-radius: 4px;
  font: 600 10px/1.5 var(--font-mono); letter-spacing: .08em; vertical-align: middle;
  color: var(--muted-foreground); border: 1px solid var(--border); }

/* LE DECLENCHEUR, sur la ligne de reglage : la pastille telle qu'elle est, et le mot
   du geste. Montrer l'avatar courant evite d'ouvrir la fenetre pour verifier. */
/* /!\ **L'AVATAR Y DEBORDAIT DU LISERE** (2026-09-16, capture du fondateur : « dans
   les parametres l'icone deborde un peu »). Il etait a sa taille pleine (38 px) dans
   une pilule dont le rembourrage ne lui laissait pas la place, et ses coins carres
   traversaient l'arrondi. Deux corrections, et il fallait les deux : le format court
   (`--sm`, 30 px, pose par le gabarit) et un rembourrage qui l'entoure vraiment. */
.av-declencheur {
  display: inline-flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 6px 14px 6px 6px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: transparent;
  color: var(--muted-foreground); font: inherit; font-size: var(--text-body-sm);
  line-height: 1;
}
.av-declencheur:hover { color: var(--foreground); border-color: var(--border-strong); }
.av-declencheur:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* ── LA FENETRE DE CHOIX ──────────────────────────────────────────────────────
   /!\ Plus large que la fenetre standard : un clavier d'emojis a moins de huit
   colonnes se lit comme une liste, pas comme un clavier.

   /!\ **TROIS ETAGES, ET C'EST LE CORPS SEUL QUI DEFILE** (2026-09-16, capture du
   fondateur : « je peux defiler tout en bas alors qu'il y a rien »). La fenetre
   entiere defilait, en-tete et boutons compris : on descendait dans le vide pour
   retrouver « Enregistrer ». Un bouton de validation qu'il faut aller chercher est
   un bouton qu'on ne trouve pas.

   /!\ `max-height` sur la fenetre ET `min-height: 0` sur le corps : sans le second,
   un enfant flex refuse de retrecir sous la taille de son contenu, donc le corps
   pousse la fenetre au-dela de l'ecran et c'est la PAGE qui defile — exactement le
   defaut qu'on repare. C'est la moitie qu'on oublie.

   /!\ `padding: 0` sur la fenetre et le rembourrage sur chaque etage : sinon le
   corps defile SOUS le rembourrage du bas, et la derniere ligne d'emojis reste
   coupee en deux quel que soit le defilement. */
/* /!\ **LA PAGE DERRIERE NE DEFILE PLUS** tant que ces deux fenetres sont ouvertes.
   Le fondateur decrivait « je peux defiler tout en bas alors qu'il y a rien » : la
   moitie du defaut etait la fenetre elle-meme (reparee ci-dessous), l'autre moitie
   est que `showModal()` ne bloque PAS le defilement du document — on croit faire
   defiler le contenu de la fenetre et c'est la page du dessous qui bouge.

   /!\ **Porte sur CES fenetres, pas sur toutes.** Le defaut vaut pour chaque modale
   de l'application, mais l'elargir a `dialog[open]` changerait le comportement de
   sept ecrans qu'on n'a pas regardes aujourd'hui. Note comme trouvaille, pas corrigee
   en passant. */
body:has(.av-fenetre[open]),
body:has(#version-fenetre[open]) { overflow: hidden; }

/* /!\ **`[open]` SUR LA REGLE QUI POSE `display`, ET C'EST OBLIGATOIRE.** Le
   navigateur rend un `<dialog>` ferme invisible par `dialog:not([open]){display:none}`,
   une regle de sa feuille a lui — de MEME specificite que `dialog.av-fenetre`. A egalite,
   la feuille de l'auteur gagne : un `display: flex` ecrit sans `[open]` fait donc paraitre
   la fenetre ENTIERE dans le flux de la page, fermee, avec ses 147 emojis. Ce n'est pas
   une hypothese : `test_dialogues_fermes` l'a attrape ici meme, et il existe parce que le
   depot l'a deja paye. Le reste (largeur, plafond, rembourrage) peut rester sans `[open]`,
   il ne rend rien visible. */
dialog.av-fenetre {
  width: min(560px, calc(100vw - 32px));
  max-height: calc(var(--fenetre) - 64px);
  padding: 0;
  overflow: hidden;                      /* le rayon coupe les etages */
}
dialog.av-fenetre[open] { display: flex; flex-direction: column; }
.av-fenetre form {
  margin: 0; display: flex; flex-direction: column; min-height: 0; max-height: 100%;
}
.av-tete {
  display: flex; align-items: center; gap: 14px; flex: none;
  padding: 20px 24px 14px; border-bottom: 1px solid var(--border);
}
.av-tete .dialogue-titre { margin: 0; }
.av-tete-quoi {
  margin: 2px 0 0; font-size: var(--text-body-sm); color: var(--muted-foreground);
}
/* L'apercu : la MEME pastille que les cartes, en plus grand. C'est le seul endroit
   ou le client voit son choix avant d'enregistrer. */
.av-apercu { width: 46px; height: 46px; }
.av-apercu .trd-emoji { font-size: calc(26px * var(--echelle-texte)); }
.av-erreur { flex: none; margin: 14px 24px 0; }
/* La barre de recherche : FIXE, entre l'en-tete et le clavier. */
.av-barre { flex: none; padding: 14px 24px 10px; }
.av-recherche { width: 100%; }
/* Le corps ne porte que le clavier, et c'est le seul etage qui defile. */
.av-corps {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 0 24px 14px;
}
/* Le sous-pied : deux reglages d'UNE ligne, toujours sous les yeux. Les laisser dans
   le corps obligeait a traverser 147 emojis pour changer une couleur. */
.av-sous-pied {
  flex: none; padding: 12px 24px 0; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 10px;
}
.av-sous-pied .av-bloc-champ { margin: 0; padding: 9px 12px; }
.av-pied {
  flex: none; display: flex; gap: 8px; justify-content: flex-end;
  padding: 14px 24px; border-top: 1px solid var(--border);
}

/* ── LA FENETRE D'ENREGISTREMENT D'UNE NOUVELLE VERSION (2026-09-16) ──────────
   Elle NOMME la version qui va naitre et recoit son commentaire. Deux champs, donc
   deux etiquettes : une fenetre qui demande d'ecrire n'est plus une confirmation. */
.vf-champ { margin: 14px 0 0; display: flex; flex-direction: column; gap: 5px; }
.vf-champ label { font-size: var(--text-body-sm); color: var(--foreground); }
.vf-quoi { margin: 2px 0 0; font-size: var(--text-xs); color: var(--muted-foreground); }
.av-bloc-champ {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 12px 14px; margin: 0 0 12px;
}
.av-bloc-champ legend {
  padding: 0 6px; font-size: var(--text-xs); font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted-foreground);
}
.av-quoi { margin: 7px 0 0; font-size: var(--text-xs); color: var(--muted-foreground); }

/* ── LE DEPOT D'IMAGE ─────────────────────────────────────────────────────────
   /!\ **UN `<input type=file>` NU EST EN ANGLAIS, et rien ne le traduit** : le
   navigateur dessine « Choose File / No file chosen » avec ses propres couleurs, et
   aucune propriete CSS ne touche a ce texte. Vu a la capture, au milieu d'une fenetre
   entierement francaise. Le seul remede est le motif classique : l'input est CACHE (et
   reste dans le formulaire, donc il s'envoie), un `<label>` l'habille, et un script
   ecrit le nom du fichier choisi a cote.

   /!\ **`sr-only` ET PAS `display:none`** : un input `display:none` reste envoye, mais
   il sort de l'ordre de tabulation et un lecteur d'ecran ne l'annonce plus. Ici il est
   invisible a l'oeil et present pour tout le reste. */
.av-depot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.av-depot-nom {
  font-size: var(--text-body-sm); color: var(--muted-foreground);
  overflow-wrap: anywhere; min-width: 0;
}

/* Le clavier DEFILE, il ne pousse pas la fenetre hors de l'ecran : 147 emojis en neuf
   familles font plus de mille pixels de haut. Le plafond suit la FENETRE pour qu'un
   telephone en montre autant qu'il peut, et pas une hauteur fixe pensee pour un
   ordinateur.

   /!\ **`var(--fenetre)` ET SURTOUT PAS `46vh`** : la charte pose un facteur
   d'echelle, et toute hauteur calee sur la fenetre doit passer par cette variable,
   sinon elle deborde de l'ecran du facteur. Un test l'epingle, et il a mordu ici. */
/* /!\ **IL N'A PLUS DE PLAFOND A LUI** (2026-09-16) : c'est `.av-corps` qui defile
   maintenant, et deux zones de defilement imbriquees donnaient une molette qui ne
   savait pas laquelle bouger. Le clavier se deroule, le corps le porte. */
.av-clavier { padding-right: 2px; }
.av-famille + .av-famille { margin-top: 12px; }
.av-famille-nom {
  position: sticky; top: 0; z-index: 1; margin: 0 0 6px; padding: 3px 0;
  background: var(--card);
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted-foreground);
}
/* `auto-fill` et non un nombre de colonnes : la grille se resserre seule sur un
   telephone, sans seconde regle a tenir d'accord avec la premiere. */
.av-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); gap: 4px;
}
.av-rien { margin: 12px 0 0; font-size: var(--text-body-sm); color: var(--muted-foreground); }
/* Une famille dont tous les emojis sont filtres disparait AVEC son intercalaire :
   un titre seul au-dessus du vide se lit comme un trou. */
/* /!\ `[hidden]` NE SUFFIT PAS sur ces deux-la : la regle au-dessus leur pose
   `display: grid`, qui GAGNE sur le `display: none` par defaut de l'attribut.
   Sans ces deux lignes, le filtre de recherche ne cachait rien du tout. */
.av-famille[hidden], .av-case[hidden] { display: none; }

.av-choix { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; border: 0; }
.av-choix input, .av-grille input { position: absolute; opacity: 0; pointer-events: none; }
.av-case {
  display: grid; place-items: center; cursor: pointer;
  width: 38px; height: 38px; border-radius: var(--radius-md);
  border: 1px solid transparent;
  color: var(--muted-foreground);
}
/* /!\ **LE FOND N'EST POSE QUE SUR LES CASES QUI N'ONT PAS DE TEINTE**, et c'est
   indispensable : une case de COULEUR porte la sienne par `.teinte-N`, declaree plus
   haut dans cette feuille. A specificite egale, la derniere gagne -- un fond ecrit ici
   sans exclusion rendait les six pastilles de couleur GRISES, c'est-a-dire un nuancier
   sans couleurs. Vu a la capture, et invisible autrement. */
/* /!\ **UNE CASE D'EMOJI N'A PAS DE FOND AU REPOS**, et c'est ce qui fait un clavier
   plutot qu'une grille de boutons. Vu a la capture : 147 pastilles grises cote a cote
   donnaient un damier ou l'oeil ne distinguait plus les dessins. Le fond ne parait
   qu'au survol et sur le choix -- c'est-a-dire quand il DIT quelque chose. */
.av-case--emoji { font-size: calc(20px * var(--echelle-texte)); line-height: 1; background: transparent; }
.av-case--emoji:hover { background: rgba(255,255,255,0.09); }
.av-case--teinte { width: 30px; height: 30px; border-radius: 50%; }
.av-case--teinte svg { width: 14px; height: 14px; opacity: 0; }
.av-case--teinte:hover { border-color: rgba(255,255,255,0.35); }
.av-choix input:checked + .av-case--teinte svg,
.av-grille input:checked + .av-case svg { opacity: 1; }
.av-choix input:checked + .av-case,
.av-grille input:checked + .av-case {
  border-color: var(--accent); background: var(--accent-wash);
}
.av-choix input:checked + .av-case--teinte { border-color: var(--foreground); }
.av-choix input:focus-visible + .av-case,
.av-grille input:focus-visible + .av-case {
  outline: 2px solid var(--ring); outline-offset: 2px;
}

/* ── LA GALERIE DES VISAGES (30/09/2026) ─────────────────────────────────────
   La fenetre d'avatar s'elargit : des portraits se choisissent a la taille d'un
   visage, pas d'un emoji. `auto-fill` : la grille se resserre seule au telephone. */
dialog.av-fenetre--visages { width: min(760px, calc(100vw - 32px));
  /* /!\ `clip` et non `hidden` (fondateur, capture du 30/09 : l'en-tete coupe en
     haut) : un `hidden` se laisse encore faire defiler par le navigateur quand un
     visage choisi (sa case cachee) prend le focus, et toute la fenetre remontait.
     `clip` n'a aucun defilement : seule la galerie defile. */
  overflow: clip; }
.av-filtres { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.av-filtre { display: flex; flex-wrap: wrap; gap: 6px; }
.av-puce {
  font: inherit; font-size: var(--text-xs); padding: 4px 10px; cursor: pointer;
  border-radius: 999px; border: 1px solid var(--border); background: transparent;
  color: var(--muted-foreground);
}
.av-puce:hover { color: var(--foreground); border-color: var(--border-strong); }
.av-puce[aria-pressed="true"] { background: var(--foreground); color: var(--background);
  border-color: var(--foreground); }
.av-galerie {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 10px;
  /* Les cases cachees se placent DANS la galerie (qui defile), jamais par rapport a
     la fenetre : les faire defiler ne deplace qu'elle. */
  position: relative;
}
.av-galerie input { position: absolute; opacity: 0; pointer-events: none; }
.av-visage {
  display: block; cursor: pointer; border-radius: 50%; padding: 3px;
  border: 2px solid transparent; transition: border-color .12s, transform .12s;
}
.av-visage img { display: block; width: 100%; height: auto; aspect-ratio: 1; border-radius: 50%;
  object-fit: cover; background: #111; }
.av-visage:hover { transform: translateY(-2px); border-color: rgba(255,255,255,0.3); }
.av-galerie input:checked + .av-visage { border-color: var(--accent); }
.av-galerie input:focus-visible + .av-visage { outline: 2px solid var(--ring); outline-offset: 2px; }
/* /!\ `[hidden]` ne suffit pas : `.av-visage` pose `display: block`. */
.av-visage[hidden] { display: none; }
.av-apercu img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }

/* L'image deja posee, et le geste qui la retire. */
.av-image-en-place {
  display: flex; align-items: center; gap: 12px; margin-bottom: 10px; flex-wrap: wrap;
}
.av-retirer {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}

.rangees { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.rangee {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start; gap: 12px;
  padding: 11px 13px;
  background: rgba(255,255,255,0.028);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  transition: border-color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
/* Une rangee qui MENE quelque part (2026-08-17). L'element entier EST le lien : pas de
   surface posee par-dessus.

   /!\ Et c'est pour une raison, pas par gout. `.carte-lien` couvre sa carte par un
   `::after` en `position: absolute`, qui se resout sur le premier ancetre POSITIONNE --
   or `.rangee` ne l'est pas. La surface serait donc remontee jusqu'au premier parent
   positionne, voire au cadre de la page, et aurait recouvert l'ecran entier d'un lien
   invisible. Rendre `.rangee` relative pour la circonstance deplacerait les elements
   absolus des autres ecrans qui l'emploient. */
/* La colonne d'ARGENT d'une rangee : le montant, puis son cumul en legende.

   /!\ Elle remplace un `style="text-align:right;white-space:nowrap"` ecrit en dur dans
   deux gabarits (2026-08-21). Deux copies d'une meme intention finissent par ne plus
   dire la meme chose, et un ecran ne doit porter aucun style local.

   /!\ `white-space: nowrap` est le point qui compte : un montant coupe entre son signe
   et son chiffre (« + » puis « 137.00 $ » a la ligne) se lit comme deux nombres. */
.rangee-argent { text-align: right; white-space: nowrap; }

/* La tete d'un graphique : ce qu'il dit a gauche, de quoi en changer a droite.

   /!\ La bascule est poussee a droite par `margin-left: auto` sur elle-meme, et non
   par un `justify-content: space-between` : la note de gauche est CONDITIONNELLE (elle
   n'existe qu'au cran final), et `space-between` aurait colle le selecteur a gauche des
   qu'elle disparait. Une commande qui change de bord selon l'echelle se cherche. */
.graphe-tete {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  margin: 0 0 12px;
}

/* ============================================================
   LE JOURNAL (prefixe `jr-`) — refonte du 2026-09-14
   ------------------------------------------------------------
   Demande du fondateur : « repense-moi la page journal pour qu'elle soit la plus
   moderne et la plus fonctionnelle possible ». Forme validee sur maquette
   (`/static/maquettes/journal.html`).

   /!\ CE QUI EST ICI N'EST QUE DE LA MISE EN FORME. Le regroupement par seance
   et le repli des abstentions se calculent dans `performance.fil_par_seance`, qui
   est teste sans serveur : une page ne doit pas savoir compter.
   ============================================================ */

/* ══ LA TETE : un seul chiffre en grand, et la bascule du graphique a droite ══ */
.jr-hero {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin: 0 0 4px;
}
.jr-hero-quoi { min-width: 0; }
/* /!\ `tabular-nums` et la police a chasse fixe : un montant qui change de largeur
   en changeant de chiffre fait sauter tout ce qui le suit. */
.jr-hero-v {
  display: block; margin: 2px 0 4px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: calc(40px * var(--echelle-texte)); font-weight: 600; line-height: 1.05;
}
/* La tete du graphique perd sa marge basse quand elle vit DANS la rangee du heros :
   elle n'y surmonte plus rien. */
.jr-hero > .graphe-tete { margin: 0; flex: 0 1 auto; }

/* ══ LE RELEVE : LES CHIFFRES DU JOURNAL, EN TROIS COLONNES ═══════════════════
   Choisi sur maquette par le fondateur (option B, 2026-09-14) : « je n'aime pas les
   gros KPI sur cette page ; je prefere plus de KPI potentiellement, mais plus
   discrets ».

   /!\ **NEUF TUILES ENCADREES SONT DEVENUES SEIZE LIGNES, ET LA PAGE EST PLUS
   BASSE** : les tuiles valaient deux rangees de 85 px, plus le chiffre geant du
   dessus. Ici, une ligne vaut sa hauteur de texte.

   /!\ **LES VALEURS S'ALIGNENT A DROITE, en chasse fixe et en chiffres tabulaires** :
   c'est ce qui les rend comparables en descendant la colonne. Un relevé dont les
   nombres dansent d'une ligne a l'autre ne se lit pas mieux qu'une grille de tuiles.

   /!\ **LE FILET EST SOUS CHAQUE LIGNE, jamais autour du bloc** : c'est lui qui fait
   lire « etiquette ... valeur » comme un couple, et qui remplace le cadre de la tuile
   sans en reprendre le poids. */
/* /!\ **LES COLONNES SONT CELLES DU NAVIGATEUR, PAS UNE GRILLE** (2026-09-14,
   fondateur : « rajoute une propriete pour que les colonnes aient le meme nombre de
   lignes »). C'etaient TROIS listes posees dans une grille, donc trois hauteurs
   differentes — cinq lignes, cinq lignes, huit — et rien ne pouvait les egaliser :
   le contenu de chacune depend du perimetre (l'amplitude se tait sans trade solde,
   « encore en cours » se tait a zero, la colonne d'activite entiere se tait sans
   analyse).

   `columns` REPARTIT une seule liste et EQUILIBRE les hauteurs lui-meme, quelle que
   soit la longueur : c'est la propriete qui fait ce travail, et il n'y en a pas
   d'autre. `270px 3` se lit « trois colonnes au plus, jamais sous 270 px » — sous
   840 px de large il en fait deux, puis une, sans media query.

   /!\ `break-inside: avoid` sur chaque ligne : sans lui, une etiquette reste en bas
   d'une colonne et sa valeur passe en haut de la suivante. */
.releve dl { margin: 0; columns: 270px 3; column-gap: 34px; }
.releve dl > div { break-inside: avoid; }
.releve dl > div {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--border);
}
.releve dt { color: var(--muted-foreground); font-size: var(--text-body-sm); }
.releve dd {
  margin: 0 0 0 auto;
  font-family: var(--font-mono); font-size: var(--text-body);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Un MOT a la place d'un nombre (« - », « Par trader ») : ni chasse fixe, ni aplomb
   de chiffre — sinon il se lit comme un nombre qu'on n'arrive pas a dechiffrer. */
.releve .releve-mot {
  font-family: var(--font-sans); font-size: var(--text-body-sm);
  color: var(--muted-foreground);
}
/* La ligne qui DIT POURQUOI un chiffre se tait. Elle n'a pas de valeur a droite et
   pas de filet : ce n'est pas un chiffre, c'est sa raison. */
.releve .releve-note {
  padding: 7px 0 0; border: 0;
  color: var(--muted-foreground); font-size: var(--text-body-sm); line-height: 1.5;
}
.releve .releve-muet dt { color: var(--disabled-foreground); }
/* /!\ La bulle de survol tient sur la LIGNE entiere (`data-bulle="chiffre"`), comme
   elle tenait sur la tuile : c'est la que vivent « peu fiable », « aucun trade
   solde », « plutot acheteur ». Le curseur le dit. */
.releve dl > div[data-bulle] { cursor: default; }
.releve dl > div[data-bulle]:hover { background: rgba(255,255,255,0.02); }

/* ══ LA BANDE DE CHIFFRES (2026-09-16) ═══════════════════════════════════════
   Fondateur, sur maquette (`/static/maquettes/gains-et-parrainage-chiffres.html`,
   option A) : « je n'aime plus les gros KPI » sur « Mes gains » et « Parrainage ».
   Une rangee de couples libelle / valeur / nuance separes par un filet, SANS cadre.
   Elle passe a la ligne d'elle-meme sur un ecran etroit.
   /!\ Le filet est a GAUCHE de chaque case sauf la premiere : une case qui passe
   a la ligne garde donc un filet devant elle, et c'est voulu — elle reste lisible
   comme une case, pas comme la suite de la precedente. */
.bande { display: flex; flex-wrap: wrap; gap: 12px 22px; margin: 0 0 20px; }
.bande > div { padding: 2px 0 2px 22px; border-left: 1px solid var(--border); min-width: 0; }
.bande > div:first-child { padding-left: 0; border-left: 0; }
.bande-l { display: block; font-size: var(--text-2xs); letter-spacing: .09em;
           text-transform: uppercase; color: var(--muted-foreground); }
.bande-v { display: block; margin-top: 2px; font-family: var(--font-mono);
           font-size: var(--text-base); font-weight: 500; font-variant-numeric: tabular-nums;
           white-space: nowrap; }
.bande-n { display: block; font-size: var(--text-2xs); color: var(--muted-foreground); }

/* ══ LA TETE DE LA LISTE : son titre, et l'echelle a laquelle on la regarde ═══ */
.jr-liste-tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin: 26px 0 12px;
}

/* ══ UNE SEANCE, ET SON FIL ═══════════════════════════════════════════════════
   /!\ L'EN-TETE COLLE EN HAUT pendant qu'on parcourt la seance : sur trente lignes,
   on ne sait plus quel jour on lit. `top: 0` et non une valeur calee sur une barre
   collante — la barre de filtres de cette page ne l'est pas, elle est partagee avec
   d'autres ecrans (decision du fondateur, 2026-09-14). */
.jr-seance { margin: 0 0 18px; }
.jr-seance-tete {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 9px 13px; margin: 0 0 6px;
  background: rgba(16,18,26,0.94); backdrop-filter: blur(6px);
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.jr-seance-jour { font-weight: 600; font-size: var(--text-base); }
.jr-seance-argent {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--text-base);
  font-variant-numeric: tabular-nums;
}

/* ══ UNE LIGNE DE TRADE, LISIBLE SANS LA LIRE ═════════════════════════════════
   L'heure a gauche, ce qu'il a fait au milieu, l'argent a droite. C'est `.rangee`,
   la grille a trois colonnes de la charte : une liste de plus n'avait pas a naitre. */
.jr-heure {
  font-family: var(--font-mono); font-size: var(--text-sm);
  color: var(--muted-foreground); min-width: 3.4em;
}
.jr-trade-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* /!\ UNE SEULE LIGNE, coupee net. Le raisonnement fait deux a quatre lignes ; a
   trente lignes de seance, il noyait l'argent et les prix. Il se lit en entier sur
   le detail de l'analyse, a un clic. */
.jr-pourquoi {
  margin: 2px 0 0; color: var(--muted-foreground);
  font-size: var(--text-body-sm); line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* LES STRATÉGIES (21/10/2026) : la stratégie qu'une analyse a retenue, sous elle (le
   journal) et sous son raisonnement (le détail), avec les gestes qui en partent. */
.jr-strategie {
  display: flex; align-items: center; gap: 5px; margin: 3px 0 0;
  color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.an-strategie { margin-top: 14px; }
.an-strategie-phrase { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--accent); }
.an-strategie-gestes { display: flex; flex-wrap: wrap; gap: 6px 18px; margin: 8px 0 0; }
/* Les consignes de séance lues par l'analyse (23/10/2026), sous sa stratégie. */
.an-consignes { margin: 8px 0 0; padding-left: 22px; display: grid; gap: 4px; font-size: var(--text-sm); }
.jr-argent-v {
  font-family: var(--font-mono); font-size: var(--text-base); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Un MOT a la place d'un montant (« aucun trade », « en cours ») : il ne prend ni la
   chasse fixe ni le gras, sinon il se lit comme un chiffre qu'on n'arrive pas a lire. */
.jr-argent-v--mot {
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 400;
  color: var(--muted-foreground);
}

/* ══ LES TICKETS DU JOURNAL (refonte-cartes, 27/09/2026) ═══════════════════════
   Décision du fondateur : « variante 1 » des cartes de trades. Ce qui est FINI se lit
   en ticket : un liseré à gauche dit l'issue d'un coup d'oeil (vert gagnant, rouge
   perdant, jaune une analyse ratée ou écartée, bleu en cours), le résultat à droite ;
   ce qui n'a rien donné (ordre expiré, refus, abstention) reste en pointillé, calme.
   ⚠️ C'est la MÊME rangée (`.rangee`), rien de retiré : un habit, pas un second
   gabarit. Le raisonnement garde sa ligne (« l'élément principal » de cette page). */
.rangee.ticket { border-left: 3px solid var(--border-strong); }
.rangee.ticket--gain { border-left-color: var(--success); }
.rangee.ticket--perte { border-left-color: var(--error); }
.rangee.ticket--vif { border-left-color: var(--info); }
.rangee.ticket--rate { border-left-color: var(--warning); background: transparent;
  border-top: 1px dashed var(--border-strong); border-right: 1px dashed var(--border-strong);
  border-bottom: 1px dashed var(--border-strong); }
.rangee.ticket--calme { background: transparent; border-top: 1px dashed var(--border);
  border-right: 1px dashed var(--border); border-bottom: 1px dashed var(--border); }
.jr-excedent.ticket { margin-left: 18px; }

/* ══ UNE SUITE D'ABSTENTIONS, REPLIEE ═════════════════════════════════════════
   /!\ Le trait POINTILLE n'est pas un ornement : il dit que ce bloc n'est pas une
   ligne comme les autres, mais un temps ou il ne s'est rien passe. */
.jr-replie { border: 1px dashed var(--border-strong); border-radius: var(--radius-md); }
.jr-replie > summary {
  list-style: none; cursor: pointer;
  display: grid; grid-template-columns: auto minmax(0,1fr) auto;
  align-items: center; gap: 12px; padding: 9px 13px;
}
.jr-replie > summary::-webkit-details-marker { display: none; }
.jr-replie > summary:hover { background: var(--accent-wash); border-radius: var(--radius-md); }
.jr-replie-quoi { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.jr-replie-geste { font-size: var(--text-sm); color: var(--muted-foreground); }
.jr-replie[open] > summary { border-bottom: 1px dashed var(--border-strong); }
.jr-replie-dedans { padding: 6px 10px 10px; }
.jr-abst { background: none; }
/* La POSITION EN TROP refermée, sous son trade (24/09/2026) : décalée et bordée
   d'ocre à gauche, elle se lit comme une suite du trade, pas comme un trade de plus.
   Elle disparaît avec sa ligne quand un tri côté page masque celle-ci. */
.jr-excedent {
  margin: 4px 0 0 22px; background: none;
  border-left: 2px solid rgb(251 191 36 / 0.55);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
[hidden] + .jr-excedent { display: none; }

/* Sous 720 px, l'argent passe sous le corps : trois colonnes ne tiennent pas. */
@media (max-width: 720px) {
  .jr-seance .rangee { grid-template-columns: auto minmax(0, 1fr); }
  .jr-seance .rangee-argent { grid-column: 2; text-align: left; }
}
.graphe-tete > .seg { margin-left: auto; }
.graphe-note { font-size: var(--text-xs); margin: 0; max-width: 46ch; }

.rangee--lien { text-decoration: none; color: inherit; cursor: pointer; }
.rangee--lien:focus-visible {
  outline: 2px solid var(--ring); outline-offset: 2px;
}
.rangee:hover, .rangee:focus-within {
  border-color: var(--accent-ring-strong);
  background: var(--accent-wash);
}
.rangee-rang {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--muted-foreground);
  min-width: 1.4em; text-align: right; padding-top: 3px;
  font-variant-numeric: tabular-nums;
}
.rangee-corps { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* Le texte replie, borne a deux lignes.
   /!\ `max-height` et NON `-webkit-line-clamp`. Le clamp ne compte que des lignes de
   texte : sur un contenu fait de plusieurs BLOCS (des paragraphes, des listes, ce
   qu'une regle de soixante lignes produit), il ne coupe rien du tout. La rangee
   s'affichait alors sur mille cinq cents pixels dans une liste censee etre
   parcourable. Trouve en mesurant, apres avoir cru l'affaire reglee.
   /!\ Les marges du markdown rendu sont posees ici : sans elles, les marges par
   defaut du navigateur (1 em au-dessus ET en dessous de chaque paragraphe) rendent la
   hauteur de repli imprevisible. */
.rangee-texte {
  font-size: var(--text-base); line-height: 1.5;
  overflow-wrap: anywhere;
  max-height: 3em;                    /* deux lignes a cette interligne */
  overflow: hidden;
}
.rangee-texte > *:first-child { margin-top: 0; }
.rangee-texte > *:last-child { margin-bottom: 0; }
.rangee-texte p { margin: 0 0 6px; }
.rangee-texte ul, .rangee-texte ol { margin: 0 0 6px; padding-left: 20px; }
.rangee-texte li { margin: 1px 0; }
/* /!\ Depliee POUR EDITION, la rangee cache son texte de lecture : le panneau qui
   s'ouvre en dessous contient exactement le meme contenu, editable. Les montrer tous
   les deux affichait la regle DEUX FOIS, et sur une regle de soixante lignes cela
   faisait deux mille huit cents pixels de doublon. Trouve en creant une regle longue
   a la demande du fondateur : la version courte ne le laissait pas voir. */
.rangee[data-ouverte="oui"] .rangee-texte { display: none; }

/* ── Lire en entier SANS ouvrir l'editeur (2026-08-17) ──
   Demande du fondateur : « comment voir la totalite d'une regle sans cliquer sur
   modifier ». Il fallait passer en edition pour lire, ce qui est le mauvais geste :
   on relit dix fois plus souvent qu'on ne corrige.

   Deux etats distincts, a ne pas confondre :
     `data-deplie`  on LIT en entier, rien n'est modifiable ;
     `data-ouverte` l'editeur est ouvert, et le texte de lecture s'efface.

   /!\ Le chevron n'apparait QUE sur une rangee dont le texte est reellement coupe
   (`data-tronquee`, pose par `rangees.js` apres mesure). Le montrer partout
   promettrait un depliage sur une rangee d'une ligne, ou il ne se passerait rien. */
.rangee[data-deplie="oui"] .rangee-texte { max-height: none; }
/* Le bas du texte coupe s'efface : une coupe franche au milieu d'une ligne se lit
   comme un defaut d'affichage, un degrade se lit comme « il y a une suite ». */
.rangee[data-tronquee="oui"] .rangee-texte {
  cursor: pointer;
  -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
}
.rangee-deplier {
  display: none;                      /* rendu visible par `data-tronquee` */
  align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  flex: 0 0 auto;
  background: transparent; border: 1px solid transparent;
  border-radius: 999px;
  color: var(--muted-foreground);
  cursor: pointer;
  transition: color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
.rangee[data-tronquee="oui"] .rangee-deplier { display: inline-flex; }
.rangee-deplier:hover { color: var(--accent); background: rgba(255,255,255,0.06); }
.rangee-deplier svg {
  width: 12px; height: 12px;
  transition: transform var(--motion-slow) var(--ease-out);
}
.rangee[data-deplie="oui"] .rangee-deplier svg { transform: rotate(180deg); }
/* Le meme chevron, quand il ouvre un PANNEAU (`data-detail`) et non un texte coupe :
   toujours actionnable, parce qu'il ne depend d'aucune mesure de troncature, et il
   tourne sur `data-ouverte` puisque c'est le panneau qui s'ouvre. */
.rangee-deplier[data-detail] { display: inline-flex; }
.rangee[data-ouverte="oui"] .rangee-deplier svg { transform: rotate(180deg); }
.rangee-actions { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
/* Sous 560 px, les actions prennent leur propre ligne. Quatre icones occupent 150 des
   375 pixels d'un telephone : le texte se retrouvait sur une colonne de soixante
   pixels, et une rangee d'une ligne s'etalait sur six. Mesure prise, pas devinee. */
@media (max-width: 560px) {
  .rangee { grid-template-columns: auto minmax(0, 1fr); }
  .rangees[data-reordonnable] .rangee { grid-template-columns: auto auto minmax(0, 1fr); }
  .rangee-actions { grid-column: 1 / -1; justify-content: flex-end; }
}
.rangee-detail {
  grid-column: 1 / -1;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--border);
}
/* Ce que la liste dit quand le filtre ne laisse rien passer. Un vide muet se lit
   comme une panne d'affichage. */
.rangees-vide { padding: 18px 4px; font-size: var(--text-base); color: var(--muted-foreground); }

/* ── Reordonner a la souris (2026-08-17) ──
   Demande du fondateur : « ca serait possible de faire un systeme de drag and drop
   pour reorganiser l'ordre plutot que des fleches ». Deux boutons par rangee, sur
   une liste de vingt, c'est quarante boutons pour deplacer une ligne de trois crans.

   /!\ La poignee est un vrai `<button>`, et les FLECHES DU CLAVIER l'animent. Le
   glisser-deposer seul est inaccessible : ni clavier, ni lecteur d'ecran, et
   maladroit au doigt. Les fleches n'ont pas disparu, elles ont juste quitte l'ecran.

   /!\ `touch-action: none` sur la poignee, sinon un glissement au doigt fait
   defiler la page au lieu de deplacer la rangee. C'est le geste par defaut du
   navigateur, et il gagne tant qu'on ne le lui retire pas explicitement. */
/* /!\ La poignee est une QUATRIEME colonne : sans cette regle, elle deborde de la
   grille a trois colonnes de `.rangee` et renvoie les actions a la ligne suivante.
   Elle ne s'applique qu'aux listes reordonnables, pour que les autres gardent leurs
   trois colonnes. */
.rangees[data-reordonnable] .rangee {
  grid-template-columns: auto auto minmax(0, 1fr) auto;
}
.rangee-poignee {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 26px; padding: 0;
  flex: 0 0 auto;
  background: transparent; border: 0; border-radius: var(--radius-sm);
  color: var(--disabled-foreground);
  cursor: grab;
  touch-action: none;
  transition: color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
.rangee:hover .rangee-poignee, .rangee-poignee:focus-visible { color: var(--accent); }
.rangee-poignee:hover { background: rgba(255,255,255,0.06); }
.rangee-poignee svg { width: 11px; height: 15px; }
/* La rangee qu'on tient : soulevee, et son ombre dit qu'elle flotte au-dessus. */
.rangee[data-tenue="oui"] {
  cursor: grabbing;
  border-color: var(--accent-ring-strong);
  background: var(--accent-wash);
  box-shadow: 0 18px 40px -14px rgba(0,0,0,0.7);
  opacity: 0.96;
}
.rangees[data-reordonne="oui"] .rangee { transition: transform 160ms var(--ease-out); }
/* Pendant un glissement, plus rien ne se selectionne : sans cela le navigateur
   surligne le texte des rangees traversees, et le geste devient sale. */
body[data-glisse="oui"] { user-select: none; -webkit-user-select: none; }

/* ── Une rangee qui porte un TITRE et des metadonnees (2026-08-17) ──
   `.rangee-texte` a ete taille pour une regle : un bloc de texte long, coupe a deux
   lignes. Une version de trader, une activite, un evenement ont une autre forme : une
   ligne qui NOMME, et une ligne de faits courts separes par des points medians.

   /!\ Les separateurs sont poses par la feuille, pas ecrits dans le gabarit. Un « · »
   tape a la main entre deux `{% if %}` laisse un point orphelin des que la condition
   qui le suit est fausse, et cela se voit sur la ligne la plus lue de l'ecran. */
.rangee-titre {
  font-size: var(--text-base); line-height: 1.45; color: var(--foreground);
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  overflow-wrap: anywhere;
}
/* /!\ Attenue, jamais en italique. La charte est SANS italique (un test l'epingle sur
   les gabarits) : « Sans motif note » se distingue par sa couleur, pas par sa pente. */
.rangee-titre--vide { color: var(--muted-foreground); }
.rangee-meta {
  display: flex; flex-wrap: wrap; align-items: baseline;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
.rangee-meta > * + *::before {
  content: "\00b7"; margin: 0 7px; color: var(--disabled-foreground);
}

/* ── Les ECARTS : « ce qui a change », libelle, avant, apres (2026-08-17) ──
   Deux ecrans le demandaient deja : la comparaison de deux versions d'un trader, et
   l'historique des versions, ou chaque ligne dit ce qu'elle a change. Le premier
   l'avait ecrit en `table` avec son `<style>` en ligne.

   /!\ Le libelle affiche est celui de `versionning._LIBELLES` (« Stop loss (x ATR) »),
   jamais le nom de la colonne. Un client ne lit pas `sl_atr`. */
/* Lu par les lecteurs d'ecran, invisible a l'oeil. Sert quand la mise en page porte un
   sens que le texte seul ne dit pas : « 1,5 [devient] 2,0 » sans la fleche. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.ecarts { list-style: none; margin: 0; padding: 0; }
.ecart {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: baseline; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--border);
  font-size: var(--text-base);
}
.ecart:last-child { border-bottom: 0; }
.ecart-libelle { color: var(--muted-foreground); min-width: 0; }
.ecart-avant, .ecart-apres {
  font-family: var(--font-mono); font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.ecart-avant { color: var(--muted-foreground); }
.ecart-fleche { color: var(--disabled-foreground); }
.ecart-apres { color: var(--foreground); font-weight: 600; }
/* Sous 560 px, quatre colonnes ne tiennent pas : le libelle prend sa ligne. */
@media (max-width: 560px) {
  .ecart { grid-template-columns: auto auto auto; }
  .ecart-libelle { grid-column: 1 / -1; }
}

/* ── Un MONTANT gagne ou perdu, en texte (2026-08-17) ──
   Le pendant de `.saisie--gain` / `.saisie--perte`, pour un chiffre affiche et non
   saisi. Les deux ecrans de versions codaient `#1a7f4b` et `#b3261e` en dur : deux
   teintes de theme CLAIR posees sur un fond sombre, illisibles.
   /!\ `tabular-nums` n'est pas cosmetique : sans lui, une colonne de montants danse
   d'une ligne a l'autre et ne se compare plus a l'oeil. */
.montant-gain, .montant-perte, .montant-nul {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.montant-gain  { color: var(--success); }
.montant-perte { color: var(--error); }
.montant-nul   { color: var(--muted-foreground); font-weight: 500; }


/* ============================================================
   Les NIVEAUX d'un ordre : entree, stop, objectif (2026-08-17)
   ============================================================
   Demande du fondateur : « les analyses, il n'y a pas assez de couleurs sur les
   elements, notamment l'objectif. Le stop devrait etre colore. »

   Trois prix, trois roles, trois couleurs : ce qu'on RISQUE en rouge, ce qu'on VISE en
   vert, ce qu'on DEMANDE en accent. Les trois etaient du meme gris, sur une ligne ou
   seule leur position les distinguait -- il fallait lire l'etiquette de chacun pour
   savoir lequel etait le stop.

   /!\ Ce n'est PAS `.montant-gain` / `.montant-perte`, malgre la meme paire de couleurs,
   et la distinction compte : un montant dit ce qui est ARRIVE sur le compte, un niveau
   dit ce qui etait VISE. Les confondre en une seule classe menerait un jour a colorer un
   objectif en rouge parce que le trade a perdu -- alors que l'objectif, lui, n'a pas
   bouge.

   /!\ La couleur est un RENFORT, jamais le seul porteur : chaque niveau garde son
   etiquette en toutes lettres (« stop 6397.5 »). Une teinte seule ne se lit pas pour huit
   hommes sur cent.

   /!\ Les trois modificateurs sont ecrits `.niveau.niveau--x` et non `.niveau--x` seul.
   Ce n'est pas une coquetterie : `.bilan-valeur` pose `color: var(--foreground)` et
   vit PLUS BAS dans cette feuille, donc a specificite egale c'est elle qui gagnait --
   les niveaux du detail d'une analyse restaient gris pendant que ceux du journal
   etaient colores. Deux classes rendent la couleur independante de l'ordre du fichier,
   qui n'est pas un contrat. */
.niveau {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.niveau.niveau--entree   { color: var(--accent); }
.niveau.niveau--stop     { color: var(--error); }
.niveau.niveau--objectif { color: var(--success); }
/* L'etiquette reste en retrait : c'est le CHIFFRE qui porte la couleur, pas le mot.
   Colorer les deux donnerait une ligne entierement rouge et verte, qui ne se lit plus. */
.niveau-l {
  font-family: var(--font-sans); font-weight: 400;
  color: var(--muted-foreground);
}


/* ============================================================
   Les CHIFFRES : une tuile etiquette / valeur / nuance (2026-08-17)
   ============================================================
   Trois copies divergentes de cette meme idee existaient, aucune dans la charte :
   `.kpi` dans « Mes trades », `.cmp-kpis` dans la comparaison de versions, et
   `.kpi-card` ici meme, vestige de theme clair (ombres bleutees, barre de ratio en
   #4A3B7A code en dur). Celle-ci est la forme reellement employee pour les chiffres
   d'un client : ce qu'on mesure, combien, et sur quel effectif.

   /!\ Posee, RETIREE le meme jour faute d'utilisateur, puis remise le lendemain : la
   carte compacte de « Mes traders » n'en voulait pas (73 px la ou une ligne en coute
   25), la bande d'en-tete de « Mes trades » est son bon usage. La lecon n'est pas
   « ne pas la creer » mais « une primitive suit son premier ecran reel ».

   /!\ La NUANCE porte l'effectif, jamais un pourcentage nu. « +840 $ » au-dessus de
   « 24 trades » se lit ; « 62 % » seul est un mensonge poli. C'est la regle de tout le
   module de performance, et le seuil de fiabilite y est a 140.

   /!\ `tabular-nums` sur la valeur : sans lui, une colonne de montants danse d'une
   tuile a l'autre et ne se compare plus a l'oeil.

   /!\ REVISION du 2026-08-17, sur remarque du fondateur : « je trouve les KPI trop
   figes, et ca manque un peu de couleur pour les mettre en evidence ». La tuile etait un
   aplat translucide uniforme, sans bordure ni relief : a un metre, une bande de sept
   rectangles identiques ou rien ne disait lequel regarder. Trois changements.
   1. Un DEGRADE et une bordure, pour que la tuile soit un objet POSE sur la page et non
      une zone un peu plus claire du fond.
   2. Un LISERE de couleur en haut, qui dit la NATURE du chiffre : vert pour un gain,
      rouge pour une perte, accent pour un chiffre informatif, gris pour ce qui n'a rien
      a annoncer. C'est lui qui donne la couleur demandee, sans peindre le fond entier --
      un aplat vert derriere un chiffre le rend illisible et crie plus fort que le
      chiffre lui-meme.
   3. La tuile mise en avant recoit en plus un lavis de sa propre couleur.

   /!\ La couleur suit le SIGNE d'un montant, JAMAIS le niveau d'un taux. Teinter en vert
   un « 62 % de gagnants » serait exactement le mensonge poli que ce module combat : un
   taux sur vingt trades est un chiffre exact et une information fausse, et une couleur
   le transformerait en jugement. Les tuiles de ratio restent donc a l'accent quelle que
   soit leur valeur -- le seuil de fiabilite est a 140, et il ne se peint pas.

   /!\ La bordure n'existait pas, au motif qu'une tuile posee dans une `.carte` aurait
   donne un cadre dans un cadre. L'usage a tranche : la bande est posee a meme la page,
   jamais dans une carte. Si un ecran l'y met un jour, c'est a lui de retirer la bordure,
   pas a la primitive de rester plate pour un cas qui n'existe pas.

   /!\ 190 px et pas 148 : mesure a 1280 px, colonne de 960. A 148 px, l'etiquette
   « Facteur de profit » (mono, capitales, interlettrage) passait a la ligne et faisait
   monter TOUTE la rangee de 89 a 122 px -- une grille aligne les hauteurs. A 190 px la
   grille rend quatre colonnes, une etiquette tient sur sa ligne, et sept chiffres (la
   tuile forte en vaut deux) tombent en deux rangees pleines au lieu de 5 + 2. */
.chiffres {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px;
}

/* L'INTERTITRE d'une rangee de chiffres (2026-08-21, fusion des onglets de Trades).

   Il nomme une FAMILLE de tuiles. Quand deux rangees se suivent -- l'argent puis
   l'activite -- deux grilles identiques separees par du vide se lisent comme une seule
   rangee qui a deborde, pas comme deux familles. Il reprend le travail que faisaient
   les onglets supprimes : l'information ne disparait pas avec eux, elle change de forme.

   /!\ Un intertitre a filet, PAS un `.encadre` par groupe : `.encadre` coute 26/28 px de
   padding, un radius, un degrade radial et une ombre portee, soit ~110 px de hauteur
   pour deux boites -- et une bordure dans laquelle les tuiles ont elles-memes une
   bordure. La regle de `.chiffre` le dit : la bande est posee a meme la page, jamais
   dans une carte. Cout ici : ~24 px par groupe, aucune boite.

   /!\ `--foreground` et pas `--muted-foreground` comme `.t-eyebrow` : en gris il se
   confondrait avec les `.chiffre-l` des tuiles qu'il coiffe, au meme registre de
   capitales. Un titre qui ressemble a ce qu'il titre ne titre plus rien. */
.chiffres-titre {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 8px;
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--tracking-eyebrow);
  color: var(--foreground);
}
.chiffres-titre::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
/* La note qui QUALIFIE la rangee juste au-dessus (le trou du cout et de la duree).
   Le retrait negatif n'est pas cosmetique : il la colle a la bande, et c'est ce
   collage qui dit « ca parle de CES tuiles-la ». Posee a distance normale, elle se
   lirait comme qualifiant tout ce qui precede, y compris le resultat cumule -- alors
   qu'elle ne parle que du cout. */
.chiffres-note { font-size: var(--text-xs); margin: -8px 0 12px; }
.chiffre {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 4px;
  padding: 13px 15px 12px;
  background: linear-gradient(158deg,
              rgba(255,255,255,0.055) 0%, rgba(255,255,255,0.018) 62%);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  min-width: 0;
  transition: transform var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
/* Le SURVOL (2026-08-17, demande du fondateur : « j'aimerais des petits effets lorsque
   je survole les KPI »).

   /!\ Un fond qui s'eclaircit, un lisere plus net, une bordure plus franche, une ombre
   portee -- et RIEN d'autre. Ces tuiles ne sont pas cliquables pour la plupart : un
   effet trop marque (curseur main, agrandissement, changement de couleur du chiffre)
   promettrait une action qui n'existe pas, et un client qui clique sans rien obtenir
   conclut que l'ecran est casse. Ce qu'on veut est que la surface REPONDE, pas
   qu'elle invite.

   /!\ L'EFFET et le MOUVEMENT sont separes, et c'est la lecon de la mesure : le panneau
   d'apercu declare `prefers-reduced-motion: reduce`, et une premiere version qui
   suspendait TOUT le survol sous cette preference ne montrait donc rien du tout -- la
   demande serait restee lettre morte chez celui qui l'a faite. La preference demande de
   ne pas faire BOUGER les choses, pas de les rendre inertes. Le souleve est donc la
   seule chose conditionnelle ; la lumiere reste pour tout le monde.

   /!\ `transform` et `box-shadow`, jamais `margin` ni `height` : les premiers sont
   composes par la carte graphique, les seconds relancent la mise en page et feraient
   sauter les six tuiles voisines a chaque passage de souris. */
.chiffre:hover {
  background: linear-gradient(158deg,
              rgba(255,255,255,0.085) 0%, rgba(255,255,255,0.03) 62%);
  border-color: var(--border-strong);
  box-shadow: 0 6px 18px -8px rgba(0,0,0,0.55);
}
.chiffre--fort.chiffre--gain:hover {
  background: linear-gradient(158deg,
              rgba(74,222,128,0.16) 0%, rgba(255,255,255,0.03) 62%);
  border-color: rgba(74,222,128,0.38);
}
.chiffre--fort.chiffre--perte:hover {
  background: linear-gradient(158deg,
              rgba(248,113,113,0.16) 0%, rgba(255,255,255,0.03) 62%);
  border-color: rgba(248,113,113,0.38);
}
.chiffre:hover::before { height: 3px; }
.chiffre--fort:hover::before { height: 4px; }
.chiffre::before {
  transition: height var(--motion-base) var(--ease-out);
}
/* Le seul MOUVEMENT, et il est conditionnel. */
@media (prefers-reduced-motion: no-preference) {
  .chiffre:hover { transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .chiffre, .chiffre::before { transition: none; }
}
/* Le lisere. Il occupe le haut de la tuile sur toute sa largeur, s'eteint vers la
   droite : un trait plein d'un bord a l'autre ferait une barre de titre. */
.chiffre::before {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: linear-gradient(90deg, var(--accent) 0%, rgba(180,168,255,0.15) 100%);
}
.chiffre--gain::before {
  background: linear-gradient(90deg, var(--success) 0%, rgba(74,222,128,0.12) 100%);
}
.chiffre--perte::before {
  background: linear-gradient(90deg, var(--error) 0%, rgba(248,113,113,0.12) 100%);
}
/* Ce qui n'a rien a annoncer -- pas encore de trade, pas de denominateur : un lisere
   eteint plutot qu'aucun, sinon la tuile parait cassee a cote de ses voisines. */
.chiffre--muet::before {
  background: linear-gradient(90deg, var(--disabled-foreground) 0%, transparent 100%);
}
/* La tuile mise en avant : son lisere s'epaissit et sa couleur passe DERRIERE le
   chiffre, tres diluee. C'est la seule qui recoit un fond teinte -- deux suffiraient a
   annuler l'effet, qui n'existe que par contraste avec les autres. */
.chiffre--fort::before { height: 3px; }
.chiffre--fort.chiffre--gain {
  background: linear-gradient(158deg,
              rgba(74,222,128,0.10) 0%, rgba(255,255,255,0.018) 62%);
  border-color: rgba(74,222,128,0.22);
}
.chiffre--fort.chiffre--perte {
  background: linear-gradient(158deg,
              rgba(248,113,113,0.10) 0%, rgba(255,255,255,0.018) 62%);
  border-color: rgba(248,113,113,0.22);
}
.chiffre-l {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.chiffre-v {
  font-family: var(--font-mono); font-size: var(--text-xl); font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--foreground); line-height: 1.15;
  overflow-wrap: anywhere;
}
/* Une valeur qui est une phrase et non un nombre (« aucun trade ») : la police de
   texte, plus petite, pour qu'elle ne se lise pas comme un chiffre. */
.chiffre-v--mot {
  font-family: var(--font-sans); font-size: var(--text-body);
  font-weight: 500; color: var(--muted-foreground);
}
/* L'UNITE collee a la valeur (« 40 €/mois »). Elle appartient au chiffre — la
   retirer le rendrait faux — mais elle n'est pas le chiffre : sans ce cran plus bas,
   « /mois » pese autant que « 40 » et la tuile ne se lit plus d'un coup d'oeil.
   /!\ Elle n'est PAS `.chiffre-n` : la note vit sous la valeur, sur sa propre ligne. */
.chiffre-u {
  font-size: var(--text-caption); font-weight: 500;
  color: var(--muted-foreground);
}
.chiffre-n { font-size: var(--text-caption); color: var(--muted-foreground); }
/* Une tuile dont la DESCRIPTION est passee en infobulle (2026-08-24, demande du
   fondateur sur « Mes trades »). Le gabarit ajoute `aide-texte` au `.chiffre-n` — la
   charte le masque alors a l'oeil sans le retirer aux lecteurs d'ecran — et pose
   `data-bulle` sur la tuile, qui devient le declencheur. Ne reste a l'ecran que
   l'etiquette et le chiffre.
   /!\ `cursor: help` n'est pas decoratif : sans lui, rien ne dit qu'il y a quelque
   chose a survoler, et la description devient invisible pour de bon. */
.chiffre[data-bulle] { cursor: help; }
/* Une description de tuile tient en une ligne ou deux ; 320 px l'etaleraient. */
#tvni-bulle[data-forme="chiffre"] {
  max-width: min(260px, calc((100vw - 24px) / var(--echelle)));
}
/* La tuile qu'on veut voir EN PREMIER (le resultat cumule) : meme boite, valeur plus
   grande. Une hierarchie par la taille, pas par une couleur de plus. */
.chiffre--fort .chiffre-v { font-size: var(--text-2xl); }
.chiffre--fort { grid-column: span 2; }
@media (max-width: 520px) { .chiffre--fort { grid-column: auto; } }
/* Une tuile CLIQUABLE mene quelque part sans se deguiser en lien : pas de soulignement,
   et c'est le survol qui le dit. */
a.chiffre { text-decoration: none; transition: background var(--motion-base) var(--ease-out); }
a.chiffre:hover, a.chiffre:focus-visible { background: var(--accent-wash); }


/* ============================================================
   La COURBE que l'on parcourt (2026-08-17)
   ============================================================
   Demande du fondateur : « on peut afficher le cumul lorsque l'on navigue le long de la
   courbe, et voir la date et les informations qu'on retrouve dans le tableau ? »

   Un curseur vertical et une bulle qui suivent le point le plus proche. Sans eux, une
   courbe sans axes ne repond a aucune question precise : elle montre une pente, jamais
   un chiffre.

   /!\ La figure est le repere de position (`position: relative`) : le curseur et la
   bulle s'y placent en pixels. Sans elle, ils remonteraient au premier ancetre positionne
   -- le meme piege que `.carte-lien` dans une rangee.

   /!\ La bulle ne capte PAS la souris (`pointer-events: none`). Sans cela, elle passe
   sous le curseur, declenche un `mouseleave` sur la courbe, se cache, ce qui redonne le
   survol a la courbe, qui la remontre : elle clignote a l'infini. */
.courbe { position: relative; }
.courbe svg { cursor: crosshair; }
/* Le dessin se prend au CLAVIER (fleches gauche/droite pour parcourir les seances) :
   il lui faut donc un anneau de focus, et un anneau qui ne se declenche PAS au clic de
   souris -- d'ou `:focus-visible`. Sans lui, le navigateur pose son trait noir par
   defaut, invisible sur un fond sombre. */
.courbe svg:focus { outline: none; }
.courbe svg:focus-visible { outline: 2px solid var(--ring); outline-offset: 3px; }
.courbe-curseur {
  position: absolute; width: 1px;
  background: var(--accent); opacity: 0.45;
  pointer-events: none;
}
/* Les REPERES de la courbe -- sommet, creux, arrivee (2026-08-18).
   /!\ Ils existent en double, et c'est voulu : en `<circle>` dans le gabarit pour qui
   n'a pas de JavaScript, et en HTML ici pour tous les autres. Le SVG est etire
   (`preserveAspectRatio="none"`), donc ses cercles sortent en ELLIPSES deux fois plus
   larges que hautes -- on croyait voir deux points cote a cote. Le script pose
   `data-courbe-js` sur la figure des qu'il prend la main, et les cercles s'effacent. */
.courbe-repere {
  position: absolute; width: 9px; height: 9px; border-radius: 50%;
  margin: -4.5px 0 0 -4.5px;
  border: 2px solid var(--background);
  pointer-events: none;
}
.courbe-repere--gain   { background: var(--success); }
.courbe-repere--perte  { background: var(--error); }
.courbe-repere--accent { background: var(--accent); }
[data-courbe-js] svg circle { display: none; }

/* La PASTILLE du point survole (2026-08-18). Le trait vertical disait la colonne, jamais
   l'endroit ou la courbe passe : l'oeil devait deviner l'intersection. */
.courbe-marque {
  position: absolute; width: 11px; height: 11px; border-radius: 50%;
  margin: -5.5px 0 0 -5.5px;
  background: var(--accent); border: 2px solid var(--background);
  /* Le halo reprend l'accent reel (#B4A8FF) : une valeur inventee aurait jure. */
  box-shadow: 0 0 0 3px rgba(180,168,255,0.20);
  pointer-events: none;
}
.courbe-bulle {
  position: absolute; z-index: var(--z-dropdown);
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 11px;
  background: linear-gradient(180deg, #1A1D27 0%, #14161F 100%);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 16px 40px -12px rgba(0,0,0,0.7);
  font-size: var(--text-caption); line-height: 1.45;
  white-space: nowrap;
  pointer-events: none;
}
.courbe-bulle-titre {
  font-family: var(--font-mono); font-weight: 600;
  color: var(--foreground);
}
.courbe-bulle-ligne { color: var(--muted-foreground); }
/* Un montant DANS la bulle garde sa couleur : c'est la meme information que dans la
   rangee correspondante, elle ne doit pas changer de sens en changeant de place. */
.courbe-bulle .montant-gain, .courbe-bulle .montant-perte, .courbe-bulle .montant-nul {
  font-size: var(--text-caption);
}


/* ============================================================
   Le BILAN : une liste libelle -> valeur (2026-08-17)
   ============================================================
   Par ligne : ce qu'on mesure a gauche, ce que ca vaut a droite, et au besoin un
   marqueur devant. Deux ecrans en portaient chacun leur copie sous un autre nom
   (`.cmp-kpis` sur la comparaison de versions, une table locale sur l'audit d'un appel)
   alors que c'est le meme objet.

   /!\ Ce n'est PAS `.chiffres`. Une tuile de `.chiffres` se regarde, une ligne de
   `.bilan` se parcourt : des qu'il y a plus de cinq ou six mesures, ou qu'elles sont
   posees dans une colonne etroite, la liste bat la grille. Le choix se fait sur le
   NOMBRE et la LARGEUR, pas sur le gout.

   /!\ Le marqueur est optionnel : `.bilan--marque` sur le conteneur ouvre sa colonne.
   L'ouvrir toujours laisserait un creux de 18 px devant chaque libelle d'une liste qui
   n'en a pas besoin.

   /!\ Sur une liste de completude, DEUX signaux distincts, et la distinction est tout
   le sujet : `--absent` dit qu'une donnee attendue n'est pas arrivee (une panne),
   `.bilan-court` dit qu'elle est arrivee plus courte que demandee (un reglage du
   client). Les confondre en un seul rouge ferait crier l'ecran a chaque demarrage de
   plugin, quand la profondeur n'est pas encore remplie ; et un rouge permanent ne se
   lit plus.

   /!\ Le marqueur est un caractere ET une couleur, jamais une couleur seule : une
   ligne rouge ne se distingue pas d'une ligne verte pour huit hommes sur cent. */
.bilan { display: flex; flex-direction: column; }
.bilan-ligne {
  display: grid; grid-template-columns: 1fr auto;
  align-items: baseline; gap: 12px;
  padding: 6px 8px; border-radius: var(--radius-sm);
  font-size: var(--text-body-sm);
}
.bilan--marque .bilan-ligne { grid-template-columns: 18px 1fr auto; gap: 10px; }
.bilan-ligne + .bilan-ligne { border-top: 1px solid var(--border); }
.bilan-marque { font-weight: 700; color: var(--success); text-align: center; }
.bilan-libelle { color: var(--muted-foreground); min-width: 0; }
.bilan-valeur {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--foreground); font-weight: 600; text-align: right;
}
/* La NUANCE sous la valeur : l'effectif, la periode, la mise en garde. Meme regle que
   `.chiffre-n` -- un taux ne voyage jamais sans elle. */
.bilan-n {
  display: block; font-family: var(--font-sans); font-weight: 400;
  font-size: var(--text-caption); color: var(--muted-foreground);
}
.bilan-ligne--absent { background: rgb(220 60 60 / 0.10); }
.bilan-ligne--absent .bilan-marque,
.bilan-ligne--absent .bilan-valeur { color: var(--error); }
.bilan-ligne--absent .bilan-libelle { color: var(--foreground); }
/* Le neutre : une donnee facultative absente n'est pas une anomalie. */
.bilan-ligne--neutre .bilan-marque { color: var(--disabled-foreground); }
/* La mention « courte », accolee a la valeur. */
.bilan-court { color: var(--warning-text); font-family: var(--font-sans); font-weight: 500; }


/* ============================================================
   Le TEXTE BRUT : une zone technique qu'on lit sans la subir (2026-08-17)
   ============================================================
   Du texte machine (un contexte envoye au modele, une reponse, un fil JSON) pose dans
   une page qui n'est pas faite pour ca. Trois ecrans en portaient une copie locale,
   toutes en couleurs de theme clair codees en dur.

   /!\ Plafonnee en hauteur et defilante DANS sa boite : sans ce plafond, vingt mille
   caracteres poussent tout le reste de la page hors de vue et l'ecran n'a plus de bas.
   /!\ `pre-wrap` + `anywhere` : une ligne de JSON sans espace mesure des milliers de
   caracteres et provoquerait un defilement HORIZONTAL de la page entiere. */
.texte-brut {
  margin: 0;
  white-space: pre-wrap; overflow-wrap: anywhere;
  font-family: var(--font-mono); font-size: var(--text-data-cell);
  line-height: 1.55; color: var(--muted-foreground);
  background: rgba(255,255,255,0.028);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 0.923em 1.077em;
  max-height: 32.308em; overflow: auto;
}
.texte-brut--court { max-height: 200px; }


/* ============================================================
   La TETE d'une carte, et le texte TRONQUE (2026-08-17)
   ============================================================
   Une ligne de tete : ce qui identifie a gauche, des marqueurs au milieu, un chiffre
   pousse a droite. `.trader-tete` en portait deja une copie locale ; c'est la deuxieme
   occurrence qui la fait monter ici.

   /!\ `align-items: baseline` et non `center` : les enfants n'ont pas la meme taille de
   police (un titre en 13.5, un badge en 10.5), et un centrage vertical les fait flotter
   les uns par rapport aux autres au lieu de poser leur texte sur la meme ligne. */
.tete { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
.tete-titre {
  font-family: var(--font-mono); font-size: var(--text-data-cell); font-weight: 600;
  color: var(--foreground); font-variant-numeric: tabular-nums;
}
/* Ce qui est pousse a droite : le chiffre qu'on vient chercher. `margin-left: auto` et
   pas un `.spacer` vide -- un element sans contenu se retrouve lu par les lecteurs
   d'ecran et casse la ligne differemment quand la tete passe a la ligne. */
.tete-fin { margin-left: auto; }

/* Un texte TRONQUE a quelques lignes, le reste etant a un clic. Le nombre de lignes se
   regle par `--lignes` : `style="--lignes:4"`.

   /!\ On tronque le RESUME, jamais l'explication complete. Ici, le raisonnement du
   modele est l'element principal de la page -- ce que le client paie. Il reste a la
   taille du texte courant et sur six lignes par defaut, pas sur deux : tronquer plus
   court transformerait la liste en index, et il faudrait ouvrir chaque ligne pour
   savoir laquelle ouvrir. */
.texte-clamp {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--lignes, 6);
  overflow: hidden;
}


/* ============================================================
   L'EDITEUR : la mise en forme DANS le texte (2026-08-17)
   ============================================================
   Demande du fondateur : « l'editeur je ne le trouve pas fluide, je veux que la mise
   en forme se fasse directement dans le texte pas avec des boutons ». Il y avait une
   barre « B / Liste / 1. Liste » au-dessus d'une zone de texte brut, ou l'on voyait
   `**gras**` et non du gras.

   Desormais une zone `contenteditable` : le gras est gras, les listes sont des
   listes, et rien ne se clique. Ctrl+B, Ctrl+I, et taper « - » ou « 1. » en debut de
   ligne ouvre une liste.

   /!\ Ce qui est STOCKE reste le markdown limite de `md_safe.py` (gras, italique,
   code, liens, deux sortes de listes) : la base ne change pas de format. Le serveur
   convertit markdown -> HTML pour remplir l'editeur, `editeur.js` reconvertit
   HTML -> markdown a l'enregistrement. Le tour est exact parce que les deux cotes
   parlent du MEME petit vocabulaire.

   /!\ Le collage est force en TEXTE BRUT. Sans cela, un copier-coller depuis Word ou
   une page web injecte des balises et des styles que le markdown ne sait pas
   representer : la mise en forme apparaitrait a l'ecran puis disparaitrait a
   l'enregistrement, sans un mot.

   /!\ Une zone `contenteditable` n'est PAS un champ de formulaire : elle n'a ni
   `name`, ni `value`, et n'est pas envoyee par un `<form>`. Ici les regles partent
   en `fetch`, ce qui tombe bien ; pour un vrai formulaire il faudrait recopier le
   markdown dans un champ cache avant l'envoi. */
.editeur {
  position: relative;
  background-color: rgba(255,255,255,0.035);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  transition: border-color var(--motion-slow) var(--ease-out),
              background-color var(--motion-slow) var(--ease-out),
              box-shadow var(--motion-slow) var(--ease-out);
}
.editeur:focus-within {
  border-color: rgba(180,168,255,0.5);
  background-color: rgba(180,168,255,0.06);
  box-shadow: 0 0 0 3px rgba(180,168,255,0.10);
}
.editeur-zone {
  /* Pas de hauteur maximale : une regle peut faire un paragraphe entier, et un
     ascenseur dans une zone de six lignes est exactement ce qu'on vient corriger. */
  min-height: 6.222em;
  padding: 0.963em 1.111em;
  font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.6;
  color: var(--foreground);
  outline: none;
  overflow-wrap: anywhere;
}
.editeur-zone > *:first-child { margin-top: 0; }
.editeur-zone > *:last-child { margin-bottom: 0; }
.editeur-zone p { margin: 0 0 8px; }
.editeur-zone ul, .editeur-zone ol { margin: 0 0 8px; padding-left: 22px; }
.editeur-zone li { margin: 2px 0; }
.editeur-zone strong { color: var(--foreground); font-weight: 600; }
.editeur-zone code {
  font-family: var(--font-mono); font-size: var(--text-sm);
  color: var(--accent);
  background: rgba(180,168,255,0.10);
  border-radius: 4px; padding: 1px 5px;
}
/* Le texte d'invite d'une zone vide. `contenteditable` n'a pas de `placeholder` :
   c'est un `::before` conditionne par un attribut que le script tient a jour. */
.editeur-zone[data-vide="oui"]::before {
  content: attr(data-invite);
  color: var(--disabled-foreground);
  pointer-events: none;
}
/* Ce que l'on peut taper. Sans barre de boutons, personne ne devine que Ctrl+B
   existe : la ou l'ancienne version montrait des boutons, celle-ci l'ECRIT. Une
   fonction cachee est une fonction absente. */
.editeur-aide {
  margin: 6px 0 0;
  font-size: var(--text-caption); line-height: 1.5;
  color: var(--muted-foreground);
}
.editeur-aide kbd {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--foreground);
  background: var(--muted);
  border: 1px solid var(--border-strong);
  border-radius: 4px; padding: 1px 5px;
}
.encadre-titre {
  margin: 0 0 6px;
  font-family: var(--font-sans);
  font-size: var(--text-lg); font-weight: 600;
  letter-spacing: -0.02em; line-height: 1.25;
  color: var(--foreground);
}
/* La phrase qui explique la boite. Elle se pose SOUS le titre et respire avant
   les champs — c'est le `.login-sub` de l'ecran de connexion. */
.encadre-sous {
  margin: 0 0 20px;
  font-size: var(--text-base); line-height: 1.55;
  color: var(--muted-foreground);
}
.encadre-sous strong { color: var(--foreground); font-weight: 500; }
.encadre-titre:last-child, .encadre-sous:last-child { margin-bottom: 0; }
/* Un titre sans phrase garde quand meme l'air avant le premier champ. */
.encadre-titre + .champ, .encadre-titre + .champs { margin-top: 20px; }

/* Le CHAMP : une etiquette au-dessus, une saisie en dessous.
   /!\ L'etiquette est en chasse fixe et en capitales — c'est le trait de
   l'ecran de connexion et de la vitrine, pas une fantaisie. Elle est donc a
   10,5 px avec un interlettrage REDUIT (0,16 em au lieu de 0,20) : les
   etiquettes d'ici sont des phrases francaises (« Zone d'entree — borne
   basse »), pas les deux mots d'un ecran de connexion, et l'espacement de la
   vitrine les rendait illisibles. */
.champ { display: flex; flex-direction: column; gap: 8px; }
.champs {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px 22px;
}
.etiquette {
  font-family: var(--font-mono);
  font-size: var(--text-2xs); font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  line-height: 1.45;
  color: var(--muted-foreground);
}
/* La saisie. Chasse fixe, comme le mot de passe de l'ecran de connexion : ici
   presque tout est un nombre (multiples d'ATR, montants, minutes) et la chasse
   fixe les aligne au chiffre. */
.saisie {
  width: 100%;
  padding: 13px 15px;
  font-family: var(--font-mono); font-size: var(--text-base); letter-spacing: 0;
  color: var(--foreground);
  /* /!\ `background-color`, PAS le raccourci `background` : celui-ci remet
     `background-image` a `none`, et l'etat de survol effacerait alors le chevron
     d'une liste deroulante — qui, lui, est peint par-dessus. */
  background-color: rgba(255,255,255,0.035);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  outline: none;
  transition: border-color var(--motion-slow) var(--ease-out),
              background-color var(--motion-slow) var(--ease-out),
              box-shadow var(--motion-slow) var(--ease-out);
}
.saisie::placeholder { color: var(--disabled-foreground); }
.saisie:focus {
  border-color: rgba(180,168,255,0.5);
  background-color: rgba(180,168,255,0.06);
  box-shadow: 0 0 0 3px rgba(180,168,255,0.10);
}
.saisie:disabled { opacity: 0.5; cursor: not-allowed; }
textarea.saisie { min-height: 84px; resize: vertical; line-height: 1.55; }
/* AU TELEPHONE, AUCUN CHAMP SOUS 16 POINTS (TVNI-142, 21/09/2026).
   Sous 16 px, Safari sur iPhone AGRANDIT toute la page au toucher d'un champ,
   et ne la rend pas en sortant : le client doit pincer pour revenir. Mesure le
   21/09 a 390 points : TOUS les champs de l'application etaient sous 16 (15 px
   pour `.saisie` et `.fi`, 13 px pour les champs glisses dans une phrase de
   reglage, la recherche du haut, les filtres de la communaute).

   /!\ `!important`, et c'est voulu : une trentaine de regles reduisent la
   taille d'un champ selon son contexte (`.reglage-phrase .saisie`,
   `.elem-phrase .saisie`, le composeur du chat, la recherche…), dans cette
   feuille et dans d'autres chargees APRES elle. Une regle de specificite
   ordinaire en perdait la moitie, en silence : le seuil n'est pas un reglage
   de style, c'est un plancher.

   /!\ Les ecrans de connexion (`.auth-socle`) sont EXCLUS : ils posent deja
   19 px, au-dessus du plancher, et une regle qui FIXE 16 les aurait rapetisses.
   Un champ qui voudrait plus de 16 ailleurs devra s'exclure de meme.

   /!\ Ordinateur inchange : la requete ne vise que la largeur d'un telephone,
   ou un ecran tactile sans souris (une tablette, qui zoome pareil). */
@media (max-width: 720px), (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"],
            [type="color"], [type="submit"], [type="button"], [type="reset"],
            [type="image"], [type="hidden"]):not(.auth-socle *),
  select:not(.auth-socle *),
  textarea:not(.auth-socle *) {
    font-size: 16px !important;
  }
}
.saisie-note {
  margin: 0; font-size: var(--text-caption); line-height: 1.5;
  color: var(--muted-foreground);
}
/* L'ALERTE : la meme place que `.saisie-note`, mais quand ca ne va pas
   (2026-08-24).

   /!\ Cette classe etait utilisee par SEPT gabarits et n'avait AUCUNE regle
   nulle part. « Ce creneau vient d'etre pris », « Ce fuseau horaire n'existe
   pas. Rien n'a ete enregistre. », « Ce compte n'existe pas » se rendaient
   tous en texte courant, indiscernables de la phrase d'a cote : le visiteur ne
   voyait pas qu'on lui parlait d'une erreur. Trouve en REGARDANT l'ecran,
   jamais par un test : un test lit la chaine, il ne voit pas sa couleur.

   /!\ Elle vit ICI, pas dans le coin d'un gabarit. La premiere correction
   l'avait posee en local dans `rdv_gestion.html` : bon reglage, mauvais
   endroit, cinq autres ecrans restaient muets. C'est la lecon des quatre
   copies d'une meme tuile que cette charte a deja du reunir.

   /!\ La taille est celle de `.saisie-note` : sous un champ, l'alerte remplace
   la note neutre et prend exactement sa place. La couleur vient du jeton
   `--error`, jamais d'un rouge ecrit a la main. A 12 px sur le fond de
   l'application, le contraste mesure est de 7,1:1 (AA demande 4,5).

   /!\ DEUX marges, et c'est ce que le premier jet avait rate : pose en
   `margin: 0` partout, le message collait au titre des qu'il n'etait pas dans
   un champ (mesure a 0 px sur la page du lien prive, vu a l'ecran). Un
   `.champ` est un flex a `gap: 8px` : LUI donne deja l'air, et une marge en
   plus detacherait le message de la saisie a laquelle il se rapporte. Partout
   ailleurs, l'alerte prend son air elle-meme.

   /!\ Elle marche aussi sur un `<span>` (l'etat d'une ligne de tableau) : rien
   ici n'impose un bloc, ni padding, ni bordure, ni fond, et les marges
   verticales ne s'appliquent pas a un element en ligne. Un bandeau encadre est
   un AUTRE composant ; `signup.html` en porte un localement, plus appuye, et il
   n'est pas promu ici sans decision du fondateur. */
.alerte {
  margin: 14px 0;
  font-size: var(--text-caption);
  line-height: 1.5;
  color: var(--error);
}
.champ > .alerte { margin: 0; }
/* Un champ de RECHERCHE porte de la prose, pas des nombres : chasse
   proportionnelle, comme les listes deroulantes. En chasse fixe, « Rechercher une
   regle » depassait deja du champ. */
input[type="search"].saisie { font-family: var(--font-sans); font-size: var(--text-base); }

/* La LISTE DEROULANTE (2026-08-16, revue le soir meme sur remarque du fondateur :
   « ce n'est pas du tout esthetique »).

   Ce qui n'allait pas, et que l'apparence native impose : une fleche dessinee par
   le systeme, minuscule et collee au bord droit ; aucune reaction au survol la ou
   tous les autres champs en ont une ; et un libelle long coupe NET au milieu d'un
   mot (« New York — actions et indices US (ES, NQ, Y »).

   /!\ Le chevron est peint par le PARENT (`.liste::after`), pas par une image de
   fond de la liste elle-meme. Une image de fond obligerait a ecrire la couleur du
   chevron EN DUR dans une adresse `data:` — impossible a relier a une variable, et
   c'est precisement ce que la charte interdit. Ici il suit `--muted-foreground`,
   et passe a l'accent au survol comme le reste.

   /!\ La liste DEPLIEE reste dessinee par le systeme : aucune feuille de style ne
   l'atteint. C'est `color-scheme: dark` (sur `html`) qui la rend sombre — sans
   elle, elle s'ouvrait en blanc au milieu d'une page noire. */
.liste { position: relative; display: block; }
.liste > select.saisie {
  appearance: none; -webkit-appearance: none;
  /* De la place pour le chevron, et l'ellipse plutot que la coupe franche. */
  padding-right: 42px;
  text-overflow: ellipsis;
  /* /!\ Chasse PROPORTIONNELLE ici, a l'inverse des autres champs. Les autres
     portent des nombres, que la chasse fixe aligne au chiffre ; une liste porte
     des phrases (« Pétrole brut WTI (CL) »), et la chasse fixe les etale sans
     rien aligner — elle aggravait la coupe en fin de champ.
     /!\ **LA TAILLE, ELLE, EST CELLE DU CHAMP** (`--text-base`, 15 px), et ce
     n'etait pas le cas : elle valait `--text-md`, 17 px. Signale a l'ecran par le
     fondateur le 2026-09-14 — « les polices sont plus grosses que le champ ».
     Deux pixels suffisaient a faire depasser la liste de la ligne de ses voisins,
     et la liste DEPLIEE (`.liste-option`) etait deja a 15 px : le menu changeait
     donc de taille en s'ouvrant. Le meme cran partout, ouvert comme ferme. */
  font-family: var(--font-sans); font-size: var(--text-base);
  cursor: pointer;
}
.liste::after {
  content: "";
  position: absolute; right: 17px; top: 50%;
  width: 7px; height: 7px; margin-top: -6px;
  border-right: 1.6px solid var(--muted-foreground);
  border-bottom: 1.6px solid var(--muted-foreground);
  transform: rotate(45deg);
  pointer-events: none;                 /* le clic traverse, il ouvre la liste */
  transition: border-color var(--motion-slow) var(--ease-out);
}
.liste:hover::after, .liste:focus-within::after { border-color: var(--accent); }
/* Le peu que certains navigateurs acceptent sur la liste depliee NATIVE, tant
   que `liste.js` n'a pas pris le relais (ou s'il ne se charge pas). */
.liste > select.saisie option,
.liste > select.saisie optgroup { background: var(--card); color: var(--foreground); }
.liste > select.saisie optgroup { font-style: normal; font-weight: 600; }

/* ── La liste DEPLIEE, dessinee par nous (2026-08-16, second retour du
   fondateur : « les bords sont rectangulaires, ca casse avec le design des
   encadres ») ──

   /!\ Une liste depliee NATIVE est hors d'atteinte de toute feuille de style :
   coins carres, liseré dur, aplat clair sur certains systemes. `color-scheme:
   dark` la fonce dans les navigateurs qui l'ecoutent, et rien de plus — ni
   l'arrondi, ni l'ombre, ni le survol. C'est pourquoi elle est REFAITE.

   /!\ Le `<select>` d'origine RESTE dans le formulaire, jamais remplace : c'est
   lui qui porte la valeur envoyee, la validation `required`, et c'est lui qui
   s'affiche si le script ne se charge pas. On ne le masque pas en
   `display: none` — un champ `required` invalide et non affichable bloque
   l'envoi du formulaire avec une erreur que personne ne voit. */
/* /!\ Tout `<select>` habille, et plus seulement `select.saisie` (2026-09-17) : les
   listes de l'espace equipe portent `.fi`, celles des fenetres de filtre aucune
   classe, et un natif laisse visible s'affichait A COTE de son bouton. */
.liste[data-enrichie] > select {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  opacity: 0;
}
.liste[data-enrichie]::after { display: none; }   /* le chevron passe au bouton */

.liste-bouton {
  display: flex; align-items: center; gap: 10px;
  text-align: left;
  font-family: var(--font-sans); font-size: var(--text-base);   /* un champ : 15 (24/09/2026) */
  padding-right: 15px;
}
/* Le bouton d'une liste `.fi` (espace equipe, pas encore migre) : le dessin du
   champ `.fi` voisin, au cas ou son ecran ne le donne pas lui-meme. Un ecran qui
   le donne (`.field-tight .fi`, ou le `.fi` propre a une page) gagne : `:where` ramene
   ce selecteur au poids d'une seule classe. */
:where(.liste > .liste-bouton).fi {
  width: 100%; box-sizing: border-box;
  padding: 9px 12px;
  font-size: var(--text-base);
  color: var(--foreground);
  background: var(--card);
  border: 1px solid var(--border); border-radius: 4px;
  cursor: pointer;
}
:where(.liste > .liste-bouton).fi:focus-visible { outline: none; border-color: var(--accent); }
.liste-valeur {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.liste-chevron {
  flex: 0 0 auto;
  width: 7px; height: 7px;
  border-right: 1.6px solid var(--muted-foreground);
  border-bottom: 1.6px solid var(--muted-foreground);
  transform: translateY(-2px) rotate(45deg);
  transition: border-color var(--motion-slow) var(--ease-out),
              transform var(--motion-slow) var(--ease-out);
}
.liste-bouton:hover .liste-chevron,
.liste-bouton[aria-expanded="true"] .liste-chevron { border-color: var(--accent); }
/* Deplie, le chevron se retourne : la liste dit dans quel sens elle va. */
.liste-bouton[aria-expanded="true"] .liste-chevron {
  transform: translateY(2px) rotate(-135deg);
}

/* /!\ **LE PANNEAU SE LIT, LE CHAMP GARDE SA LARGEUR** (2026-09-15, fondateur :
   « le texte ne s'affiche pas bien quand le champ a une petite largeur ; j'aimerais
   qu'on conserve la largeur du champ, mais que la liste deroulante soit ergonomique
   a l'oeil »). Il suivait la largeur du bouton (`left:0; right:0`) : dans la phrase
   d'un reglage, ou un champ fait 130 px, « la divergence prix / delta » sortait sur
   trois lignes et « LE DELTA CUMULE, SUR UNE SEANCE » sur quatre.

   C'est exactement le correctif que `.liste--compact` portait deja pour la barre de
   filtres, et qui n'avait pas ete generalise : il devient la regle, et la barre de
   filtres n'a plus besoin de sa propre version.

   /!\ **`min-width` ET `max-width`, les deux.** Sans le premier, un panneau d'options
   courtes serait PLUS ETROIT que son champ — le defaut inverse, tout aussi laid.
   Sans le second, une note d'option longue (les seances en portent) etirerait le
   panneau hors de la fenetre. Le debordement a DROITE, lui, ne se regle pas en CSS :
   c'est `liste.js` qui mesure et pose `.liste--droite`. */
.liste-panneau {
  position: absolute; left: 0; top: calc(100% + 6px);
  min-width: 100%; width: max-content;
  max-width: min(360px, calc(100vw - 24px));
  z-index: var(--z-dropdown);
  max-height: 300px; overflow-y: auto;
  padding: 6px;
  background: linear-gradient(180deg, #1A1D27 0%, #14161F 100%);
  border: 1px solid var(--border-strong);
  /* L'arrondi des encadres, en plus petit : la liste appartient a la meme
     famille de surfaces, elle ne doit pas ramener un coin carre dans la page. */
  border-radius: 12px;
  box-shadow: 0 24px 60px -16px rgba(0,0,0,0.75),
              0 0 0 1px rgba(255,255,255,0.04) inset;
}
/* Pres du bas de l'ecran, elle s'ouvre VERS LE HAUT plutot que de sortir de la
   fenetre — posee par le script, qui mesure la place restante. */
.liste--haut > .liste-panneau { top: auto; bottom: calc(100% + 6px); }
/* Et pres du bord DROIT, elle s'aligne sur ce bord-la. Meme script, meme mesure :
   depuis que le panneau prend la largeur de son contenu, il peut depasser a droite
   ce que le champ, lui, ne depassait jamais. */
.liste--droite > .liste-panneau { left: auto; right: 0; }

.liste-option {
  display: block; width: 100%;
  padding: 9px 12px;
  text-align: left;
  font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.4;
  color: var(--foreground);
  background: transparent; border: 0; border-radius: 8px;
  cursor: pointer;
}
/* /!\ `data-actif` est la ligne que le CLAVIER designe, `aria-selected` celle
   qui est CHOISIE. Les confondre ferait perdre de vue son propre reglage des
   qu'on presse une fleche. */
.liste-option:hover,
.liste-option[data-actif="oui"] { background: rgba(180,168,255,0.10); }
.liste-option[aria-selected="true"] { color: var(--accent); font-weight: 500; }
/* La PRECISION d'une option, sur sa propre ligne (`data-note`, 2026-09-06). Posee
   pour les seances — « Globex » puis « 17:00 → 16:00 Chicago · 18:00 → 17:00 chez
   vous » — parce que tout mettre sur une ligne donnait un pave qui se repliait sur
   deux et qu'on ne lisait plus. Elle sert a toute liste qui a une precision a
   donner. */
.liste-option-note {
  display: block; margin-top: 2px;
  font-size: var(--text-sm); line-height: 1.35; font-weight: 400;
  color: var(--muted-foreground);
}
.liste-option[aria-selected="true"] .liste-option-note { color: var(--muted-foreground); }
.liste-groupe {
  padding: 12px 12px 5px;
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.liste-groupe:first-child { padding-top: 6px; }

/* Une case a cocher ou un bouton radio, avec son libelle sur la meme ligne.
   `accent-color` suffit a teinter la coche native : la refaire a la main
   couterait un faux carre, un vrai `<input>` masque et trois etats a peindre,
   pour le meme resultat en moins accessible. */
.case {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: var(--text-base); line-height: 1.4;
  color: var(--foreground); cursor: pointer;
}
.case > input[type="checkbox"], .case > input[type="radio"] {
  flex: 0 0 auto;
  width: 16px; height: 16px; margin: 0;
  accent-color: var(--primary);
  cursor: pointer;
}
.cases { display: flex; flex-wrap: wrap; gap: 12px 22px; align-items: center; }
/* /!\ `.case` est en `inline-flex` : deux cases posees a la suite se rangent sur
   la MEME ligne. Quand ce sont deux choix independants — « ne pas trader autour
   des annonces » et « solder avant l'annonce » — les mettre cote a cote les fait
   lire comme un seul reglage a deux moities. Elles s'empilent alors. */
.cases--liste { flex-direction: column; align-items: flex-start; gap: 12px; }

/* La BASCULE : le selecteur segmente `.seg`, mais fait de vrais boutons radio
   (2026-08-17, demande du fondateur : « je verrais bien un bouton de bascule
   d'un mode de calcul a l'autre »).

   Deux ronds a cocher poses cote a cote ne DISENT pas qu'ils commandent la meme
   chose : ils se lisent comme deux reglages independants dont on cocherait l'un.
   Une bascule dit « c'est un seul reglage, a deux positions ».

   /!\ Les `<input type="radio">` restent la, seulement invisibles. C'est eux que
   le formulaire envoie, eux que les fleches du clavier parcourent, eux que le
   lecteur d'ecran annonce comme un groupe. Les remplacer par des boutons aurait
   coute tout cela pour la meme image. */
.seg--champ > label { position: relative; }
.seg--champ > label > input {
  position: absolute; width: 1px; height: 1px;
  margin: 0; opacity: 0; pointer-events: none;
}
.seg--champ > label:has(input:checked) {
  background: var(--card);
  color: var(--foreground);
  box-shadow: 0 1px 2px rgba(0,0,0,0.35);
}
/* Le contour de mise au point doit suivre le bouton radio CACHE : sans cette
   regle, la bascule se parcourt au clavier sans qu'on voie ou l'on est. */
.seg--champ > label:has(input:focus-visible) {
  outline: 2px solid var(--ring); outline-offset: 2px;
}

/* Le DUO : deux valeurs qui vont ensemble, dans une zone a elles.
   Son SEUL appelant est desormais l'ecran d'inscription (`signup.html`), qui
   redeclare ses colonnes dans son propre `<style>` mais herite ce remplissage.
   /!\ Ne pas le supprimer en le croyant mort : le retirer changerait cet ecran-la.
   /!\ Les reglages d'un trader l'ont quitte le 2026-08-29. Il y etait toujours
   pose avec `verre-zone`, DANS une carte `.groupe` qui a deja sa bordure : le
   cadre dans un cadre que le commentaire de `.verre-zone` interdit, et trois
   alignements differents dans une meme carte. Les deux valeurs continuent de se
   lire l'une par rapport a l'autre, par la COULEUR des saisies
   (`.saisie--perte` / `.saisie--gain`), qui ne demande aucune boite. */
.duo {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px 24px;
  padding: 16px 18px;
}

/* Ce qui COUTE en rouge, ce qui RAPPORTE en vert : le meme code que partout
   ailleurs dans l'application (`--loss` / `--profit`), pose sur la saisie
   elle-meme. Le risque et la perte partagent le role « ce qui coute ».
   /!\ Le rouge et le vert ne disent pas « mal » et « bien » : ils disent de quel
   cote on est. Un client qui augmente son objectif ne fait rien de « bien ». */
.saisie--perte { color: var(--loss); }
.saisie--gain { color: var(--profit); }

/* Une valeur que le serveur ou le navigateur CALCULE, et que l'on ne tape pas.
   Le liseré en pointilles le dit sans une phrase. Elle reste lisible : la griser
   comme un champ desactive ferait croire a un champ en panne, alors que c'est la
   moitie de l'information.
   /!\ Ces champs portent `disabled`, et c'est VOULU : un champ desactive n'est
   pas envoye par le formulaire. Sans cela, les deux moities du duo partiraient
   sous le meme nom et le serveur en choisirait une au hasard. */
.saisie--deduite {
  background-color: transparent;
  border-style: dashed;
  cursor: default;
}
.saisie--deduite:disabled { opacity: 1; }

/* ============================================================
   La BULLE d'aide (2026-08-16)
   ============================================================
   Une description longue sous chaque champ transforme un formulaire de trente
   reglages en mur de texte, et on finit par ne plus rien lire. Elle passe donc
   derriere une petite icone, et ne s'affiche qu'a la demande.

   /!\ Ce n'est PAS un avertissement. Ce qui coute cher s'il n'est pas lu — « le
   cout affiche sera faux pour ce modele » — reste ECRIT, en clair. La bulle est
   pour ce qui aide, pas pour ce qui protege.

   /!\ Le declencheur est un vrai `<button>`, jamais un `<span>` : le bouton
   donne le clavier (tabulation + Entree) et le tactile (une tape) sans une
   ligne de script, la ou un survol seul n'existe ni au clavier ni au doigt.
   Le texte d'aide vit DANS le bouton, masque a l'oeil mais lu par les lecteurs
   d'ecran : la bulle peut disparaitre, l'aide reste.

   /!\ La bulle elle-meme est posee sur `<body>` par `bulle.js`, en
   `position: fixed`. Elle ne peut PAS vivre dans le champ : `.panel` et
   `.encadre` coupent ce qui depasse (`overflow`, `border-radius`), et une bulle
   ancree pres d'un bord serait tronquee. */
.aide {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; margin-left: 6px;
  flex: 0 0 auto; vertical-align: middle;
  background: transparent;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  color: var(--muted-foreground);
  cursor: help;
  transition: color var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out);
}
/* ⚠️ LE « i » SUR LA LIGNE DE SON DERNIER MOT (fondateur, 22/10/2026). `middle` le
   centre sur la hauteur des minuscules (mesuré : à 1 px du milieu du texte, contre 4 px
   trop bas avec l'ancien `-3px`). Qu'il ne tombe jamais seul sur sa ligne, c'est
   `bulle.js` (`souder`) : il enveloppe le dernier mot et le « i » dans `.aide-soudure`,
   qui ne se coupe pas (une espace insécable ne suffit pas dans Chrome). */
.aide-soudure { white-space: nowrap; }
.aide svg { width: 9px; height: 9px; }
.aide:hover, .aide:focus-visible {
  color: var(--accent);
  border-color: var(--accent-ring-strong);
}
/* Le texte d'aide : invisible a l'oeil, present pour les lecteurs d'ecran.
   Pas de `display:none` ni de `visibility:hidden` — ils le retireraient AUSSI
   des lecteurs d'ecran, ce qui est exactement ce qu'on veut eviter. */
/* /!\ Le selecteur porte sur `.aide-texte` SEUL depuis le 2026-08-18. Il etait
   `.aide > .aide-texte`, donc reserve au bouton d'aide ; la rangee d'un trade, qui
   porte le meme texte sans etre un bouton, l'affichait EN CLAIR au milieu de la ligne
   et poussait tout le reste. Le texte de bulle est masque parce qu'il est un texte de
   bulle, pas parce qu'il est dans un bouton. */
.aide-texte {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
#tvni-bulle {
  position: fixed; top: 0; left: 0;
  z-index: var(--z-toast);
  /* /!\ `100vw` ignore le `zoom` de `body` : a l'echelle 1.1 la bulle pouvait se
     croire autorisee a 10 % de plus que la largeur reelle. On divise par l'echelle,
     comme `bulle.js` le fait pour la position. */
  max-width: min(320px, calc((100vw - 24px) / var(--echelle)));
  padding: 11px 14px;
  font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.55;
  font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--foreground);
  background: linear-gradient(180deg, #1A1D27 0%, #14161F 100%);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: 0 18px 40px -12px rgba(0,0,0,0.7);
  opacity: 0; visibility: hidden;
  transition: opacity var(--motion-base) var(--ease-out),
              visibility var(--motion-base) var(--ease-out);
  pointer-events: none;
}
#tvni-bulle[data-ouverte="oui"] { opacity: 1; visibility: visible; }
#tvni-bulle code {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--accent);
}
#tvni-bulle strong { color: var(--foreground); font-weight: 600; }
/* La bulle d'un TRADE (2026-08-18) : elle porte le plan, l'issue et le raisonnement du
   modele, la ou une aide de champ porte deux phrases. 320 px la rendaient illisible. */
#tvni-bulle[data-forme="periode"] {
  max-width: min(300px, calc((100vw - 24px) / var(--echelle)));
}
#tvni-bulle[data-forme="trade"] {
  max-width: min(440px, calc((100vw - 24px) / var(--echelle)));
}
/* Le « pourquoi » entier d'une ligne du journal coupée (30/10/2026, `data-bulle-coupe`). */
#tvni-bulle[data-forme="pourquoi"] {
  max-width: min(480px, calc((100vw - 24px) / var(--echelle)));
  white-space: pre-line;
}
.bulle-titre {
  display: block; font-family: var(--font-mono); font-weight: 600;
  margin-bottom: 6px;
}
.bulle-faits {
  display: grid; grid-template-columns: auto 1fr; gap: 2px 12px;
  font-size: var(--text-sm); margin-bottom: 8px;
}
.bulle-faits dt { color: var(--muted-foreground); }
.bulle-faits dd { margin: 0; font-variant-numeric: tabular-nums; }
.bulle-pourquoi {
  display: block; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: var(--text-sm); line-height: 1.5; color: var(--muted-foreground);
}
/* /!\ Le raisonnement est CLAMPE a cinq lignes, pas tronque par le serveur : une bulle
   qui prendrait la moitie de l'ecran ne se survole plus, elle se subit. Le texte entier
   reste a un clic, et la ligne suivante le dit. */
.bulle-texte {
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;
  overflow: hidden; color: var(--foreground);
}
.bulle-suite { display: block; margin-top: 7px; color: var(--disabled-foreground); }

/* ============================================================
   La FENETRE de confirmation (2026-08-16)
   ============================================================
   /!\ JAMAIS `confirm()`. C'est la boite du NAVIGATEUR : hors de la charte,
   elle tronque les textes longs, et certains contextes l'ecartent sans rien
   afficher — le clic semble alors n'avoir rien fait. Une fenetre de
   l'application a la place de NOMMER la consequence au lieu de demander
   « etes-vous sur ? ».

   /!\ `<dialog>` natif : superposition, piege du focus et touche Echap sans une
   ligne de script. Une fausse fenetre en `<div>` rate presque toujours les
   trois. `.verre` lui donne sa surface ; il ne reste que la largeur, le voile
   de fond et l'espacement, que `<dialog>` ne pose pas seul. */
/* /!\ `position: fixed` (08/10/2026) : `.verre` pose `position: relative`, que le
   navigateur change en `absolute` dans la couche superieure. La fenetre se placait
   alors par rapport au HAUT DE LA PAGE, pas de l'ecran : ouverte apres un defilement,
   elle s'affichait coupee en haut (titre hors de l'ecran), sur toutes les fenetres
   de l'application. `fixed` rend son comportement natif a `showModal()`. */
dialog.verre {
  position: fixed;
  width: min(460px, calc(100vw - 32px));
  padding: 24px;
  color: var(--foreground);
}
dialog.verre::backdrop { background: rgba(5, 6, 10, 0.72); }
.dialogue-titre {
  margin: 0 0 12px;
  font-family: var(--font-sans);
  font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.015em; line-height: 1.3;
  color: var(--foreground);
}
.dialogue-texte {
  margin: 0 0 8px;
  font-size: var(--text-base); line-height: 1.55; color: var(--muted-foreground);
}
.dialogue-texte strong { color: var(--foreground); font-weight: 500; }
.dialogue-alerte { color: var(--warning-text); }
.dialogue-actions {
  display: flex; gap: 8px; justify-content: flex-end; margin-top: 18px;
}

/* Cards */
.card {
  padding: var(--space-6) 26px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}

/* Inputs */
.input, .select, textarea.textarea {
  height: 2.714em; padding: 0 var(--space-3);
  font-family: var(--font-sans); font-size: var(--text-body); color: var(--foreground);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  transition: border-color var(--motion-base) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
textarea.textarea { height: auto; min-height: 80px; padding: var(--space-3); }
.input::placeholder { color: var(--disabled-foreground); }
.input:focus, .select:focus, textarea.textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgb(74 59 122 / 0.12);
}
.input:disabled { background: var(--muted); opacity: 0.6; }
.input[aria-invalid="true"] { border-color: var(--error); }

/* Search input with leading icon */
.search-wrapper { position: relative; max-width: 360px; }
.search-icon {
  position: absolute;
  left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--muted-foreground);
  pointer-events: none;
  display: flex; align-items: center;
}
.search-input { width: 100%; padding-left: 40px; }

/* KPI — polish v4 (sentence-case label, elevation, hover lift, ratio bar) */
.kpi-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  min-height: 88px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  box-shadow: 0 1px 2px rgba(14,17,38,.04), 0 8px 24px -8px rgba(14,17,38,.06);
  transition: transform .22s cubic-bezier(.2,.8,.2,1), box-shadow .22s ease, border-color .22s ease;
  animation: kpiFadeIn .5s cubic-bezier(.2,.8,.2,1) backwards;
}
.kpi-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 2px 4px rgba(14,17,38,.06), 0 16px 32px -8px rgba(74,59,122,.16);
  border-color: rgba(74,59,122,.25);
}
@keyframes kpiFadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.kpi-strip .kpi-card:nth-child(1) { animation-delay: 0ms; }
.kpi-strip .kpi-card:nth-child(2) { animation-delay: 60ms; }
.kpi-strip .kpi-card:nth-child(3) { animation-delay: 120ms; }
.kpi-strip .kpi-card:nth-child(4) { animation-delay: 180ms; }
.kpi-strip .kpi-card:nth-child(5) { animation-delay: 240ms; }
.kpi-strip .kpi-card:nth-child(6) { animation-delay: 300ms; }

.kpi-card .kpi-label {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--muted-foreground);
  font-weight: 500;
  letter-spacing: -0.005em;
  text-transform: none;
  margin-bottom: 8px;
  display: block;
}
.kpi-card .kpi-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.kpi-card .kpi-value {
  font-size: var(--text-3xl);
  font-weight: 500;
  color: var(--foreground);
  font-variant-numeric: tabular-nums;
  line-height: 1.0;
  letter-spacing: -0.025em;
}
.kpi-card .kpi-value.sage       { color: var(--success); }
.kpi-card .kpi-value.terracotta { color: var(--error); }
.kpi-card .kpi-value.muted      { color: var(--disabled-foreground); }
.kpi-card .kpi-unit {
  font-family: var(--font-sans);
  font-size: var(--text-md);
  color: var(--disabled-foreground);
  font-weight: 400;
}
.kpi-card .kpi-delta-pct {
  margin-left: auto;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.kpi-card .kpi-delta-pct.up::before    { content: '↑'; font-size: var(--text-2xs); }
.kpi-card .kpi-delta-pct.down::before  { content: '↓'; font-size: var(--text-2xs); }
.kpi-card .kpi-delta-pct.up    { color: var(--success); }
.kpi-card .kpi-delta-pct.down  { color: var(--error); }
.kpi-card .kpi-delta-pct.stable,
.kpi-card .kpi-delta-pct.new   { color: var(--muted-foreground); }
.kpi-card .kpi-secondary {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--muted-foreground);
  margin-top: 10px;
}
.kpi-card .kpi-ratio {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}
.kpi-card .kpi-ratio-bar {
  flex: 1;
  height: 4px;
  background: var(--muted);
  border-radius: 2px;
  overflow: hidden;
}
.kpi-card .kpi-ratio-bar > i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, #4A3B7A 0%, #6B5BA3 100%);
  border-radius: 2px;
  transform-origin: left;
  animation: kpiBarFill 1.1s cubic-bezier(.2,.8,.2,1) .25s backwards;
}
@keyframes kpiBarFill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.kpi-card .kpi-ratio-pct {
  font-weight: 600;
  color: var(--foreground);
  font-variant-numeric: tabular-nums;
}

/* Data table — polish v4 (gradient header, accent bar on hover, row actions) */
.data-table { width: 100%; border-collapse: separate; border-spacing: 0; }
.data-table th {
  font-family: var(--font-sans); font-size: var(--text-2xs);
  text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted-foreground);
  padding: 12px 16px; text-align: left;
  font-weight: 600;
  /* Le degrade clair code en dur (#F4F5F8) et sa bordure #D6DAE2 ont ete
     retires le 2026-08-23 : ils peignaient une BARRE BLANCHE en haut de chacun
     des 19 tableaux de l application, qui est sombre. Le meme couple etait
     duplique dans `admin_base.html` ; les deux ont ete corriges ensemble. */
  background: var(--muted);
  border-bottom: 1px solid var(--border);
}
.data-table td {
  font-size: var(--text-body); color: var(--foreground);
  padding: 14px 16px;
  border-bottom: 1px solid var(--muted);
  vertical-align: middle;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr { transition: background .22s ease; }
.data-table tbody tr:hover {
  background: linear-gradient(90deg, rgba(74,59,122,.05) 0%, rgba(74,59,122,0) 100%);
}
.data-table tbody tr.selected,
.data-table tbody tr.selected:hover {
  background: rgba(74,59,122,.11);
  box-shadow: inset 3px 0 0 var(--accent);
}
/* Accent bar handled via border-left on td:first-child in admin_base.html (layout-safe) */

/* Numeric / muted helpers for data cells */
.data-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table td.num.muted, .data-table td .muted-cell { color: var(--disabled-foreground); }
.data-table .row-id {
  font-size: var(--text-xs); color: var(--disabled-foreground);
  margin-left: 8px; padding: 1px 6px;
  background: var(--muted); border-radius: 3px;
  font-variant-numeric: tabular-nums;
}
.data-table .row-name { font-weight: 600; color: var(--foreground); font-size: var(--text-base); }
.data-table .row-email { font-size: var(--text-base); color: var(--foreground); }
.data-table .row-phone {
  font-size: var(--text-sm); color: var(--muted-foreground);
  margin-top: 2px; display: block; font-variant-numeric: tabular-nums;
}
.data-table .row-date { font-variant-numeric: tabular-nums; font-size: var(--text-sm); color: var(--muted-foreground); }
.data-table .row-actions {
  opacity: 0;
  transition: opacity .22s ease;
  display: inline-flex; gap: 4px;
  margin-left: 12px;
  vertical-align: middle;
}
.data-table tbody tr:hover .row-actions { opacity: 1; }
.data-table .row-actions button,
.data-table .row-actions a {
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--card);
  border-radius: 4px; color: var(--muted-foreground);
  cursor: pointer; transition: all .14s; text-decoration: none;
}
.data-table .row-actions button:hover,
.data-table .row-actions a:hover {
  border-color: var(--accent); color: var(--accent);
  background: rgba(74,59,122,.05);
}

/* Colonne de case à cocher (sélection multiple) — Décisions/Users (A2/D1) */
.data-table th.sel-col,
.data-table td.sel-col {
  width: 36px;
  padding-left: var(--space-3);
  padding-right: 0;
  text-align: center;
}
.sel-col input[type="checkbox"] { vertical-align: middle; cursor: pointer; }

/* Barre d'actions de sélection multiple (corbeille Décisions, rôles Users — A2/D1) */
.trade-selection-bar:not([hidden]) {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.tsb-count {
  font-size: var(--text-body-sm);
  color: var(--muted-foreground);
  white-space: nowrap;
}
.tsb-count b { color: var(--foreground); font-variant-numeric: tabular-nums; }
.tsb-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* Status dot — defined in admin_base.html for higher specificity context. */

/* Badges */
.badge {
  display: inline-flex; align-items: center;
  padding: 3px 9px;
  font-family: var(--font-sans); font-size: var(--text-xs); font-weight: 500;
  line-height: 1; letter-spacing: 0.02em;
  border-radius: var(--radius-pill);
}
/* /!\ Fonds RENFORCES le 2026-08-17, sur remarque du fondateur (« c'est un peu trop
   gris, je trouve »). Les teintes etaient posees a 0.12 d'opacite, et dans une famille
   de couleur (verts et bruns sourds) HERITEE DU THEME CLAIR, sans rapport avec la
   couleur du texte qu'elles portent. Resultat : a un metre de l'ecran un badge de
   couleur et un badge neutre se ressemblaient, et il fallait lire le mot pour savoir si
   l'etat etait bon ou mauvais.
   Le fond reprend desormais la teinte du TEXTE qu'il porte, a 0.16. La couleur du texte,
   elle, ne bouge pas : c'est elle qui porte le sens, le fond ne fait que la detacher. */
.badge-success    { background: rgb(74 222 128 / 0.16);  color: var(--success); }
.badge-warning    { background: rgb(251 191 36 / 0.16);  color: var(--warning); }
.badge-error      { background: rgb(248 113 113 / 0.16); color: var(--error); }
.badge-info       { background: rgb(125 211 252 / 0.16); color: var(--info); }
.badge-neutral    { background: var(--muted);           color: var(--muted-foreground); }
.badge-prospect   { background: var(--muted);           color: var(--muted-foreground); }
.badge-client     { background: rgb(44 58 82 / 0.10);   color: var(--primary); }
.badge-ambassador { background: rgb(74 59 122 / 0.12);  color: var(--accent); }
/* Le meme badge que `-ambassador`, sous un nom qui n'est pas un role de CRM : « active »
   sur une version de trader n'a rien a voir avec un ambassadeur. */
.badge-accent     { background: rgb(74 59 122 / 0.12);  color: var(--accent); }

/* Sidebar nav */
.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px var(--space-3);
  font-size: var(--text-body); font-weight: 500;
  color: var(--muted-foreground); text-decoration: none;
  border-radius: var(--radius-md);
  transition: background var(--motion-base) var(--ease-out),
              color var(--motion-base) var(--ease-out);
}
.nav-item:hover { background: var(--muted); color: var(--foreground); }
.nav-item.active,
.nav-item[aria-current="page"] {
  background: rgb(74 59 122 / 0.08);
  color: var(--accent);
  border-left: 2px solid var(--accent);
}

/* ══ La LIGNE DE FAITS sous un titre de page (montee dans la charte le
   2026-08-23) ══════════════════════════════════════════════════════════════
   « 2 traders crees · 1 actif sur 3 disponibles · aucun plugin connecte ». Elle
   repond, avant tout le reste, a la question qu'on se pose en arrivant sur
   l'ecran. Le CHIFFRE est en clair, le mot qui le qualifie en gris : c'est ce
   qui la rend lisible d'un coup d'oeil sans qu'elle pese comme une rangee de
   tuiles.

   /!\ Elle vivait en local dans `user_traders.html` depuis le 2026-08-16. Elle
   monte ici parce qu'un DEUXIEME ecran l'emploie (« Parrainage », puis « Mes
   gains ») -- la regle de cette feuille : la charte ne prend que ce qui a servi
   deux fois, et une primitive suit son premier ecran reel.

   /!\ Elle n'apparait JAMAIS sur un ecran vide. « 0 filleul, 0 client » est un
   tableau de bord pose au-dessus du rien ; c'est `.vide-accueil` qui parle a ce
   moment-la. */
.resume-etat {
  display: flex; gap: 18px; flex-wrap: wrap; align-items: baseline;
  margin-top: 10px;
}
.resume-etat .item { font-size: var(--text-body-sm); color: var(--muted-foreground); }
.resume-etat .item strong { color: var(--foreground); }

/* Empty state */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-3);
  padding: var(--space-12) var(--space-6);
  text-align: center;
}
.empty-state svg { width: 48px; height: 48px; color: var(--muted-foreground); opacity: 0.4; }
.empty-title { font-size: var(--text-h4); font-weight: 500; color: var(--foreground); margin: 0; }
.empty-body { font-size: var(--text-body); color: var(--muted-foreground); margin: 0; }

/* ══ L'ACCUEIL D'UN ECRAN VIDE (2026-08-23) ═══════════════════════════════════
   /!\ A ne pas confondre avec `.empty-state` juste au-dessus, qui reste ce qu'il
   est : la petite ligne grise d'un tableau d'administration qui n'a rien a
   montrer. Ce bloc-ci s'adresse a un CLIENT qui vient de payer et qui ouvre un
   ecran pour la premiere fois. Il ne dit pas « aucun resultat », il dit ce qu'il
   trouvera ici et par quoi commencer.

   Il sert DEUX ecrans, et c'est ce qui l'a fait monter dans la charte plutot que
   de rester local : « Mes traders » sans trader, et « Mes trades » sans la
   moindre analyse. `user_trades.html` n'a d'ailleurs plus aucun style local
   depuis le 2026-08-17 -- il ne pouvait pas les declarer chez lui.

   /!\ La pastille d'icone est TEINTEE d'accent, pas grise. Un rond gris pose
   au-dessus de « vous n'avez rien » se lit comme une erreur ; cet ecran
   accueille. C'est la difference de ton avec `.empty-state`. */
.vide-accueil { text-align: center; max-width: 640px; margin: 14px auto 30px; }
.vide-accueil-marque {
  width: 52px; height: 52px; margin: 0 auto 16px; border-radius: var(--radius-lg);
  display: flex; align-items: center; justify-content: center;
  background: rgb(180 168 255 / 0.10); border: 1px solid rgb(180 168 255 / 0.22);
  color: var(--accent);
}
/* /!\ UN TITRE DE BLOC, PAS UN TITRE DE PAGE (echelle des polices, 24/09/2026) :
   19 px (`--text-lg`), sous le `h1` de l'ecran qui en fait 22. Il etait a 28 px,
   plus gros que le titre de la page qui le porte. `test_echelle_des_polices`. */
.vide-accueil-titre { font-size: var(--text-lg); margin: 0 0 8px; letter-spacing: -0.02em; }
.vide-accueil-lede { margin: 0; font-size: var(--text-body); }
/* /!\ **L'ESPACE ENTRE LA PHRASE ET LE GESTE** (2026-09-14, fondateur : « espace un
   peu le bouton du texte si tu en mets un »). Il est pose sur la PHRASE et non sur le
   bouton : la moitie de ces boutons vivent dans un `<form style="margin:0">`, et un
   style en ligne bat la feuille de style — la regle n'aurait rien fait, en silence.
   /!\ `:not(:last-child)` : un etat vide SANS geste ne doit pas trainer 18 px de
   vide sous sa derniere ligne. */
.vide-accueil-lede:not(:last-child) { margin-bottom: 18px; }

/* /!\ **LES STYLES DES TROIS CARTES NUMEROTEES SONT PARTIS le 2026-09-14**, avec
   les cartes qu'ils habillaient (`.vide-cartes`, `.vide-cartes-tete`, `.vide-num`
   et son etat courant). Elles dessinaient un SECOND parcours sur l'ecran ou la
   carte de mise en route en annonce deja un, en six etapes et dans un autre
   ordre. Un client neuf lisait donc deux chemins contradictoires.
   /!\ Le parcours vit dans `.mer-*`, plus haut dans cette feuille, et nulle part
   ailleurs. */

/* Le pied de l'ecran d'attente : l'etat a gauche, le geste a droite. */
.vide-pied {
  display: flex; gap: 14px; flex-wrap: wrap;
  align-items: center; justify-content: space-between;
}

/* ⚠️ `.api-key-display` (clé masquée + oeil + copier) retiré le 2026-08-25 avec
   la section « Authentification API » du tiroir utilisateur, son unique porteur. */

/* Liens */
a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
a:hover { text-decoration-thickness: 2px; }

/* ----------------------- Wave D — Profile page ----------------------- */

.profile-body {
  background: var(--background, #F4F4F2);
  color: var(--foreground, #1E1E1E);
  font-family: var(--font-sans, 'Inter', sans-serif);
  margin: 0;
}

.profile-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 32px;
  background: var(--surface, #FFFFFF);
  border-bottom: 1px solid var(--border, #E5E5E5);
}

.profile-nav-brand .profile-nav-logo {
  font-weight: 600;
  font-size: var(--text-xl);
  color: var(--marine, #2C3A52);
  letter-spacing: 0.02em;
}

.profile-nav-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

.profile-nav-user {
  font-size: var(--text-md);
  color: var(--foreground-muted, #555);
}

.profile-container {
  max-width: 720px;
  margin: 48px auto;
  padding: 0 32px;
}

.profile-container h1 {
  font-size: var(--text-3xl);
  font-weight: 400;
  margin: 0 0 32px;
  letter-spacing: -0.01em;
}

.profile-section {
  background: var(--surface, #FFFFFF);
  border: 1px solid var(--border, #E5E5E5);
  border-radius: 8px;
  padding: 24px;
  margin-bottom: 24px;
}

.profile-section h2 {
  font-size: var(--text-xl);
  font-weight: 500;
  margin: 0 0 16px;
}

.profile-form {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.profile-form label {
  display: flex;
  flex-direction: column;
  font-size: var(--text-base);
  color: var(--foreground-muted, #555);
}

.profile-form input {
  padding: 10px 12px;
  border: 1px solid var(--border, #E5E5E5);
  border-radius: 6px;
  font-size: var(--text-md);
  font-family: inherit;
  margin-top: 4px;
}

.profile-btn {
  padding: 8px 16px;
  border-radius: 6px;
  border: 1px solid transparent;
  font-size: var(--text-md);
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}

.profile-btn-primary {
  background: var(--marine, #2C3A52);
  color: #fff;
}

.profile-btn-ghost {
  background: transparent;
  border-color: var(--border, #E5E5E5);
  color: var(--foreground, #1E1E1E);
}

.profile-btn-danger {
  background: transparent;
  border-color: #C0392B;
  color: #C0392B;
}

.profile-key-row {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
  padding: 12px;
  background: var(--surface-alt, #FAFAFA);
  border-radius: 6px;
}

.profile-key-row code {
  flex: 1;
  font-size: var(--text-base);
}

/* `.profile-new-key` et `.filter-inline-select` sont partis le 2026-09-21
   (TVNI-100) : aucun gabarit, script ni test ne les employait, et chacune
   portait encore le fond blanc ecrit en dur du theme clair abandonne en aout. */

.profile-alert {
  padding: 16px;
  border-radius: 8px;
  margin-bottom: 24px;
}

.profile-alert-success { background: #E6F7EC; border: 1px solid #5FCB80; color: #1E5A35; }
.profile-alert-error   { background: #FDECEA; border: 1px solid #E57373; color: #8B2D24; }
.profile-alert-warning { background: #FFF8E1; border: 1px solid #E0B324; color: #6B5318; }

.profile-dl {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 8px 16px;
  margin: 0;
}

.profile-dl dt { color: var(--foreground-muted, #555); font-size: var(--text-base); }
.profile-dl dd { margin: 0; font-size: var(--text-md); }

/* Le bloc « V2 — Dashboard charts » (.dashboard-gran-toggle,
   .dashboard-charts-grid, .dashboard-chart-card) est parti le 2026-09-01 avec
   la page « Tableau de bord » : plus aucun gabarit ne portait ces classes. */

/* ----------------------- V3 — Filter toolbar + drawer flashes ----------------------- */

.filter-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--card, #FFFFFF);
  border: 1px solid var(--border, #E1E4EC);
  border-radius: 8px;
  position: sticky;
  top: 0;
  z-index: 10;
  flex-wrap: wrap;
  box-shadow: 0 1px 2px rgba(14,17,38,.04), 0 4px 12px -4px rgba(14,17,38,.04);
  margin-bottom: 16px;
  animation: kpiFadeIn .5s cubic-bezier(.2,.8,.2,1) 240ms backwards;
}

.filter-toolbar input[type="search"] {
  flex: 1;
  min-width: 200px;
  max-width: 320px;
  padding: 6px 12px;
  border: 1px solid var(--border, #E5E5E5);
  border-radius: 6px;
  font-size: var(--text-md);
  font-family: inherit;
}

.btn-filter-advanced {
  margin-left: auto;
  padding: 6px 12px;
  border: 1px solid var(--border, #E5E5E5);
  background: transparent;
  border-radius: 6px;
  font-size: var(--text-base);
  font-family: inherit;
  cursor: pointer;
  position: relative;
}

.filter-badge {
  background: var(--marine, #2C3A52);
  color: #fff;
  border-radius: 10px;
  padding: 1px 8px;
  font-size: var(--text-xs);
  margin-left: 6px;
}

/* UN TEXTE D'INFORMATION EST DISCRET (2026-09-16, demande du fondateur, capture a
   l'appui du bandeau « Cette position est encore ouverte pour TVNI ») : « ils ont une
   tres grosse police et prennent beaucoup trop de place. J'aimerais que tu les
   reduises pour que ce soit discret et pas que ca prenne toute la page. »

   /!\ Il etait a `--text-md` (17 px), soit DEUX CRANS AU-DESSUS du texte courant de
   l'application (15 px) : un encadre d'information criait plus fort que la page qu'il
   commente. Il descend au cran des textes secondaires (13 px), celui que
   `.reglage-indication` avait deja choisi le 2026-09-13 pour exactement la meme
   raison — et cette parente est le point : il n'existe plus deux facons de poser une
   information a cote d'un reglage.

   /!\ LA COULEUR NE BOUGE PAS. Ce qui rendait ces encadres visibles, c'est leur
   teinte et leur liseret, pas leur corps de texte — un avertissement reste un
   avertissement. On retire le volume, pas le signal.

   /!\ `line-height` EXPLICITE : sans lui, le bandeau garde l'interligne herite du
   corps de page, calcule pour du 15 px. Un texte plus petit dans des lignes espacees
   pour un texte plus grand ne gagne presque rien en hauteur — or c'est la HAUTEUR
   qu'il s'agissait de rendre. */
.drawer-flash {
  padding: 10px 14px;
  margin: 12px 0;
  border-radius: 6px;
  font-size: var(--text-body-sm);
  line-height: 1.55;
}
/* Les paragraphes qui suivent le premier, dans un bandeau qui en porte plusieurs :
   meme resserrement, sinon la marge de paragraphe du corps de page (calculee pour du
   15 px) rend a l'encadre la hauteur qu'on vient de lui retirer. */
.drawer-flash > p { margin: 6px 0 8px; }
.drawer-flash > p:last-child { margin-bottom: 0; }
/* Un lien dans un bandeau prend la couleur du texte : sans soulignement, « Le
   compléter » se lisait comme la fin de la phrase (09/10/2026). Un `.btn` garde
   son habit. */
.drawer-flash a:not(.btn) {
  color: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px;
}

/* /!\ Ces trois encadres etaient des APLATS CLAIRS (#E6F7EC, #FDECEA) avec un texte
   sombre : herites du theme clair, ils restaient blancs au milieu d'une page noire.
   Trouves a l'oeil sur « Mes traders » le 2026-08-16. Ils passent au principe du
   theme sombre : la couleur ne remplit plus le fond, elle le TEINTE (voile a 12 %),
   et c'est le TEXTE qui la porte en clair. Meme construction que `-warning`, qui
   etait deja le seul des trois a etre bati ainsi. */
.drawer-flash-success {
  background: rgba(74, 222, 128, 0.12);
  border: 1px solid rgba(74, 222, 128, 0.40);
  color: var(--success);
}

.drawer-flash-error {
  background: rgba(248, 113, 113, 0.12);
  border: 1px solid rgba(248, 113, 113, 0.40);
  color: var(--error);
}

/* Avertissement : ce qui suit n'est pas faux, mais ne dit pas ce qu'on croit. Le rejeu,
   typiquement. Distinct de `-error` à dessein — réutiliser le rouge crierait à la panne
   là où il n'y en a aucune. Même ocre que `.badge-warning` et que `.mode-replay` : sur
   une page, tout ce qui porte cette teinte est du rejeu. */
.drawer-flash-warning {
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.40);
  color: var(--warning-text);   /* #8a5d00 sur fond sombre etait illisible */
}

@media (max-width: 768px) {
  .filter-toolbar { flex-direction: column; align-items: stretch; }
  .btn-filter-advanced { margin-left: 0; }
}

/* ----------------------- V4 — Filter chips (Notion-style) ----------------------- */

.chips-area {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  flex: 1;
  min-width: 200px;
  align-items: center;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: 5px 4px 5px 11px;
  background: rgba(74, 59, 122, 0.06);
  border: 1px solid rgba(74, 59, 122, 0.22);
  border-radius: 5px;
  font-size: var(--text-sm);
  line-height: 1.4;
  cursor: pointer;
  font-family: var(--font-sans, inherit);
  transition: background .18s ease, border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.filter-chip:hover {
  background: rgba(74, 59, 122, 0.12);
  border-color: rgba(74, 59, 122, 0.45);
  box-shadow: 0 2px 6px -2px rgba(74,59,122,.25);
  transform: translateY(-1px);
}
.filter-chip .col { font-weight: 600; color: var(--foreground); }
.filter-chip .op { color: var(--muted-foreground, #6B7280); margin: 0 6px; font-size: var(--text-xs); }
.filter-chip .val { color: var(--accent); font-weight: 500; margin-right: 4px; }
.filter-chip .remove {
  width: 1.286em;
  height: 1.286em;
  border: none;
  background: transparent;
  cursor: pointer;
  color: var(--disabled-foreground);
  border-radius: 2px;
  font-size: var(--text-md);
  line-height: 1;
  padding: 0;
  opacity: 0;
  transition: opacity .14s ease, color .14s ease, background .14s ease;
}
.filter-chip:hover .remove { opacity: 1; }
.filter-chip .remove:hover {
  background: rgba(159, 63, 78, 0.10);
  color: var(--error);
}

.filter-group {
  display: inline-flex;
  flex-direction: column;
  gap: 8px;
  border: 1px solid var(--border, #E1E4EC);
  background: rgba(250, 250, 251, .5);
  border-radius: 6px;
  padding: 8px 10px;
}
.filter-group-header {
  font-size: var(--text-2xs);
  font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  cursor: pointer;
  border: none;
  background: rgba(74, 59, 122, 0.10);
  padding: 2px 6px;
  border-radius: 3px;
  font-family: inherit;
  transition: background .14s ease;
}
.filter-group-header:hover {
  background: rgba(74, 59, 122, 0.18);
}
.filter-group-add {
  font-size: var(--text-xs);
  color: var(--muted-foreground, #5B6577);
  border: 1px dashed var(--border, #E1E4EC);
  background: transparent;
  border-radius: 4px;
  padding: 3px 8px;
  cursor: pointer;
  font-family: var(--font-sans, inherit);
  align-self: flex-start;
  font-weight: 500;
  transition: all .14s ease;
}
.filter-group-add:hover {
  color: var(--accent);
  border-color: var(--accent);
  border-style: solid;
  background: rgba(74, 59, 122, 0.04);
}
.filter-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.filter-group-remove {
  border: none;
  background: transparent;
  color: var(--disabled-foreground);
  cursor: pointer;
  font-size: var(--text-md);
  line-height: 1;
  padding: 0 4px;
  border-radius: 2px;
  transition: color .14s ease, background .14s ease;
}
.filter-group-remove:hover {
  color: var(--error);
  background: rgba(159, 63, 78, 0.08);
}
.filter-top-logic {
  font-size: var(--text-2xs);
  font-weight: 700;
  color: var(--muted-foreground);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  border: none;
  background: transparent;
  border-radius: 3px;
  padding: 0 4px;
  cursor: pointer;
  font-family: inherit;
  align-self: center;
  transition: color .14s ease;
}
.filter-top-logic:hover {
  color: var(--accent);
}

.btn-add-filter {
  font-size: var(--text-sm);
  padding: 6px 12px;
  border: 1px dashed #C9CDD7;
  background: transparent;
  border-radius: 5px;
  color: var(--muted-foreground);
  cursor: pointer;
  font-family: var(--font-sans, inherit);
  font-weight: 500;
  transition: all .18s ease;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.btn-add-filter:hover {
  border-color: var(--accent);
  color: var(--accent);
  border-style: solid;
  background: rgba(74, 59, 122, 0.04);
  transform: translateY(-1px);
}

.icon-btn {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border, #E1E4EC);
  background: var(--card);
  border-radius: 5px;
  cursor: pointer;
  font-family: var(--font-sans, inherit);
  color: var(--muted-foreground);
  transition: all .18s ease;
}
.icon-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(74, 59, 122, 0.05);
  box-shadow: 0 2px 6px -2px rgba(74,59,122,.20);
}
.icon-btn[data-active="1"] {
  background: rgba(74, 59, 122, 0.08);
  color: var(--accent);
  border-color: var(--accent);
}

/* Search input — focus ring violet */
.filter-toolbar .search-wrapper,
.filter-toolbar > div:has(> input[type="text"][name="q"]),
.filter-toolbar .search-input-wrapper {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--background);
  transition: border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.filter-toolbar .search-wrapper:focus-within,
.filter-toolbar .search-input-wrapper:focus-within {
  border-color: var(--accent);
  background: var(--card);
  box-shadow: 0 0 0 3px rgba(74, 59, 122, 0.10);
}
.filter-toolbar input[type="text"][name="q"] {
  border: none;
  outline: none;
  background: transparent;
  font-size: var(--text-base);
  font-family: inherit;
  min-width: 180px;
}

/* ── Les POPOVERS de filtre, repasses en SOMBRE (2026-08-17) ──
   Remarque du fondateur, sur la liste des operateurs : « revois le style des listes
   deroulantes dans les filtres, ca ne colle pas avec le theme ».

   /!\ Ce bloc entier datait du theme CLAIR, abandonne le 2026-08-16, et il le trahissait
   de trois facons a la fois :
   - des REPLIS en dur (`var(--card, #FFFFFF)`) : la variable existe, donc le repli ne
     servait jamais... sauf a masquer que la valeur n'avait pas ete revue ;
   - des survols et des ombres en `rgba(0,0,0,…)`, c'est-a-dire du NOIR pose sur du noir :
     invisibles ;
   - et surtout AUCUN fond ni AUCUNE couleur de texte sur les champs et les listes. Un
     `<select>` sans fond declare retombe sur celui du navigateur, donc BLANC : c'est
     exactement ce que le fondateur voyait, une liste blanche au milieu d'un panneau
     sombre.

   /!\ Les survols passent au blanc translucide (`rgba(255,255,255,…)`), la seule teinte
   qui eclaircisse un fond sombre. C'est le meme geste que partout ailleurs dans cette
   feuille ; le noir translucide y assombrit et ne se voit pas. */
.filter-popover {
  position: absolute;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
  min-width: 240px;
  max-width: 360px;
  z-index: 1000;
  font-family: var(--font-sans, inherit);
  font-size: var(--text-base);
  color: var(--foreground);
}
.filter-popover .picker-search {
  width: 100%;
  padding: 7px 11px;
  color: var(--foreground);
  background-color: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  margin-bottom: 6px;
  font-family: inherit;
  outline: none;
}
.filter-popover .picker-search::placeholder { color: var(--disabled-foreground); }
.filter-popover .picker-search:focus {
  border-color: rgba(180, 168, 255, 0.5);
  background-color: rgba(180, 168, 255, 0.06);
}
.filter-popover .picker-list {
  max-height: 320px;
  overflow-y: auto;
}
.filter-popover .picker-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 7px 10px;
  border: none;
  background: transparent;
  color: var(--foreground);
  cursor: pointer;
  font-size: var(--text-base);
  font-family: inherit;
  border-radius: var(--radius-sm);
}
.filter-popover .picker-item:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--foreground);
}

.popover-section {
  margin-bottom: 10px;
}
.popover-label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
  margin-bottom: 5px;
}
.popover-input,
.popover-op-select {
  width: 100%;
  padding: 7px 11px;
  color: var(--foreground);
  /* `background-color` et PAS le raccourci `background` : celui-ci remettrait
     `background-image` a `none`, et effacerait le chevron que certains navigateurs
     peignent sur un `<select>`. Meme piege que `.saisie`. */
  background-color: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  font-family: inherit;
  box-sizing: border-box;
  outline: none;
}
.popover-op-select { cursor: pointer; }
.popover-input:focus,
.popover-op-select:focus {
  border-color: rgba(180, 168, 255, 0.5);
  background-color: rgba(180, 168, 255, 0.06);
}
/* Le peu qu'un navigateur accepte sur la liste DEPLIEE native. Le reste vient de
   `color-scheme: dark` sur `html` : sans lui, elle s'ouvre en blanc. Ce repli ne sert
   que si `liste.js` ne se charge pas -- sinon la liste est redessinee, cf. ci-dessous. */
.popover-op-select option { background: var(--card); color: var(--foreground); }

/* ── Les listes de la fenetre de filtre, redessinees (2026-08-17) ──
   Demande du fondateur : « inspire-toi des listes deroulantes qu'on a utilisees sur la
   fiche d'un trader ». `filter-chip-popover.js` enveloppe desormais ses `<select>` et
   appelle `liste.js` : le champ ferme devient un `.liste-bouton`, et le menu ouvert un
   `.liste-panneau` arrondi. Le natif restait blanc et carre des qu'il s'ouvrait -- une
   liste depliee native est hors d'atteinte de toute feuille de style.

   /!\ Ici le bouton prend TOUTE la largeur, a l'inverse de la barre de filtres ou il
   se dimensionne a son texte : dans un panneau de 240 px, un bouton court laisserait un
   creux a droite et casserait l'alignement avec le champ de valeur juste en dessous. */
.filter-popover .liste { display: block; width: 100%; }
.filter-popover .liste > .liste-bouton {
  width: 100%;
  padding: 7px 11px;
  font-size: var(--text-base);
  border-radius: var(--radius-sm);
}
.filter-popover .liste > .liste-panneau { min-width: 100%; }
.popover-checkboxes {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 200px;
  overflow-y: auto;
}
.popover-checkbox-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  font-size: var(--text-base);
  cursor: pointer;
}
.popover-checkbox-row:hover {
  background: rgba(255, 255, 255, 0.06);
  border-radius: var(--radius-sm);
}
.popover-between {
  display: flex;
  align-items: center;
  gap: 8px;
}
.popover-between input {
  flex: 1;
  padding: 7px 11px;
  color: var(--foreground);
  background-color: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  font-family: inherit;
  outline: none;
}
.popover-hint {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--muted-foreground, #6B7280);
  font-style: italic;
}
/* Date filter → period-preset buttons (filter-chip-popover.js renderDateEditor). */
.popover-date-presets {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: 8px;
}
.popover-preset-btn {
  text-align: left;
  padding: 7px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font-size: var(--text-base);
  font-family: inherit;
  color: var(--foreground);
  cursor: pointer;
}
.popover-preset-btn:hover {
  background: rgba(255, 255, 255, 0.06);
}
/* /!\ La periode retenue se marque a l'ACCENT, et non par une inversion
   fond-sombre/texte-clair : sur un theme deja sombre, l'inversion donnait un bouton
   presque blanc, le seul aplat clair de toute l'application. */
.popover-preset-btn.active {
  background: rgba(180, 168, 255, 0.14);
  color: var(--accent);
  border-color: rgba(180, 168, 255, 0.35);
}
.popover-preset-custom {
  border-top: 1px solid var(--border);
  border-radius: 0;
  margin-top: 2px;
}
.popover-date-custom {
  margin-top: 4px;
}
.popover-actions {
  border-top: 1px solid var(--border);
  padding-top: 8px;
  margin-top: 8px;
}
/* `#C0392B` etait un rouge de theme clair, absent de la palette : le rouge semantique
   est `--error`. */
.popover-delete {
  background: transparent;
  color: var(--error);
  border: none;
  font-size: var(--text-sm);
  cursor: pointer;
  font-family: inherit;
  padding: 5px 8px;
  width: 100%;
  text-align: left;
  border-radius: var(--radius-sm);
}
.popover-delete:hover {
  background: rgb(248 113 113 / 0.12);
}

/* ----------------------- V4 — Convert-to-group buttons ----------------------- */

.popover-convert-and,
.popover-convert-or {
  background: transparent;
  color: var(--muted-foreground, #6B7280);
  border: none;
  font-size: var(--text-sm);
  cursor: pointer;
  font-family: inherit;
  padding: 4px 8px;
  width: 100%;
  text-align: left;
}
.popover-convert-and:hover,
.popover-convert-or:hover {
  background: rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

/* ----------------------- V4 — Sort + Group popovers ----------------------- */

.sort-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}

.sort-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.filter-popover .sort-row > .sort-col { flex: 1; width: auto; min-width: 0; }
.sort-row select {
  flex: 1;
  padding: 4px 8px;
  border: 1px solid var(--border, #E5E5E5);
  border-radius: 4px;
  font-size: var(--text-base);
  font-family: inherit;
}

.sort-dir-btn,
.sort-remove-btn {
  width: 2em;
  height: 2em;
  border: 1px solid var(--border, #E5E5E5);
  background: transparent;
  border-radius: 4px;
  cursor: pointer;
  font-size: var(--text-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.sort-dir-btn:hover,
.sort-remove-btn:hover {
  background: rgba(0, 0, 0, 0.05);
}

.popover-add-sort {
  width: 100%;
  padding: 6px 10px;
  border: 1px dashed var(--border, #E5E5E5);
  background: transparent;
  border-radius: 4px;
  font-size: var(--text-sm);
  color: var(--muted-foreground, #6B7280);
  cursor: pointer;
  font-family: inherit;
  margin-bottom: 8px;
}

.popover-clear-sort {
  background: transparent;
  color: var(--muted-foreground, #6B7280);
  border: none;
  font-size: var(--text-sm);
  cursor: pointer;
  font-family: inherit;
  padding: 4px 8px;
  width: 100%;
  text-align: left;
}

.popover-radio-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  font-size: var(--text-base);
  cursor: pointer;
  border-radius: 4px;
}
.popover-radio-row:hover {
  background: rgba(0, 0, 0, 0.04);
}

.group-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* /profile/strategies — cards grid */
.strat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: 16px;
}
.strat-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  box-shadow: 0 1px 2px rgba(14,17,38,.04), 0 8px 24px -8px rgba(14,17,38,.08);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
  cursor: pointer;
}
.strat-card:hover {
  transform: translateY(-2px);
  border-color: rgba(74,59,122,.25);
  box-shadow: 0 2px 4px rgba(14,17,38,.06), 0 14px 32px -8px rgba(74,59,122,.16);
}
/* Light colored left border by status. */
.strat-card--active { border-left-color: var(--success, #16a34a); }
.strat-card--active:hover { border-left-color: var(--success, #16a34a); }
.strat-card--archived { border-left-color: var(--disabled-foreground); }
.strat-card--archived:hover { border-left-color: var(--disabled-foreground); }
.strat-card .card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.strat-card .card-head-id { min-width: 0; }
.strat-card .name {
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: -.01em;
}
.strat-card .inst {
  font-size: var(--text-sm);
  color: var(--muted-foreground);
  margin-top: 2px;
}
.strat-card .params {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px 10px;
  padding: 12px 14px;
  background: var(--background);
  border-radius: 6px;
  margin-bottom: 10px;
}
.strat-card .params--essentials {
  grid-template-columns: repeat(5, 1fr);
}
.strat-card .param { min-width: 0; }
.strat-card .param .label {
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--disabled-foreground);
  font-weight: 600;
  margin-bottom: 3px;
  white-space: nowrap;
}
.strat-card .param .val {
  font-size: var(--text-md);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.strat-card .param .val--text {
  font-size: var(--text-base);
  font-variant-numeric: normal;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.strat-card .param .val-unit {
  font-size: var(--text-2xs);
  font-weight: 500;
  color: var(--muted-foreground);
}
/* Période de trading + blackout news (compact). */
.strat-card .strat-period {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  padding: 8px 12px;
  margin-bottom: 10px;
  background: var(--background);
  border-radius: 6px;
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}
.strat-card .strat-period .sp-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.strat-card .strat-period svg { flex: none; opacity: .75; }
.strat-card .qty-input-inline {
  width: 58px;
  padding: 3px 6px;
  font-size: var(--text-md);
  border: 1px solid #C9CDD7;
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.strat-card .desc {
  font-size: var(--text-base);
  color: var(--muted-foreground);
  line-height: 1.5;
  margin-bottom: 12px;
}
.strat-card .foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--muted);
  font-size: var(--text-sm);
  color: var(--muted-foreground);
}
.strat-card .foot-qty {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--disabled-foreground);
  font-weight: 600;
}
.strat-card .foot-qty b {
  color: var(--foreground);
  font-variant-numeric: tabular-nums;
}
.strat-card .foot .open-link { margin-left: auto; }
.strat-card .notes-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-xs);
  color: var(--accent);
}
.strat-card .open-link {
  font-size: var(--text-sm);
  color: var(--accent);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.strat-card .open-link:hover { text-decoration: underline; }

/* ── Refonte liste U3 §4 : carte enrichie (pill statut, stats, meta, perf) ── */
.strat-card .card-head {
  align-items: flex-start;
  justify-content: space-between;
}
.strat-card .card-head-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  flex: none;
}
/* Pill de statut (Actif=vert · Inactif=gris · Brouillon=ambre). */
.strat-card .st {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 20px;
  white-space: nowrap;
}
.strat-card .st .dot { width: 7px; height: 7px; border-radius: 50%; }
.strat-card .st-actif { background: #e7f1ec; color: #3f7d5e; }
.strat-card .st-actif .dot { background: #3f7d5e; }
.strat-card .st-inactif { background: #eceef2; color: #6b7280; }
.strat-card .st-inactif .dot { background: #9aa0ac; }
.strat-card .st-brouillon { background: #f3f1e6; color: #b8860b; }
.strat-card .st-brouillon .dot { background: #b8860b; }
/* Boutons d'action de carte (Dupliquer / Supprimer). */
.strat-card .cardacts { display: flex; gap: 4px; }
.strat-card .cardact-form { display: inline; margin: 0; }
.strat-card .ca {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 1px solid var(--border);
  background: var(--card);
  border-radius: 7px;
  cursor: pointer;
  color: var(--muted-foreground);
  padding: 0;
}
.strat-card .ca:hover { background: var(--background); color: var(--foreground); }
.strat-card .ca-del:hover { background: #f9efec; color: #c0654e; border-color: #e7d2ca; }
/* Grille de stats 5 colonnes. */
.strat-card .stats--5 {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  background: var(--background);
  border: 1px solid var(--muted);
  border-radius: 8px;
  padding: 11px 13px;
  margin-bottom: 10px;
}
.strat-card .stats--5 .stat { min-width: 0; }
.strat-card .stats--5 .stat .l {
  font-size: var(--text-2xs);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--disabled-foreground);
  font-weight: 600;
  white-space: nowrap;
}
.strat-card .stats--5 .stat .v {
  font-size: var(--text-md);
  font-weight: 700;
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.strat-card .stats--5 .stat .v--text { font-size: var(--text-base); font-variant-numeric: normal; }
.strat-card .stats--5 .stat .val-unit { font-size: var(--text-2xs); font-weight: 500; color: var(--muted-foreground); }
/* Badge break-even. */
.strat-card .be { font-size: var(--text-xs); margin-top: 2px; color: #3f7d5e; font-weight: 600; }
/* Ligne meta (sessions · news · caps/jour). */
.strat-card .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
  margin-bottom: 10px;
}
.strat-card .meta .it { display: inline-flex; align-items: center; gap: 6px; }
.strat-card .meta svg { flex: none; opacity: .75; }
/* Résumé de perf. */
.strat-card .perf {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  font-size: var(--text-sm);
  color: var(--muted-foreground);
  border-top: 1px dashed var(--border);
  padding-top: 11px;
  margin-bottom: 4px;
}
.strat-card .perf b { color: var(--foreground); }
.strat-card .perf .pnl.pos { color: #3f7d5e; font-weight: 700; }
.strat-card .perf .pnl.neg { color: #c0654e; font-weight: 700; }
.strat-card .perf.empty { color: var(--disabled-foreground); font-style: italic; }
.strat-card .foot .acc {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

/* Responsive : essentials grid → 2 columns on mobile. */
@media (max-width: 640px) {
  .strat-card .params--essentials,
  .strat-card .stats--5 {
    grid-template-columns: 1fr 1fr;
  }
  .strat-card .strat-period {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ── Drawer trade : bloc « Calcul SL/TP · ATR » + tags de provenance ─────────
   .tagsrc = pastille indiquant d'où vient un bloc de paramètres (App/edge vs
   NinjaTrader). .calc = liste de dérivation monospace (ATR → SL → volume → TP).
   Aligné sur la maquette + les tokens TVNI. */
.tagsrc {
  margin-left: auto;
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: .02em;
  padding: 2px 9px;
  border-radius: 999px;
}
.tagsrc.app { background: rgb(74 59 122 / .10); color: var(--accent, #4A3B7A); }
.tagsrc.nt  { background: rgb(59 110 168 / .12); color: var(--info, #3B6EA8); }
.atrbadge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgb(181 121 60 / .10);
  border: 1px solid rgb(181 121 60 / .25);
  color: #9A6328;
  border-radius: 8px;
  padding: 7px 11px;
  font-size: var(--text-base);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-bottom: 11px;
}
.calc {
  background: linear-gradient(180deg, #F7F8FC, #F1F3F9);
  border: 1px solid var(--border, #E1E4EC);
  border-radius: 10px;
  padding: 13px 15px;
  font-size: var(--text-base);
  font-family: var(--font-mono, 'JetBrains Mono', monospace);
}
.calc .line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 0;
  border-bottom: 1px dashed var(--border, #E1E4EC);
}
.calc .line:last-child { border: 0; }
.calc .lbl { color: var(--muted-foreground, #5B6577); }
.calc .val { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; }
.calc .val .arr { color: var(--accent, #4A3B7A); }
.calc .step { font-size: var(--text-2xs); color: var(--disabled-foreground, #9CA3B0); font-family: var(--font-sans, 'Inter', sans-serif); }
.coh {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--success, #3F6E5C);
}
.coh .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--success, #3F6E5C); }

/* ── Bascule segmentée Infos / Suivi (drawer contact v2, §C) ─────────────────
   Control à deux états dans le sous-en-tête ; le bouton actif prend un fond
   blanc surélevé. Le badge « ct » porte le nombre d'activités. */
.drawer-seg {
  display: inline-flex;
  background: var(--muted, #F2F3F7);
  border-radius: 10px;
  padding: 4px;
  margin-top: 14px;
}
/* Bascule des deux vues + masquage des chips en vue Suivi. */
.contact-view.is-hidden { display: none; }

/* ── Stratégies open source (onglet « Toutes les stratégies ») ─────────────── */
.os-tabs { gap: 6px; }

/* ── Sous-onglets secondaires (Pages / Mails d'un événement) ───────────────── */
/* Segmented control léger : plus discret que les onglets principaux. */
.ev-subtabs {
  background: var(--muted, #F7F7FB);
  border: 1px solid var(--border, #E1E4EC);
  border-radius: 8px;
  padding: 4px;
  gap: 4px;
}
.ev-subtabs a {
  padding: 5px 12px;
  font-size: var(--text-sm);
  border-radius: 6px;
}
.ev-subtabs a.active {
  background: var(--card, #fff);
  border-color: var(--border, #E1E4EC);
}
/* « Toutes les stratégies » : toolbar alignée sur la command-bar de
   « Mes stratégies » (search-wrapper stylé + tri stylé). */
.os-command-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 16px;
}
/* Recherche — même look que .filter-toolbar .search-wrapper (focus ring violet). */
.os-search-wrapper {
  flex: 1 1 240px; min-width: 200px; max-width: 360px;
  display: flex; align-items: center; gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border); border-radius: 5px;
  background: var(--background);
  transition: border-color .18s ease, background .18s ease, box-shadow .18s ease;
}
.os-search-wrapper:focus-within {
  border-color: var(--accent);
  background: var(--card);
  box-shadow: 0 0 0 3px rgba(74, 59, 122, 0.10);
}
.os-search-wrapper input {
  border: none; outline: none; background: transparent;
  font-size: var(--text-base); font-family: inherit; color: var(--foreground);
  flex: 1; min-width: 0;
}
.os-sort { display: inline-flex; align-items: center; gap: 8px; font-size: var(--text-sm); color: var(--muted-foreground); flex-shrink: 0; }
.os-sort > label { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }
/* Sélecteur stylé (caret custom) cohérent avec les contrôles de la charte. */
.os-sort-control { position: relative; display: inline-flex; align-items: center; }
.os-sort-control select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding: 6px 28px 6px 12px; font-size: var(--text-sm); font-weight: 500;
  border: 1px solid var(--border); border-radius: 5px;
  background: var(--card); color: var(--foreground); font-family: inherit;
  cursor: pointer; transition: all .18s ease;
}
.os-sort-control select:hover { border-color: var(--accent); background: rgba(74, 59, 122, 0.04); }
.os-sort-control select:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(74, 59, 122, 0.10);
}
.os-sort-caret {
  position: absolute; right: 9px; pointer-events: none;
  color: var(--muted-foreground);
}
.os-card { border-left-color: var(--accent, #4a3b7a); }
.os-card .params {
  grid-template-columns: repeat(3, 1fr);
}
.os-badge-mine {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .02em;
  padding: 2px 8px; border-radius: 999px;
  background: rgba(22,163,74,.10); color: var(--success, #16a34a);
  border: 1px solid rgba(22,163,74,.25); white-space: nowrap;
}
.os-perf {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 12px; margin-bottom: 10px;
  background: var(--background); border-radius: 6px;
}
.os-perf-stats {
  flex: 1; display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px 10px;
}
.os-stat .os-stat-lbl {
  font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: .1em;
  color: var(--disabled-foreground); font-weight: 600; margin-bottom: 2px;
}
.os-stat .os-stat-val {
  font-size: var(--text-base); font-weight: 600; font-variant-numeric: tabular-nums;
}
.os-stat-val.pos, .os-kpi-val.pos { color: var(--success, #16a34a); }
.os-stat-val.neg, .os-kpi-val.neg { color: var(--error, #dc2626); }
.os-spark { flex: none; color: var(--accent, #4a3b7a); display: flex; align-items: center; }
.os-spark.pos { color: var(--success, #16a34a); }
.os-spark.neg { color: var(--error, #dc2626); }
.os-users-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); color: var(--muted-foreground);
}
.os-users-pill svg { opacity: .7; }

/* Détail canonique. */
.os-detail-kpis {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
.os-kpi {
  padding: 12px 14px; background: var(--background); border-radius: 8px;
  border: 1px solid var(--border);
}
.os-kpi-lbl {
  font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: .1em;
  color: var(--disabled-foreground); font-weight: 600; margin-bottom: 4px;
}
.os-kpi-val { font-size: var(--text-xl); font-weight: 700; font-variant-numeric: tabular-nums; }
.os-equity { color: var(--accent, #4a3b7a); }
.os-equity.pos { color: var(--success, #16a34a); }
.os-equity.neg { color: var(--error, #dc2626); }
.os-equity-lbl {
  font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: .1em;
  color: var(--disabled-foreground); font-weight: 600; margin-bottom: 6px;
}
@media (max-width: 720px) {
  .os-detail-kpis { grid-template-columns: repeat(2, 1fr); }
  .os-perf { flex-direction: column; align-items: stretch; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   LE TABLEAU DE PALIERS (gestion d'un trade, 2026-08-20)

   La charte n'avait AUCUNE classe de tableau : le premier jet rendait des colonnes
   de largeurs aleatoires, la premiere trois fois trop large pour un nombre a une
   decimale. Les largeurs sont donc posees ici, une fois, sur les en-tetes.

   /!\ `table-layout: fixed` est ce qui fait tenir les largeurs : sans lui le
   navigateur les recalcule sur le contenu, et une ligne longue les redistribue.
   ───────────────────────────────────────────────────────────────────────────── */
.tableau--paliers {
  width: 100%; max-width: 720px;
  border-collapse: collapse; table-layout: fixed;
  margin: 4px 0 10px;
}
.tableau--paliers th {
  text-align: left; padding: 0 12px 8px 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs); font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted-foreground);
}
.tableau--paliers td { padding: 0 12px 10px 0; vertical-align: top; }
.tableau--paliers th:nth-child(1) { width: 22%; }
.tableau--paliers th:nth-child(2) { width: 18%; }
.tableau--paliers th:nth-child(3) { width: 32%; }
.tableau--paliers th:nth-child(4) { width: 20%; }
.tableau--paliers th:nth-child(5) { width: 8%; padding-right: 0; }
.tableau--paliers td:last-child { padding-right: 0; text-align: right; }
.tableau--paliers .saisie { width: 100%; }

/* Les trois modeles prets a l'emploi, au-dessus du tableau. */
.modeles-gestion {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
.modeles-gestion .etiquette { margin: 0; }
.modeles-gestion .saisie-note { flex-basis: 100%; margin: 0; }

/* Une case a cocher qui suit un bloc de champs a besoin d'air (2026-08-20).
   /!\ DEUX noms, pas un seul porte par deux objets : `.champ--espace` marque un
   champ isole, `.champs--espace` la GRILLE de champs. Ils partagent une valeur,
   jamais une classe -- ce depot a deja paye une classe unique posee sur deux
   objets differents (cf. `.puce`), et un modificateur se lit avec le bloc qu'il
   modifie.
   `.champs--espace` sert la ou une grille de champs suit une case a cocher ou une
   bascule : sans lui, elle la touche. Elle etait separee par le remplissage d'une
   `verre-zone`, qui posait au passage un cadre dans un cadre (2026-08-29). */
.champ--espace { margin-top: 16px; }
.champs--espace { margin-top: 16px; }

/* ============================================================
   Le GUIDE (2026-08-23)
   /!\ Ces regles vivaient dans un `{% block extra_css %}` de 200 lignes qui
   redeclarait un `:root` en THEME CLAIR -- une page blanche au milieu d'une
   application sombre. Elles sont ici pour la meme raison que tout le reste :
   un ecran ne possede pas ses couleurs.
   ============================================================ */
.guide-hero { margin: 0 0 var(--space-6); }
.guide-eyebrow {
  font-family: var(--font-mono); font-size: var(--text-eyebrow);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--accent); margin: 0 0 var(--space-3);
}
.guide h1 { font-size: var(--text-display-2); line-height: 1.1;
            letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-3);
            font-weight: 600; }
.guide-chapeau { font-size: var(--text-lg); line-height: var(--leading-relaxed);
                 color: var(--muted-foreground); max-width: 62ch; margin: 0; }
.guide-article h2 { font-size: var(--text-h4); font-weight: 500;
                    letter-spacing: var(--tracking-tight);
                    margin: var(--space-8) 0 var(--space-3); }
.guide-article p, .guide-article li {
  font-size: var(--text-md); line-height: var(--leading-relaxed);
  color: var(--muted-foreground); max-width: 68ch;
}
.guide-article p { margin: 0 0 var(--space-3); }
.guide-article ul { padding-left: 20px; margin: 0 0 var(--space-3); }
.guide-article li { margin: 6px 0; }
.guide-article strong { color: var(--foreground); font-weight: 500; }
/* /!\ `:not(.btn)` N'EST PAS DECORATIF. Sans lui, cette regle (0,1,1) l'emporte
   sur `.btn-primary` (0,1,0) et repeint le libelle du bouton en `--accent` —
   c'est-a-dire du violet sur un fond violet. Le bouton « Aller a mes traders »
   du dernier chapitre etait un rectangle VIDE, et le test ne l'a pas vu : il
   verifiait le `href`, pas la lisibilite. Trouve a l'oeil le 2026-08-23. */
.guide-article a:not(.btn) { color: var(--accent); text-decoration: none;
                             border-bottom: 1px solid rgba(180,168,255,0.3); }
.guide-article a:not(.btn):hover { border-bottom-color: var(--accent); }
/* Un AVERTISSEMENT dans le corps d'un chapitre : un filet d'accent a gauche,
   pas un emoji. La charte proscrit les pictogrammes dans l'interface, et un
   « /!\ » en toutes lettres serait pire. */
.guide-article p.guide-attention {
  border-left: 2px solid var(--warning);
  padding-left: var(--space-3);
  color: var(--muted-foreground);
}
/* Un SOUS-TITRE dans une section : c'est lui qui porte le nom d'une plateforme
   sous une regle qui vaut pour toutes (guide, chapitres 2 et 4). Sans cette
   regle, `h3` retombe sur le style de l'agent utilisateur -- donc PLUS GROS que
   le `h2` de la section qui le contient. */
/* Le filet d'accent n'est pas décoratif : à 15,5 px sans lui, ce sous-titre se
   lisait comme une simple ligne en gras au milieu du texte (vu à l'écran le
   2026-08-28). Il marque le début du bloc PROPRE À UNE PLATEFORME -- violet,
   la couleur de l'application, là où `guide-attention` prend l'ambre. */
.guide-article h3 { font-size: var(--text-lg); font-weight: 500;
                    color: var(--foreground);
                    letter-spacing: var(--tracking-tight);
                    border-left: 2px solid var(--accent);
                    padding-left: var(--space-3);
                    margin: var(--space-6) 0 var(--space-3); }
/* /!\ `nowrap`, comme le `.k` de la page Plateformes : un libellé d'interface
   coupé en deux (« NinjaScript Add- / On… ») ne se retrouve plus à l'écran de
   la plateforme, et c'est tout l'intérêt de le recopier au caractère près. */
.guide-article .k { font-family: var(--font-code); font-size: var(--text-base);
                    background: var(--muted); border: 1px solid var(--border);
                    border-radius: var(--radius-sm); padding: 1px 6px;
                    white-space: nowrap;
                    color: var(--foreground); }

/* La carte du gestionnaire et celle des jalons. */
.guide-gestionnaire { margin-bottom: var(--space-4); }
.guide-gest-nom { font-size: var(--text-h4); font-weight: 500;
                  color: var(--foreground); margin: 0 0 2px; }
.guide-gest-contacts { display: flex; gap: var(--space-4); flex-wrap: wrap;
                       margin: var(--space-3) 0 0; }
.guide-gest-contacts a {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-body-sm); color: var(--accent); text-decoration: none;
}
.guide-gest-contacts a:hover { text-decoration: underline; }

.guide-jalon-liste { list-style: none; margin: var(--space-3) 0 var(--space-4);
                     padding: 0; display: grid; gap: 2px; }
.guide-jalon { display: flex; align-items: center; gap: var(--space-3);
               padding: 9px 0; font-size: var(--text-body);
               color: var(--disabled-foreground); }
.guide-jalon-marque {
  display: grid; place-items: center; width: 2em; height: 2em;
  flex: 0 0 2em; border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  font-family: var(--font-mono); font-size: var(--text-xs);
}
.guide-jalon.est-fait { color: var(--muted-foreground); }
.guide-jalon.est-fait .guide-jalon-marque {
  background: rgba(74, 222, 128, 0.14); border-color: rgba(74, 222, 128, 0.45);
  color: var(--success);
}
.guide-jalon.est-courant { color: var(--foreground); font-weight: 500; }
.guide-jalon.est-courant .guide-jalon-marque {
  border-color: var(--accent); color: var(--accent);
}
.guide-note { font-size: var(--text-body-sm); color: var(--muted-foreground);
              border-left: 2px solid var(--accent); padding-left: var(--space-3);
              margin: var(--space-4) 0; }

/* Le sommaire de l'accueil. */
.guide-sommaire { margin-top: var(--space-8); }
.guide-sommaire-titre { font-size: var(--text-h4); font-weight: 500;
                        margin: 0 0 var(--space-3); }
.guide-carte {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-4); margin-bottom: var(--space-2);
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); text-decoration: none;
  transition: border-color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
.guide-carte:hover { border-color: var(--accent-ring-strong);
                     background: var(--accent-wash); }
.guide-carte svg { color: var(--muted-foreground); flex: 0 0 auto; }
.guide-carte-num { font-family: var(--font-mono); font-size: var(--text-h4);
                   color: var(--accent); flex: 0 0 auto; width: 22px; }
.guide-carte-corps { display: grid; gap: 3px; min-width: 0; flex: 1 1 auto; }
.guide-carte-titre { font-size: var(--text-body); font-weight: 500;
                     color: var(--foreground); }
.guide-carte-resume { font-size: var(--text-body-sm);
                      color: var(--muted-foreground); }

/* Un chapitre : le rail a gauche, l'article a droite. */
.guide-doc { display: grid; grid-template-columns: 250px 1fr;
             gap: var(--space-12); align-items: start; }
.guide-rail { position: sticky; top: var(--space-6); }
.guide-rail-retour { display: inline-flex; align-items: center; gap: 6px;
                     font-size: var(--text-caption);
                     color: var(--muted-foreground); text-decoration: none;
                     margin-bottom: var(--space-4); }
.guide-rail-retour:hover { color: var(--foreground); }
.guide-rail-liste { list-style: none; margin: 0; padding: 0; display: grid;
                    gap: 1px; }
.guide-rail-liste a {
  display: flex; align-items: baseline; gap: var(--space-2);
  padding: 8px 10px; border-radius: var(--radius-md);
  font-size: var(--text-body-sm); color: var(--muted-foreground);
  text-decoration: none; line-height: 1.35;
}
.guide-rail-liste a:hover { background: var(--muted); color: var(--foreground); }
.guide-rail-liste a.est-courant {
  background: var(--muted); color: var(--foreground); font-weight: 500;
  box-shadow: inset 2px 0 0 var(--accent);
}
.guide-rail-num { font-family: var(--font-mono); font-size: var(--text-2xs);
                  color: var(--disabled-foreground); flex: 0 0 auto; }
.guide-article { min-width: 0; }
.guide-suite { display: flex; gap: var(--space-3); flex-wrap: wrap;
               margin-top: var(--space-8); padding-top: var(--space-6);
               border-top: 1px solid var(--border); }

@media (max-width: 900px) {
  .guide-doc { grid-template-columns: 1fr; gap: var(--space-6); }
  .guide-rail { position: static; }
}

/* ==========================================================================
   La VISITE GUIDEE (2026-08-23) : une bulle posee sur l'element dont elle
   parle, le reste de l'ecran assombri. Style A de la maquette, choisi par le
   fondateur.

   /!\ Le voile est SOUS le halo et la bulle, jamais au-dessus : c'est lui qui
   efface la page, et l'element commente doit rester net.
   ========================================================================== */
.visite-halo {
  position: fixed; z-index: 901; pointer-events: none;
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 8px rgba(180,168,255,0.10),
              0 0 40px rgba(139,127,224,0.45);
  /* Le trou dans le voile : c'est ce rectangle qui laisse voir l'element. */
  background: transparent;
  /* L'`outline` géante EST le voile : elle assombrit tout sauf ce rectangle.
     Une seule couche, sinon la page devient noire. */
  outline: 9999px solid rgba(6, 7, 12, 0.70);
  animation: visite-apparait .18s ease-out;
  transition: top .2s ease, left .2s ease, width .2s ease, height .2s ease;
}
.visite-bulle {
  position: fixed; z-index: 902; box-sizing: border-box;
  /* /!\ `100vw` ignore le `zoom` du corps : a l'echelle 1.1, une bulle bornee a
     `100vw` deborde encore de 10 %. Meme correction que `.bulle` et `--fenetre`. */
  max-width: calc((100vw - 24px) / var(--echelle));
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); padding: 16px 18px;
  box-shadow: 0 20px 50px rgba(0,0,0,0.6);
  display: flex; flex-direction: column; gap: 10px;
  animation: visite-monte .2s ease-out;
  /* /!\ AUCUNE transition sur `top`/`left`, et ce n'est pas un detail de gout.
     L'application declare `transition: all` en global ; une bulle qui saute d'un
     element a l'autre partirait donc en glissade a chaque « Suivant », et
     surtout : tant que la transition n'est pas finie, la position LUE est celle
     du depart. Le calcul de placement, qui mesure puis repositionne, se
     retrouvait a lire des zeros. Vu au banc le 2026-08-23. */
  transition: none;
}
.visite-bulle.sans-animation { animation: none; }
.visite-tete { display: flex; align-items: baseline; justify-content: space-between;
               gap: 10px; }
.visite-titre { font-size: var(--text-body); font-weight: 500; color: var(--foreground); }
.visite-compteur { font-family: var(--font-mono); font-size: var(--text-caption);
                   color: var(--accent); flex: 0 0 auto; }
.visite-texte { margin: 0; font-size: var(--text-body-sm); line-height: 1.55;
                color: var(--muted-foreground); }
/* /!\ `flex-end` + `margin-right: auto` sur « Passer », et NON `space-between`
   (2026-08-30, avec l'arrivee du bouton « Precedent »). A deux enfants les deux
   ecritures donnent le meme rendu ; a TROIS, `space-between` etalait les boutons
   a intervalles egaux et « Precedent » se retrouvait seul au milieu de la bulle,
   loin du « Suivant » avec lequel il fait paire. */
.visite-pied { display: flex; align-items: center; justify-content: flex-end;
               gap: 10px; margin-top: 2px; }
.visite-passer {
  margin-right: auto;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: inherit; font-size: var(--text-caption);
  color: var(--disabled-foreground);
}
.visite-passer:hover { color: var(--muted-foreground); }
.visite-suivant, .visite-precedent { cursor: pointer; }

@keyframes visite-apparait { from { opacity: 0; } to { opacity: 1; } }
@keyframes visite-monte { from { opacity: 0; transform: translateY(6px); }
                          to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .visite-bulle { animation: none; }
  .visite-halo { transition: none; }
}

/* /!\ La classe `guide-revoir` a ete RETIREE le 2026-08-29 avec son bouton
   (« Revoir la visite », en tete du guide). Ce bouton relancait la visite
   ENTIERE ; il n'y a plus de « tout », et le rappel vit maintenant dans le point
   d'interrogation de la barre du haut, qui reutilise `.menu` / `.menu-panneau`
   plutot que d'inventer un decor de plus. Ne pas recreer ce nom. */

/* ============================================================
   LA COQUILLE : la barre du haut, le menu, le cadre d'une page
   ============================================================
   Rapatriee depuis le `<style>` de `admin_base.html` le 2026-08-25 (chantier
   « la coquille », § 12 de la spec de l'espace collaborateur).

   /!\ **Elle habille les DEUX espaces.** Les pages du client etendent le meme
   gabarit que celles de l'equipe : tant que ce style vivait dans la page, un
   ecran client chargeait mille cent lignes de CSS d'administration dont il
   n'utilisait rien, et aucun des deux ne pouvait le corriger sans toucher
   l'autre a l'aveugle.

   /!\ Ce qui est ici est la COQUILLE, pas les ecrans. Le tiroir, les barres
   d'outils, les filtres rapides et les cartes de KPI restent dans le gabarit :
   ils seront SUPPRIMES au profit de `.tiroir`, `.filtres` et `.seg` quand leur
   ecran sera repris, pas demenages. Les ranger ici serait les classer avant de
   les jeter.
   ============================================================ */
html,body{margin:0;padding:0}
/* /!\ LE CORPS EST A `--text-base` (15 px) DEPUIS LE 24/09/2026 (echelle des polices
   validee par le fondateur). Il valait `--text-md`, 17 px, et defaisait en silence
   la regle du haut de la feuille (« body », qui le voulait a 15) : tout paragraphe
   sans taille propre heritait de 17, si bien que les ecrans qui ne posaient rien
   (Plateformes, les reglages d'un trader, l'abonnement) parlaient deux crans plus
   fort que « Mes traders » et « News », qui posent tout. `test_echelle_des_polices`. */
body{background:var(--background);color:var(--foreground);font-family:var(--font-sans);font-size: var(--text-base);line-height:1.5;-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}

/* Topbar -- indigo gradient + global search + notif */
.topbar{position:sticky;top:0;z-index:50;background:#0F0F1A;background-image:radial-gradient(120% 200% at 20% 0%, rgba(167,139,250,.10) 0%, transparent 50%), linear-gradient(180deg, #0F0F1A 0%, #14111E 100%);border-bottom:1px solid rgba(255,255,255,.06);height:56px;box-shadow:0 1px 3px rgba(14,17,38,.20)}
.topbar-inner{display:flex;align-items:center;gap:16px;height:100%;padding:0 24px}
.mark{display:flex;align-items:center;gap:12px;text-decoration:none;color:#FFFFFF;flex-shrink:0}
/* Le T BLANC du kit de marque (2026-09-17), sans marge autour : 18 px de haut
   rendent la meme taille a l'oeil que l'ancienne image de 28 px, qui portait
   un quart de vide sur chaque bord. Plus de filtre : le fichier est deja blanc. */
.logo-img{display:inline-block;height:18px;width:auto}
.wordmark{font-family:var(--font-sans);font-weight:500;font-size: var(--text-lg);letter-spacing:0.20em;text-transform:uppercase;color:#FFFFFF}

/* Global search -- V4 design : right-aligned, soft compact */
/* /!\ SA LARGEUR SE COMPTE EN TEXTE, PAS EN PIXELS (2026-09-12, correctif du
   chantier typographie). Elle etait figee a 320 px, une largeur choisie quand le
   champ ecrivait en 12,5 px. L'echelle l'a porte a 14 : mesure dans la page
   rendue, le libelle « Rechercher dans toute l'application... » demande alors
   235 px la ou le champ n'en offre que 225, et il se tronque — sur CHAQUE page.
   En `em` de son propre texte, la boite et ce qu'elle contient grandissent
   ensemble : 25em valent 350 px aujourd'hui, et ce qu'il faudra demain.
   /!\ C'est pour cela que `font-size` est pose ICI et non sur le seul `input` :
   sans lui, `em` se compterait sur le texte courant (16 px) et non sur celui du
   champ, et la largeur serait fausse d'un quart. */
.topbar-search{margin-left:auto;font-size: var(--text-sm);width:25em;display:flex;align-items:center;gap:0.571em;padding:0 0.857em;height:2.143em;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);border-radius:6px;transition:all .18s ease}
.topbar-search:focus-within{background:rgba(255,255,255,.12);border-color:rgba(167,139,250,.40);box-shadow:0 0 0 3px rgba(167,139,250,.08)}
.topbar-search svg{color:rgba(255,255,255,.55);flex-shrink:0}
.topbar-search input{flex:1;border:none;outline:none;background:transparent;color:#FFFFFF;font-size:inherit;font-family:inherit}
.topbar-search input::placeholder{color:rgba(255,255,255,.45)}
.topbar-search .kbd{font-size: var(--text-2xs);color:rgba(255,255,255,.50);padding:1px 6px;border:1px solid rgba(255,255,255,.18);border-radius:3px;font-family:var(--font-mono);letter-spacing:0}

/* Topbar right group : tout colle à droite, gap léger */
.topbar-right{display:flex;align-items:center;gap:6px;flex-shrink:0}
/* ── LE VOLET : une page de l'application ouverte DANS un volet (un cadre de la même
   origine) s'affiche sans sa coquille (`admin_base.html`, classe `en-volet`). Le volet
   de « Ma journée » (équipe, 15/10/2026) et celui de la communauté (client, 30/10/2026). */
body.en-volet .topbar, body.en-volet .sidebar, body.en-volet .onglets-entree,
body.en-volet .sidebar-mobile, body.en-volet .drawer-backdrop { display: none !important; }
body.en-volet .app-shell { display: block; min-height: 0; }
body.en-volet .main { padding: 16px 20px 40px; }
/* ⚠️ Sans la barre de recherche (côté client, 2026-09-17), c'est elle qui
   poussait le groupe à droite par sa marge : le groupe prend le relais. */
.topbar-inner > .mark + .topbar-right{margin-left:auto}

/* Option C -- Soft modern, icônes discrètes */
.topbar-icon-btn{position:relative;width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);border-radius:6px;color:rgba(255,255,255,.78);cursor:pointer;transition:all .14s ease}
.topbar-icon-btn:hover{background:rgba(255,255,255,.12);border-color:rgba(167,139,250,.40);color:#FFFFFF}
.topbar-icon-btn .badge-dot{position:absolute;top:5px;right:5px;width:7px;height:7px;background:var(--error);border:2px solid #0F0F1A;border-radius:50%}

/* Bouton Inviter -- accent violet doux qui rappelle la marque */
/* L'appel a l'achat (2026-08-23). Plein, teinte d'accent : c'est le seul bouton
   de la barre qui demande de l'argent, il doit se distinguer de « Inviter » (qui
   est un geste gratuit) sans crier. Sur mobile il garde son libelle, contrairement
   a « Inviter » : c'est le geste qui rapporte. */
/* /!\ APLAT, sans degrade ni halo (2026-08-23, direction « B » validee) : c'est
   la voix forte de la charte (`.btn-fort`), et le seul geste d'argent de la
   coquille. Il ne s'affiche PAS sur la page profil, ou l'onglet Abonnement
   porte deja le vrai bouton — deux voix fortes sur un ecran, c'est une de trop. */
.topbar-cta{display:inline-flex;align-items:center;gap: 0.5em;height: 2.5em;padding: 0 1.083em;background:var(--accent);border:1px solid var(--accent);border-radius:6px;color:var(--on-accent);text-decoration:none;font-size: var(--text-sm);font-weight:600;font-family:inherit;transition:background .14s ease,border-color .14s ease;flex-shrink:0}
.topbar-cta:hover{background:var(--accent-hi);border-color:var(--accent-hi)}
.topbar-cta svg{opacity:.9}
/* Bouton « Parrainez un proche » (refonte 2026-08-23, maquette validée).
   /!\ Volontairement DIFFÉRENT de `.topbar-cta`, son voisin immédiat : verre
   violet + pastille verte du gain, contre un aplat d'accent. Deux boutons du
   même aplat côte à côte se lisent comme un seul. */
.topbar-invite-btn{display:inline-flex;align-items:center;gap: 0.667em;height: 2.5em;padding: 0 1em;background:linear-gradient(180deg,rgba(200,190,255,.18),rgba(139,127,224,.14));border:1px solid rgba(180,168,255,.34);border-radius:8px;color:#E4DEFF;cursor:pointer;font-size: var(--text-sm);font-weight:600;font-family:inherit;letter-spacing:-.005em;box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 4px 14px -8px rgba(139,127,224,.7);transition:all .16s ease;flex-shrink:0}
.topbar-invite-btn:hover{background:linear-gradient(180deg,rgba(200,190,255,.30),rgba(139,127,224,.22));border-color:rgba(180,168,255,.55);color:#FFFFFF}
.topbar-invite-btn svg{opacity:.95;flex-shrink:0}
/* Le point de « Ma machine » dans le menu (15/10/2026) : vert prete, orange
   en preparation. Pousse a droite de la ligne, comme une pastille. */
.machine-point{margin-left:auto;width:7px;height:7px;border-radius:50%;background:var(--success);box-shadow:0 0 0 3px rgba(74,222,128,.15);flex-shrink:0}
.machine-point.attente{background:var(--warning);box-shadow:0 0 0 3px rgba(251,191,36,.15)}
.topbar-invite-gain{display:inline-flex;align-items:center;height: 1.714em;padding: 0 0.571em;background:rgba(74,222,128,.14);border:1px solid rgba(74,222,128,.28);border-radius:999px;font-size: var(--text-2xs);font-weight:600;color:#7CE7A6;letter-spacing:.01em}
/* Bouton « Télécharger le plugin NinjaTrader » (§3) -- neutre, admin + user. */
/* Sous 1024px, on réduit le bouton plugin à l'icône seule (gain de place). */

.hamburger{display:none;background:none;border:none;padding:8px;cursor:pointer}
.hamburger span{display:block;width:20px;height:1px;background:#FFFFFF;margin:5px 0;transition:transform .2s}

/* App shell : sidebar + main */
/* /!\ `--fenetre` et jamais `100vh` : cf. la section « L'ECHELLE » de charte.css. */
.app-shell{display:flex;min-height:calc(var(--fenetre) - 56px)}
.app-shell-anon{justify-content:center;align-items:flex-start}
.sidebar{width:200px;flex-shrink:0;background:var(--card);border-right:1px solid var(--border);padding:16px 12px;position:sticky;top:56px;align-self:flex-start;height:calc(var(--fenetre) - 56px);display:flex;flex-direction:column;overflow:hidden}
.sidebar nav{display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;min-height:0}

/* Sidebar bottom -- profile card + admin/user toggle + logout */
.sidebar-bottom{flex-shrink:0;padding-top:12px;margin-top:12px;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:8px}
/* « Nous suivre » (25/10/2026) : les réseaux de TVNI, une rangée d'icônes discrète
   au pied de la colonne de la Communauté (`partials/reseaux_icones.html`). */
.reseaux-tvni{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 4px 4px}
.reseaux-tvni-titre{font-size:var(--text-xs);color:var(--muted-foreground);white-space:nowrap}
.reseaux-tvni-liens{display:flex;flex-wrap:wrap;gap:2px;justify-content:flex-end}
.reseaux-tvni-liens a{display:grid;place-items:center;width:30px;height:30px;border-radius:var(--radius-md);color:var(--muted-foreground);transition:color var(--motion-base) var(--ease-out),background var(--motion-base) var(--ease-out)}
.reseaux-tvni-liens a:hover,.reseaux-tvni-liens a:focus-visible{color:var(--foreground);background:var(--accent-wash)}
.sidebar-space-toggle{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:rgba(74,59,122,.06);border:1px solid rgba(74,59,122,.20);border-radius:6px;color:var(--accent);font-size: var(--text-sm);font-weight:500;text-decoration:none;cursor:pointer;transition:all .14s ease}
.sidebar-space-toggle:hover{background:rgba(74,59,122,.12);border-color:rgba(74,59,122,.40)}
.sidebar-space-toggle svg{flex-shrink:0;opacity:.7}
/* Le groupe WhatsApp des membres, au bas du menu de l'espace client (23/10/2026). */
.sidebar-whatsapp{display:flex;align-items:center;gap:9px;padding:8px 12px;border-radius:6px;font-size:var(--text-sm);font-weight:500;color:var(--muted-foreground);text-decoration:none;border:1px solid var(--border);transition:all .14s ease}
.sidebar-whatsapp svg{flex-shrink:0;color:#4ADE80}
.sidebar-whatsapp:hover{color:var(--foreground);border-color:rgba(74,222,128,.35);background:rgba(74,222,128,.06)}
.sidebar-profile-card{display:flex;align-items:center;gap:10px;padding:8px 10px;background:transparent;border:1px solid transparent;border-radius:6px;color:var(--foreground);text-decoration:none;cursor:pointer;transition:background .14s ease,border-color .14s ease}
.sidebar-profile-card:hover{background:var(--background);border-color:var(--border)}
.sidebar-profile-avatar{width: 2.667em;height: 2.667em;border-radius:50%;background:linear-gradient(135deg,var(--accent) 0%,#6B5BA3 100%);color:#FFFFFF;display:inline-flex;align-items:center;justify-content:center;font-size: var(--text-sm);font-weight:600;letter-spacing:.02em;flex-shrink:0}
.sidebar-profile-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.sidebar-profile-name{font-size: var(--text-base);font-weight:600;color:var(--foreground);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidebar-profile-email{font-size: var(--text-xs);color:var(--muted-foreground);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sidebar-logout{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size: var(--text-sm);color:var(--muted-foreground);background:transparent;border:none;border-radius:6px;cursor:pointer;font-family:inherit;transition:color .14s ease,background .14s ease;text-decoration:none;width:100%;text-align:left}
.sidebar-logout:hover{color:var(--error);background:rgba(159,63,78,.06)}
.sidebar-logout svg{flex-shrink:0;opacity:.8}
.sidebar nav a, .sidebar-mobile nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; font-family: var(--font-sans); font-size: var(--text-base);
  color: var(--muted-foreground); text-decoration: none;
  border-radius: var(--radius-md);
  transition: background var(--motion-base) var(--ease-out), color var(--motion-base) var(--ease-out);
}
.sidebar nav a:hover, .sidebar-mobile nav a:hover {
  background: var(--muted); color: var(--foreground);
}
.sidebar nav a.active, .sidebar nav a[aria-current="page"],
.sidebar-mobile nav a.active, .sidebar-mobile nav a[aria-current="page"] {
  background: rgb(74 59 122 / 0.08); color: var(--accent);
  font-weight: 500; border-left: 2px solid var(--accent); padding-left: 10px;
}
.sidebar nav a svg, .sidebar-mobile nav a svg { flex-shrink: 0; }
.sidebar-section {
  font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--accent); font-weight: 500;
  padding: 20px 12px 8px; margin-top: 12px; border-top: 1px solid var(--border);
}
/* Le mot d'un menu VIDE (2026-08-25, F3 tache 2, tour de correction 1).
   Un collaborateur a qui personne n'a encore attribue de role recevait un
   <nav> vide, sans un mot : il croyait a une panne. Le filet en pointilles
   reprend `.empty-state-notes` -- il dit « il n'y a rien ICI », par opposition
   a une carte pleine qu'on aurait pu croire coupee. */
.sidebar-vide {
  margin: 4px 0 0; padding: 14px 12px;
  border: 1px dashed var(--border); border-radius: var(--radius-md);
  font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.5;
  color: var(--muted-foreground);
}
/* ⚠️ Ce dégradé était CLAIR et codé en dur : au passage au thème sombre, le
   texte est devenu clair et se retrouvait illisible sur ce fond. C'était le
   seul vrai défaut visible de la bascule -- les panneaux, la barre latérale et
   le corps employaient déjà `var(--…)` et ont suivi tout seuls. */
.main{flex:1;padding:32px 40px 64px;min-width:0;background:var(--background);position:relative}
.main::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(74,59,122,.04) 1px,transparent 1px);background-size:24px 24px;pointer-events:none;z-index:0}
.main > *{position:relative;z-index:1}
/* ⚠️ Une fenêtre MODALE enfant direct de `.main` garde la place que lui donne le
   navigateur (22/10/2026) : la règle du dessus la passait en `relative`, et la fenêtre
   des réglages du graphique s'ouvrait hors de l'écran, sur le journal. */
.main > dialog:modal{position:fixed}
.main-centered{display:flex;align-items:center;justify-content:center;padding:64px 24px}

/* Drawer hamburger mobile */
.drawer-backdrop{position:fixed;inset:0;background:rgb(28 35 51 / 0.40);z-index:80;display:none;transition:opacity var(--motion-slow) var(--ease-out)}
.drawer-backdrop.open{display:block}

/* Global search dropdown */
.global-search-popover{position:absolute;z-index:200;background:#FFFFFF;border:1px solid var(--border);border-radius:8px;box-shadow:0 12px 40px rgba(14,17,38,.16),0 4px 8px rgba(14,17,38,.04);padding:6px;max-height:480px;overflow-y:auto;animation:gsFade .14s ease}
@keyframes gsFade{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
.global-search-popover .gs-empty{padding:24px 16px;text-align:center;color:var(--muted-foreground);font-size: var(--text-base)}
.global-search-popover .gs-group-label{font-size: var(--text-2xs);text-transform:uppercase;letter-spacing:.14em;color:var(--muted-foreground);font-weight:600;padding:10px 12px 6px}
.global-search-popover .gs-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:5px;text-decoration:none;color:var(--foreground);transition:background .12s ease}
.global-search-popover .gs-item:hover,.global-search-popover .gs-item.active{background:rgba(74,59,122,.06)}
.global-search-popover .gs-icon{width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;background:var(--muted);color:var(--accent);border-radius:4px;flex-shrink:0}
.global-search-popover .gs-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.global-search-popover .gs-label{font-size: var(--text-base);font-weight:500;color:var(--foreground);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.global-search-popover .gs-subtitle{font-size: var(--text-xs);color:var(--muted-foreground);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.global-search-popover mark{background:rgba(74,59,122,.18);color:var(--accent);padding:0 2px;border-radius:2px}

.sidebar-mobile{position:fixed;top:0;left:0;bottom:0;width:80%;max-width:320px;background:var(--card);border-right:1px solid var(--border);padding:20px 16px;z-index:90;transform:translateX(-100%);transition:transform .2s ease-out;overflow-y:auto}
.sidebar-mobile.open{transform:translateX(0)}
.sidebar-mobile .wordmark{display:block;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}

/* Responsive */
@media (max-width:1023px){
  .sidebar{display:none}
  .hamburger{display:block}
  .main{padding:24px 16px 48px}
  .topbar-search{display:none}
  .topbar-invite-btn{display:none}  /* label "Inviter" mange trop sur mobile ; le bouton reste accessible via le drawer mobile */
  .wordmark{font-size: var(--text-base);letter-spacing:.14em}
}
/* AU TELEPHONE, le bas de page passe AU-DESSUS de la bulle d'aide (02/10/2026).
   Le lanceur flotte a 24 points du bord, haut de 54 : avec 48 points de marge,
   le dernier bouton d'une page restait dessous, meme tout en bas. */
@media (max-width:720px){
  .main{padding-bottom:calc(96px + env(safe-area-inset-bottom, 0px))}
}
/* /!\ LA BARRE DU HAUT TIENT DANS UN TELEPHONE (TVNI-66, 2026-09-19).
   Chez un client qui paie, elle alignait le logo et son mot « T V N I »,
   « Mon abonnement », jusqu'a trois icones et le menu : environ 450 px, pour un
   ecran de 390. TOUTE la page defilait de gauche a droite, et a 360 le ☰
   sortait de l'ecran. Mesure au navigateur, a 360, 390 et 430.
   Ce qu'on retire, et pourquoi ceux-la :
   - le mot « T V N I » : le T suffit a signer la marque, et reste cliquable ;
   - l'icone du billet dans « Mon abonnement » : le LIBELLE reste, c'est le
     geste qui rapporte (cf. `.topbar-cta`) ;
   - les marges, ramenees a la gouttiere de 16 px du reste de la page.
   /!\ Ne PAS poser `overflow:hidden` sur la barre « pour etre tranquille » : le
   panneau de la cloche en descend, il serait coupe. */
@media (max-width:520px){
  .topbar-inner{padding:0 16px;gap:10px}
  .topbar .wordmark{display:none}
  .topbar-right{gap:4px}
  .topbar-cta{padding:0 .75em}
  .topbar-cta svg{display:none}
  .hamburger{padding:8px 6px}
}

/* Headings & basic typography */
h1,h2,h3{margin:0;font-family:var(--font-sans);font-weight:500;color:var(--foreground)}
p{margin:0 0 12px}

/* Section headers */
.sec-head{margin-bottom:32px}
.sec-body{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;flex-wrap:wrap}
.sec-body h1{font-family:var(--font-sans);font-weight:400;font-size: var(--text-3xl);line-height:1.1;letter-spacing:-.01em;margin:0 0 12px;color:var(--foreground)}
.sec-body h1 em,h1 em,h2 em,h3 em{font-style:normal;font-weight:300;color:var(--muted-foreground)}
.sec-body .lede{margin:0;color:var(--foreground);font-size: var(--text-md);font-weight:300;max-width:600px;line-height:1.55}
.sec-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}

@media (max-width:1023px){
  .sec-body h1{font-size: var(--text-2xl)}
}

/* ───── Breadcrumb (Admin / Page) ───── */
.admin-bcr{font-size: var(--text-xs);color:var(--muted-foreground);margin-bottom:6px;display:flex;align-items:center;gap:6px;font-family:var(--font-sans)}
.admin-bcr a{color:inherit;text-decoration:none;transition:color .14s}
.admin-bcr a:hover{color:var(--accent)}
.admin-bcr b{color:var(--foreground);font-weight:500}
.admin-bcr .sep{opacity:.5;font-size: var(--text-2xs)}
/* AU TELEPHONE, le fil tient sur UNE ligne (02/10/2026). En flex sans retour,
   chaque maillon se tassait jusqu'a passer a la ligne mot par mot : « Mon /
   espace / Mes / traders » en colonnes, au-dessus d'une analyse. Les maillons
   gardent leur largeur, le dernier (la page ou l'on est) se coupe en « … », et
   « Mon espace » s'efface quand le fil a trois liens : le logo y mene deja. */
@media (max-width: 720px) {
  /* `contain: inline-size` : le fil ne pese plus sur la largeur de son parent.
     Sans lui, un en-tete en flex s'elargissait a la longueur du fil, et la page
     defilait de travers (vu sur le detail d'une analyse). */
  .admin-bcr { flex-wrap: nowrap; white-space: nowrap; min-width: 0; overflow: hidden;
    contain: inline-size; }
  .admin-bcr > * { flex-shrink: 0; }
  .admin-bcr > b { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .admin-bcr:has(> a:nth-of-type(3)) > a:first-of-type,
  .admin-bcr:has(> a:nth-of-type(3)) > a:first-of-type + .sep { display: none; }
}

/* ───── Sec-head compact variant (replaces giant title) ───── */
.sec-head--compact{margin-bottom:18px}
.sec-head--compact .sec-body h1{font-size: var(--text-xl);font-weight:600;letter-spacing:-.01em;margin:0 0 4px}
.sec-head--compact .sec-body .lede{font-size: var(--text-base)}
@media(max-width:1023px){.sec-head--compact .sec-body h1{font-size: var(--text-xl)}}

/* ── La barre d'ONGLETS D'UNE ENTREE (2026-08-25) ─────────────────────────────
   Les pages d'une meme entree de menu : « Tous » et « Doublons » pour Contacts,
   les dix ecrans de Reglages. Rendue par la COQUILLE depuis
   `espace/navigation.py`, jamais par l'ecran -- c'est ce qui permet de regrouper
   une quarantaine de pages sans en modifier une seule.

   /!\ Volontairement DIFFERENTE de `.seg--onglets`, la pilule. Plusieurs ecrans
   portent deja une barre de VUES en pilule (« Tous 2 · Clients 2 · Prospects 0 »)
   quelques dizaines de pixels plus bas : deux pilules empilees se lisent comme
   une seule commande coupee en deux. Celle-ci est un SOULIGNEMENT pleine
   largeur, le vocabulaire d'une navigation de section.

   /!\ Elle est au-dessus du titre parce qu'elle ne parle pas de la page mais de
   la SECTION qui la contient -- le fil d'Ariane la reprend juste en dessous.

   /!\ Elle defile a plat quand elle deborde (onze onglets sur Reglages), elle ne
   passe pas a la ligne : une barre de navigation sur deux lignes ne se lit plus
   comme une barre. */
.onglets-entree {
  display: flex;
  gap: 2px;
  margin: -8px 0 22px;
  border-bottom: 1px solid var(--border);
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.onglets-entree::-webkit-scrollbar { height: 0; }
.onglets-entree > a {
  flex: 0 0 auto;
  padding: 9px 14px;
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--muted-foreground);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--motion-base) var(--ease-out),
              border-color var(--motion-base) var(--ease-out);
}
.onglets-entree > a:hover { color: var(--foreground); }
.onglets-entree > a[aria-current="page"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
/* Le nombre d'un onglet (« A traiter 7 », « Demandes 2 », vague 4). Il se lit
   comme une quantite, pas comme une alerte : chiffres a chasse fixe, ton
   attenue, et la couleur de l'onglet actif quand on y est. Un zero ne s'affiche
   jamais (regle du gabarit, pas d'ici).
   /!\ Les DEUX rendus de la barre le portent -- la coquille (`.onglets-entree`)
   et la pilule en page (`.seg--onglets`, cf. `partials/onglets_en_page.html`) :
   le meme onglet change de forme selon l'ecran, jamais de sens.
   /!\ UNE SEULE classe, `.onglet-compte` : l'assemblage de la vague 4 a trouve
   deux habillages concurrents du meme chiffre (`.onglet-compte` et `.n`), nes
   dans deux chantiers paralleles. Deux noms pour une chose, ce sont deux
   apparences le jour ou l'un des deux bouge. */
.onglets-entree > a .onglet-compte,
.seg--onglets > a .onglet-compte {
  margin-left: 6px;
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}
.onglets-entree > a[aria-current="page"] .onglet-compte { color: var(--accent); }
.seg--onglets > a[aria-current="page"] .onglet-compte { color: inherit; }

/* Un titre de carte qui porte une icone (2026-08-25, page Reglages).
   /!\ En `flex`, et non l'icone posee dans le flux : `.carte-titre` porte une
   marge basse, donc il est employe sur un BLOC -- l'icone tombait alors sur sa
   propre ligne, au-dessus du titre. */
.carte-titre--icone { display: flex; align-items: center; gap: 8px; }
.carte-titre--icone svg { flex-shrink: 0; color: var(--accent); }

/* /!\ `.etape-titre` et `.etape-sous` (le titre et le sous-titre du recapitulatif
   d'arrivee de l'ancienne fiche) sont PARTIES le 24/09/2026 avec
   `user_trader_edit.html` : la fiche est devenue le parcours pas a pas, et son
   recapitulatif porte ses propres regles (`creation-guidee.css`, `.cg-arrivee`). */

/* -----------------------------------------------------------------------------
   LES DEUX TABLEAUX DE /team/relances (2026-08-30)

   « Les miennes » et « La pioche » se lisent comme un SEUL tableau : le
   commercial descend de l'un a l'autre, et rien ne doit bouger sous ses yeux.

   /!\ `table-layout: fixed` est ce qui fait tenir les largeurs, exactement
   comme pour `.tableau--paliers` plus haut. Sans lui, le navigateur les
   recalcule sur le CONTENU : le bouton « Prendre », present dans le second
   tableau seulement, elargissait sa colonne et decalait toutes les autres. Les
   deux tableaux ne s'alignaient donc plus entre eux.

   /!\ **`text-align: left` sur les en-tetes, et c'est la moitie du defaut.**
   Un `<th>` est CENTRE par defaut (regle de l'agent utilisateur), les cellules
   sont a gauche : l'en-tete flottait au milieu de sa colonne. Rien dans ce
   depot ne posait la regle, parce que `.tbl` n'a AUCUNE feuille de style — il
   n'est qu'un nom, porte par quatre autres ecrans. D'ou le modificateur : une
   classe partagee par deux objets est un piege paye au moins une fois ici.

   /!\ La colonne d'action RESTE dans « Les miennes », vide. La retirer
   redonnerait deux grilles differentes, c'est-a-dire le defaut lui-meme.
   -------------------------------------------------------------------------- */
.tbl--relances { width: 100%; border-collapse: collapse; table-layout: fixed; }
.tbl--relances th {
  text-align: left; padding: 0 10px 8px 0; vertical-align: bottom;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted-foreground);
}
.tbl--relances td {
  padding: 10px 10px 10px 0; vertical-align: top;
  border-top: 1px solid var(--border);
}
.tbl--relances th:last-child, .tbl--relances td:last-child { padding-right: 0; }
/* Les SEPT largeurs, en POURCENTAGES : posees une seule fois, sur les
   en-tetes, et les deux tableaux sortent du meme macro de gabarit.
   /!\ La septieme est arrivee avec le bouton « Fait » (lot 3) : sans
   `table-layout: fixed`, elle aurait a elle seule redecale les six autres,
   c'est-a-dire exactement le defaut que ce bloc existe pour empecher. */
.tbl--relances th:nth-child(1) { width: 9%; }
.tbl--relances th:nth-child(2) { width: 17%; }
.tbl--relances th:nth-child(3) { width: 11%; }
.tbl--relances th:nth-child(4) { width: 19%; }
.tbl--relances th:nth-child(5) { width: 27%; }
.tbl--relances th:nth-child(6) { width: 8%; }
.tbl--relances th:nth-child(7) { width: 9%; }
/* Le commentaire est une PHRASE : elle s'enroule dans sa colonne au lieu de la
   pousser. `overflow-wrap` couvre une adresse ou un mot sans espace. */
.tbl--relances .relance-commentaire {
  overflow-wrap: anywhere;
  color: var(--muted-foreground); font-size: var(--text-sm); line-height: 1.45;
}
/* /!\ Le paragraphe d'explication de la pioche courait jusqu'au bord de la
   FENETRE, bien au-dela du tableau. Meme mesure que `.sec-body .lede` (600 px,
   plus haut dans ce fichier) : une explication se lit sur une colonne, jamais
   sur un ecran. */
.relances-explication { max-width: 600px; }

/* -----------------------------------------------------------------------------
   LES DEUX TABLEAUX PROPRES A « MA JOURNEE » (2026-08-30)

   /!\ **Un modificateur a eux, pas `.tbl--relances`.** Leurs colonnes ne sont
   pas les memes (quatre au lieu de sept) : leur preter la grille des relances
   les aurait ecrases sur les largeurs d'un autre objet. Une classe partagee
   par deux objets est le piege le plus cher de ce depot, ecrit trois fois
   dans ce fichier.

   /!\ **ET C'EST POURQUOI ILS EN ONT DEUX, PAS UN.** Un seul modificateur,
   nomme tbl--journee, les habillait tous les DEUX (sous un commentaire qui
   promettait pourtant le contraire) :
   les rendez-vous (Heure / Personne / Objet / Ou) et les dossiers silencieux
   (Silence / Dossier / Phase / bouton) n'ont pas les memes colonnes, et la
   justification de la 1re largeur ci-dessous — « elle porte aussi *jamais de
   nouvelles* » — appartient au SECOND et s'imposait au premier. Une heure n'a
   jamais eu besoin de 15 % de la ligne.

   /!\ Le squelette reste ECRIT UNE FOIS, en listant les deux selecteurs ;
   seules les LARGEURS distinguent les deux objets. Recopier le squelette pour
   chacun serait le defaut symetrique : deux copies derivent.

   /!\ `.tbl` n'a AUCUNE feuille de style : il n'est qu'un nom. Tout ce qui
   suit doit donc etre pose ici, y compris le `text-align: left` des en-tetes,
   qu'un `<th>` centre par defaut.
   -------------------------------------------------------------------------- */
.tbl--reunions, .tbl--silence {
  width: 100%; border-collapse: collapse; table-layout: fixed;
}
.tbl--reunions th, .tbl--silence th {
  text-align: left; padding: 0 10px 8px 0; vertical-align: bottom;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted-foreground);
}
.tbl--reunions td, .tbl--silence td {
  padding: 10px 10px 10px 0; vertical-align: top;
  border-top: 1px solid var(--border);
}
.tbl--reunions th:last-child, .tbl--reunions td:last-child,
.tbl--silence th:last-child, .tbl--silence td:last-child { padding-right: 0; }
/* Les rendez-vous : une heure tient en peu de place, l'objet en demande. */
.tbl--reunions th:nth-child(1) { width: 10%; }
.tbl--reunions th:nth-child(2) { width: 30%; }
.tbl--reunions th:nth-child(3) { width: 40%; }
.tbl--reunions th:nth-child(4) { width: 20%; }
/* /!\ Les dossiers silencieux : la 1re colonne est plus large que ce qu'un
   nombre de jours demande, EXPRES, parce qu'elle porte aussi « jamais de
   nouvelles » (un dossier sans aucun signal ne s'annonce pas par un nombre de
   jours, qui serait exact et trompeur). A 10 % elle cassait cette phrase sur
   trois lignes. */
.tbl--silence th:nth-child(1) { width: 15%; }
.tbl--silence th:nth-child(2) { width: 31%; }
.tbl--silence th:nth-child(3) { width: 34%; }
.tbl--silence th:nth-child(4) { width: 20%; }

/* ============================================================
   Le BANDEAU DE TRAITEMENT d'une fiche : `.traiter` (2026-08-31)
   ============================================================
   Deux colonnes, au-dessus des onglets d'une fiche contact : a gauche ce qui
   s'est DIT (l'etat du dossier, les deux derniers echanges en clair), a droite
   les faits qu'on CITE au telephone (`.bilan`, deja de la charte).

   /!\ Il repond a « que ca prenne tres peu de temps de traiter un contact ».
   L'information la plus utile de l'ecran etait la plus enterree : le texte du
   dernier echange vivait dans un onglet, donc a un clic et un defilement.

   /!\ IL NE REUTILISE PAS `.vivant`. Cette classe existe plus haut dans ce
   fichier et aucun gabarit ne la porte, mais ses quatre modificateurs
   qualifient un TRADER : une position ouverte, un ordre en attente, l'arret.
   Les emprunter pour la phase d'un dossier CRM ferait une classe partagee par
   deux objets, le piege le plus cher de ce depot, ecrit trois fois ici.

   /!\ La colonne droite ne descend pas sous 300 px : `.bilan-valeur` est en
   chasse fixe, et un numero de telephone qui revient a la ligne se lit comme
   deux numeros. Sous 900 px les deux colonnes s'empilent, seul arbitrage
   possible sur un ecran etroit.

   /!\ Le texte d'un echange garde ses retours a la ligne (`pre-wrap`) : un
   commentaire d'appel se saisit en plusieurs lignes, et les aplatir le rend
   illisible. */
.traiter {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: 20px;
  padding: 18px 20px;
  margin-bottom: 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--card);
}
@media (max-width: 900px) {
  .traiter { grid-template-columns: 1fr; }
}
.traiter-dit   { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.traiter-faits { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* L'etat du dossier. La pastille DOUBLE la couleur du texte au lieu de la
   remplacer : une teinte seule est illisible pour huit hommes sur cent. */
.traiter-dossier { display: flex; align-items: flex-start; gap: 10px; }
.traiter-dossier-pastille {
  width: 8px; height: 8px; border-radius: 50%; flex: none; margin-top: 6px;
  background: var(--disabled-foreground);
}
.traiter-dossier-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.traiter-dossier--open .traiter-dossier-pastille { background: var(--info); }
.traiter-dossier--won  .traiter-dossier-pastille { background: var(--success); }
.traiter-dossier--lost .traiter-dossier-pastille { background: var(--error); }

.traiter-echange {
  padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--muted); min-width: 0;
}
.traiter-echange-tete {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: var(--text-body-sm);
}
/* /!\ DEUX classes, et la difference est `pre-wrap`. `.traiter-echange-texte`
   porte un texte SAISI par un commercial : ses retours a la ligne comptent.
   `.traiter-phrase` porte un texte du GABARIT, ecrit sur plusieurs lignes dans
   le fichier pour tenir dans quatre-vingts colonnes : sous `pre-wrap`, son
   indentation de source devenait visible a l'ecran et la premiere ligne
   commencait plus a gauche que les suivantes (vu par le fondateur le
   2026-08-31, sur le bandeau d'un contact sans historique). Une phrase du
   gabarit ne doit jamais dependre de la facon dont on l'a coupee dans le
   fichier. */
.traiter-echange-texte, .traiter-phrase {
  margin: 6px 0 0; overflow-wrap: anywhere;
  font-size: var(--text-body-sm); color: var(--foreground);
}
.traiter-echange-texte { white-space: pre-wrap; }
.traiter-etiquettes { display: flex; flex-wrap: wrap; gap: 6px; }

/* ══ OU EN EST CETTE PERSONNE ════════════════════════════════════════════════
   La pastille d'etat, sur la fiche ET dans la liste des contacts. Decision du
   fondateur du 2026-09-17 : l'etat se deduit des faits, il ne se saisit pas.

   /!\ UNE SEULE brique pour les DEUX ecrans. Deux jumelles divergent toujours,
   et c'est l'ecran qu'on regarde le moins qui affiche l'autre couleur pour le
   meme mot -- le meme reproche est deja ecrit plus haut au sujet des deux
   moities du tableau de bord.

   /!\ `.etat-personne`, et pas `.etat-pastille` : celle-la existe deja
   (ligne 2707), porte un TRADER (« Actif · ES »), et se remplit de `.etat-point`.
   Une classe partagee par deux objets est le piege le plus cher de ce depot.

   /!\ LE MOT PORTE L'INFORMATION, LA COULEUR NE FAIT QUE LA DOUBLER. Huit
   hommes sur cent ne distinguent pas ces teintes ; c'est la meme regle que la
   pastille de dossier juste au-dessus, ou la couleur double le texte au lieu de
   le remplacer. Un etat sans regle de couleur reste donc parfaitement lisible,
   en gris -- et c'est le cas de « Nouveau », volontairement. */
.etat-personne {
  display: inline-block; padding: 2px 9px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--muted); color: var(--muted-foreground);
  font-size: var(--text-xs); font-weight: 500; white-space: nowrap;
}
/* Ce qui avance vers le client : l'information. */
.etat-personne--inscrit,
.etat-personne--mise_en_service_prevue { color: var(--info); border-color: var(--info); }
/* Ce qui paie et ce qui tourne : la reussite. */
.etat-personne--client,
.etat-personne--installe,
.etat-personne--actif { color: var(--success); border-color: var(--success); }
/* Les deux fins. /!\ Elles ne partagent PAS la meme teinte : « Perdu » est un
   prospect qu'on n'a pas convaincu, « Parti » un client qui s'en va -- le
   second coute infiniment plus cher, et il doit sauter aux yeux dans une
   liste. */
.etat-personne--perdu { color: var(--warning-text); border-color: var(--warning); }
.etat-personne--parti { color: var(--error); border-color: var(--error); }

/* /!\ « C'est terminé » est REPLIE. Ce n'est pas un geste du quotidien, et il
   retire la personne des files de travail de toute l'equipe : pose a plat a
   cote du telephone et de l'e-mail, il se cliquerait par erreur. */
.traiter-fin { margin-top: 4px; }
/* Les points de suivi d'un client (refuser / reprendre, 21/09/2026). */
.suivi-client { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 4px 0 8px; }
.suivi-client p { margin: 0; font-size: var(--text-body-sm); color: var(--muted-foreground); }
/* L'échéance d'une file d'actions : « 21/09 à 15:39 » tient sur une ligne. */
.relance-echeance { white-space: nowrap; padding-right: 14px; }
.traiter-fin > summary {
  cursor: pointer; font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.traiter-fin form { display: flex; gap: 8px; align-items: center; margin-top: 8px; }
.traiter-fin p { margin: 6px 0 0; font-size: var(--text-xs); }

/* /!\ Le bandeau DEGRADE le dit. `fiche_contact.bandeau` ne leve jamais : sur
   une exception il rend des replis, qui se lisent comme des faits. Le ton est
   celui d'un avertissement, pas d'une erreur : la page, elle, est bonne. */
.traiter-incomplet {
  padding: 8px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--warning); color: var(--foreground);
  font-size: var(--text-body-sm);
}

/* /!\ LE TEXTE D'UN ECHANGE DANS LE FIL, et il a sa propre regle EXPRES.
   Le voisin evident, `.rangee-texte`, tronque a `max-height: 3em` (deux lignes)
   pour une liste de regles depliables : le fil d'un contact n'a pas de
   depliement, un commentaire d'appel y serait donc coupe net et le reste
   deviendrait irrecuperable. Cette classe n'avait AUCUNE regle jusqu'au
   2026-08-31, ce qui invitait a la « reparer » vers celle qui tronque. Elle en a
   une, et c'est la meme que le texte d'un echange du bandeau : les retours a la
   ligne d'un commentaire saisi comptent. */
.fil-texte {
  margin-top: 4px; white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: var(--text-base); line-height: 1.5;
}

/* ============================================================
   L'ETIQUETTE d'un contact : `.tag-chip` (2026-08-31)
   ============================================================
   /!\ CE N'EST PAS UNE BRIQUE NEUVE, c'est un RAPATRIEMENT. Ces regles vivaient
   dans le `<style>` du gabarit du tiroir contact (`admin_contact_drawer.html`,
   retire le 2026-08-31), et nulle part ailleurs : ni dans cette feuille, ni dans
   `admin_base.html`. La fiche etend `admin_base.html`, donc sans ce
   rapatriement une etiquette serait un texte sur un aplat de couleur pleine,
   sans arrondi et sans marge.

   /!\ UN SEUL `<style>` local redefinit `.tag-chip` (`admin_contacts.html`), et
   il se charge APRES cette feuille : il continue de gagner sur son ecran, ce
   rapatriement ne change rien pour lui. /!\ **La redaction du 2026-08-31 en
   nommait DEUX et se trompait sur le second** : `admin_rules.html` definit
   `.tag-chip-preview`, une AUTRE classe, et n'emploie `.tag-chip` nulle part.
   Sans consequence, et c'est justement le genre de phrase qu'on relit dans deux
   mois pour decider quelque chose.

   /!\ La couleur, elle, reste INLINE : elle vient de la base
   (`tags.color`), une regle statique ne peut pas la porter. La convention est
   celle des cinq emplois du depot : fond a 10 %, texte et bordure a pleine
   teinte. */
.tag-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); padding: 2px 9px; border-radius: 999px; line-height: 1.6;
  border: 1px solid transparent; font-family: var(--font-sans);
}
.tag-chip--editable { padding-right: 4px; }
.tag-chip-x {
  border: none; background: transparent; color: inherit; cursor: pointer;
  font-size: var(--text-md); line-height: 1; padding: 0 2px; opacity: .7;
}
.tag-chip-x:hover { opacity: 1; }
.tag-editor-chips {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center; min-height: 24px;
}

/* ============================================================
   La BARRE DES GESTES d'une fiche : `.barre-fiche` (2026-08-31)
   ============================================================
   Six gestes toujours sous la main, en bas de la fiche d'un contact.
   Emplacement retenu par le fondateur apres comparaison de trois maquettes.

   /!\ ELLE NE REUTILISE PAS `.barre-enregistrement`, et c'est mesure : cette
   derniere se CACHE tant que rien n'a change (`[data-cachable]`, plus haut dans
   ce fichier), parce qu'elle sert a ENREGISTRER. Les six gestes, eux, doivent
   rester visibles en permanence. Une variante de la meme classe ferait dependre
   les gestes d'un attribut pose par `enregistrer.js` pour un autre objet, et une
   classe partagee par deux objets est le piege le plus cher de ce depot.

   La GEOMETRIE, elle, est recopiee, avec ses raisons :

   /!\ `position: fixed`, PAS `sticky` : c'est la FENETRE qui defile (`.main` n'a
   ni hauteur ni `overflow`) et `.main` porte un `padding-bottom` de 64 px. Une
   barre collante cesserait de coller 64 px avant le bas de page.

   /!\ Elle doit rester DANS `.wrap`, jamais enfant direct de `.main` :
   `admin_base.html` pose `.main > * { position: relative }` avec la MEME
   specificite, dans un `<style>` charge APRES cette feuille. En enfant direct,
   elle redeviendrait `relative` et flotterait en bas du flux -- et le defaut ne
   se verrait meme pas sur une page courte. Bonus du bon placement : `.wrap`
   porte `z-index: 1`, donc la barre passe seule sous la barre d'application et
   sous le menu mobile.

   /!\ Le decalage a gauche suit la barre laterale (200 px), qui disparait sous
   1024 px comme partout ailleurs. Meme borne, une seule requete media.

   /!\ La reserve sous la page est posee sur `.main`, pas sur la barre : c'est la
   PAGE qui doit rendre de la place, pas la barre qui doit en prendre moins.

   /!\ Le jour ou quelqu'un pose `transform`, `filter`, `backdrop-filter` ou
   `contain` sur `.main` ou `.wrap`, la barre s'ancrera a ce conteneur au lieu de
   l'ecran et cessera d'etre fixe. Aucun test HTML n'attrape cette casse. */
.barre-fiche {
  position: fixed;
  left: 200px; right: 0; bottom: 0;
  z-index: 45;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 12px 40px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  background: rgba(14, 16, 24, 0.94);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border-top: 1px solid var(--border-strong);
  box-shadow: 0 -16px 44px -18px rgba(0,0,0,0.75);
}
@media (max-width: 1023px) {
  .barre-fiche { left: 0; padding: 10px 16px; }
}
body:has(.barre-fiche) .main { padding-bottom: 132px; }
.barre-fiche-gestes { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* UN geste. La couleur est sur le PICTOGRAMME, jamais en aplat sur le fond :
   six pastilles pleines cote a cote se lisent comme six boutons principaux, et
   l'ecran n'a plus de voix forte. Au survol, le bouton s'entoure d'un lisere et
   d'un voile de SA couleur. */
.geste {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 12px; border-radius: var(--radius-sm);
  border: 1px solid transparent; background: transparent;
  color: var(--muted-foreground); font-size: var(--text-body-sm);
  cursor: pointer; text-decoration: none;
  transition: border-color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
.geste[data-couleur="success"] svg { color: var(--success); }
.geste[data-couleur="info"]    svg { color: var(--info); }
.geste[data-couleur="accent"]  svg { color: var(--accent); }
.geste[data-couleur="warning"] svg { color: var(--warning); }
/* /!\ Le ROUGE d'« A relancer » n'est PAS un avertissement, c'est un repere de
   famille. La charte reserve `--error` aux erreurs partout ailleurs ; ici il
   qualifie un geste banal, et le fondateur l'a valide a l'ecran. Si l'ambiguite
   gene un jour, c'est ce geste qui prend `--warning` et « Note » un autre
   repere -- pas `--error` qui change de sens. */
.geste[data-couleur="error"]   svg { color: var(--error); }
.geste:hover { border-color: currentColor; background: var(--muted); }

/* /!\ Un geste IMPOSSIBLE reste AFFICHE, grise, avec sa raison. Un bouton absent
   fait chercher ce qu'on a mal fait ; c'est la regle deja ecrite pour le bouton
   de retrait de « Mes gains ». */
.geste[aria-disabled="true"] { opacity: 0.55; cursor: default; }
.geste[aria-disabled="true"]:hover { border-color: transparent; background: transparent; }
.geste-raison {
  font-style: normal; font-size: var(--text-caption);
  color: var(--disabled-foreground);
}

/* La touche, REVELEE AU SURVOL. Un raccourci que rien n'annonce n'existe pour
   personne ; six raccourcis affiches en permanence ajoutent six symboles a lire
   en travaillant. Le survol est le compromis, et le `title` porte la meme
   information pour le clavier et les lecteurs d'ecran.
   /!\ `visibility` et non `display` : un `display:none` ferait sauter la largeur
   du bouton au survol, et les six gestes se decaleraient les uns les autres. */
.geste-kbd {
  visibility: hidden;
  font-family: var(--font-mono); font-size: var(--text-2xs); line-height: 1;
  padding: 2px 5px; border: 1px solid currentColor; border-radius: 3px;
  color: inherit; opacity: 0.7;
}
.geste:hover .geste-kbd, .geste:focus-visible .geste-kbd { visibility: visible; }

/* Les six raccourcis de la fenetre « A relancer ».
   /!\ ELLE NE S'APPELLE PAS `.jetons`, et c'est mesure : `.jetons` existe deja,
   c'est le conteneur des `.jeton`, les pastilles de FILTRE, et le commentaire
   juste au-dessus dit « Un jeton est un FILTRE, pas une etiquette ». Elle est
   employee par `admin_calendar_fragment.html`. Ajoutee a la fin du fichier, une
   seconde `.jetons` gagne (derniere regle) et fait passer les pastilles de
   devise du calendrier de `inline-flex` a `flex`, avec une marge verticale en
   prime : une regression visuelle sur un AUTRE ecran, qu'aucun test ne voit. */
.jetons-report { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; }

/* La rangee d'enregistrement d'une fenetre ou d'un panneau de fiche. DANS LE
   FLUX, jamais fixe : `.barre-fiche` occupe deja le bas de l'ecran.
   /!\ Ce n'est PAS `.actions-partie` : cette classe-la n'existe pas dans
   `charte.css`, elle est definie localement dans `abonnement.html`,
   `user_gains.html` et `user_profile.html`, trois gabarits que
   `admin_contact_fiche.html` n'etend pas. L'employer ici donnerait une rangee
   sans style, et aucun test ne le verrait. */
.fiche-actions { display: flex; align-items: center; gap: 10px; margin-top: 16px; }

/* La TETE d'une ligne de fil : le type de l'echange, sa marque « A venir », son
   issue. Une ligne a part, parce que ces trois-la se lisent ensemble et
   doivent pouvoir passer a la ligne sans desaligner la date en dessous.
   /!\ Elle ne touche PAS a `.rangee-corps`, portee par cinq autres gabarits :
   voir le commentaire de `partials/fil_activites.html`. */
.rangee-tete { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }

/* Les GESTES d'une ligne de fil (2026-08-31), dans la TROISIEME colonne de
   `.rangee`. Ils viennent du panneau d'activites du tiroir de contact, retire ce
   jour-la : « Modifier » et « Supprimer » ne vivaient que la.
   /!\ Ils ne touchent PAS a `.rangee-corps`, portee par cinq autres gabarits :
   voir le commentaire de `partials/fil_activites.html`. */
.fil-gestes { display: flex; align-items: flex-start; gap: 6px; }
/* Un rendez-vous ANNULÉ dans le fil de la fiche (21/09/2026) : la maquette
   validée le veut en rouge, et c'est le seul état du fil qui le soit. */
.fil-annule { color: var(--error); }

/* Le depliant « Modifier » d'une ligne de fil.
   /!\ La fleche que le navigateur ajoute a tout `<summary>` doublerait le bouton :
   le `<summary>` EST le bouton ici. Meme geste que `.menu > summary`
   (`charte.css:1989`), qu'on ne peut pas reutiliser tel quel — `.menu` est un
   menu deroulant positionne, pas un depliant en ligne. */
/* Le depliant « C'etait une reunion » de la fenetre « Consigner » (2026-08-31).
   Meme motif que `.fil-editer` ci-dessous, et meme raison : le `<summary>` EST
   le bouton, sa fleche native le doublerait. */
.fiche-reunion { margin-top: 14px; border-top: 1px solid var(--muted); padding-top: 14px; }
.fiche-reunion > summary { list-style: none; cursor: pointer; display: inline-flex; }
.fiche-reunion > summary::-webkit-details-marker { display: none; }
.fiche-reunion > summary::marker { content: ""; }

.fil-editer { margin-top: 6px; }
.fil-editer > summary { list-style: none; cursor: pointer; display: inline-flex; }
.fil-editer > summary::-webkit-details-marker { display: none; }
.fil-editer > summary::marker { content: ""; }
.fil-editer form { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.fil-editer .btn { align-self: flex-start; }

/* Le compteur d'un onglet de `.seg--onglets` (« Le fil 12 »). Meme objet que
   `.drawer-seg-ct` (`charte.css:6158`), mais celle-la est reservee a la barre
   d'un TIROIR : on ne partage pas un nom entre deux objets, on en ecrit un pour
   celui-ci. */
.seg-ct {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-caption); color: var(--muted-foreground);
  margin-left: 6px;
}

/* ============================================================
   L'EDITEUR MULTI-VALEURS : `.mv-*` (2026-08-31)
   ============================================================
   /!\ CE N'EST PAS UNE BRIQUE NEUVE, c'est un RAPATRIEMENT, comme `.tag-chip`
   plus haut. Ces huit regles vivaient dans le `<style>` du gabarit du tiroir
   contact (`admin_contact_drawer.html:12-20`, retire le 2026-08-31), et NULLE
   PART ailleurs. La fiche etend `admin_base.html` : sans elles, l'editeur
   d'e-mails et de telephones est une pile de champs pleine largeur, l'indicatif
   pays occupe une ligne a lui seul et la croix de retrait est un gros « x »
   noir.

   /!\ Elles n'ecrasaient rien : le `<style>` du tiroir se chargeait APRES cette
   feuille et continue de gagner sur son ecran, a l'identique, jusqu'a son
   retrait. */
.mv-rows { display: flex; flex-direction: column; gap: 8px; }
.mv-row { display: flex; align-items: center; gap: 8px; }
.mv-row .mv-input { flex: 1; min-width: 0; }
.mv-row .mv-dial { flex: 0 0 92px; width: 92px; }
.mv-primary {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--text-xs); color: var(--muted-foreground);
  white-space: nowrap; cursor: pointer; user-select: none;
}
.mv-primary input { margin: 0; }
.mv-remove {
  border: none; background: transparent; color: var(--muted-foreground);
  cursor: pointer; font-size: var(--text-xl); line-height: 1; padding: 0 4px;
  border-radius: 6px;
}
.mv-remove:hover { color: var(--danger, #b4451f); background: var(--muted); }
.mv-add { margin-top: 8px; }

/* La NAVIGATION d'une fiche : retour, deux fleches, et « 3 sur 128 » entre
   elles (2026-08-31). Elle vit dans le fil d'Ariane, au-dessus du titre : c'est
   la place ou l'oeil cherche « ou suis-je », et elle ne prend pas de ligne. */
/* ── LES ANCRES D'UNE FICHE D'OBJET (2026-09-02, lot 7) ───────────────────────
   /!\ Des LIENS `#id` natifs, pas des boutons. Le tiroir avait besoin de
   `drawer-anchors.js` (boutons `data-go`, observateur de mutations, defilement
   calcule) parce que son corps defilait dans un panneau ; une page defile toute
   seule. /!\ Classe PROPRE et non `.basc` : celle-la habille une bascule
   (un choix exclusif), et deux objets differents sous une meme classe est le piege
   `.puce` deja paye dans ce depot. */
.fiche-ancres {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 0 0 18px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.fiche-ancres a {
  padding: 4px 11px; border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: var(--text-sm); color: var(--muted-foreground); text-decoration: none;
}
.fiche-ancres a:hover {
  border-color: var(--border-strong); color: var(--foreground);
}
/* /!\ `:target` habille la section VISEE, pas l'ancre : c'est la seule facon, sans
   script, de dire ou l'on vient d'atterrir. */
.encadre:target { border-color: rgba(180,168,255,0.55); }

/* ── LA BARRE D'ENREGISTREMENT D'UNE FICHE (2026-09-03) ──────────────────────
   /!\ Ces classes ont ete ECRITES au lot 7 et jamais HABILLEES : `.fiche-barre`,
   `.role-chips` et `.role-chip-toggle` n'avaient AUCUNE regle. Le bouton
   « Enregistrer » se collait donc au bas de la carte precedente, et la liste des
   roles empilait ses cases a cocher brutes. Dans le tiroir, ces memes elements
   portaient leur mise en page en STYLE EN LIGNE ; la page les a repris en classes,
   sans emporter le style avec. Un nom de classe sans regle ne casse rien de
   visible dans un test : il ne se voit qu'a l'ecran. */
.fiche-barre {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0 4px;
}
.fiche-barre .muted { font-size: var(--text-sm); }

/* Les ROLES d'un compte : une case a cocher, son libelle, et ce que le role ouvre. */
.role-chips { display: flex; flex-direction: column; gap: 6px; }
.role-chip-toggle {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 8px 11px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  font-size: var(--text-sm); cursor: pointer;
  transition: border-color .14s, background .14s;
}
.role-chip-toggle:hover { border-color: var(--border-strong); }
.role-chip-toggle > input { margin: 2px 0 0; flex-shrink: 0; }
.role-chip-toggle > span {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
/* /!\ Le NOM du role et son cadenas sur UNE ligne : les icones de ce depot sont
   des `svg` en display:block, donc le libelle tombait sous le cadenas. */
.role-chip-toggle > span > span:first-child {
  display: flex; align-items: center; gap: 6px;
  font-weight: 500; color: var(--foreground);
}
.role-chip-toggle .muted {
  font-size: var(--text-xs); line-height: 1.35;
  font-weight: 400; color: var(--muted-foreground);
}
/* /!\ L'etat coche s'habille en CSS (`:has`), pas en Jinja comme le faisait le
   tiroir : cote serveur, la case cochee ne changeait de couleur qu'au rechargement
   suivant — on cochait, rien ne bougeait, et on recochait. */
.role-chip-toggle:has(input:checked) {
  background: rgb(74 59 122 / 0.10);
  border-color: var(--accent);
}

.fiche-nav { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.fiche-nav-rang {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-caption); color: var(--muted-foreground);
  padding: 0 4px; white-space: nowrap;
}

/* ============================================================
   .t-onglets — les QUATRE ecrans d'un trader (chantier C9, lot L1)
   ------------------------------------------------------------
   /!\ C'est une NAVIGATION, pas un selecteur de panneaux : quatre liens vers
   quatre adresses, sans une ligne de script. A ne pas confondre avec la barre
   DANS les reglages, qui masque des panneaux d'un meme formulaire et vit sur
   le crochet `[data-onglets]` que `onglets-formulaire.js` cherche UNE seule
   fois dans la page. Poser ce crochet ici ferait cesser les sections de
   basculer.

   /!\ L'ecran courant se marque par `aria-current="page"`, pas par une classe :
   l'etat est alors dit au lecteur d'ecran en meme temps qu'il est peint.
   ============================================================ */
.t-onglets {
  display: flex; gap: 2px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin: 0 0 28px;
}
/* /!\ `inline-flex` depuis que l'onglet porte une ICONE (2026-09-14) : sans contexte
   flex, le SVG se pose sur la ligne de base du texte et descend de deux pixels sous
   lui. Vu a l'ecran. */
.t-onglets a {
  display: inline-flex; align-items: center;
  padding: 9px 15px; text-decoration: none; font-size: var(--text-base);
  color: var(--muted-foreground);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .14s, border-color .14s;
}
/* /!\ L'icone est PLUS PALE que le libelle, et le rejoint quand l'onglet est actif ou
   survole : elle accompagne le mot, elle ne se lit pas a sa place. Au meme ton, trois
   onglets cote a cote donnent six objets a lire au lieu de trois. */
/* /!\ **UNE MARGE SUR L'ICONE, PAS UN `gap` SUR L'ONGLET** (2026-09-14, et ce n'est
   pas un detail de style). `gap` ne separe que des ELEMENTS : il fallait donc envelopper
   le libelle d'un `<span>`. Or TROIS releves de tests retirent les `<span>` d'un bouton
   d'onglet — c'est ainsi qu'ils ignorent la pastille du compteur (`<span class="n">3
   </span>`). Le libelle enveloppe devenait invisible pour eux, et trois tests lisaient
   un onglet SANS NOM. Un quatrieme, venu des abonnements, cherchait la chaine exacte
   `<span>Abonnement</span>` ailleurs dans la page : mon enveloppe la fabriquait.
   /!\ La marge separe un noeud de TEXTE aussi bien qu'un element. Le libelle reste nu,
   les releves le lisent, et l'ecran ne change pas d'un pixel. */
.t-onglets a > svg {
  flex: 0 0 auto; margin-right: 7px; opacity: .62; transition: opacity .14s;
}
.t-onglets a:hover > svg,
.t-onglets a[aria-current="page"] > svg { opacity: 1; }
/* LES ONGLETS D'UN TRADER (08/10/2026, fondateur) : le même dessin, une pastille par
   onglet (ce qu'il contient), et au téléphone une barre qui DÉFILE sur le côté sans
   faire défiler la page. */
.t-onglets--trader {
  flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; margin: 4px 0 24px;
}
.t-onglets--trader::-webkit-scrollbar { display: none; }
/* /!\ SIX ONGLETS DOIVENT TENIR SANS DÉFILER DÈS 1024 PX (zone de contenu ~736 px,
   revue du 08/10/2026 : à 15 px, avec icônes et pastilles longues, la barre faisait
   1159 px et « Réglages » sortait de l'écran). D'où : le texte à 13 px, des marges
   serrées, les icônes seulement sur un grand écran, des pastilles courtes ; et si la
   barre déborde quand même, `t-onglets--serre` (posé par le script de la barre) ne
   garde que le chiffre des pastilles. Le défilement reste pour le téléphone. */
.t-onglets--trader { gap: 0; }
.t-onglets--trader a {
  flex: 0 0 auto; white-space: nowrap; padding: 9px 8px; font-size: var(--text-sm);
}
.t-onglets--trader a > svg { margin-right: 5px; }
@media (min-width: 768px) and (max-width: 1279px) {
  .t-onglets--trader a > svg { display: none; }
}
.t-onglets--serre .t-pastille-mot { display: none; }
.t-onglets--trader a[aria-current="page"] {
  color: var(--foreground); border-bottom-color: var(--accent); font-weight: 500;
}
.t-pastille {
  margin-left: 5px; padding: 1px 6px; border-radius: 999px;
  font-size: var(--text-2xs); font-weight: 500; line-height: 1.6;
  background: var(--muted); color: var(--muted-foreground);
}
.t-pastille--alerte { color: var(--warning); background: rgba(250, 204, 21, .12); }
/* LES STRATÉGIES (22/10/2026, interrupteur `STRATEGIES`) : CINQ onglets (Cadre ·
   Stratégies · Déclencheurs · Journal · En faire profiter) qui tiennent EN ENTIER à
   375 px. ⚠️ Cinq noms n'y tiennent pas lisiblement (≈ 420 px à 13 px) : au téléphone,
   les icônes seules, l'onglet OUVERT garde son nom et prend la place qu'il lui faut,
   les pastilles tombent ; plus aucun onglet coupé ni de pastille sans nom (audit du
   22/10). Le nom de chaque onglet reste dans sa bulle (`title`). */
@media (max-width: 767px) {
  .t-onglets--courts a {
    flex: 1 1 0; justify-content: center; padding: 11px 4px; min-width: 0;
  }
  .t-onglets--courts a[aria-current="page"] { flex: 0 0 auto; padding: 11px 10px; }
  .t-onglets--courts a > svg { margin-right: 0; width: 18px; height: 18px; }
  .t-onglets--courts a[aria-current="page"] > svg { margin-right: 6px; }
  .t-onglets--courts a:not([aria-current="page"]) .t-onglet-nom { display: none; }
  .t-onglets--courts .t-pastille { display: none; }
}
/* Le bouton de marche À MOT (en-tête) : un `btn btn-ghost btn-sm` comme ses voisins ;
   arrêté, il reste neutre (pas de contour rouge), l'icône seule est verte en marche. */
.bouton-marche--texte.bouton-marche--off,
.bouton-marche--texte.bouton-marche--on { color: var(--foreground); border-color: var(--border); }
.bouton-marche--texte.bouton-marche--off:hover,
.bouton-marche--texte.bouton-marche--on:hover { color: var(--foreground); border-color: var(--border-strong); }
/* L'état du plugin, dans la rangée : la MÊME surface que le bouton de marche (un
   `.btn` dans un formulaire, 38 px, cf. « La rangée de gestes ») et que « … ». */
.e-gestes > .e-plugin,
.e-gestes > .bouton-marche--texte {
  height: 38px; padding: 0 1em; font-size: var(--text-base);
  background: var(--card); border-color: var(--border);
}
.e-gestes > .e-plugin:hover { background: var(--muted); border-color: var(--border-strong); }
.bouton-marche--texte.bouton-marche--on > svg { color: var(--success); }
.bouton-marche--texte.bouton-marche--off > svg { color: var(--muted-foreground); }
.bouton-marche-mot { font: inherit; }
.e-gestes > .e-plugin--a_mettre_a_jour { color: var(--warning); border-color: rgba(250, 204, 21, .35); }
.e-plugin--jamais { color: var(--accent); }
.e-plugin--nulle_part { color: var(--muted-foreground); }
@media (max-width: 767px) {
  .sec-head--trader .sec-body { flex-direction: column; align-items: stretch; gap: 12px; }
  .sec-head--trader .e-gestes { justify-content: flex-start; }
  /* ⚠️ LE GRAND VIDE SOUS LE TITRE (300 px, vu le 22/10/2026 sur chaque page d'un
     trader, « Réglages » compris). Le partage « le chapeau cède » plus bas pose
     `flex: 1 1 300px` sur le chapeau : 300 px de LARGEUR dans une rangée. Ici la
     rangée passe en COLONNE, et la même base devient 300 px de HAUTEUR. En colonne,
     le chapeau prend sa hauteur de contenu. (Même sélecteur que la règle d'origine,
     un cran plus précis : elle est déclarée plus bas dans ce fichier.) */
  .sec-head--trader.sec-head--compact .sec-body:has(> .e-gestes) > :first-child {
    flex: 0 0 auto;
  }
}
/* ============================================================
   .cfg-ligne — une CONFIGURATION du client (chantier C5, 2026-09-07)
   ------------------------------------------------------------
   Un nom court a gauche, sa description a droite, la croix au bout. Deux
   niveaux, aucun cycle de vie : ni poignee de glissement, ni rang, ni badge
   d'archive — c'est ce greffage qui avait porte les anciennes regles a 1 955
   lignes, et il ne doit pas revenir par le style.
   ============================================================ */
.cfg-ligne {
  display: grid;
  grid-template-columns: minmax(140px, 220px) minmax(0, 1fr) auto;
  gap: 10px;
  align-items: start;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.cfg-ligne:last-of-type { border-bottom: 0; }
.cfg-ligne .cfg-nom {
  /* Le nom sert d'etiquette et de colonne de regroupement : on le montre tel
     qu'il partira, en capitales, pour que le client voie ce qu'il ecrit. */
  text-transform: uppercase;
  letter-spacing: .04em;
  font-weight: 500;
}
.cfg-ligne .cfg-desc { resize: vertical; min-height: 44px; }
@media (max-width: 720px) {
  .cfg-ligne { grid-template-columns: minmax(0, 1fr) auto; }
  .cfg-ligne .cfg-desc { grid-column: 1 / -1; }
}

.t-onglets a:hover { color: var(--foreground); }

/* ============================================================
   .wrap--trader — LA BARRE DES ECRANS EST REVENUE EN LIGNE (2026-09-11)
   ------------------------------------------------------------
   Le 2026-09-10, cette classe mettait les écrans d'un trader sur le côté, à la
   demande du fondateur (« les onglets journal et consignes il faut les mettre
   dans ce format »). Renversé le lendemain, par lui : « quand je suis sur
   l'onglet journal et analyse, les onglets sont toujours à la verticale, peux-tu
   uniformiser l'affichage à l'horizontal ».

   /!\ CE QUI A RENDU LA COLONNE INTENABLE, ce n'est pas le goût : c'est qu'elle
   ne pouvait PAS être posée partout. Les Paramètres portent DÉJÀ une colonne,
   celle de leurs sections (`.wrap--onglets`), et deux colonnes voisines auraient
   demandé au client laquelle commande quoi — la clause de l'époque le disait
   elle-même. Les écrans d'un trader s'affichaient donc en colonne sur trois
   pages et en ligne sur la quatrième. L'uniformité ne pouvait se faire que dans
   l'autre sens.

   /!\ LA CLASSE RESTE POSÉE dans les quatre gabarits, sans style propre : elle
   ne coûte rien et nomme l'enveloppe. La retirer demanderait de toucher quatre
   fichiers pour rien, et `user_trades.html` s'appuie sur sa présence dans un
   commentaire de structure.
   ============================================================ */
/* /!\ L'écran ouvert est ÉCRIT à l'accent (fondateur, 2026-09-11 : « applique la
   même mise en forme qu'on a faite sur Mon profil »). Même geste, même raison
   qu'au profil : un filet de 2 px sous un libellé blanc se remarque peu quand
   les quatre libellés sont déjà clairs. */
.t-onglets a[aria-current="page"] {
  color: var(--accent); font-weight: 600;
  border-bottom-color: var(--accent);
}

/* ============================================================
   Les sections des REGLAGES passent SUR LE COTE (chantier C9, lot L2)
   ------------------------------------------------------------
   Demande du fondateur (2026-09-07) : « pour l'affichage des onglets dans les
   reglages, j'aimerais bien l'affichage sur le cote et non pas en ligne comme
   les onglets secondaires ». Deux barres horizontales l'une sous l'autre — la
   navigation entre ECRANS puis celle entre SECTIONS — se lisaient comme un
   seul niveau ; les mettre a la perpendiculaire dit qu'elles ne font pas la
   meme chose.

   /!\ RIEN NE CHANGE DANS LE HTML. La barre `.seg--onglets` et le `<form>`
   sont deja freres dans `.wrap--onglets` : il suffit de poser une grille. On
   ne touche donc ni au contrat `data-onglets` d'`onglets-formulaire.js`, ni a
   l'ordre des panneaux (dont depend l'affichage d'une cle regeneree), ni a
   `visite.py`.

   /!\ L'en-tete et la navigation entre ECRANS restent PLEINE LARGEUR : elles
   parlent du trader, pas de ses reglages.

   /!\ **LA REGLE VAUT POUR TOUTE PAGE A ONGLETS** depuis le 2026-09-09 (demande
   du fondateur : « fais pareil pour la page mes gains »). Elle ne visait que
   `.wrap--reglages.wrap--onglets`, donc les reglages d'un trader et « Mon
   profil » ; « Mes gains » portait la meme barre et le meme contrat, sans la
   premiere classe, et gardait ses onglets en ligne. Une regle qui decrit une
   MISE EN PAGE ne doit pas dependre d'une classe qui parle de REGLAGES.
   ============================================================ */
@media (min-width: 900px) {
  .wrap--onglets:not(.wrap--onglets-en-ligne) {
    display: grid;
    grid-template-columns: 210px minmax(0, 1fr);
    column-gap: 32px;
    align-items: start;
  }
  /* Tout ce qui n'est ni la barre ni un PANNEAU tient la largeur entiere :
     l'en-tete, la barre des quatre ecrans, les bandeaux d'arrivee.

     /!\ **`[data-section]` AUTANT QUE `form`** (2026-09-09). La regle ne nommait
     que `form`, parce que les reglages d'un trader posent leurs panneaux DEDANS.
     « Mon profil » emploie la meme barre et le meme contrat `data-onglets`, mais
     ses panneaux sont des freres directs : ils retombaient donc dans « pleine
     largeur » et s'empilaient SOUS la navigation, colonne de gauche vide en
     face. Vu a l'ecran par le fondateur, jamais par un test — aucun ne regarde
     une mise en page.

     La regle dit desormais ce qu'elle veut dire : **un panneau va a cote de la
     barre**, quelle que soit la balise qui le porte. */
  .wrap--onglets:not(.wrap--onglets-en-ligne) > *:not(.seg--onglets):not(form):not([data-section]) {
    grid-column: 1 / -1;
  }
  .wrap--onglets:not(.wrap--onglets-en-ligne) > .seg--onglets {
    /* /!\ On ANNULE `inline-flex` : la barre horizontale epousait ses onglets,
       celle-ci doit tenir sa colonne. */
    display: flex;
    flex-direction: column;
    align-items: stretch;
    overflow-x: visible;
    position: sticky;
    top: 72px;
    margin: 0;
    gap: 2px;
    /* /!\ ET ON ANNULE LA PILULE. `.seg` porte `border-radius: pill` avec un
       fond et un liseré : etire en colonne, ce rayon dessine un enorme DISQUE
       pale derriere les entrees. Vu a l'ecran le 2026-09-07 — un test ne l'aurait
       jamais montre. Une navigation laterale n'est pas un selecteur de segments :
       elle n'a ni fond ni contour. */
    background: none;
    border: 0;
    border-radius: 0;
    padding: 0;
  }
  .wrap--onglets:not(.wrap--onglets-en-ligne) > .seg--onglets > button {
    text-align: left;
    border-radius: var(--radius-sm, 8px);
    border-left: 2px solid transparent;
    padding: 8px 12px;
  }
  /* `aria-selected` est ce que pose `onglets-formulaire.js` — le seul marqueur
     de l'onglet ouvert, et il le pose deja pour les lecteurs d'ecran. */
  /* Même accent que la barre du dessus : la section ouverte porte le violet,
     pas seulement son filet de gauche (2026-09-11). */
  .wrap--onglets:not(.wrap--onglets-en-ligne) > .seg--onglets > button[aria-selected="true"] {
    border-left-color: var(--accent);
    color: var(--accent);
    font-weight: 600;
  }
  .wrap--onglets:not(.wrap--onglets-en-ligne) > form,
  .wrap--onglets:not(.wrap--onglets-en-ligne) > [data-section] { grid-column: 2; }
}

/* ============================================================
   .liste-consignes — ce que le client demande a son trader (2026-09-10, lot L4)
   ------------------------------------------------------------
   /!\ UNE LIGNE PAR CONSIGNE, separees par un filet — pas des cartes empilees.
   Une carte par consigne ferait dix objets a l'ecran la ou le client lit une
   liste ; la charte reserve le cadre a ce qui doit se detacher.

   /!\ UNE CONSIGNE ETEINTE S'ESTOMPE, elle ne disparait pas : elle ne part plus
   au modele et ne coute plus rien, mais le client la garde sous le coude. La
   cacher lui ferait croire qu'il l'a perdue.
   ============================================================ */
/* /!\ **UN CADRE AUTOUR DE LA LISTE, PAS AUTOUR DE CHAQUE CONSIGNE** (2026-09-13,
   le fondateur devant l'ecran : « quand il y a plusieurs consignes ce n'est pas evident
   de les distinguer »). Les lignes flottaient sur le fond de page, separees par un
   filet de un pixel : a cinq consignes, un titre et son paragraphe gris ressemblaient
   au paragraphe suivant, et on lisait un mur de texte.
   /!\ Le commentaire d'origine reste VRAI et ne change pas : une carte PAR consigne
   ferait dix objets a l'ecran la ou le client lit une liste. Ce qu'on ajoute, c'est le
   cadre de LA LISTE — un seul objet, celui qu'on vient lire — exactement comme les
   elements de « Ce qu'il reçoit » et les paliers. */
.liste-consignes {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.liste-consignes .consigne {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px; align-items: start;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
/* /!\ Le survol souligne LA LIGNE SURVOLEE, et c'est ce qui rend cinq consignes
   distinctes sans ajouter un trait de plus : on voit ou l'on est. */
.liste-consignes .consigne:hover { background: rgba(255, 255, 255, .02); }
.liste-consignes .consigne:last-child { border-bottom: 0; }
.liste-consignes .consigne--off .consigne-titre,
.liste-consignes .consigne--off .consigne-texte { color: var(--disabled-foreground); }
/* /!\ **`flex-wrap` ET `min-width: 0` : SANS EUX LA LIGNE POUSSE LA PAGE ENTIÈRE.**
   Cette rangée porte le titre d'une consigne ET son badge (« Éteinte », ou la date de
   fin). Un `flex` sans enroulement ne rétrécit pas : sur un téléphone de 390 px, un
   titre un peu long suivi de « Jusqu'au 30/09/2026 » faisait déborder toute la page
   de 37 px, en défilement horizontal. Le défaut dormait depuis le lot L4 — aucune
   consigne n'avait d'échéance, faute de pouvoir en poser une simplement — et s'est
   réveillé le 2026-09-20, quand la fenêtre de modification l'a permis.
   `min-width: 0` sur le titre l'autorise à se couper ; `flex-wrap` envoie le badge
   à la ligne quand il ne reste vraiment plus la place. */
.consigne-titre {
  display: flex; align-items: center; flex-wrap: wrap; gap: 9px;
  min-width: 0;
  font-weight: 500; font-size: var(--text-body);
}
.consigne-titre > .badge { flex: none; }
/* /!\ `pre-line` (2026-09-21) : une consigne faite d'une LISTE (« - un point » par
   ligne, geste offert depuis une conversation) se relit en lignes, pas en un bloc. */
.consigne-texte, .t-consigne-texte { white-space: pre-line; }
.consigne-texte {
  margin: 4px 0 0; color: var(--muted-foreground);
  font-size: var(--text-body-sm); line-height: var(--leading-relaxed);
  max-width: 74ch;
}
.consigne-meta {
  margin: 7px 0 0; color: var(--disabled-foreground);
  font-size: var(--text-caption); font-family: var(--font-code);
}
.consigne-actions { display: flex; align-items: center; gap: 7px; }

/* LES VERSIONS D'UNE CONSIGNE (08/10/2026, `partials/consigne_versions.html`).
   /!\ « Ses versions » est un BOUTON (il ouvre une fenetre) habille en lien : il vit
   dans la ligne de compte, en petit, et ne doit pas peser comme un geste. */
.cv-lien {
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: var(--accent); text-decoration: underline;
  text-underline-offset: 2px;
}
.cv-lien:hover { color: var(--foreground); }
/* /!\ Plus haute que l'ecran, la fenetre defile EN ENTIER (titre compris) : une
   liste qui defile seule dans une fenetre coupee cachait son titre. */
dialog.verre.cv-fenetre {
  width: min(620px, calc(100vw - 32px));
  max-height: calc(var(--fenetre) - 32px); overflow-y: auto;
}
.cv-legende { margin: 0 0 14px; color: var(--muted-foreground); }
.cv-liste {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 10px;
}
.cv-version {
  padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.cv-version--courante { border-color: var(--accent); }
.cv-tete { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.cv-texte { max-width: none; }
.cv-pied {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 8px; margin-top: 8px;
}
.cv-pied .consigne-meta { margin: 0; }
.cv-fenetre ins, .cv-legende ins {
  text-decoration: underline; text-decoration-color: var(--success);
  text-underline-offset: 2px; color: var(--success);
}
.cv-fenetre del, .cv-legende del { color: var(--error); opacity: 0.85; }
.cv-fenetre del + ins { margin-left: 4px; }

/* LE RANG, ECRIT (2026-09-13). /!\ C'est lui qui dit que l'ordre veut dire quelque
   chose : « celle du haut est lue en premier » etait une phrase au-dessus de la liste,
   et rien dans la liste ne la rappelait.
   /!\ Chasse FIXE : sans elle, passer de 9 a 10 decale toute la ligne. Meme regle que
   `.elem-rang`, qui fait le meme travail deux ecrans plus loin. */
.consigne-rang {
  min-width: 20px; padding-top: 1px; text-align: center;
  font-size: var(--text-sm); font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

/* Le geste d'ajout, AU BOUT de la liste et DANS son cadre. /!\ Aligne sur le texte des
   lignes, pas sur leur poignee : c'est une action, pas une sixieme consigne.
   /!\ **IL PORTE `.ajouter-ligne`**, la classe du « + Ajouter » des donnees envoyees au
   trader (2026-09-14) : le meme geste, au bout de deux listes, ne peut pas avoir deux
   apparences. Ce qui reste ici est sa PLACE, pas son allure. */
.consigne-ajouter { margin: 12px 0 14px 16px; align-self: flex-start; }

/* Ce que le filtre laisse quand il ne laisse rien. /!\ Dans la liste, pas dans la
   barre : c'est la LISTE qui est vide, et c'est elle qu'on regarde. */
.consigne-aucune {
  margin: 0; padding: 18px 16px;
  color: var(--muted-foreground); font-size: var(--text-body-sm);
}

@media (max-width: 640px) {
  .liste-consignes .consigne { grid-template-columns: minmax(0, 1fr); }
}

/* La POIGNEE de glissement d'une consigne (2026-09-10, lot L5).
   /!\ L'ordre est la PRIORITE DE LECTURE du modele, pas du rangement : c'est la
   seule raison pour laquelle ce geste existe. */
/* /!\ QUATRE COLONNES depuis que le rang est ecrit : poignee, rang, corps, gestes. */
.liste-consignes .consigne { grid-template-columns: auto auto minmax(0, 1fr) auto; }
.consigne-poignee {
  color: var(--disabled-foreground); font-size: var(--text-md); line-height: 1;
  letter-spacing: -2px; padding: 2px 2px 0; cursor: grab; user-select: none;
}
.consigne-poignee:focus-visible {
  outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 3px;
}
.consigne--prise { opacity: .5; }
.consigne--cible { box-shadow: inset 0 2px 0 var(--accent); }

/* /!\ **LA POIGNEE SE MONTRE AU SURVOL DE SA LIGNE** (2026-09-12). Elle etait
   au repos de la meme couleur que le texte desactive, donc invisible sur un
   ecran sombre : le seul reglage vraiment consequent de cette page — l'ordre de
   lecture du modele — etait aussi le seul sans affordance. On ne la fait PAS
   crier au repos (douze poignees accrocheraient l'oeil avant les consignes) ;
   on la reveille quand la souris est sur la ligne, et au clavier. */
.liste-consignes .consigne:hover .consigne-poignee,
.consigne-poignee:focus-visible { color: var(--foreground); }
.consigne-poignee:active { cursor: grabbing; }
/* /!\ LA POIGNEE EST UN BOUTON depuis le 24/09/2026 (le geste est celui de
   `rangees.js`, au pointeur) : un bouton se remet a plat, et `touch-action: none`,
   sinon un glissement au doigt fait defiler la page au lieu de deplacer la ligne.
   Hors de l'ecran Consignes (fenetres du journal, declencheurs), elle se montre
   au survol de SA ligne, comme la. */
button.consigne-poignee {
  background: transparent; border: 0; border-radius: var(--radius-sm);
  font-family: inherit; touch-action: none;
  min-width: 22px; min-height: 26px; display: inline-flex;
  align-items: center; justify-content: center;
}
button.consigne-poignee:hover { background: rgba(255,255,255,0.06); }
[data-glisser-ligne]:hover > .consigne-poignee { color: var(--foreground); }
.liste-consignes .consigne[data-tenue="oui"],
.t-bande-ligne[data-tenue="oui"] {
  cursor: grabbing; position: relative; z-index: 1;
  background: var(--accent-wash);
  box-shadow: 0 18px 40px -14px rgba(0,0,0,0.7);
}

/* Le MOT qui dit que l'ordre se glisse, au-dessus de la liste.
   /!\ Il nomme la CONSEQUENCE (« celle du haut est lue en premier »), pas le
   geste : un client qui sait glisser mais ignore ce que l'ordre change ne s'en
   sert pas. */
.consignes-ordre-mot {
  display: flex; align-items: center; gap: 7px;
  margin: 0 0 6px; padding: 0 2px;
  color: var(--muted-foreground); font-size: var(--text-body-sm);
}
.consignes-ordre-glyphe {
  color: var(--disabled-foreground); letter-spacing: -2px; line-height: 1;
  font-size: var(--text-md);
}

/* /!\ « Supprimer » est le SEUL geste irreversible de cette liste : il rougit au
   survol, jamais au repos. Douze corbeilles rouges en permanence feraient une
   liste qui a l'air en panne. */
.consigne-oter:hover { color: var(--danger, #b4451f); }

@media (max-width: 640px) {
  .liste-consignes .consigne { grid-template-columns: auto auto minmax(0, 1fr); }
  /* /!\ Au doigt il n'y a pas de survol : la poignee reste donc lisible. */
  .consigne-poignee { color: var(--muted-foreground); }
  /* /!\ **LA RANGÉE D'ACTIONS PREND LA LIGNE ENTIÈRE, SINON ELLE GONFLE UNE
     COLONNE.** Sous 640 px la grille passe à trois colonnes pour quatre enfants :
     les actions retombent donc en ligne 2, COLONNE 1 — celle de la poignée, qui est
     en `auto`. Une colonne `auto` prend la largeur de son contenu le plus large :
     la poignée, large de 12 px, se retrouvait à 173 px, et le texte de la consigne
     était écrasé à 99 px sur un écran de 390. Avec une quatrième icône (le crayon,
     2026-09-20), la ligne a fini par pousser la page entière en défilement
     horizontal. `1 / -1` les sort du calcul des colonnes. */
  .liste-consignes .consigne-actions { grid-column: 1 / -1; justify-content: flex-end; }
}

/* L'INTERTITRE d'une carte (2026-09-12) — un second sujet DANS le meme cadre.
   /!\ Ne : il separe sans fabriquer un cadre de plus. Pose quand le modele et la
   cle Anthropic ont fusionne : deux cartes cote a cote repondaient deja a la meme
   question (« qui paie les analyses, et combien »), mais avec deux titres, deux
   paragraphes et deux bordures.
   /!\ Plus petit que `.groupe-titre` et separe par un filet : il doit se lire
   comme une SUITE, jamais comme une carte voisine. */
.groupe-intertitre {
  display: block;
  margin: 20px 0 10px; padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: var(--text-body); font-weight: 600; color: var(--foreground);
}
.groupe-intertitre-quoi {
  display: block; margin-top: 3px;
  font-size: var(--text-body-sm); font-weight: 400; color: var(--muted-foreground);
}

/* ============================================================
   .elem-* — UNE DONNEE, UNE LIGNE (2026-09-13)
   ------------------------------------------------------------
   Maquette validee ecran par ecran avec le fondateur avant d'etre codee
   (`static/maquettes/reglages-ce-qu-il-regarde.html`).

   /!\ **CE QUE CA REMPLACE : UNE GRILLE DE CARTES ET UNE FENETRE.** Une carte
   montrait un resume qu'on ne pouvait que LIRE, et il fallait ouvrir un modal
   pour changer un chiffre. Ici on lit et on regle au meme endroit.

   /!\ **L'ORDRE SE VOIT, et c'est pour ca que c'est une liste.** Une grille de
   trois colonnes se lit de gauche a droite puis on retourne a la ligne : personne
   n'y voit un classement, et c'est pour ca qu'il fallait deux petites fleches sur
   chaque carte pour le manipuler. En liste, le glisser-deposer devient le geste
   evident.
   ============================================================ */
.elem-cat {
  font-size: var(--text-base); font-weight: 600; color: var(--foreground);
  margin: 18px 0 4px;
}
.elem-cat-note {
  margin: 0 0 8px; max-width: 72ch;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.elem-liste {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.elem-ligne {
  display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 12px; align-items: center;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
}
.elem-ligne:last-child { border-bottom: 0; }
.elem-ligne:hover { background: rgba(255, 255, 255, .02); }

/* /!\ La poignee se reveille au survol de SA ligne : dix poignees qui crient en
   permanence accrochent l'oeil avant le contenu. */
.elem-poignee {
  color: var(--disabled-foreground); font-size: var(--text-md); line-height: 1;
  letter-spacing: -2px; padding: 2px; cursor: grab; user-select: none;
}
.elem-ligne:hover .elem-poignee,
.elem-poignee:focus-visible { color: var(--foreground); }
.elem-poignee:active { cursor: grabbing; }
.elem-poignee:focus-visible {
  outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 3px;
}

/* Le RANG, ecrit. C'est lui qui dit que l'ordre veut dire quelque chose.
   /!\ Chasse FIXE : sans elle, passer de 9 a 10 decale toute la ligne. */
.elem-rang {
  min-width: 20px; text-align: center;
  font-size: var(--text-sm); font-variant-numeric: tabular-nums;
  color: var(--muted-foreground);
}

.elem-corps { min-width: 0; }
/* Le nom reste un MENU (c'est lui qui choisit le type), mais il a l'allure d'un
   titre : sans cela « + Ajouter » ne saurait poser que des bougies. */
.elem-nom-liste > select.saisie,
.elem-nom-liste > .liste-bouton {
  border: 0; background: none; box-shadow: none;
  padding: 0 20px 0 0; height: auto; min-height: 0;
  font-size: var(--text-base); font-weight: 600; color: var(--foreground);
}
.elem-nom-liste::after { right: 6px; }
.elem-nom-liste { display: inline-block; width: auto; }
/* /!\ **LE PANNEAU NE PEUT PAS SUIVRE LA LARGEUR D'UN NOM** (signale a l'ecran par le
   fondateur le 2026-09-13 : « on ne voit pas bien les indicateurs lorsqu'on clique sur
   la fleche »). Le declencheur fait la largeur du mot affiche — « Bougies », 90 px — et
   `.liste-panneau` se cale par defaut sur `left: 0; right: 0`. Le menu du catalogue s'y
   pliait donc en colonne : « PRICE ACTION » sur deux lignes, « Volume climax » sur deux,
   et les indicateurs rejetes tout en bas d'une gouttiere qu'il fallait faire defiler
   pour les trouver. Il repart du bord gauche et prend la place qu'il lui faut - le meme
   remede que `.liste--compact`, pour la meme cause.
   /!\ Et il respire plus haut que les 300 px du defaut : quinze elements en trois
   familles tiennent alors presque d'un seul regard, ce qui est tout l'interet d'un menu
   range par familles. Le script le retourne vers le haut si la place manque en bas. */
.elem-nom-liste > .liste-panneau {
  right: auto; min-width: 100%; width: max-content;
  max-width: min(320px, 80vw);
  /* /!\ `--fenetre`, JAMAIS `76vh` : le corps porte un `zoom`, donc `100vh` rend
     l'echelle en plus et le panneau deborderait du bas de l'ecran. Un test l'epingle,
     et il a mordu sur cette ligne meme. */
  max-height: min(calc(var(--fenetre) * 0.76), 700px);
}

.elem-phrase {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 4px;
  font-size: var(--text-sm); color: var(--muted-foreground);
}
/* /!\ `.mini` est en COLONNE partout ailleurs (libelle au-dessus du champ), et
   c'est juste dans un formulaire. Dans une phrase, la colonne casse la ligne en
   deux et le libelle se detache de sa valeur : « Sensibilité » d'un cote, « 2 » de
   l'autre. On la remet en ligne ICI et nulle part ailleurs. */
.elem-phrase > span[data-si-bougies],
.elem-phrase .mini {
  display: inline-flex; flex-direction: row; align-items: center; gap: 6px;
}
.elem-phrase .mini[hidden] { display: none; }
/* /!\ `::first-letter`, et surtout pas `text-transform: lowercase` sur tout le
   libelle : les reglages du catalogue s'appellent « Periode », « Sensibilite »,
   mais rien n'interdit qu'un type neuf en nomme un « ATR » ou « MACD ». Abaisser
   la premiere lettre suffit a fondre le libelle dans la phrase sans toucher au
   reste du mot. */
.elem-phrase .mini::first-letter { text-transform: lowercase; }
/* Le point mediane entre deux reglages, pose par la feuille : l'ecrire dans le
   gabarit demanderait de savoir, en Jinja, lequel est le dernier VISIBLE — or
   c'est le type choisi qui en cache la moitie, cote navigateur. */
.elem-phrase .mini + .mini::before,
.elem-phrase > span[data-si-bougies] + .mini::before {
  content: "·"; color: var(--disabled-foreground); margin-right: 4px;
}
.elem-sep { color: var(--disabled-foreground); padding: 0 2px; }
/* Le mot du client pour une echelle, pose apres l'unite : « bougies de 10080 min
   · hebdomadaire ». Il n'apparait que lorsqu'il apporte quelque chose, donc la regle
   ne peut pas laisser un point median orphelin - c'est le gabarit qui porte le point,
   avec le mot ou rien. */
.elem-mot { color: var(--muted-foreground); white-space: nowrap; }
.elem-mot:empty { display: none; }

/* /!\ **LES FLECHES DU CHAMP NOMBRE MANGENT LE CHIFFRE.** Un champ de 55 px garde
   ses deux petites fleches natives et ne laisse plus la place a deux chiffres :
   « 15 » s'affichait « 1[ ». On les retire — personne ne regle une periode en
   cliquant dix-huit fois — et on rend la largeur au texte. */
.elem-phrase .saisie { padding: 6px 9px; font-size: var(--text-sm); text-align: center; }
.elem-phrase input[type="number"] {
  width: 62px; appearance: textfield; -moz-appearance: textfield;
}
.elem-phrase input[type="number"]::-webkit-outer-spin-button,
.elem-phrase input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.elem-phrase .liste { display: inline-block; width: auto; min-width: 96px; }
.elem-phrase .liste > select.saisie { text-align: left; }
.elem-liste-detail { min-width: 168px; }
.elem-liste-ancrage { min-width: 170px; }

/* La seance sur mesure : SOUS la ligne, jamais dedans. Quatre champs de plus dans
   la phrase la feraient deborder sur trois lignes pour un cas rare. */
.elem-sur-mesure {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.elem-sur-mesure[hidden] { display: none; }


.elem-prise { opacity: .4; }
.elem-cible { box-shadow: inset 0 2px 0 var(--accent); }
/* Le geste d'ajout, AU BOUT de la liste et DANS son cadre (2026-09-14).
   /!\ `align-self` : `.elem-liste` est une colonne flex, et sans lui le bouton
   s'etirerait sur toute la largeur du cadre — il se lirait comme une ligne de plus,
   pas comme une action.
   /!\ Aligne sur le TEXTE des lignes (16 px), pas sur leur poignee : meme retrait que
   « + Ajouter une consigne », qui a servi de modele parce que le fondateur l'a vu
   d'abord la. */
.elem-ajouter { margin: 12px 0 14px 16px; align-self: flex-start; }

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

/* ============================================================
   .section-titre — LE TITRE D'UN ONGLET DE REGLAGES (2026-09-08)
   ------------------------------------------------------------
   /!\ **CETTE REGLE A VECU DEUX MOIS RECOPIEE DANS DEUX GABARITS**, et elle est
   remontee ici le 2026-09-13, sur son propre chantier. Elle etait dans
   l'`extra_css` de `user_profile.html` ET dans celui de `user_trader_edit.html`,
   zero fois dans la charte. La copie du profil portait meme sa propre note :
   « A FUSIONNER DANS charte.css DES QUE CE FICHIER EST LIBRE » — le chantier du
   profil avait recopie plutot que d'entrer dans ce fichier pendant qu'un autre y
   travaillait, ce qui etait le bon reflexe sur le moment et une dette ensuite.

   /!\ **LES DEUX COPIES ETAIENT IDENTIQUES AU CARACTERE PRES**, verifie avant de
   fusionner : aucun ecran ne change d'apparence en remontant. Si elles avaient
   diverge, le choix se serait dit au fondateur AVANT — c'est le seul vrai risque
   de ce genre de demenagement, et il ne se decide pas en silence.

   /!\ **CE QU'ELLE COMMANDE MAINTENANT : DEUX ECRANS.** Les six onglets d'un
   trader et les cinq de « Mon profil », qui a repris le meme motif le 2026-09-13.
   Une retouche ici se regarde sur les DEUX, pas seulement sur celui qu'on tient :
   le profil a perdu 115 px de largeur de champ sans qu'une ligne de son gabarit
   bouge, et aucun test ne l'a vu.

   ── Ce que les deux copies disaient, et qui reste vrai ───────────────────────

   LE TITRE D'UNE SECTION EMPILEE. Un onglet peut montrer plusieurs
   `[data-section]` a la suite ; sans titre, le client voit des blocs anonymes et
   ne sait plus lequel repond a sa question. Le bouton d'onglet FAISAIT ce
   travail : il faut le refaire a l'interieur.

   /!\ **L'EN-TETE SE FERME, IL NE S'OUVRE PLUS** (2026-09-13, proposition validee
   par le fondateur : « il faut distinguer le titre principal de l'onglet et la
   courte description de ce qu'il y a dans les sous-parties »). Le defaut etait
   mesurable : 19 px pour le titre d'onglet, 17 px pour celui d'une sous-partie,
   meme graisse, meme couleur, et deux phrases grises identiques dessous. Rien ne
   disait lequel CONTENAIT l'autre. Le trait passe donc DESSOUS : c'est lui qui dit
   « ce qui suit est dedans ». Au-dessus, il ne disait que « quelque chose se
   termine » — vrai, mais ce n'est pas la question qu'on se pose en arrivant.
   /!\ 19 px (`--text-lg`) DEPUIS LE 24/09/2026 (echelle des polices) : un titre de
   section, sous le `h1` de la page (22). A 22, il egalait le titre de la page.
   ============================================================ */
.section-titre {
  font-family: var(--font-sans); font-size: var(--text-lg); font-weight: 600;
  letter-spacing: -.01em;
  color: var(--foreground); margin: 46px 0 26px;
  padding-bottom: 18px; border-bottom: 1px solid var(--border);
}
/* La PREMIERE section d'un onglet n'a rien au-dessus d'elle : pas d'air en trop.
   /!\ **LES SIX ONGLETS D'UN TRADER LE PORTENT DEPUIS LE 2026-09-13**, et c'est une
   correction, pas un elargissement : trois titres sur six ne l'avaient jamais recu,
   donc leur titre demarrait 42 px plus bas que celui de « Le trader ». Pointe a
   l'ecran par le fondateur — « les titres des onglets ne sont pas tous a la meme
   hauteur, aligne-les sur Le trader » — et le defaut se voyait en passant d'un onglet
   a l'autre : le titre sautait.
   /!\ **NE PAS FONDRE CETTE REGLE DANS `.section-titre` en croyant simplifier.**
   Aucune section EMPILEE ne porte plus de titre aujourd'hui, mais la marge de 46 px
   existe pour elles : le jour ou un onglet en remontre deux a la suite, c'est elle qui
   les separe, et la fondre reviendrait a coller le second au premier. */
.section-titre--tete { margin-top: 4px; }
/* La phrase de l'onglet est a la taille du TEXTE COURANT : c'est elle qui dit ce qu'on
   vient faire ici, et elle etait ecrite en 13 px, plus petite que la moitie des
   libelles qu'elle annonce.
   /!\ Elle vit DANS le titre : posee dehors, elle se retrouve de l'autre cote du
   filet, c'est-a-dire dans le contenu qu'elle est censee annoncer. */
.section-titre .s-quoi {
  display: block; margin-top: 6px; max-width: 74ch;
  font-size: var(--text-base); font-weight: 400; line-height: 1.6;
  color: var(--muted-foreground);
}

/* ══ L'ECART ENTRE DEUX BLOCS D'UNE SECTION : **UNE SEULE VALEUR** ══════════
   Chantier C13 lot 3, 2026-09-10. Demande du fondateur : « les espaces entre les
   encadres ne sont pas partout pareils ; il y a des encadres qui sont colles ».

   Mesure a l'ecran, c'etait pire que « pas pareils ». Les ecarts entre blocs de
   premier niveau valaient, dans l'ordre ou on les lit :

     onglet « Le trader »             15  13  20  29
     onglet « Sa strategie »          15  18  20  18  18
     onglet « Ce qu'on lui demande »   0   0     <- les encadres COLLES

   Six valeurs pour un seul rythme. La cause n'etait pas dans la charte : elle etait
   dans quatorze marges ecrites en dur dans un gabarit, chacune juste pour son bloc,
   aucune d'accord avec sa voisine.

   /!\ **UNE SEULE DIRECTION.** Les marges basses des enfants directs sont remises a
   zero, et c'est la marge HAUTE du suivant qui porte tout l'ecart. Deux directions, et
   l'ecart depend de qui est au-dessus : c'est exactement ainsi que 13, 15, 18, 20 et 29
   ont pu coexister. C'est aussi ce qui rend l'ecart entre DEUX SECTIONS constant : sans
   marge basse a la traine, la derniere carte d'une section ne pousse plus le titre de
   la suivante (62 px mesures contre 44 ailleurs).

   /!\ Elle ne vise QUE les enfants DIRECTS d'une `[data-section]` : ce qui vit a
   l'interieur d'une carte garde son propre rythme.

   /!\ Une marge ECRITE EN DUR gagnerait toujours sur cette regle. Elles ont donc ete
   retirees en meme temps, pas recouvertes — une regle qu'on peut contredire au cas par
   cas n'est pas une regle, c'est un defaut.

   /!\ **ELLE ETAIT RECOPIEE, ELLE AUSSI** (2026-09-13) : les deux gabarits la
   portaient, a l'identique. Elle remonte avec `.section-titre`, et pour la meme raison
   — les separer aurait laisse la moitie du rythme d'un onglet dans un gabarit. */
.wrap--reglages.wrap--onglets .encadre[data-section] > * { margin-bottom: 0; }
.wrap--reglages.wrap--onglets .encadre[data-section] > * + * { margin-top: 18px; }

/* ============================================================
   .reglage-* — UN REGLAGE, UNE LIGNE (2026-09-12)
   ------------------------------------------------------------
   Valide en maquette par le fondateur avant d'etre code
   (`static/maquettes/reglages-le-trader.html`), sur sa demande : « on ne code
   plus rien tant qu'on n'a pas valide le design final, sous-onglet par
   sous-onglet ».

   /!\ **CE QUE CA REMPLACE : SIX NIVEAUX AVANT D'ATTEINDRE UN REGLAGE** — titre
   de page, titre de section, titre de carte, sous-titre de carte, etiquette en
   capitales espacees, puis le champ. Ici il y en a DEUX : le nom du reglage, et
   ce qu'il fait. Mesure sur « Le trader » : 1 553 px avant, ~1 050 apres, sans
   qu'un seul reglage disparaisse.

   /!\ **LE NOM D'UN REGLAGE EST DU TEXTE NORMAL**, pas des capitales espacees.
   Celles-ci ralentissent la lecture et faisaient tenir « Gravite de l'annonce a
   partir de laquelle je m'ecarte » sur deux lignes au-dessus d'un champ qui en
   fait une.

   /!\ Le titre de groupe vit DEHORS du cadre : il l'annonce. Un titre dans la
   boite qu'il nomme ajoute une bordure pour rien.
   ============================================================ */
.reglage-groupe { margin: 0 0 56px; }
.reglage-groupe:last-child { margin-bottom: 0; }
/* /!\ **LES REPLIS APPARTIENNENT A LA PARTIE QU'ILS PROLONGENT** (fondateur,
   2026-09-26, sur la page d'une analyse : « l'espace avant les nouveaux titres
   n'est pas assez »). « Le detail du trade », « Ce qui etait en vigueur »… suivent
   la partie 3 en freres : la marge de 56 px les en detachait, et la partie 4, qui
   les suit, n'avait AUCUNE marge au-dessus. On colle les replis a leur partie, et
   la partie suivante reprend son ecart. */
.reglage-groupe:has(+ .depli) { margin-bottom: 12px; }
.depli + .reglage-groupe { margin-top: 56px; }
/* /!\ **UNE SOUS-PARTIE N'EST PAS UN TITRE PLUS PETIT** (2026-09-13, proposition 2
   validee par le fondateur). Elle etait un `h2` de 17 px face a un titre d'onglet de
   19 : deux pixels d'ecart pour dire lequel contient l'autre. Elle devient une
   ETIQUETTE — petites capitales espacees, couleur de l'application — et la confusion
   cesse parce que ce ne sont plus les memes objets typographiques.
   /!\ C'est le vocabulaire que la charte emploie deja pour les intertitres d'un
   formulaire (`.etiquette`, `.rgroupe-nom`) : on n'en invente pas un troisieme. */
/* /!\ **L'ETIQUETTE EST REDEVENUE UN VRAI TITRE** (2026-09-26, option B choisie par
   le fondateur sur maquette : « les espaces entre les parties ne sont pas assez
   grands »). Les petites capitales de 12 px se perdaient en faisant defiler. Ce qui
   la distingue du titre d'onglet n'est plus la casse mais le NUMERO PLEIN, et les
   56 px qui separent deux parties. */
.reglage-groupe > h2 {
  display: flex; align-items: center; gap: 14px;
  font-family: var(--font-sans); font-size: var(--text-lg); font-weight: 600;
  letter-spacing: var(--tracking-tight); text-transform: none;
  color: var(--foreground); line-height: 1.3;
  margin: 0 0 6px;
}
/* /!\ **LE NUMERO VIENT DE LA POSITION, JAMAIS D'UNE SAISIE** (2026-09-13, demande du
   fondateur : « mets le numero aussi »). C'est un compteur CSS, remis a zero par la
   section : une sous-partie ajoutee, deplacee ou retiree renumerote tout le monde sans
   qu'une ligne de gabarit change. Un chiffre ecrit a la main devient faux au premier
   remaniement, et personne ne s'en apercoit — c'est deja la regle des rangs de la liste
   des elements, et elle vaut ici pour la meme raison.
   /!\ Le compteur se remet a zero sur `[data-section]`, pas sur l'onglet : un onglet
   peut empiler plusieurs sections, et chacune est une question a part. « Son identite »
   est la premiere de « Le trader », pas la troisieme de la page. */
[data-section] { counter-reset: sous-partie; }
/* /!\ **UNE SECTION QUI CONTINUE LA PRECEDENTE NE REMET RIEN A ZERO** (2026-09-13).
   Un onglet peut empiler plusieurs `[data-section]` — « Combien il risque » en porte
   deux, `s-limites` et `s-gestion` — et elles gardent leurs identifiants un par un
   parce que les guides les visent. Quand elles ne sont plus separees par un titre de
   section, le client lit UN ecran : la numerotation repartant a 1 en plein milieu se
   lisait comme un defaut. */
[data-section][data-suite] { counter-reset: none; }
.reglage-groupe > h2::before {
  counter-increment: sous-partie;
  content: counter(sous-partie);
  flex: 0 0 auto;
  width: 32px; height: 32px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--card);
  font-size: var(--text-base); font-weight: 600; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.reglage-groupe > p {
  margin: 0 0 14px; color: var(--muted-foreground);
  font-size: var(--text-body-sm); max-width: 70ch;
}
/* La phrase d'introduction s'aligne sous le TEXTE du titre, pas sous son numero :
   32 px de pastille + 14 d'ecart. Sur telephone, elle reprend toute la largeur. */
.reglage-groupe > h2 + p { margin-left: 46px; }
@media (max-width: 640px) {
  .reglage-groupe { margin-bottom: 40px; }
  .depli + .reglage-groupe { margin-top: 40px; }
  .reglage-groupe > h2 + p { margin-left: 0; }
}
/* /!\ **SURTOUT PAS `overflow: hidden` ICI.** Il y etait, pour que les filets des
   rangees s'arretent proprement dans les coins arrondis — et il COUPAIT le panneau
   de la liste deroulante, qui se deplie en `position: absolute` hors du cadre.
   Signale a l'ecran par le fondateur le 2026-09-13 : « l'affichage est coupe ».
   Le fond du cadre est deja borne par son propre `border-radius` ; les filets, eux,
   n'atteignent jamais les coins puisque la derniere rangee n'en porte pas. */
.reglage-lignes, .bloc-plat {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
/* /!\ **LE MEME CADRE, POUR UNE SOUS-PARTIE QUI NE PORTE PAS DE REGLAGES**
   (2026-09-14, refonte du detail d'un trade, validee sur maquette). Le motif
   « pastille chiffree + etiquette + une phrase + un cadre plat » sert desormais un
   ecran en LECTURE SEULE : le dessin d'un trade, le raisonnement du modele, le texte
   envoye au modele. Le cadre est donc DECLARE AVEC `.reglage-lignes`, jamais recopie
   sous d'autres valeurs — deux cadres censes etre identiques qui derivent d'un pixel
   sont le defaut que cette charte passe son temps a rattraper.
   /!\ Il n'herite RIEN d'autre : `.reglage-ligne` (grille a deux colonnes, filets
   entre rangees) reste propre aux reglages. */
.bloc-plat { padding: 18px 20px; }
/* Un dessin et une frise portent deja leur propre respiration : le cadre ne rajoute
   que ses cotes, sinon le trait du haut flotte a quarante pixels du bord. */
.bloc-plat--dessin { padding: 4px 18px 18px; }
/* /!\ **LA COLONNE DE DROITE PREND LA TAILLE DE CE QU'ELLE PORTE** (2026-09-13,
   demande du fondateur : « la taille des champs je veux que ce soit comme dans
   l'onglet Ce qu'il reçoit »). Elle valait `minmax(0, 320px)` et le champ s'y
   ETIRAIT : le nom d'un trader, une liste de deux options et un nombre a un
   chiffre se lisaient tous les trois dans la meme boite de 320 px.

   /!\ C'est le defaut que cette charte avait DEJA nomme trente lignes plus bas,
   dans la phrase reglable — « un champ a la taille de ce qu'on y tape : 120 dans
   une boite de 240 px se lit comme un oubli » — mais jamais applique a la colonne
   de droite. Les deux moities du meme ecran suivaient deux regles.

   /!\ `auto` et non une largeur fixe : c'est ce qui rend la place a la colonne de
   GAUCHE, ou vit le texte qu'on lit. */
/* /!\ **MAQUETTE, VARIANTE 4 : LA REPONSE SE LIT A COTE DE SA QUESTION.**
   La colonne de gauche valait `1fr` : elle prenait TOUT ce qui restait, et
   poussait le champ au bord droit de la page. A 1150 de cadre, le vide entre
   les deux faisait 300 points et personne ne s'en plaignait ; a 2000, il en
   fait 1 700 et le client qui regle son trader lit « Ne pas entrer trop pres
   du prix » a gauche, traverse l'ecran des yeux, et trouve son interrupteur
   a l'autre bout. Mesure a l'ecran sur `ES matin`, onglet Parametres.

   /!\ **`justify-content: start` PLUTOT QU'UNE TROISIEME COLONNE.** La rangee
   garde ses DEUX pistes — une colonne de plus aurait casse
   `.reglage-ligne.porte-alerte > .champ-alerte`, qui s'etale sur `1 / -1` et
   s'aligne sur la fin du champ. Les pistes se tassent a gauche, et l'espace en
   trop reste a droite, DEHORS de la paire question/reponse.

   /!\ **62ch, la meme borne que `.reglage-quoi`** : la question etait deja
   bornee a sa largeur de lecture, c'est sa COLONNE qui ne l'etait pas. Les
   deux disaient donc deux choses differentes du meme texte.

   /!\ Sous 720 px, la rangee s'empile (voir la requete media plus bas) et ces
   deux lignes ne s'appliquent plus : rien a y redire. */
.reglage-ligne {
  display: grid; grid-template-columns: minmax(0, 62ch) auto;
  justify-content: start;
  gap: 20px; align-items: center;
  padding: 15px 18px;
  border-bottom: 1px solid var(--border);
}
.reglage-ligne:last-child { border-bottom: 0; }
.reglage-nom {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: var(--text-base); font-weight: 500; color: var(--foreground);
}
.reglage-quoi {
  margin: 3px 0 0; max-width: 62ch;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
/* Un AVERTISSEMENT de réglage (le tarif inconnu d'un modèle) : une
   seconde rangée sous la paire question/réponse, sur toute sa largeur. Posé dans
   `.reglage-valeur`, il s'étalait à DROITE du menu, et se lisait comme la valeur
   du réglage. Même piège que `.reglage-ligne.porte-alerte > .champ-alerte`. */
.reglage-ligne > .reglage-avertissement {
  grid-column: 1 / -1; min-width: 0; margin: -8px 0 0; max-width: 80ch;
  font-size: var(--text-body-sm); line-height: 1.5; color: var(--warning-text);
}
.reglage-ligne > .reglage-avertissement[hidden] { display: none; }
/* /!\ EN FLEX, et ce n'est pas cosmetique : `reglages-figeants.js` pose la
   pastille « v » sur le PARENT du champ, donc ici. En bloc, elle tombait SOUS la
   liste deroulante et se lisait comme un message d'erreur. */
/* /!\ `start` ET NON `end` (variante 4) : la piste `auto` fait deja la taille
   du champ, donc les deux se valaient tant que la rangee remplissait la page.
   Depuis que les pistes se tassent a gauche, `end` recollerait le champ au
   bord de sa propre piste et rouvrirait un demi-vide. */
.reglage-valeur {
  justify-self: start; width: auto;
  display: flex; align-items: center; gap: 7px;
}
/* `0 1 auto` et non `1 1 auto` : le champ ne GROSSIT plus pour remplir la
   colonne, il garde sa taille naturelle et se contente de retrecir si la place
   manque. */
.reglage-valeur > .liste,
.reglage-valeur > .saisie { flex: 0 1 auto; min-width: 0; }
/* Le remplissage et la taille de texte de la PHRASE REGLABLE : les deux endroits
   ou l'on saisit une valeur sur cet ecran se lisent desormais pareil. */
.reglage-valeur .saisie { padding: 6px 9px; font-size: var(--text-sm); }
/* Un NOMBRE fait la largeur d'un nombre. /!\ Et ses fleches natives partent avec :
   dans un champ etroit elles mangent le chiffre — « 15 » s'affichait « 1[ ». */
.reglage-valeur .reglage-valeur-nb { width: 92px; text-align: center; }
.reglage-valeur input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.reglage-valeur input[type="number"]::-webkit-outer-spin-button,
.reglage-valeur input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
/* Un NOM se tape : il garde de la place. Une LISTE prend la largeur de son option
   la plus longue, sans deborder la colonne. */
.reglage-valeur .reglage-valeur-txt { width: 240px; }
/* ⚠️ SUR UN TÉLÉPHONE, 240 POINTS SONT UNE LARGEUR ARBITRAIRE (2026-09-20,
   TVNI-93). Mesuré sur `/user/profile` à 390 points : « Prénom », « Nom »,
   « Adresse e-mail » et « Téléphone » faisaient 240 points dans une carte qui
   en offrait 320. Sur un écran large, brider un champ de texte est un choix de
   composition — une ligne de saisie de 900 points ne se lit pas. Sur un
   téléphone il n'y a rien à brider : 80 points de vide à droite de chaque
   champ, et une cible de frappe plus étroite qu'elle ne pourrait l'être, sur
   l'écran où l'on tape le plus mal. Le champ prend donc toute sa carte. */
@media (max-width: 720px) {
  .reglage-valeur .reglage-valeur-txt { width: 100%; }
}
.reglage-valeur > .liste { display: inline-block; width: auto;
                           min-width: 140px; max-width: 300px; }
/* L'UNITE se dit apres le nombre, dans la colonne meme : « 5 min », et non une
   etiquette « (en minutes) » posee au-dessus du champ. */
.reglage-unite { font-size: var(--text-sm); color: var(--muted-foreground);
                 white-space: nowrap; }
/* Un interrupteur ne s'etire pas : il garde sa taille et se cale a droite. */
.reglage-valeur--bascule { width: auto; }

/* L'etat d'un reglage, SUR sa ligne. /!\ Plus de bandeau pleine largeur pose au
   milieu de la page pour dire qu'un champ est vide : on lit le probleme la ou on
   peut le corriger. */
.reglage-manque {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 6px; font-size: var(--text-body-sm); color: #e0a33a;
}
.reglage-manque a { color: inherit; }

/* /!\ `.action` est GRISE au repos et ne se colore qu'au survol : dans une
   phrase, elle ne se lit pas comme un lien. Celui-ci se voit sans la souris. */
.reglage-lien {
  display: inline-block; margin-top: 6px; color: var(--accent);
  font-size: var(--text-body-sm); text-decoration: none;
  border-bottom: 1px solid rgba(180, 168, 255, .35);
}
.reglage-lien:hover { border-bottom-color: var(--accent); }

/* LA PHRASE REGLABLE : sous le nom d'un reglage, une phrase francaise dont les
   nombres sont les champs. On lit et on regle au meme endroit.
   /!\ Elle s'appelait `.reglage-atr` jusqu'au 2026-09-13, parce qu'elle est nee pour
   la rubrique « Volatilite (ATR) ». Elle n'a jamais rien eu d'un reglage d'ATR : c'est
   le motif, et « Quand il decide » l'emprunte pour la cadence, l'ecart tolere et les
   minutes d'annonce. Un nom qui designe son premier usage devient faux au second.
   /!\ **UNE PHRASE, PAS TROIS ETIQUETTES** (fondateur, 2026-09-13, devant l'ecran :
   « les parametres sur 3 lignes ne sont pas esthetique, et pour le pas ca ne parle
   pas »). « Pas : 1 » ne dit rien tant qu'on n'a pas lu l'unite, qui vivait dans une
   autre liste. Ici l'unite suit le nombre, et la ligne se lit d'un trait. */
.reglage-phrase {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
/* /!\ Un champ a la taille de ce qu'on y tape : « 120 » dans une boite de 240 px se
   lit comme un oubli. Et les FLECHES NATIVES mangent le chiffre — « 15 » s'affichait
   « 1[ » — donc on les retire : personne ne regle une periode en cliquant dix-huit
   fois. */
.reglage-phrase .saisie { padding: 6px 9px; font-size: var(--text-sm); }
.reglage-phrase .reglage-phrase-nb { width: 72px; text-align: center; }
.reglage-phrase input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.reglage-phrase input[type="number"]::-webkit-outer-spin-button,
.reglage-phrase input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.reglage-phrase .reglage-phrase-unite { display: inline-block; width: auto; min-width: 96px; }
.reglage-phrase .reglage-phrase-unite > select.saisie { text-align: left; }
.reglage-phrase-sep { color: var(--disabled-foreground); }

/* UN MONTANT, avec son signe DANS le champ (2026-09-13). /!\ L'unite vivait dans
   l'etiquette — « Perte maximale du jour ($) » — a six centimetres du chiffre, et
   l'etiquette a disparu avec les capitales. Le signe se pose donc la ou on tape.
   /!\ `position: absolute` sur le signe, et le remplissage GAUCHE du champ lui fait
   sa place : un signe pose a cote aurait saute a la ligne des que la phrase revient
   a la ligne. */
.reglage-montant { position: relative; display: inline-flex; align-items: center; }
.reglage-montant > .devise {
  position: absolute; left: 10px; z-index: 1;
  font-size: var(--text-sm); color: var(--muted-foreground); pointer-events: none;
}
.reglage-montant > .saisie {
  width: 118px; padding: 6px 9px 6px 22px; text-align: right;
  font-size: var(--text-sm);
}
.reglage-montant > input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.reglage-montant > input[type="number"]::-webkit-outer-spin-button,
.reglage-montant > input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}

/* Les MODELES de paliers, au-dessus de leur liste : « Pour demarrer : » et trois
   boutons qui remplissent la liste d'un clic. */
.reglage-modeles {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
}
.reglage-modeles > span { font-size: var(--text-body-sm); color: var(--muted-foreground); }
/* Le bouton d'ajout vit DANS le cadre de la liste, aligne sur ses lignes. */
.reglage-ajouter { margin: 10px 0 12px 14px; }

/* UNE INDICATION : ce que le client doit SAVOIR avant de regler, pose dans un encadre
   jaune (2026-09-13, demande du fondateur, pointee a l'ecran sur « Ce qu'il reçoit » :
   « tu peux mettre ce texte dans un encadre jaune comme c'est une indication »).

   /!\ **CE N'EST PAS UN AVERTISSEMENT, et les deux ne se confondent pas** : un
   `.drawer-flash-warning` dit qu'un reglage POSE PROBLEME maintenant ; une indication
   dit ce qu'il faut savoir pour regler juste — ici, qu'aucune licence n'est a acheter
   et combien de jours le graphique doit charger. Elle est donc plus discrete : le fond
   et le liseret sont poses a 8 % et 28 %, la ou un avertissement crie.

   /!\ Le liseret a GAUCHE et non un cadre complet : pose au fil d'un panneau de
   reglages, un cadre de plus fabrique l'empilement de boites que la refonte a passe
   deux jours a defaire. */
.reglage-indication {
  margin: 0 0 12px; padding: 10px 12px;
  border-left: 3px solid rgba(251, 191, 36, .55);
  background: rgba(251, 191, 36, .08);
  border-radius: 0 8px 8px 0;
  font-size: var(--text-body-sm); color: var(--muted-foreground); max-width: 78ch;
}
.reglage-indication strong { color: var(--warning-text); font-weight: 600; }
/* Une indication posée juste sous un champ s'en détache (recette du 24/09/2026 :
   collée au champ de date du déclencheur, elle se lisait comme sa bordure). */
.champ + .reglage-indication { margin-top: 12px; }

/* LE RENVOI d'une sous-partie : une precision qui vaut pour TOUTES ses lignes, et
   pour aucune en particulier. /!\ Elle vit SOUS le cadre, sur le fond de page : posee
   dans une ligne, elle se lirait comme la description de ce reglage-la. Precedent
   vecu : « votre montant par trade devient un plafond » etait accrochee a la borne
   haute de l'objectif, et s'affichait donc encore quand cette borne etait ETEINTE —
   on lisait « votre montant devient un plafond » sous un reglage sans plafond. */
/* /!\ **L'ECART EST PASSE DE 10 A 18 PX le 2026-09-13**, pointe a l'ecran par le
   fondateur : « les paragraphes en dessous des conteneurs sont trop proches ». A
   10 px, la phrase touchait presque le bas du cadre et se lisait comme la derniere
   ligne DEDANS — alors qu'elle vaut pour toutes les lignes, et pour aucune en
   particulier. Un renvoi doit se detacher de ce qu'il commente. */
/* /!\ **LE SELECTEUR PORTE `.reglage-groupe >`, ET CE N'EST PAS DU ZELE** (2026-09-14).
   La marge de ce renvoi valait 18 px sur le papier et **0 a l'ecran** : le renvoi est un
   `<p>` enfant direct d'un `.reglage-groupe`, et la regle du paragraphe d'INTRODUCTION
   d'une sous-partie — `.reglage-groupe > p { margin: 0 0 10px }` — est plus specifique
   qu'une simple classe. Elle gagnait donc, et collait la phrase au bas du cadre.
   /!\ Pointe a l'ecran par le fondateur, DEUX FOIS : « les paragraphes en dessous des
   conteneurs sont trop proches », puis « il y a encore des endroits ou l'espace n'est
   pas assez espace ». La premiere correction n'avait rien change parce qu'elle n'a
   jamais pris. Une valeur qu'on ecrit sans la MESURER est une valeur qu'on croit avoir
   posee.
   /!\ 18 px : la valeur unique d'ecart entre deux blocs de cet ecran, celle que
   `[data-section] > * + *` applique partout ailleurs. On n'en invente pas une
   dix-neuvieme. */
.reglage-groupe > .reglage-renvoi,
.reglage-renvoi {
  margin: 18px 0 0; font-size: var(--text-body-sm);
  color: var(--muted-foreground); max-width: 78ch; line-height: 1.55;
}
.reglage-renvoi strong { color: var(--foreground); font-weight: 500; }

/* /!\ **LA SURBRILLANCE DE CELUI QU'ON VIENT CHERCHER** (2026-09-13). Un renvoi qui
   ouvre un autre onglet depose le client au milieu d'une liste de reglages : sans ce
   bref halo, il doit relire les titres pour retrouver celui qu'il venait voir.
   /!\ BREVE, et retiree par le script : un etat qui reste ferait croire a une
   selection, ou pire, a un reglage en defaut. */
.reglage-vise {
  animation: reglage-vise 1.6s ease-out;
  border-radius: var(--radius-lg);
}
@keyframes reglage-vise {
  0%, 40% { background: rgba(180, 168, 255, .16); }
  100% { background: transparent; }
}
/* /!\ Le halo ne clignote pas pour qui a demande moins d'animations : il se pose,
   puis disparait avec la classe. */
@media (prefers-reduced-motion: reduce) {
  .reglage-vise { animation: none; background: rgba(180, 168, 255, .16); }
}

/* Ce qu'un reglage COUTE ou ECONOMISE, sous sa phrase. /!\ Il ne se devine pas, et
   c'est le seul endroit de l'application ou le client peut agir sur sa facture. */
.reglage-chiffre {
  margin: 8px 0 0; font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.reglage-chiffre strong { color: var(--foreground); font-weight: 500; }

/* /!\ **UN TEXTE SOUS UN CADRE S'EN DÉTACHE, PARTOUT** (fondateur, 28/09/2026 :
   « l'espace entre l'encadré et le texte est vraiment trop petit », TROISIÈME
   signalement après ceux du `.reglage-renvoi` ci-dessus). Le défaut ne venait pas
   d'un écran mais d'une règle : `.reglage-groupe > p` (marge du haut à 0) gagnait
   sur la classe du paragraphe, et collait la phrase au bas du cadre (mesuré : 0 px).
   La règle vit donc sur le CADRE, pas sur chaque phrase : quelle que soit sa classe,
   un paragraphe qui suit un `.reglage-lignes` ou un `.bloc-plat` prend 18 px, la
   valeur unique d'écart entre deux blocs de ces écrans.
   /!\ Le préfixe `.reglage-groupe >` est là pour battre `.reglage-groupe > p` ;
   la seconde forme couvre un cadre posé hors d'une sous-partie.
   /!\ `.cg-carte-bascule` (la carte à interrupteur des réglages d'un trader) en
   est : mesurée à 8 px de sa note le même jour. Un cadre de plus s'AJOUTE ICI.
   Une aide sous un CHAMP de saisie, elle, reste serrée : elle fait corps avec lui. */
.reglage-groupe > :is(.reglage-lignes, .bloc-plat, .cg-carte-bascule) + p,
:is(.reglage-lignes, .bloc-plat, .cg-carte-bascule) + p { margin-top: 18px; }

/* UN CRENEAU, UNE LIGNE : « de 09:30 a 12:00 x ». /!\ Les deux champs etaient une
   « paire » anonyme sous une etiquette en capitales, et le bouton de retrait portait
   le mot « Retirer » en toutes lettres — trois fois plus large que le champ qu'il
   suit. Le geste destructeur s'ecrit partout ailleurs dans l'application avec la
   meme croix. */
.creneau-ligne { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.creneau-ligne > .saisie { width: auto; padding: 6px 9px; font-size: var(--text-sm); }

/* LES JOURS EN PASTILLES. /!\ Sept cases a cocher alignees se lisaient comme un
   formulaire ; sept pastilles qu'on allume se lisent comme un calendrier, et l'etat
   se voit de loin — c'est ce qu'on vient regarder. */
.jours-pastilles { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.jour-pastille { position: relative; margin: 0; }
.jour-pastille > input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.jour-pastille > span {
  display: inline-block; min-width: 48px; text-align: center;
  padding: 7px 10px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: transparent;
  font-size: var(--text-sm); color: var(--muted-foreground); cursor: pointer;
}
.jour-pastille > input:checked + span {
  border-color: var(--accent); color: var(--foreground);
  background: rgba(180, 168, 255, .12);
}
.jour-pastille > input:focus-visible + span {
  outline: 2px solid var(--ring); outline-offset: 2px;
}


@media (max-width: 720px) {
  .reglage-ligne { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .reglage-valeur { justify-self: stretch; }
}

/* ============================================================
   .bascule — L'INTERRUPTEUR (2026-09-12)
   ------------------------------------------------------------
   Demande du fondateur, sur l'ecran des consignes : « pour l'activer et
   desactiver, ca sera pas mal de le mettre sous forme de toggle violet ».

   /!\ **IL DIT L'ETAT EN PLUS DE LE CHANGER**, et c'est tout ce qu'il apporte
   sur le bouton « Eteindre » qu'il remplace : ce dernier ne disait l'etat qu'en
   creux, et il fallait lire le mot pour savoir dans quel sens on allait.

   /!\ **C'est un `<button type="submit">`, jamais une case a cocher.** Le
   formulaire part au clic, sans une ligne de script. Une case aurait exige un
   script pour envoyer, donc un interrupteur MORT sur un navigateur qui n'en
   execute pas. `role="switch"` + `aria-checked` portent au lecteur d'ecran ce
   que la forme porte a l'oeil — sans eux, ce bouton n'annoncerait rien du tout.
   ============================================================ */
.bascule {
  appearance: none; border: 0; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; flex: 0 0 auto;
  width: 38px; height: 22px; border-radius: 999px;
  background: var(--muted);
  box-shadow: inset 0 0 0 1px var(--border);
  transition: background .16s ease, box-shadow .16s ease;
}
.bascule-bouton {
  width: 16px; height: 16px; margin-left: 3px; border-radius: 50%;
  background: var(--muted-foreground);
  transition: transform .16s ease, background .16s ease;
}
.bascule[aria-checked="true"] {
  background: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.bascule[aria-checked="true"] .bascule-bouton {
  background: var(--background);
  transform: translateX(16px);
}
.bascule:hover { box-shadow: inset 0 0 0 1px var(--muted-foreground); }
.bascule[aria-checked="true"]:hover {
  background: var(--accent-hi); box-shadow: inset 0 0 0 1px var(--accent-hi);
}
.bascule:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* L'etat « A CHOISIR » (2026-09-17) : ni allume ni eteint.
   /!\ Ne sert qu'a la perte maximale a completer (`perte-a-choisir.js`), ou
   l'etat eteint voudrait dire « aucune limite » d'un reglage jamais regarde.
   Le pointille et le bouton au MILIEU disent « pas encore choisi » ; aucune
   couleur d'accent, pour ne pas le lire comme allume. */
.bascule[aria-checked="mixed"] {
  background: transparent;
  border: 1px dashed var(--muted-foreground);
  box-shadow: none;
}
/* Bordure de 1 px : le bouton part a 4 px, son centre est a 11 px → 7 px. */
.bascule[aria-checked="mixed"] .bascule-bouton { transform: translateX(7px); opacity: .6; }
.bascule[aria-checked="mixed"]:hover { background: var(--muted); box-shadow: none; }

/* Une CASE A COCHER habillee en interrupteur (2026-09-12).
   /!\ **EN CSS PUR, sans une ligne de script.** La case `<input>` reste : c'est
   elle qui porte le `name`, qui part au serveur et que `_parse_trader_form` relit.
   Un interrupteur qui dependrait d'un script serait mort le jour ou il ne se
   charge pas, et le client ne pourrait plus rien regler.
   /!\ `+` et non `~` : le voyant suit IMMEDIATEMENT la case dans le balisage.
   Intercaler quoi que ce soit entre les deux eteint la regle en silence. */
.case--bascule { display: inline-flex; align-items: center; cursor: pointer; margin: 0; }
.case--bascule > input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.case--bascule > .bascule { pointer-events: none; }
.case--bascule > input:checked + .bascule {
  background: var(--accent); box-shadow: inset 0 0 0 1px var(--accent);
}
.case--bascule > input:checked + .bascule .bascule-bouton {
  background: var(--background); transform: translateX(16px);
}
.case--bascule:hover > .bascule { box-shadow: inset 0 0 0 1px var(--muted-foreground); }
.case--bascule:hover > input:checked + .bascule {
  background: var(--accent-hi); box-shadow: inset 0 0 0 1px var(--accent-hi);
}
.case--bascule > input:focus-visible + .bascule {
  outline: 2px solid var(--ring); outline-offset: 2px;
}

/* La MARQUE d'un reglage qui fige une version (2026-09-10, lot L7).
   /!\ Discrete : elle informe, elle ne crie pas. Un ecran ou 35 champs sur 52
   portent un avertissement rouge ne se lit plus. */
.fige-marque {
  display: inline-grid; place-items: center;
  width: 1.667em; height: 1.667em; margin-left: 6px;
  border-radius: 4px; font-size: var(--text-2xs); font-weight: 600; line-height: 1;
  color: var(--accent); background: var(--accent-wash);
  border: 1px solid var(--accent-ring);
  vertical-align: middle; cursor: help; user-select: none;
}

/* Les OUTILS de la barre de filtres, pousses a droite (2026-09-11).
   /!\ La regle validee le 2026-09-10 : a GAUCHE ce qui reduit la liste, la
   recherche comprise ; a DROITE ce qui agit sur la presentation — colonnes, vues,
   export. Une page nouvelle n'a plus a se demander ou poser sa commande. */
.cmdbar .cmd-outils {
  /* /!\ **`order` ET PAS SEULEMENT `margin-left: auto`.** Cette barre range ses
     elements par `order` (les pastilles valent 1 et 2, « tout remettre a zero » 4) :
     une marge automatique sur un element d'ordre 0 le pousse a droite DE SON GROUPE,
     donc avant les pastilles. Mesure a l'ecran le 2026-09-11 — l'export se posait au
     milieu de la barre, entre « Periode » et « + Filtre ». */
  order: 9; margin-left: auto;
  display: flex; align-items: center; gap: 8px;
}

/* La RECHERCHE d'une liste, forme R3 (2026-09-11).
   /!\ Le champ est un CREUX, pas un bouton : fond enfonce, lisere neutre, loupe
   grise dedans. Une premiere maquette lui avait donne le fond d'un bouton et un
   lisere violet — il se lisait comme un filtre POSE au lieu d'un champ ou l'on tape.
   /!\ La seconde ligne n'existe QUE pendant la recherche : la barre ne change pas de
   hauteur tant qu'on ne cherche pas. */
/* /!\ **`order` ET `flex-basis`, SINON ELLE S'INSERE AU MILIEU DES FILTRES.**
   `.cmdbar` reordonne ses enfants (`.pastilles-ajouter` order 1, `.cmd-raz` 4,
   `.cmd-outils` 9) : un enfant sans `order` retombe a 0 et passe DEVANT eux. Vu a la
   capture sur Contacts — la ligne de recherche s'ouvrait entre « Colonnes » et
   « + Filtre », qui se retrouvaient relegues sur une TROISIEME ligne. C'est le meme
   piege que le bouton d'export a paye le 2026-09-10, au meme endroit.
   /!\ `20`, pas `10` : la place est laissee libre pour ce qui s'ajoutera entre les
   outils (9) et cette ligne, qui doit rester la DERNIERE. */
.recherche-ligne {
  display: flex; align-items: center; gap: 10px;
  margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--border);
  order: 20; flex-basis: 100%;
}
.recherche-champ {
  display: flex; align-items: center; gap: 8px;
  height: 28px; padding: 0 10px; flex: 1 1 320px; max-width: 340px;
  border-radius: var(--radius-md); background: var(--background);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.recherche-champ:focus-within {
  border-color: rgba(167, 139, 250, 0.30);
  box-shadow: 0 0 0 3px rgba(167, 139, 250, 0.10);
}
.recherche-champ .lp { color: var(--disabled-foreground); display: flex; flex: none; }
.recherche-champ input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--foreground); font-family: var(--font-sans); font-size: var(--text-sm);
}
.recherche-compte {
  font-size: var(--text-caption); color: var(--disabled-foreground);
  font-family: var(--font-code); white-space: nowrap;
}
.recherche-ligne .cmd-raz { margin-left: auto; }

/* La LOUPE de la barre de filtres (2026-09-11).
   /!\ A GAUCHE, avec les filtres : une recherche REDUIT la liste. */
.cmdbar .cmd-loupe {
  display: inline-grid; place-items: center; width: 28px; height: 28px;
  border-radius: var(--radius-md); color: var(--muted-foreground);
  background: var(--card); border: 1px solid rgba(255, 255, 255, 0.10);
  cursor: pointer;
}
.cmdbar .cmd-loupe:hover { color: var(--foreground); }
.cmdbar .cmd-loupe[aria-expanded="true"] {
  color: #C9BCFF; background: rgba(167, 139, 250, 0.18);
  border-color: rgba(167, 139, 250, 0.30);
}

/* ── L'ANCIEN OUTIL D'ANALYSE (boîte `.an-inbox`, fil, « Verser dans ses consignes »,
   surlignage « En faire une consigne ») : RETIRÉ avec lui (A4d, 23/09/2026). Les
   classes `.an-*` qui restent (`.an-bouton`, `.an-table`…) servent « Régler avec … »,
   « Brancher mon IA » et l'écran d'équipe `/team/analyses`, pas l'outil. */

/* ============================================================
   .consigne-action — « donner une consigne », partout où le geste s'offre
   ------------------------------------------------------------
   Choix du fondateur, 2026-09-11. Le bouton était un `btn btn-primary btn-sm`
   plein : sur le tableau de bord il pesait autant que « Nouveau trader »,
   alors qu'il ne fait que glisser un mot à l'un des traders de la liste.

   /!\ **LE MOT « CONSIGNE » RESTE ÉCRIT EN ENTIER.** Une première série de
   propositions remplaçait l'invite par une amorce de conversation (« Dire un
   mot à ES matin… ») : écartée par le fondateur, et pour la bonne raison —
   elle laisse croire qu'on ouvre un échange, voire qu'on demande une analyse,
   alors que le geste est « je lui donne une consigne, vite ». On allège le
   POIDS, jamais le vocabulaire. Le libellé par défaut de la macro est donc la
   phrase complète, et le tableau de bord ne l'abrège plus.

   /!\ VIT DANS LA CHARTE, pas dans `partials/dashboard.css` : le même bouton
   est dans l'en-tête d'un trader, présent sur toutes ses pages, et cette
   feuille-là n'est incluse que par le tableau de bord.

   /!\ Le contour est en POINTILLÉS et se referme au survol : c'est ce qui le
   distingue des boutons voisins (« Son journal », « Actions ») sans lui donner
   un remplissage. Réserve connue, dite au fondateur avant son choix : un
   pointillé peut se lire comme un emplacement vide ; il a tranché en le voyant.
   ============================================================ */
.consigne-action {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; font-size: var(--text-sm); line-height: 1; white-space: nowrap;
  border-radius: var(--radius-pill); cursor: pointer;
  color: var(--muted-foreground);
  background: transparent;
  border: 1px dashed var(--border-strong);
  transition: color .12s, border-color .12s, background .12s;
}
.consigne-action:hover {
  color: var(--accent);
  background: var(--accent-wash);
  border-color: var(--accent-ring-strong);
  border-style: solid;
}
.consigne-action:focus-visible {
  outline: 2px solid var(--ring); outline-offset: 2px;
}
.consigne-action svg { flex: none; opacity: .9; }

/* ============================================================
   Le champ DATE d'un dialogue — aligné sur la charte (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « la sélection du calendrier n'est pas du tout alignée sur le
   style de l'app ». Un `<input type="date">` est peint par le navigateur : ni
   son `jj/mm/aaaa`, ni sa pastille de calendrier ne suivent la charte, et la
   chasse fixe de `.saisie` — pensée pour des NOMBRES — donnait un gabarit de
   date en police de code au milieu d'un formulaire en texte.

   /!\ Ce qu'on ne peut PAS atteindre : le calendrier DÉPLIÉ, dessiné par le
   système. `color-scheme: dark` (posé sur `html`) le fonce, et c'est tout —
   même limite que les listes déroulantes natives, déjà documentée plus haut.
   ============================================================ */
input[type="date"].saisie {
  font-family: var(--font-sans); font-size: var(--text-base);   /* un champ : 15 (24/09/2026) */
  padding: 11px 15px;
  cursor: pointer;
}
/* Le gabarit « jj/mm/aaaa » d'un champ vide est un ESPACE RÉSERVÉ, pas une
   valeur : il se lit comme un placeholder, à la couleur des placeholders. */
input[type="date"].saisie:not(:focus):invalid::-webkit-datetime-edit,
input[type="date"].saisie::-webkit-datetime-edit-year-field:not([aria-valuenow]),
input[type="date"].saisie::-webkit-datetime-edit-month-field:not([aria-valuenow]),
input[type="date"].saisie::-webkit-datetime-edit-day-field:not([aria-valuenow]) {
  color: var(--disabled-foreground);
}
/* La pastille du calendrier : blanche et pleine par défaut, elle criait plus
   fort que le champ. `filter` est le seul levier sur cette icône native. */
input[type="date"].saisie::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: .55;
  filter: invert(1) grayscale(1) brightness(1.4);
  transition: opacity var(--motion-fast) var(--ease-out);
}
input[type="date"].saisie:hover::-webkit-calendar-picker-indicator,
input[type="date"].saisie:focus::-webkit-calendar-picker-indicator { opacity: 1; }

/* ============================================================
   LES CHAMPS DATE, HEURE ET COULEUR DE LA MAISON (TVNI-241, 24/09/2026)
   ------------------------------------------------------------
   Fondateur : « il y a encore des listes deroulantes ou des calendriers qui ne
   sont pas du tout dans le theme de l'application ». Les regles ci-dessus
   n'habillaient que le champ FERME : le calendrier deplie restait celui du
   systeme, et une heure s'affichait en AM/PM sur un poste regle en anglais.
   `static/js/champs-a-la-charte.js` les refait, comme `liste.js` refait la liste.

   /!\ Le natif RESTE dans le formulaire (il porte le `name` et la valeur ISO),
   masque comme le `<select>` d'une liste habillee ; un champ texte a la
   francaise s'affiche a sa place. Les regles ci-dessus restent : ce sont elles
   qui s'appliquent si le script ne se charge pas.
   ============================================================ */
/* `width: max-content` : dans une colonne flex (`.field-tight`), la boite
   s'etirait a toute la largeur et son bouton partait au bout de la ligne, loin
   d'un champ de couleur de sept caracteres. */
.champ-maison { position: relative; display: inline-block; vertical-align: middle; width: max-content; max-width: 100%; }
.champ-maison.champ-maison--plein, .champ-maison.champ-maison--flexible { width: auto; }
.champ-maison.champ-maison--plein { display: block; }
.champ-maison.champ-maison--flexible { flex: 1 1 0%; min-width: 0; }
.champ-maison--plein > .champ-maison-texte,
.champ-maison--flexible > .champ-maison-texte { width: 100%; box-sizing: border-box; }
/* `left`/`top` a 0 : sans eux, le natif absolu se pose a sa place « en ligne »,
   APRES le champ visible, et un champ pose au bord droit elargissait la page
   d'un telephone de cinquante points (mesure a 390 px). */
.champ-maison > .champ-maison-natif {
  position: absolute; left: 0; top: 0; width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  opacity: 0; pointer-events: none;
}
/* Le texte d'une date ou d'une heure : la police du texte courant (les
   chiffres alignes par `tabular-nums`), comme le champ date natif habille. */
.champ-maison > input.champ-maison-texte {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}
.champ-maison--couleur > input.champ-maison-texte {
  width: 10.5em; box-sizing: border-box;
  font-family: var(--font-code); font-size: var(--text-sm);
  padding-top: 8px; padding-bottom: 8px; padding-left: 34px;
}
.champ-maison-pastille {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.18) inset;
  pointer-events: none;
}

/* Le bouton qui ouvre le panneau, DANS le champ, a droite. */
.champ-maison-ouvre {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  color: var(--muted-foreground);
  background: transparent; border: 0; border-radius: 6px;
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out), background-color var(--motion-fast) var(--ease-out);
}
.champ-maison-ouvre:hover,
.champ-maison--ouvert > .champ-maison-ouvre { color: var(--accent); background: rgba(180,168,255,0.10); }
.champ-maison-ouvre:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
.champ-maison-ouvre:disabled { opacity: .45; cursor: not-allowed; background: transparent; }
.champ-maison-chevron {
  width: 7px; height: 7px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
}

/* Le PANNEAU : la meme surface que la liste depliee (`.liste-panneau`). */
.champ-maison-panneau {
  position: absolute; left: 0; top: calc(100% + 6px);
  z-index: var(--z-dropdown);
  width: max-content; max-width: calc(100vw - 24px);
  padding: 12px;
  font-family: var(--font-sans); font-size: var(--text-base);
  color: var(--foreground); text-align: left;
  background: linear-gradient(180deg, #1A1D27 0%, #14161F 100%);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  box-shadow: 0 24px 60px -16px rgba(0,0,0,0.75),
              0 0 0 1px rgba(255,255,255,0.04) inset;
}
/* Ouvert, il flotte hors de son champ (cf. `placer` dans le script) : le script
   pose `left`/`top`. Au-dessus des fenetres et de la bulle d'aide. */
.champ-maison-panneau.champ-maison-panneau--flottant { position: fixed; z-index: 2147482000; }

.champ-maison-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 8px;
}
.champ-maison-titre {
  font-weight: 600; font-size: var(--text-base);
  text-transform: capitalize; font-variant-numeric: tabular-nums;
}
.champ-maison-nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  color: var(--muted-foreground); background: transparent;
  border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
}
.champ-maison-nav:hover { color: var(--accent); border-color: var(--accent-ring-strong); background: var(--accent-wash); }

.champ-maison-grille { border-collapse: separate; border-spacing: 2px; margin: 0 auto; }
.champ-maison-grille th {
  padding: 4px 0 6px; width: 38px;
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 500;
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground); text-align: center;
}
.champ-maison-grille td { padding: 0; text-align: center; }
.champ-maison-jour {
  width: 38px; height: 36px; padding: 0;
  font-family: var(--font-sans); font-size: var(--text-base);
  font-variant-numeric: tabular-nums;
  color: var(--foreground); background: transparent;
  border: 1px solid transparent; border-radius: 8px; cursor: pointer;
}
.champ-maison-jour--dehors { color: var(--disabled-foreground); }
@media (hover: hover) { .champ-maison-jour:hover:not(:disabled) { background: rgba(180,168,255,0.10); } }
.champ-maison-jour[aria-current="date"] { border-color: var(--accent-ring-strong); color: var(--accent); }
.champ-maison-jour[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 600;
}
.champ-maison-jour:disabled { color: var(--disabled-foreground); opacity: .45; cursor: not-allowed; text-decoration: line-through; }
.champ-maison-jour:focus-visible,
.champ-maison-case:focus-visible,
.champ-maison-teinte:focus-visible,
.champ-maison-nav:focus-visible,
.champ-maison-lien:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }

.champ-maison-pied {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--border);
}
.champ-maison-lien {
  padding: 6px 10px;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 500;
  color: var(--muted-foreground); background: transparent;
  border: 0; border-radius: 6px; cursor: pointer;
}
.champ-maison-lien:hover:not(:disabled) { color: var(--foreground); background: rgba(255,255,255,0.05); }
.champ-maison-lien--fort { color: var(--accent); }
.champ-maison-lien--fort:hover:not(:disabled) { color: var(--accent-hi); background: rgba(180,168,255,0.10); }
.champ-maison-lien:disabled { opacity: .45; cursor: not-allowed; }
.champ-maison-note { font-size: var(--text-xs); color: var(--muted-foreground); }

/* L'heure : deux grilles, heures puis minutes, en 24 h. */
.champ-maison-colonnes { display: flex; gap: 14px; }
.champ-maison-sous-titre {
  margin: 0 0 6px 2px;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--muted-foreground);
}
.champ-maison-cases { display: grid; gap: 3px; }
.champ-maison-cases--heures { grid-template-columns: repeat(6, 40px); }
.champ-maison-cases--minutes { grid-template-columns: repeat(3, 44px); }
.champ-maison-case {
  height: 32px; padding: 0;
  font-family: var(--font-sans); font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--foreground); background: transparent;
  border: 1px solid transparent; border-radius: 7px; cursor: pointer;
}
@media (hover: hover) { .champ-maison-case:hover:not([aria-pressed="true"]) { background: rgba(180,168,255,0.10); } }
.champ-maison-case[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent);
  color: var(--on-accent); font-weight: 600;
}

/* La couleur : les teintes de la charte, en pastilles. */
.champ-maison-teintes { display: grid; grid-template-columns: repeat(6, 30px); gap: 8px; }
.champ-maison-teinte {
  width: 30px; height: 30px; padding: 0;
  background: var(--teinte); border: 0; border-radius: 50%; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.12) inset;
}
.champ-maison-teinte[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent);
}

/* AU TELEPHONE, UNE FEUILLE EN BAS DE L'ECRAN : un calendrier pose sous un champ
   sortait de l'ecran des qu'il n'etait pas a gauche, et les cases doivent etre
   assez grandes pour un pouce. Le voile est l'ombre du panneau lui-meme : pas
   d'element de plus, et un toucher dessus referme (il est hors du champ).
   /!\ Le script la deplace dans `body` le temps qu'elle est ouverte (cf.
   `placer`) : ces regles visent donc la feuille par SA classe, jamais par son
   champ. */
.champ-maison-panneau.champ-maison-panneau--feuille {
  position: fixed; left: 0; right: 0; bottom: 0; top: auto;
  width: auto; max-width: none;
  z-index: 2147482000;   /* au-dessus de la bulle d'aide et de la barre du haut */
  padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  border-radius: 18px 18px 0 0;
  border-bottom: 0;
  box-shadow: 0 0 0 100vmax rgba(5,6,10,0.62), 0 -12px 40px rgba(0,0,0,0.5);
}
/* De la place sous la page tant que la feuille est ouverte : un champ pose en bas
   de page (la date de fin d'un declencheur) ne pouvait pas remonter au-dessus
   d'elle, et on choisissait une date sans voir le champ qu'on remplissait. */
html.champ-maison-feuille-ouverte body { padding-bottom: 75vh; }
.champ-maison-panneau--feuille .champ-maison-grille { width: 100%; }
.champ-maison-panneau--feuille .champ-maison-grille th { width: auto; }
.champ-maison-panneau--feuille .champ-maison-jour { width: 100%; height: 44px; font-size: calc(16px * var(--echelle-texte)); }
.champ-maison-panneau--feuille .champ-maison-nav { width: 40px; height: 40px; }
.champ-maison-panneau--feuille .champ-maison-lien { padding: 10px 14px; font-size: var(--text-base); }
.champ-maison-panneau--feuille .champ-maison-colonnes { flex-direction: column; gap: 12px; }
.champ-maison-panneau--feuille .champ-maison-cases--heures { grid-template-columns: repeat(6, 1fr); }
.champ-maison-panneau--feuille .champ-maison-cases--minutes { grid-template-columns: repeat(6, 1fr); }
.champ-maison-panneau--feuille .champ-maison-case { height: 40px; font-size: calc(16px * var(--echelle-texte)); }
.champ-maison-panneau--feuille .champ-maison-teintes { grid-template-columns: repeat(6, 1fr); justify-items: center; }
.champ-maison-panneau--feuille .champ-maison-teinte { width: 38px; height: 38px; }

/* ============================================================
   Les onglets d'une page NE SONT PAS une barre de filtres (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « mets les onglets de la page Mon profil à l'horizontal et ne
   mets pas la bande car c'est pour les filtres ». La pilule pleine (`.seg`) est
   le vocabulaire des FILTRES — elle dit « je restreins ce que tu vois ». Des
   onglets de page disent « je change de page » : c'est `.t-onglets`, le soulign
   é, déjà employé par les quatre écrans d'un trader.

   /!\ RIEN NE CHANGE DANS LE HTML, et surtout pas la classe : `.seg--onglets`
   porte le contrat `data-onglets` qu'`onglets-formulaire.js` cherche UNE fois
   dans la page, et c'est `aria-selected` qui marque l'onglet ouvert. On ne
   renomme pas, on REPEINT — sinon les panneaux cessent de basculer.
   ============================================================ */
.wrap--onglets-en-ligne > .seg--onglets {
  display: flex; gap: 2px; flex-wrap: wrap;
  background: none; border: 0; border-radius: 0;
  border-bottom: 1px solid var(--border);
  padding: 0; margin: 0 0 28px;
}
.wrap--onglets-en-ligne > .seg--onglets > * {
  padding: 9px 15px; font-size: var(--text-base); font-weight: 500;
  color: var(--muted-foreground);
  background: none; box-shadow: none; border-radius: 0;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  transition: color .14s, border-color .14s;
}
.wrap--onglets-en-ligne > .seg--onglets > *:hover { color: var(--foreground); }
/* /!\ L'onglet ouvert est ÉCRIT à l'accent, pas seulement souligné (demande du
   fondateur, 2026-09-11 : « j'aimerais que l'onglet actif soit mis plus en
   violet »). Un filet de 2 px sous un libellé blanc se remarquait peu quand les
   quatre libellés étaient déjà clairs. */
.wrap--onglets-en-ligne > .seg--onglets > [aria-selected="true"],
.wrap--onglets-en-ligne > .seg--onglets > [aria-current="page"] {
  color: var(--accent);
  font-weight: 600;
  background: none; box-shadow: none;
  border-bottom-color: var(--accent);
}

/* Les champs d'un DIALOGUE respirent (2026-09-11). `.champ` ne pose que l'écart
   entre son étiquette et sa saisie ; empilés dans une fenêtre, les blocs se
   touchaient et la lecture n'avait plus de rythme. Les écrans, eux, passent par
   `.champs` (une grille qui porte son `gap`) — une fenêtre n'a qu'une colonne. */
dialog.verre .champ + .champ { margin-top: 16px; }
dialog.verre .champ:first-of-type { margin-top: 4px; }

/* ============================================================
   La BARRE DE FILTRES parle d'une seule voix (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « les boutons de la bande étaient rectangulaires avec les bords
   arrondis ». C'est la maquette « B » du 2026-08-23, appliquée jusqu'au bout à
   la barre : `.cmd-choix` et `.perimetre-raz` passent de la pilule à
   `--radius-md` dans leurs propres règles, et les deux intrus restants sont
   traités ici.

   /!\ LA PORTÉE S'ARRÊTE À `.cmdbar`, volontairement. `.seg` compte une
   cinquantaine d'usages hors de la barre (le sélecteur de période du tableau de
   bord, par exemple) : les carrer tous serait un autre chantier, pas celui-ci.
   Le carnet `NOTES-guide-et-boutons.md` le gardait justement en arbitrage
   ouvert — il n'est tranché ICI que pour la barre de filtres.
   ============================================================ */
.cmdbar .seg,
/* `.basc` est la BASCULE de la barre (« Tous / Clients / Prospects ») : même
   objet que `.seg` pour qui la regarde, autre classe dans le code. Elle avait
   été oubliée d'un premier passage — sur Contacts, elle restait le seul rond au
   milieu d'une barre devenue carrée. */
.cmdbar .basc { border-radius: var(--radius-md); }
.cmdbar .seg > *,
.cmdbar .basc > * { border-radius: var(--radius-sm); }
/* « + Filtre » n'avait AUCUN arrondi (0 px) : seul angle vif d'une barre où
   tout le reste est adouci. Il ne se voyait pas tant que ses voisins étaient
   ronds — un angle droit au milieu de pilules se lit comme un autre composant,
   pas comme un défaut. */
.cmdbar .pastilles-ajouter { border-radius: var(--radius-md); }

/* ⚠️ **UN SÉLECTEUR PLUS LARGE QUE LE TÉLÉPHONE DÉBORDAIT LA PAGE ENTIÈRE**
   (2026-09-16). Sur News, les quatre segments d'impact (« Tous 104 · Fort 16 ·
   Moyen 11 · Faible 77 ») mesurent 441 px : à 390 px de large, « Faible » était
   coupé net et TOUTE la page prenait 480 px, donc un défilement horizontal.
   `.cmdbar` sait pourtant passer à la ligne, mais un `.seg` est une pilule d'un
   seul tenant, que `flex-wrap` ne peut pas couper.

   ⚠️ **La réponse est celle de `.seg--onglets`, pas une nouvelle** : la pilule
   se borne à la largeur disponible et défile à plat, barre de défilement cachée.
   Elle garde sa forme, ses quatre segments et leurs effectifs : les faire
   disparaître sous une largeur ferait choisir à l'aveugle, ce que ces effectifs
   existent précisément pour éviter.

   ⚠️ Portée : `.cmdbar`, comme la règle d'arrondis juste au-dessus. Les
   cinquante `.seg` posés hors d'une barre ne changent pas de comportement. */
.cmdbar .seg, .cmdbar .basc { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.cmdbar .seg::-webkit-scrollbar, .cmdbar .basc::-webkit-scrollbar { height: 0; }
/* Sans cela un segment se COMPRIME au lieu de défiler : flex rétrécit ses enfants
   avant de déborder, et « Moyen 11 » devient « Moy… » dans une pilule qui, elle,
   tient. C'est la même ligne que `.seg--onglets > *`. */
.cmdbar .seg > *, .cmdbar .basc > * { flex: 0 0 auto; }

/* ============================================================
   .an-fenetre — la boîte de réception en FENÊTRE CENTRALE (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « j'aimerais que la page s'ouvre sous forme de pop-up centrale
   assez grande pour que l'inbox soit userfriendly ». L'inbox a deux colonnes
   (la liste des conversations, le fil) et un composeur : à la largeur d'un
   dialogue ordinaire (460 px, cf. `dialog.verre`), elle serait illisible.

   /!\ ELLE PREND PRESQUE L'ÉCRAN, mais pas tout : une fenêtre collée aux quatre
   bords ne se distingue plus d'une page, et on ne sait plus qu'on peut la
   fermer. Les marges restantes SONT l'information.

   /!\ LA HAUTEUR EST CONTRAINTE, et c'est ce qui fait tenir le fil : sans
   `min-height: 0` sur le corps, un enfant en `overflow: auto` refuse de
   rétrécir sous sa hauteur de contenu et c'est la FENÊTRE qui s'allonge — le
   fil ne défile plus, la page derrière si.
   ============================================================ */
dialog.an-fenetre {
  width: min(1180px, calc(100vw - 48px));
  max-width: none;
  /* /!\ `--fenetre`, JAMAIS `100vh` : l'application porte un facteur d'echelle, et
     une hauteur calee sur la fenetre en brut deborde de l'ecran d'autant. Ecrit en
     `100vh` le 2026-09-11, attrape le lendemain par
     `tests/test_echelle_hauteurs.py::test_le_squelette_ne_lit_pas_la_fenetre_en_brut`
     — qui tient cet invariant justement parce qu'il s'oublie. */
  height: min(860px, calc(var(--fenetre) - 48px));
  padding: 0;
  /* /!\ PAS de `display` ici, et c'est tout l'objet de la regle suivante : un
     `<dialog>` ferme ne disparait que parce que le navigateur lui pose
     `display: none`. Une declaration de `display` sur le selecteur de base
     l'ecrase, et la fenetre se rend alors DANS LE FLUX de la page, fermee :
     1180 x 860 px de vide, titre et croix compris, en bas de chaque Journal.
     Vecu jusqu'au 2026-09-13 ; `tests/test_dialogues_fermes.py` le tient. */
  flex-direction: column;
  overflow: hidden;
}
dialog.an-fenetre[open] { display: flex; }
.an-fenetre-tete {
  flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.an-fenetre-corps {
  flex: 1 1 auto; min-height: 0;
  overflow: auto;
  padding: 18px 20px 20px;
}
/* /!\ DANS LA FENÊTRE, L'INBOX REMPLIT LA HAUTEUR. Sur la page, ses hauteurs sont
   calées sur la FENÊTRE DU NAVIGATEUR (`--fenetre * 0.7`) : posée telle quelle dans
   un dialogue qui a déjà sa propre hauteur, la liste s'arrêtait aux deux tiers et
   laissait un grand vide gris sous elle — vu à l'écran. Ici, c'est le dialogue qui
   commande, et chaque colonne défile pour son compte.

   /!\ `min-height: 0` à chaque étage : sans lui, un enfant en `overflow: auto`
   refuse de rétrécir sous la hauteur de son contenu, et c'est la fenêtre qui
   s'allonge au lieu du fil qui défile. */
.an-fenetre-corps { padding: 0; display: flex; flex-direction: column; }
.an-fenetre-corps > [data-section="analyses"] {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
}
.an-fenetre-corps .an-inbox {
  flex: 1 1 auto; min-height: 0;
  border: 0; border-radius: 0; background: transparent;
}
.an-fenetre-corps .an-inbox { height: auto; }
.an-fenetre-corps .an-liste { max-height: none; min-height: 0; }
.an-fenetre-corps .an-conv-ouverte { min-height: 0; }
.an-fenetre-corps .an-fil { max-height: none; }
/* L'état vide et le formulaire d'une analyse neuve respirent, eux, dans la marge. */
.an-fenetre-corps > [data-section="analyses"] > .carte,
.an-fenetre-corps > [data-section="analyses"] > .an-neuve { margin: 18px 20px; }
.an-fenetre-corps [aria-busy="true"] { opacity: .5; }

/* ============================================================
   « Tout remettre à zéro » redevient DISCRET dans la barre (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « le bouton Tout remettre à zéro n'est pas du tout au format qu'on
   avait validé, il doit s'afficher que quand nécessaire et discret ».

   Il portait `.perimetre-raz`, dessiné pour une rangée de PÉRIMÈTRE où il est la
   seule commande : fond de carte, liseré, 8/14 de rembourrage. Posé dans la barre
   de filtres, il y pesait plus que les filtres eux-mêmes — alors qu'il ne fait que
   défaire ce qu'ils posent. Ici : pas de fond, pas de liseré, la couleur d'un texte
   secondaire, et l'accent au survol.

   /!\ LA PORTÉE S'ARRÊTE À `.cmdbar` : la rangée de périmètre garde son bouton
   encadré, là où il est seul et doit se voir.
   ============================================================ */
.cmdbar .cmd-raz .perimetre-raz,
.cmdbar .cmd-raz.perimetre-raz {
  padding: 5px 8px;
  background: none;
  border-color: transparent;
  color: var(--muted-foreground);
  font-size: var(--text-sm);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--border-strong);
}
.cmdbar .cmd-raz .perimetre-raz:hover,
.cmdbar .cmd-raz.perimetre-raz:hover {
  color: var(--accent);
  background: none;
  border-color: transparent;
  text-decoration-color: var(--accent);
}
/* Dans le JOURNAL d'un trader, il devient une ICÔNE (TVNI-132, 21/09/2026) : son
   libellé de 133 px repoussait les outils sur une deuxième ligne dès qu'un filtre
   était posé. Même boîte que la loupe voisine (28 px, coin `--radius-md`), l'accent
   au survol comme le texte discret qu'il remplace. Les autres barres (Contacts)
   gardent le mot. */
.cmdbar .cmd-raz-icone {
  display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0;
  font: inherit; line-height: 0;
  border-radius: var(--radius-md); color: var(--muted-foreground);
  background: var(--card); border: 1px solid rgba(255, 255, 255, 0.10);
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out),
              border-color var(--motion-fast) var(--ease-out);
}
.cmdbar .cmd-raz-icone:hover, .cmdbar .cmd-raz-icone:focus-visible {
  color: var(--accent); border-color: var(--accent-ring-strong);
}

/* ============================================================
   La rangée de gestes d'un trader parle d'une seule voix (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « aligne le style du bouton Arrêter / Activer sur la clé et les
   trois petits points ».

   Mesuré avant de corriger : la clé et le menu `⋯` partagent la même surface —
   38 px de haut, fond de carte, liseré à 7 % de blanc, coin à 6 px — parce que
   tous deux sont bâtis sur `.btn-icon`. Le bouton d'état, lui, arrivait en
   `.btn-ghost.btn-sm` : 26 px de haut, fond transparent, liseré à 14 %, libellé
   à 11,5 px. Trois objets côte à côte, deux familles.

   /!\ ON ALIGNE SUR LA CLÉ, PAS L'INVERSE. C'est elle le repère : la spec du
   2026-09-10 en fait l'élément « visible en permanence » de cette rangée, et
   c'est autour d'elle que l'oeil se cale.

   /!\ LA PORTÉE S'ARRÊTE À `.e-gestes` : `.btn-ghost.btn-sm` est employé partout
   dans l'application, et c'est ici qu'il détonne — pas ailleurs.
   ============================================================ */
/* /!\ **CETTE REGLE AVAIT DERIVE, et le fondateur l'a revu le 2026-09-13** : « mets
   les boutons a la meme hauteur ». Elle visait 38 px — la taille de la cle et du menu
   `⋯` — mais l'ecrivait `2.923em` sur `--text-base`. Le jour ou ce jeton est passe de
   13 a 15 px, les trois boutons ont recommence a differer : 43,8 px contre 38, mesure a
   l'ecran. **Une hauteur qui doit EGALER celle d'une icone carree ne se dit pas en
   `em`** : elle suit alors la taille du texte, que l'icone ne suit pas.
   /!\ ON ALIGNE SUR LA CLE, PAS L'INVERSE (regle du 2026-09-11, inchangee) : c'est
   elle le repere de la rangee, et c'est autour d'elle que l'oeil se cale. */
.e-gestes form > .btn {
  height: 38px;
  padding: 0 1em;
  font-size: var(--text-base);
}
/* /!\ **LA PEAU NEUTRE NE VAUT QUE POUR LES BOUTONS NEUTRES** (2026-09-13). Elle etait
   posee sur TOUT `.btn` de la rangee — fond de carte, liseré de bordure, texte clair —
   et elle ecrasait donc la couleur des variantes. « Arreter » en rouge et « Activer »
   en vert, demandes le meme jour, sortaient gris. Elle se limite desormais a
   `.btn-ghost`, le seul bouton de cette rangee qui n'a rien a dire par sa couleur. */
.e-gestes form > .btn-ghost {
  background: var(--card);
  border-color: var(--border);
  color: var(--foreground);
}
.e-gestes form > .btn-ghost:hover {
  background: var(--muted);
  border-color: var(--border-strong);
  color: var(--foreground);
}

/* ============================================================
   « Analyser » se remarque (2026-09-11)
   ------------------------------------------------------------
   Fondateur : « un icône coloré qui fasse penser à une analyse et qui soit
   voyant, pour que la personne le remarque facilement ».

   /!\ LA COULEUR EST SUR L'ICÔNE, PAS SUR LE BOUTON. Repeindre le bouton entier
   en aurait fait un second bouton principal dans une barre qui en a déjà un ;
   l'étincelle ambrée sur l'aplat violet se voit de loin sans déranger la
   hiérarchie. C'est le seul endroit de l'application où `--warning-text` ne
   signale pas un avertissement : ici il attire, il n'alerte pas — d'où le halo,
   qui dit « ça brille » plutôt que « attention ».

   /!\ AUCUNE ANIMATION. Un bouton qui clignote dans une barre de filtres qu'on
   regarde toute la journée devient le premier réglage qu'on cherche à éteindre.
   ============================================================ */
/* ══ LE BOUTON « ANALYSER » EST JAUNE (2026-09-14) ═════════════════════════
   Fondateur : « le bouton analyser, mets-le en jaune, et mets juste le mot
   Analyser, pareil sur le journal ».

   /!\ **IL PORTAIT `.btn-primary`**, c'est-a-dire le violet de l'application —
   la teinte de TOUS les gestes principaux. Il se fondait donc dans la rangee des
   autres boutons alors qu'il est le seul a DEPENSER : une question au modele est
   payee sur la cle du client.

   /!\ La teinte est celle de l'etincelle qu'il porte deja (`--warning-text`), pas
   un jaune neuf : sans ca le bouton et son icone se liraient en deux couleurs.

   /!\ Il n'est PAS un avertissement pour autant, et le jaune de `--warning` en sert
   aussi : ce qui les separe est la FORME — un bouton qu'on clique, un bandeau qu'on
   lit — jamais la couleur seule. */
.an-bouton {
  background: rgba(251, 191, 36, 0.16);
  color: var(--warning-text);
  border: 1px solid rgba(251, 191, 36, 0.32);
}
.an-bouton:hover:not(:disabled) {
  background: rgba(251, 191, 36, 0.26);
  border-color: rgba(251, 191, 36, 0.52);
  color: #FDE68A;
}
.an-bouton:active:not(:disabled) { background: rgba(251, 191, 36, 0.32); }

/* L'étincelle dorée de « Demander à l'IA » et de « Régler avec … ». */
.an-bouton-etincelle {
  display: inline-flex; align-items: center;
  color: var(--warning-text);
  filter: drop-shadow(0 0 5px rgba(252, 211, 77, 0.55));
}
.an-bouton:hover .an-bouton-etincelle {
  filter: drop-shadow(0 0 7px rgba(252, 211, 77, 0.85));
}

/* Une infobulle d'OUTIL (les icônes de la barre de filtres, 2026-09-11) : une
   phrase ou deux, pas un paragraphe. Même boîte que les autres bulles — c'est la
   largeur seule qui change, comme pour `data-forme="chiffre"` juste au-dessus. */
#tvni-bulle[data-forme="outil"] {
  max-width: min(300px, calc((100vw - 24px) / var(--echelle)));
}

/* ============================================================
   LES GESTES D'UN TRADER RESTENT EN HAUT À DROITE (2026-09-12)
   ------------------------------------------------------------
   Fondateur : « je veux que les boutons soient toujours au même endroit sur les
   3 onglets ».

   /!\ LA CAUSE N'ÉTAIT NI LA STRUCTURE NI L'ORDRE — c'était la LARGEUR, et je
   l'ai cherchée ailleurs avant de la mesurer. `.sec-body` est un `flex` en
   `space-between` avec `flex-wrap: wrap` : le chapeau et la rangée de gestes
   tiennent sur une ligne tant que leur somme passe. Sur le Journal, 420 + 547 :
   ça passe. Sur Consignes, le chapeau fait 600 (son texte est plus long) et la
   rangée 698 (elle porte un bouton de plus) : 1318 pour ~1220 de large, et le
   `wrap` renvoie les boutons à la ligne suivante, à gauche.

   La règle dit donc QUI CÈDE : le chapeau, qui est du texte et se replie sans
   rien perdre. Les gestes, eux, ne rétrécissent pas — un bouton à moitié coupé
   ne se clique pas.

   /!\ `:has(> .e-gestes)` BORNE LA PORTÉE aux en-têtes qui portent cette rangée.
   `.sec-head--compact` coiffe des dizaines d'écrans ; leur imposer ce partage
   changerait des mises en page que personne n'a demandé de changer.
   ============================================================ */
.sec-head--compact .sec-body:has(> .e-gestes) > :first-child {
  flex: 1 1 300px;
  min-width: 0;
}
.sec-head--compact .sec-body:has(> .e-gestes) > .e-gestes {
  flex: 0 0 auto;
}

/* ⚠️ SUR UN TÉLÉPHONE, C'EST L'INVERSE QUI EST VRAI (2026-09-20, TVNI-93).
   La règle ci-dessus dit « les gestes ne rétrécissent pas, un bouton à moitié
   coupé ne se clique pas ». Le raisonnement tient tant qu'il RESTE une place
   où aller : sur un écran large, la rangée déborde du titre et se replie sous
   lui. Sur un écran de 390 points il n'y a pas d'ailleurs — mesuré sur
   `/user/traders/1/analyses` : la rangée faisait 380 points de large à partir
   de l'abscisse 16, donc 396 au total, et « + Nouvelle analyse » était coupé
   net par le bord droit. Pire, c'est TOUTE LA PAGE qui défilait alors de
   travers : le titre, le fil d'Ariane et les messages se lisaient décalés.

   ⚠️ Un bouton coupé ne se clique pas — et un bouton hors de l'écran non plus.
   La rangée prend donc sa propre ligne et ses boutons se replient les uns sous
   les autres, ce que `.e-gestes` sait déjà faire (`flex-wrap: wrap`) mais ne
   faisait jamais, faute d'être un jour trop à l'étroit.

   ⚠️ `min-width: 0` EN PLUS de `flex-basis: 100%` : sans lui, un élément de
   flex refuse de descendre sous la largeur de son contenu, et les 380 points
   revenaient par la fenêtre. C'est le même piège que `auth_public_fragment`
   documente déjà pour sa grille. */
@media (max-width: 720px) {
  .sec-head--compact .sec-body:has(> .e-gestes) > .e-gestes {
    flex: 1 1 100%;
    min-width: 0;
  }
}

/* ============================================================
   LES ONGLETS D'UN TRADER DÉMARRENT TOUS À LA MÊME HAUTEUR (2026-09-12)
   ------------------------------------------------------------
   Fondateur : « les onglets diffèrent dans l'affichage, peux-tu aligner sur
   l'onglet Journal ».

   /!\ MESURÉ AVANT DE CORRIGER : la barre d'onglets est IDENTIQUE partout —
   même abscisse, même largeur, même marge, même accent sur l'onglet ouvert. Ce
   qui change est son ORDONNÉE : 228 sur le Journal, 250 sur Paramètres, 268 sur
   Consignes. La cause est au-dessus d'elle, dans le chapeau : une ligne de texte
   sur le Journal, deux sur Paramètres, trois sur Consignes.

   /!\ ALIGNER SUR LE JOURNAL — le plus COURT — voudrait dire couper deux phrases
   d'explication. Ce sont des phrases de produit, pas du remplissage : « Les
   modifier ne fige aucune version et ne remet aucun compteur à zéro » répond à la
   question que le client se pose en arrivant. On aligne donc sur la hauteur qui
   les contient toutes, et le Journal gagne un peu de blanc — rien ne se perd.

   ⚠️ **112 px SONT DEVENUS 78 le 2026-09-15** (fondateur : « réduis la hauteur
   entre le titre et les onglets, je la trouve trop élevée, et pareil sur toutes les
   pages qui ont des onglets »). Le raisonnement ci-dessus tenait tant qu'un chapeau
   faisait trois lignes ; l'épuration les a tous ramenés à une, y compris celui des
   Consignes — sa phrase sur les versions est descendue SOUS les onglets, à la place
   exacte qu'occupe la sienne sur les Paramètres. La hauteur de calage suit donc le
   chapeau d'une ligne, et les trois barres restent à la même ordonnée.

   ⚠️ **NE PAS RELEVER CETTE VALEUR POUR RATTRAPER UN CHAPEAU QUI REDEVIENT LONG.**
   C'est le chapeau qu'on raccourcit : la valeur n'aligne que par le BAS, et la
   remonter réintroduit exactement le blanc qu'on vient d'enlever, sur les trois
   écrans à la fois.

   /!\ `min-height`, jamais `height` : un chapeau plus long un jour pousserait
   simplement sa barre, au lieu de passer dessous.
   ============================================================ */
.sec-head--compact .sec-body:has(> .e-gestes) {
  min-height: 78px;
  align-items: flex-start;
}

/* ============================================================
   LA LENTILLE DE VERSION (2026-09-12)
   ------------------------------------------------------------
   Fondateur : « quand j'arrive sur un trader, tout ce que je vois doit être pour
   la version actuelle », et « pouvoir en un clic passer à une autre version pour
   voir ce qu'il en était, sans que ça devienne la version active ».

   /!\ LA PASTILLE CHANGE DE COULEUR QUAND ON REGARDE LE PASSÉ, et ce n'est pas
   décoratif : c'est le seul repère permanent qui dise qu'on ne lit pas
   aujourd'hui. Le bandeau, lui, se scrolle hors de vue.
   ============================================================ */
.lentille-pastille .vv { color: var(--muted-foreground); }
.lentille-pastille--archive {
  border-color: var(--warning-text);
  color: var(--warning-text);
}
.lentille-pastille--archive .k,
.lentille-pastille--archive .v,
.lentille-pastille--archive .vv,
.lentille-pastille--archive .chev { color: inherit; }

.lentille-bandeau {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px;
  margin: 0 0 20px;
  padding: 10px 14px;
  border: 1px solid var(--warning-text);
  border-radius: var(--radius-lg);
  background: rgba(252, 211, 77, 0.06);
  font-size: var(--text-base);
}
.lentille-bandeau-mot {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--warning-text);
}
.lentille-bandeau-mot b { color: var(--foreground); }

/* ============================================================
   La CLÉ dans l'en-tête : trois formes proposées (2026-09-12)
   ------------------------------------------------------------
   Fondateur : « je n'aime pas l'affichage de la clé ». Aujourd'hui elle occupe
   un bloc de ~470 px : une étiquette en capitales, la clé masquée, et TROIS
   boutons (voir, copier, régénérer) au milieu d'une rangée qui en porte déjà
   trois autres.

   /!\ CE QUI NE SE NÉGOCIE PAS, quelle que soit la forme retenue : la clé reste
   MASQUÉE au chargement, elle n'est jamais dans le HTML servi, et sa lecture est
   journalisée. Une capture d'écran envoyée au support ne doit pas la livrer.
   Les trois formes ci-dessous ne changent que l'emballage.
   ============================================================ */

/* — A — La puce : un seul objet, un seul geste. Cliquer copie. */
.cle-puce {
  display: inline-flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 12px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--card);
  font: 12.5px/1 var(--font-mono);
  color: var(--muted-foreground);
  cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.cle-puce:hover { border-color: var(--accent-ring-strong); color: var(--foreground); }
.cle-puce svg { color: var(--accent); opacity: .9; }

/* — B — L'icône seule : la rangée respire, la clé est à un survol. */
.cle-icone {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--muted-foreground);
  cursor: pointer;
  transition: border-color .12s, color .12s;
}
.cle-icone:hover { border-color: var(--accent-ring-strong); color: var(--accent); }

/* Le bouton MARCHE / ARRÊT d'un trader (28/09/2026, `partials/bouton_marche.html`) :
   la couleur dit l'ÉTAT, vert en marche, rouge arrêté. */
.bouton-marche--on  { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); }
.bouton-marche--off { color: var(--error);   border-color: color-mix(in srgb, var(--error) 45%, transparent); }
.bouton-marche--on:hover  { color: var(--success); border-color: var(--success); }
.bouton-marche--off:hover { color: var(--error);   border-color: var(--error); }
.bouton-marche:disabled { opacity: .45; cursor: not-allowed; }

/* — C — Le tiroir : un bouton « Clé », et tout le reste dans un panneau. */
.cle-tiroir-panneau { min-width: 300px; padding: 12px 14px; }
.cle-tiroir-panneau code {
  display: block; margin: 8px 0 10px; padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: var(--background);
  font-size: var(--text-sm); color: var(--foreground); word-break: break-all;
}
.cle-tiroir-actions { display: flex; gap: 6px; }

/* ============================================================
   .chat-barre — QUI RÉPOND DANS LA BULLE (2026-09-14)
   ------------------------------------------------------------
   Maquette validée par le fondateur, option C : une ligne au-dessus de la
   saisie, toujours au même endroit, dans les deux sens.

   /!\ Elle garde sa place quand elle est VIDE (au montage, avant le premier
   fragment) : une barre qui apparaîtrait en cours de route déplacerait le champ
   sous le doigt de quelqu'un en train d'écrire.

   /!\ Deux teintes SÉMANTIQUES, pas décoratives : l'accent pour l'assistant,
   l'ambre pour une personne. C'est la couleur qui porte l'information quand la
   ligne est lue en diagonale.
   ============================================================ */
.chat-barre {
  display: flex; align-items: center; gap: 8px;
  min-height: 20px; padding: 0 2px 6px;
  font-size: calc(12px * var(--echelle-texte)); line-height: 1.4;
}
.chat-barre:empty { padding-bottom: 0; }
.chat-barre-point {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--primary);
}
.chat-barre[data-qui="equipe"] .chat-barre-point { background: var(--warning, #b4541e); }
.chat-barre-qui { color: var(--muted-foreground); min-width: 0; }
.chat-barre-humain {
  margin-left: auto; flex: none;
  background: none; border: 0; padding: 2px 0;
  color: var(--primary); font: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.chat-barre-humain:hover { color: var(--foreground); }
.chat-barre-humain:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 3px;
}

/* /!\ **LE GESTE INVERSE, DANS LES DEUX AUTRES ÉTATS** (fondateur, 2026-09-15 :
   « il faut que le client puisse rebasculer sur le mode IA »). Il PREND LA PLACE
   de « Parler à une personne », il ne s'ajoute pas à côté : deux gestes
   contraires sur une ligne qui porte déjà une phrase ne tiendraient pas, et la
   bulle fait 384 px.
   /!\ **PLUS DISCRET QUE SON SYMÉTRIQUE, et c'est voulu** : demander quelqu'un
   est la porte de secours qu'on doit trouver du premier coup d'œil ; revenir à
   l'assistant est un geste de confort, qu'on ne cherche que si on le veut. Il
   n'est donc pas souligné et reste en gris jusqu'au survol. */
.chat-barre-ia {
  margin-left: auto; flex: none;
  background: none; border: 0; padding: 2px 0;
  color: var(--muted-foreground); font: inherit; font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
.chat-barre-ia:hover { color: var(--primary); }
.chat-barre-ia:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 3px;
}

/* /!\ **UN TROISIÈME ÉTAT : « attente »** (2026-09-14). La demande est partie,
   personne n'a encore repris. Il lui fallait sa propre teinte : peint en accent
   comme l'assistant, il aurait dit le contraire de ce qu'il annonce ; peint en
   ambre comme une personne, il aurait promis quelqu'un qui n'est pas là.
   /!\ La pastille RESPIRE, elle ne clignote pas — et pas du tout si la personne
   a demandé moins d'animation. C'est le seul mouvement de la bulle : il dit
   « ça travaille » sans rien exiger de l'œil. */
.chat-barre[data-qui="attente"] .chat-barre-point { background: var(--muted-foreground); }
@media (prefers-reduced-motion: no-preference) {
  .chat-barre[data-qui="attente"] .chat-barre-point {
    animation: chat-barre-respire 1.8s ease-in-out infinite;
  }
}
@keyframes chat-barre-respire {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

/* /!\ **LES STYLES DU MESSAGE DE L'ASSISTANT IA VIVENT DANS `chat.css`**, pas
   ici, et ce n'est pas un rangement : `chat.css` est chargé APRÈS cette feuille
   et il pose `.chat-auteur { display: block }`. À spécificité égale (0,1,0), le
   dernier chargé gagne — écrite ici, l'étiquette « Assistant IA » se dépliait
   sur deux lignes, l'icône au-dessus du mot. Vu à l'écran le 2026-09-14. */

/* ═══════════════════════════════════════════════════════════════════════════
   CHANTIER « ÉCRANS : LA BULLE, LA MISE EN ROUTE, LA BARRE » (2026-09-16)
   Trois retouches demandées le même soir. Le bloc est en FIN de feuille, d'un
   seul tenant : trois chantiers écrivaient dans ce fichier la même nuit.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── E1 · LE MESSAGE D'UN CHAMP, À LA PLACE DE LA BULLE DU NAVIGATEUR ───────
   Le mécanisme est dans `static/js/validation-champs.js`, et son en-tête dit
   pourquoi la bulle native ne s'habille pas.

   ⚠️ **PAS `.alerte`, ET CE N'EST PAS UN OUBLI.** `charte.css` porte bien un
   `.alerte` fait pour ça (même taille, même jeton `--error`), mais DEUX des
   trois écrans concernés le redéfinissent localement en BANDEAU encadré
   (`parcours.html`, et `.form-error` sur `reset_password.html`) : le même mot
   aurait rendu un pavé rouge sous chaque champ ici, et un message discret
   ailleurs. Une classe propre dit une chose, une seule, partout.

   ⚠️ **L'ÉLÉMENT EST UN `<output>`.** Sur les écrans `auth-socle`,
   `auth_public_fragment.html` est émis APRÈS la feuille de la page et peint
   tout `form label > span:not(.saisie-oeil)` en libellé de champ, à (0,3,3) :
   un `<span>` de message serait ressorti gris et minuscule, et rien ne l'aurait
   dit. */
.champ-alerte {
  display: block; margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-caption); line-height: 1.5;
  color: var(--error);
}
/* Le bloc d'un champ qui porte un message : c'est le `gap` du bloc qui donne
   l'air, jamais une marge du message — cf. `.champ > .alerte`, même raison. */
label.champ.porte-alerte > .champ-alerte { margin: 0; }
/* ⚠️⚠️ **LE MESSAGE D'UNE LIGNE DE RÉGLAGE PREND UNE SECONDE RANGÉE, IL NE SE
   GLISSE PAS DANS LA COLONNE DE DROITE.** Premier jet, posé dans
   `.reglage-valeur` : cette colonne vaut `auto` dans la grille, elle a grossi à
   la largeur de la phrase, et la colonne de gauche est tombée à soixante
   pixels — « Confirmez-le » s'écrivait un mot par ligne, et « 10 caractères
   minimum » sur trois. Mesuré et VU à l'écran le 2026-09-16, en dix secondes.
   ⚠️ `min-width: 0` : sans lui, un élément qui traverse les deux colonnes
   retend exactement le même piège, par sa largeur maximale. */
.reglage-ligne.porte-alerte > .champ-alerte {
  grid-column: 1 / -1; justify-self: end; min-width: 0;
  text-align: right; margin-top: 2px;
}

/* ── E2 · LA MISE EN ROUTE, RÉDUITE À L'ÉTAPE COURANTE ──────────────────────
   Le fondateur : « pour la mise en route je ne suis pas fan de l'affichage du
   guide par étape, ça prend de la place ».

   ⚠️ **CE QUI PRENAIT LA PLACE N'ÉTAIT PAS LA CARTE, C'ÉTAIT LA LISTE DÉPLIÉE
   EN PERMANENCE** : 520 px mesurés à 1280 px sur un compte de démonstration,
   soit l'écran d'arrivée entier, avant le « Bonjour » et avant les traders du
   client. La carte montre désormais l'étape courante et son geste ; les six
   étapes et leurs mini-tutos vivent dans un `<details>` replié.

   ⚠️ Trois règles sont parties avec la refonte (`.mer-carte-tete`,
   `.mer-etape-phrase`, `.mer-etape-gestes`) : plus aucun gabarit ne les porte,
   et une classe morte dans une feuille de 10 000 lignes est un piège pour le
   suivant, qui croit lire un composant vivant. */
.mer-carte { padding: 18px 22px; }
.mer-tete {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
}
.mer-tete-mots { flex: 1 1 320px; min-width: 0; }
.mer-tete-sur {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 7px; flex-wrap: wrap;
}
/* Le titre de la carte passe en sur-titre : ce qu'on doit lire en premier est
   l'ÉTAPE, pas le nom de la liste qui la contient. */
.mer-tete-titre {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: var(--text-2xs, .68rem); letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--muted-foreground);
}
.mer-tete-compte {
  font-size: var(--text-body-sm); font-weight: 600; white-space: nowrap;
}
.mer-tete-compte span { color: var(--muted-foreground); font-weight: 500; }
.mer-tete-etape {
  margin: 0; font-family: var(--font-sans);
  font-size: var(--text-lg); font-weight: 600; letter-spacing: -0.01em;
}
.mer-tete-phrase {
  margin: 3px 0 0; max-width: 62ch;
  font-size: var(--text-body-sm); line-height: 1.5;
  color: var(--muted-foreground);
}
.mer-tete-gestes {
  flex: 0 0 auto; display: flex; align-items: center; gap: 14px;
  margin-top: 4px;
}

/* ── Le dépli des étapes ─────────────────────────────────────────────────── */
/* ⚠️ Le chevron et la casse reprennent ceux du mini-tuto (`guide.css`) : deux
   déplis qui se ressemblent sur le même écran se lisent comme un seul geste. */
.mer-toutes {
  margin-top: 14px; padding-top: 11px;
  border-top: 1px solid var(--border-subtle, rgba(255,255,255,.06));
}
.mer-toutes-tete {
  display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; list-style: none;
  font-size: var(--text-body-sm, .85rem);
  color: var(--accent, rgb(124, 108, 246));
}
.mer-toutes-tete::-webkit-details-marker { display: none; }
.mer-toutes-tete::before {
  content: ''; width: 6px; height: 6px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg); transition: transform 160ms ease;
}
.mer-toutes[open] .mer-toutes-tete::before { transform: rotate(45deg); }
.mer-toutes-tete:hover { color: var(--foreground); }
.mer-toutes-tete:focus-visible {
  outline: 2px solid var(--ring); outline-offset: 3px; border-radius: 4px;
}
/* Un seul des deux mots est montré à la fois : le résumé dit ce que le clic
   FERA, pas ce qu'on voit déjà. */
.mer-toutes .mer-toutes-depliee { display: none; }
.mer-toutes[open] .mer-toutes-plie { display: none; }
.mer-toutes[open] .mer-toutes-depliee { display: inline; }
.mer-toutes .mer-carte-sous { margin-top: 12px; }

/* ── E3 · LA BARRE DE GAUCHE SE REPLIE, ET NE GARDE QUE LES ICÔNES ──────────
   Le fondateur : « j'aimerais également rendre la barre de navigation de gauche
   avec les pages rétractable, et d'afficher que les icônes pour gagner de la
   place si l'utilisateur le souhaite. »

   ⚠️ **LA CLASSE EST SUR `<html>`**, posée par une amorce dans l'en-tête de
   `admin_base.html`. Sur le `<aside>`, il aurait fallu attendre le script
   différé, et la barre se serait repliée sous les yeux à chaque page.

   ⚠️ **LES LIBELLÉS SONT MASQUÉS PAR `font-size: 0`, JAMAIS PAR
   `display: none`.** Le second les retire de l'arbre d'accessibilité : la barre
   repliée deviendrait une rangée de dessins sans nom pour un lecteur d'écran.
   Masqués ainsi, ils restent lus, et l'infobulle de `barre-laterale.js` les
   montre à l'œil. C'est aussi ce qui permet de cacher « Se déconnecter », qui
   n'est pas dans un `<span>` mais posé en texte nu dans le bouton.

   ⚠️ **RIEN N'EST TOUCHÉ SOUS 1024 px** : la barre y est déjà cachée au profit
   du tiroir du menu, qui est un panneau plein écran. Un repli à 64 px n'y
   voudrait rien dire. */
.sidebar { transition: width 160ms var(--ease-out, ease); }

/* ── La flèche qui replie, À CHEVAL SUR LA BORDURE (2026-09-17) ─────────────
   Le fondateur trouvait inesthétique le bouton « Réduire le menu » posé en bas
   de la barre. Il devient une pastille ronde de 26 px, centrée sur le filet
   droit de la barre, en haut : la moitié dans la barre, la moitié dehors.

   ⚠️ **`.sidebar` LAISSE DONC DÉBORDER** (`overflow: visible`). Elle portait
   `overflow: hidden`, qui aurait coupé la pastille en deux. Rien d'autre n'en
   dépendait : la navigation défile dans SON `<nav>` (`overflow-y: auto`,
   `min-height: 0`), et le bas de la barre ne grandit pas.
   ⚠️ **`z-index` SUR LA BARRE** : les enfants de `.main` sont positionnés
   (`z-index: 1`) et, venant après elle, peignaient PAR-DESSUS la moitié
   extérieure de la pastille. 40 reste sous la barre du haut (50). */
.sidebar { position: sticky; overflow: visible; z-index: 40; }
.sidebar-repli {
  position: absolute; top: 18px; right: -13px;
  width: 26px; height: 26px; padding: 0; margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted-foreground);
  background: var(--card);
  border: 1px solid var(--border-strong); border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0,0,0,.35);
  cursor: pointer;
  transition: color 140ms var(--ease-out), border-color 140ms var(--ease-out),
              background 140ms var(--ease-out);
}
.sidebar-repli:hover { color: var(--foreground); border-color: var(--accent); background: var(--muted); }
.sidebar-repli:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.sidebar-repli svg { flex-shrink: 0; transition: transform 160ms var(--ease-out); }

/* ── L'état replié ───────────────────────────────────────────────────────── */
.barre-repliee .sidebar { width: 64px; padding-left: 8px; padding-right: 8px; }
/* ⚠️ `overflow: visible` sur le `<nav>` : il porte `overflow-y: auto`, qui
   rognerait toute infobulle posée à sa droite. Celle de `barre-laterale.js` est
   posée sur le `<body>` et positionnée en `fixed` pour cette raison exacte ;
   cette ligne évite simplement d'avoir en plus une barre de défilement
   horizontale sur 64 px de large. */
.barre-repliee .sidebar nav { overflow-x: visible; }
.barre-repliee .sidebar nav a,
.barre-repliee .sidebar-space-toggle,
.barre-repliee .sidebar-profile-card,
.barre-repliee .sidebar-logout {
  justify-content: center; gap: 0;
  padding-left: 0; padding-right: 0;
  font-size: 0;
}
/* L'entrée ouverte : le filet de gauche n'a plus de place, la pastille pleine
   le remplace. Sans ça, l'icône de la page où l'on se trouve se décalait de
   deux points par rapport aux autres. */
.barre-repliee .sidebar nav a.active,
.barre-repliee .sidebar nav a[aria-current="page"] {
  border-left: 0; padding-left: 0;
}
/* Le nom d'un groupe ne tient pas en 64 px : il ne reste que son filet, qui
   sépare toujours les groupes. */
.barre-repliee .sidebar-section {
  font-size: 0; padding: 12px 0 4px; margin-top: 8px;
}
.barre-repliee .sidebar-profile-info,
.barre-repliee .sidebar-vide { display: none; }
.barre-repliee .sidebar-profile-card { padding-left: 0; padding-right: 0; }
.barre-repliee .sidebar-repli svg { transform: rotate(180deg); }

/* L'infobulle DE L'APPLICATION, jamais celle du navigateur (même refus que la
   bulle du mot de passe, le même soir). En `fixed` : le `<nav>` défile et
   rognerait tout ce qui dépasse de lui. */
.barre-infobulle {
  position: fixed; z-index: 120; pointer-events: none;
  padding: 6px 10px;
  font-family: var(--font-sans); font-size: var(--text-xs);
  color: var(--foreground); white-space: nowrap;
  background: var(--card); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.barre-infobulle[hidden] { display: none; }
/* ⚠️ **LA PASTILLE DES MESSAGES EN ATTENTE SURVIT AU REPLI, EN POINT.** Elle
   est chargée par htmx dans un `<span style="margin-left:auto">` : laissée dans
   le flux d'une entrée de 64 px, elle poussait l'icône vers la gauche, et son
   chiffre disparaissait de toute façon avec la taille de texte. En position
   absolue, elle redevient ce qu'elle doit être : « quelqu'un attend ».
   ⚠️ `position: absolute` SANS `!important` : une marge automatique se résout à
   zéro dès que l'élément est positionné, et le `margin-left` est en style en
   ligne, donc imbattable autrement. */
.barre-repliee .sidebar nav a { position: relative; }
.barre-repliee .sidebar nav a > span[hx-get] {
  position: absolute; top: 4px; right: 9px; margin: 0;
}
.barre-repliee .sidebar .nav-badge-new {
  display: block; width: 8px; height: 8px; padding: 0; margin: 0;
  border-radius: 50%;
}
/* ══ LA CARTE D'UNE ANALYSE PARTAGÉE DANS LA COMMUNAUTÉ (2026-09-16) ═════════════
   Le fragment `partials/communaute_carte.html`, tel qu'il paraît dans le salon d'un
   membre ET dans l'écran de modération de l'équipe.

   /!\ **ELLE EST ICI PARCE QU'ELLE SERT À DEUX ESPACES**, et pour la raison exacte
   qui a fait remonter les six `.teinte-N` : recopiée dans le second gabarit, elle
   aurait montré la MÊME carte de deux façons à deux personnes qui en parlent
   ensemble -- celui qui signale et celui qui modère. Le jour où l'une bouge, l'autre
   ne suit pas, et c'est la modération qui juge sur un autre écran que le plaignant.

   /!\ Elle vit DANS la bulle d'un message : une surface un cran plus sombre et un
   liseré d'accent à gauche. Un lecteur doit voir d'un coup d'oeil ce qui vient du
   trader et ce que le membre en dit lui -- ce sont deux paroles différentes. */
.cm-carte-trade {
  margin: 8px 0 0; padding: 10px 12px;
  background: var(--card); border: 1px solid var(--border);
  border-left: 2px solid var(--accent-ring-strong);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}
/* LE TRADER D'UNE CARTE PARTAGEE (30/09/2026, `partials/partage_trader.html`) : son
   visage cerne de sa couleur, son nom, « Trader IA ». */
.teinte-bord-1 { --pt-teinte: #C9BEFF; } .teinte-bord-2 { --pt-teinte: #8FD6C2; }
.teinte-bord-3 { --pt-teinte: #EEBC8C; } .teinte-bord-4 { --pt-teinte: #A9C9F7; }
.teinte-bord-5 { --pt-teinte: #EBA9CE; } .teinte-bord-6 { --pt-teinte: #DCDCA0; }
.pt-trader { display: flex; align-items: center; gap: 10px; margin: 0 0 10px;
  padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.pt-visage { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex: none;
  border: 2px solid var(--pt-teinte, var(--border)); background: #111; }
.pt-visage--initiale { display: grid; place-items: center; font-weight: 700; }
.pt-qui { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.pt-qui b { font-size: var(--text-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pt-ia { font: 600 10px/1.4 var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted-foreground); }
.cm-ct-tete { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.cm-ct-instrument { font-weight: 700; font-size: var(--text-base); }
.cm-ct-sens {
  padding: 1px 8px; border-radius: var(--radius-pill);
  font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.06em;
  background: var(--accent-ring); color: var(--accent);
}
/* ⚠️ La couleur ne suffit jamais seule : le MOT est écrit à côté, dans la
   pastille elle-meme. Vert et rouge ne se distinguent pas pour tout le
   monde, et c'est la règle de la charte. */
.cm-ct-sens--achat { background: rgba(74,222,128,0.12); color: var(--profit); }
.cm-ct-sens--vente { background: rgba(248,113,113,0.12); color: var(--loss); }
.cm-ct-unite, .cm-ct-date { font-size: var(--text-2xs); color: var(--disabled-foreground); }
.cm-ct-date { margin-left: auto; }
.cm-ct-niveaux {
  display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 7px;
  color: var(--muted-foreground); font-size: var(--text-xs);
}
.cm-ct-niveaux b { color: var(--foreground); font-weight: 500; }
.cm-ct-issue { margin: 7px 0 0; font-size: var(--text-base); font-weight: 600; }
.cm-ct-issue.est-gain { color: var(--profit); }
.cm-ct-issue.est-perte { color: var(--loss); }
.cm-ct-reglage {
  display: flex; flex-wrap: wrap; gap: 5px 8px; margin: 8px 0 0; padding: 0;
  list-style: none;
}
.cm-ct-reglage li {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 8px; border-radius: var(--radius-pill);
  background: var(--muted); border: 1px solid var(--border);
  font-size: var(--text-2xs); color: var(--muted-foreground);
}
.cm-ct-reglage b { color: var(--foreground); }
.cm-ct-raison {
  margin: 9px 0 0; padding-left: 9px;
  border-left: 2px solid var(--border);
  color: var(--muted-foreground); font-style: italic; line-height: 1.5;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.cm-ct-risque {
  margin: 9px 0 0; font-size: var(--text-2xs); color: var(--disabled-foreground);
  line-height: 1.45;
}

/* ============================================================
   .cm-photo — LA PHOTO D'UN MEMBRE, DANS SA PASTILLE (2026-09-17)
   ------------------------------------------------------------
   Le fondateur, 2026-09-16 : « leur photo d'utilisateur, qui sera utilisée
   dans la communauté ». La pastille ne change pas de taille : la photo la
   REMPLIT, et c'est le navigateur qui cadre (`object-fit`). Rien n'est
   redimensionne a l'envoi — le depot n'a pas Pillow — et c'est le plafond de
   2 Mo qui tient l'autre bout.

   /!\ Ici et pas dans `user_communaute.html` : la meme pastille est dessinee
   par la colonne de gauche (28 px) et par le fil (32 px). Une regle ecrite
   dans l'un des deux gabarits aurait montre le meme membre de deux facons.
   ============================================================ */
.cm-photo { display: block; width: 100%; height: 100%;
            border-radius: 50%; object-fit: cover; }

/* LA CARTE D'UN JOURNAL FILTRE, dans la meme bulle (2026-09-16).

   /!\ **ELLE REPREND `.cm-carte-trade` et n'ajoute que ses differences.** Les
   deux cartes se lisent dans le meme fil, l'une sous l'autre : deux dessins
   independants s'y seraient vus comme deux produits. Ce qui change vraiment
   est qu'elle porte des TOTAUX et une liste, la ou l'autre porte un evenement.

   /!\ `.cm-ct-aussi` : les ticks A COTE du montant, quand les deux sont coches.
   Le montant prend la taille, les ticks la discretion -- mais ils restent, parce
   qu'un tick est la seule mesure comparable d'un compte a l'autre. */
.cm-ct-aussi {
  margin-left: 8px; font-size: var(--text-xs); font-weight: 500;
  color: var(--muted-foreground);
}
.cm-cj-chiffres {
  display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 9px 0 0;
}
.cm-cj-chiffres dt {
  font-size: var(--text-2xs); color: var(--muted-foreground);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.cm-cj-chiffres dd {
  margin: 1px 0 0; font-size: var(--text-base); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.cm-cj-chiffres dd.est-gain { color: var(--profit); }
.cm-cj-chiffres dd.est-perte { color: var(--loss); }
/* /!\ « sur 7 » ne se met JAMAIS en petit au point de disparaitre : c'est la
   moitie de l'information. Il est discret, pas absent. */
.cm-cj-sur { font-size: var(--text-xs); font-weight: 500; color: var(--muted-foreground); }

/* LES STATISTIQUES D'UNE CARTE PARTAGEE (2026-10-02), trade ou journal : une
   famille par case cochee, en grille de petits chiffres. Les memes lectures que
   `.cm-cj-chiffres`, en plus discret : ce sont des complements, pas le titre. */
.cm-stats { margin: 10px 0 0; display: grid; gap: 10px; }
.cm-stats-titre {
  margin: 0 0 4px; font-size: var(--text-2xs); font-weight: 600;
  color: var(--muted-foreground); text-transform: uppercase; letter-spacing: 0.05em;
}
.cm-stats-titre span { text-transform: none; letter-spacing: 0; font-weight: 400; }
.cm-stats dl {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 6px 16px; margin: 0;
}
.cm-stats dt { font-size: var(--text-2xs); color: var(--muted-foreground); }
.cm-stats dd {
  margin: 1px 0 0; font-size: var(--text-sm); font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.cm-stats dd.est-gain { color: var(--profit); }
.cm-stats dd.est-perte { color: var(--loss); }
.cm-stats dd.cm-stats-note {
  margin: 0; font-size: var(--text-2xs); font-weight: 400; color: var(--muted-foreground);
}
.cm-cj-lignes { margin: 10px 0 0; padding: 0; list-style: none; }
.cm-cj-lignes li {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 4px 0; border-top: 1px solid var(--border);
  font-size: var(--text-xs); color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
.cm-cj-lignes b { color: var(--foreground); font-weight: 600; }
.cm-cj-lignes .est-gain { color: var(--profit); font-weight: 600; }
.cm-cj-lignes .est-perte { color: var(--loss); font-weight: 600; }
.cm-cj-sens { font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: 0.05em; }
.cm-cj-reste { margin: 7px 0 0; font-size: var(--text-2xs); color: var(--disabled-foreground); }


/* ============================================================
   .menu-pastille -- CE QUI ATTEND LE CLIENT, DANS LA BARRE (2026-09-17)
   ------------------------------------------------------------
   Refonte de l'affichage de la communaute. Le fondateur, 2026-09-16 :
   « pour que les gens aiment passer sur la communaute a dialoguer ». Le
   compteur est le seul mecanisme de ce chantier qui agisse depuis les AUTRES
   ecrans : sans lui, il faut ouvrir la communaute pour savoir s'il s'y passe
   quelque chose, donc on ne l'ouvre pas.

   /!\ Ici et pas dans un gabarit : la barre laterale est dessinee par
   `sidebar_user.html` ET par `sidebar_admin.html`, et une regle ecrite dans
   l'un des deux aurait montre la meme pastille de deux facons.

   /!\ `margin-left: auto` la pousse au bout de la ligne, et `flex: none`
   l'empeche de retrecir quand le libelle est long. La barre repliee sur ses
   icones (chantier du 2026-09-16) masque les libelles : la pastille se replie
   avec eux, en point, sinon elle deborderait d'une barre de 64 points.
   ============================================================ */
.menu-pastille {
  margin-left: auto; flex: none;
  min-width: 19px; height: 19px; padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--accent); color: #14161F;
  font-size: var(--text-2xs); font-weight: 700; line-height: 19px;
  text-align: center;
}
/* /!\ **LA BARRE REPLIEE MET `font-size: 0` SUR TOUTE L'ENTREE**, libelle ET
   pastille : le compteur disparaissait exactement la ou il est le plus utile,
   sur une barre reduite a ses icones. Il devient un POINT, pose sur le coin de
   l'icone. Le nombre se relit en depliant, ou dans l'infobulle.
   /!\ `position: relative` sur l'entree repliee et pas partout : posee en
   permanence, elle aurait fait du filet de l'entree active un enfant positionne
   et decale de deux points. */
.barre-repliee .sidebar nav a { position: relative; }
.barre-repliee .menu-pastille {
  position: absolute; top: 6px; right: 12px;
  min-width: 9px; width: 9px; height: 9px; padding: 0;
  font-size: 0; line-height: 0;
  border: 2px solid var(--background);
}


/* ============================================================
   .cm-carte-trade -- RESSERREE POUR LE SALON (2026-09-17)
   ------------------------------------------------------------
   Refonte de l'affichage. MESURE sur un salon plein : la carte d'un trade
   partage faisait a elle seule plus de quatre cents points, soit plus de la
   moitie de la conversation visible. Un partage est une PIECE JOINTE a un
   message, pas un rapport : il doit se reconnaitre d'un coup d'oeil et
   s'ouvrir si on veut le detail.

   /!\ **AUCUN CHAMP N'EST RETIRE**, et c'est la limite qu'on se donne : la
   mention de risque, les niveaux, les reglages et le resultat restent tous
   la, parce qu'ils portent des engagements (spec, invariant B5) et que les
   retirer serait un autre chantier que celui de l'affichage. Ce qui bouge est
   le rembourrage, la taille du resultat et l'ecart entre les lignes.

   /!\ Ici et pas dans `user_communaute.html` : la MEME carte est dessinee par
   le salon et par l'ecran de moderation (`/team/communaute`). Une regle ecrite
   dans le gabarit du salon aurait montre au moderateur une autre carte que
   celle qui a ete signalee.
   ============================================================ */
.cm-bulle .cm-carte-trade { padding: 9px 11px; margin-top: 7px; }
.cm-bulle .cm-ct-issue { margin-top: 5px; font-size: var(--text-base); }
.cm-bulle .cm-ct-niveaux { margin-top: 5px; }
.cm-bulle .cm-ct-reglage { margin-top: 6px; gap: 4px 6px; }
.cm-bulle .cm-ct-raison { margin-top: 7px; }
.cm-bulle .cm-ct-risque { margin-top: 7px; }

/* ── La durée que couvrent N bougies (cahier des charges, 2026-09-17) ─────────
   Posée par `duree-bougies.js` à côté de chaque nombre de bougies, sur la fiche du
   trader et dans le parcours guidé. Une indication, pas un réglage : même teinte que
   « le mot d'une échelle » (`.elem-mot`), en italique pour ne pas se lire comme un
   champ. Vide, elle ne prend aucune place. */
.duree-bougies {
  font-size: var(--text-xs); font-style: italic; line-height: 1.3;
  color: var(--muted-foreground); white-space: nowrap;
}
.duree-bougies:empty { display: none; }

/* ============================================================
   .t-bande — LES CONSIGNES EN BANDE COMPACTE AU-DESSUS DU JOURNAL (2026-09-21)
   ------------------------------------------------------------
   Choix du fondateur (« bandeau compact ») : la colonne de droite est partie, le
   journal reprend TOUTE la largeur, et avec elle disparaît le seuil de 1460 px qui
   n'existait que pour elle. Repliée, la bande n'est qu'UNE ligne — le compte et
   les gestes, aucune consigne (21/09/2026) ; « Tout voir » déplie la liste sur
   place, sans script (case à cocher + `:has`).
   ============================================================ */
.t-duo { display: block; }
.t-bande {
  margin: 0 0 16px;
  padding: 10px 14px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  display: flex; flex-direction: column; gap: 6px;
}
.t-bande-tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
.t-bande-titre {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  font-size: var(--text-body-sm); color: var(--foreground);
}
.t-bande-titre svg { color: var(--accent); flex: none; }
.t-bande-titre b { font-weight: 600; }
.t-bande-pause { color: var(--muted-foreground); }
.t-bande-gestes { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.t-bande-voir { gap: 4px; cursor: pointer; }
.t-bande-voir span { display: inline-flex; align-items: center; gap: 4px; }
.t-bande-voir .t-bande-voir-moins { display: none; }
.t-bande:has(.t-bande-bascule:checked) .t-bande-voir .t-bande-voir-plus { display: none; }
.t-bande:has(.t-bande-bascule:checked) .t-bande-voir .t-bande-voir-moins { display: inline-flex; }
.t-bande-voir-moins svg { transform: rotate(180deg); }

/* /!\ LA NUMÉROTATION EST UNE DONNÉE (l'ordre de lecture du modèle) : le rang de
   la liste ENTIÈRE, écrit par le serveur, jamais recalculé sur ce qui est visible. */
.t-bande-liste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
/* /!\ REPLIÉE, LA BANDE NE MONTRE AUCUNE CONSIGNE (fondateur, 21/09/2026 : « c'est
   juste l'information, 4 consignes actives, avec le bouton pour tout voir, pour ne
   pas prendre de la place pour rien »). Une seule ligne : le compte et les gestes. */
.t-bande:not(:has(.t-bande-bascule:checked)) .t-bande-liste { display: none; }
.t-bande-ligne {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  padding: 5px 0;
  border-top: 1px solid var(--border);
}
.t-bande-rang {
  flex: none; width: 1.4em; text-align: right;
  font-family: var(--font-mono); font-size: var(--text-caption);
  color: var(--muted-foreground);
}
.t-bande-corps { flex: 1 1 0; min-width: 0; }
.t-bande-corps > summary { list-style: none; cursor: pointer; }
.t-bande-corps > summary::-webkit-details-marker { display: none; }
.t-bande-corps > summary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; border-radius: var(--radius-sm); }
.t-bande-texte {
  display: block;
  font-size: var(--text-body-sm); line-height: var(--leading-normal);
  color: var(--foreground);
}
/* UNE ligne, coupée par « … », tant qu'on ne l'a pas ouverte ; ouverte, en entier,
   avec ses retours à la ligne (une consigne en liste reste une liste). */
.t-bande-corps:not([open]) .t-bande-texte {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.t-bande-corps[open] .t-bande-texte { white-space: pre-line; }
.t-bande-ligne--pause .t-bande-texte,
.t-bande-ligne--pause .t-bande-rang { opacity: 0.45; }
.t-bande-echeance {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px; border-radius: var(--radius-pill);
  background: rgba(167,139,250,0.12); color: #C9BCFF;
  font-size: var(--text-xs); font-family: var(--font-mono);
}
.t-bande-ligne-gestes { flex: none; display: inline-flex; align-items: center; gap: 6px; }
.t-bande-archiver { margin: 0; display: none; }
.t-bande:has(.t-bande-bascule:checked) .t-bande-archiver { display: inline-flex; }
/* Les ARCHIVÉES de l'écran Consignes (2026-09-21) : une section repliée. */
.consignes-archivees {
  margin-top: 18px; padding: 10px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card);
}
.consignes-archivees > summary {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: var(--text-body-sm); font-weight: 500; color: var(--muted-foreground);
}
.consignes-archivees[open] > summary { color: var(--foreground); }
.consignes-archivees-liste { list-style: none; margin: 0; padding: 0; }
.consignes-archivees-liste li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; padding: 8px 0; border-top: 1px solid var(--border);
}
.consignes-archivees-texte {
  flex: 1 1 260px; min-width: 0; white-space: pre-line;
  font-size: var(--text-body-sm); color: var(--muted-foreground);
}
.consignes-archivees-gestes { display: inline-flex; gap: 6px; flex: none; }
/* Sur un grand écran, le crayon se fait discret tant qu'on ne survole pas la ligne :
   il est là (le clavier l'atteint), mais la liste se lit d'abord. L'interrupteur, lui,
   reste net : pâli, un interrupteur ALLUMÉ se lirait éteint. */
@media (hover: hover) and (min-width: 721px) {
  .t-bande-ligne-gestes .t-consigne-crayon { opacity: 0.35; transition: opacity 160ms var(--ease-out); }
  .t-bande-ligne:hover .t-consigne-crayon,
  .t-bande-ligne:focus-within .t-consigne-crayon { opacity: 1; }
}
/* Le poids et « Gérer » : dépliée seulement. Le poids est un FAIT D'ARGENT. */
.t-bande-pied {
  display: none;
  align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding-top: 6px; border-top: 1px solid var(--border);
  font-size: var(--text-caption); color: var(--muted-foreground);
}
.t-bande:has(.t-bande-bascule:checked) .t-bande-pied { display: flex; }
.t-bande-pied a {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--accent); text-decoration: none; font-weight: 500;
}
.t-bande-pied a:hover { text-decoration: underline; }
/* Téléphone : la même bande, un peu plus serrée. */
@media (max-width: 720px) {
  .t-bande { padding: 9px 11px; }
}

.t-consigne-bascule { margin: 0; flex: none; }
/* L'interrupteur de la charte, en petit. /!\ `role="switch"` porte l'état ; la
   couleur seule ne le dirait pas à un lecteur d'écran. */
.t-consigne-interrupteur {
  width: 34px; height: 20px; padding: 0;
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--muted); cursor: pointer; position: relative;
  transition: background 180ms var(--ease-out), border-color 180ms var(--ease-out);
}
.t-consigne-interrupteur::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--muted-foreground);
  transition: transform 180ms var(--ease-out), background 180ms var(--ease-out);
}
.t-consigne-interrupteur[aria-checked="true"] {
  background: var(--accent); border-color: var(--accent);
}
.t-consigne-interrupteur[aria-checked="true"]::after {
  transform: translateX(14px); background: #14161F;
}

/* Le retour au journal, qui a remplacé la barre des trois écrans (2026-09-20).
   /!\ Il occupe la place de `.t-onglets` : même écart au-dessus et au-dessous,
   sinon les deux écrans qui le portent remontent de 20 px et l'en-tête paraît
   décollé de son contenu. */
.t-retour {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 4px 0 18px;
  font-size: var(--text-body-sm); font-weight: 500;
  color: var(--muted-foreground); text-decoration: none;
  transition: color 180ms var(--ease-out);
}
.t-retour:hover { color: var(--foreground); }

/* ============================================================
   `.seg` SUR UN TÉLÉPHONE — LA BARRE DÉBORDAIT DE 9 px (2026-09-20)
   ------------------------------------------------------------
   Trouvé en mesurant la page d'un trader à 390 px (TVNI-90) : toute la page
   défilait horizontalement de 13 px, et la mesure a désigné le dernier cran de
   « Année · Mois · Semaine · Jour · Analyse ». Cinq crans à 14 px de rembourrage
   ne tiennent pas dans 390 px, et `inline-flex` ne sait pas rétrécir : il pousse.

   /!\ **LE DÉFAUT EST ANTÉRIEUR À CE CHANTIER** — mesuré identique avec et sans
   la colonne des consignes — mais il est sur l'écran qu'il refait, et il coûte
   quatre déclarations.

   /!\ **ON FAIT DÉFILER, ON N'ENROULE PAS.** Un `flex-wrap` casserait la pilule
   en deux lignes : le fond arrondi se dédouble, et les crans du bas paraissent
   appartenir à autre chose. Une barre de segments est UNE forme ; elle glisse.

   /!\ **`scrollbar-width: none`** : la barre de défilement grise sur la pilule
   ferait plus laid que le débordement qu'on corrige. Le glissement au doigt
   reste, et `scroll-snap` arrête le geste sur un cran entier plutôt qu'au milieu
   d'un mot.

   /!\ **AU-DESSUS DE 720 px, RIEN NE CHANGE** : la barre tient largement, et une
   règle qui s'appliquerait partout ferait défiler des barres qui n'en ont pas
   besoin — avec le risque de masquer un vrai débordement ailleurs.
   ============================================================ */
@media (max-width: 720px) {
  .seg {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
  }
  .seg::-webkit-scrollbar { display: none; }
  /* /!\ `flex: none` : sans lui, les crans se COMPRESSENT au lieu de glisser, et
     « Semaine » se coupe en « Semai… ». On préfère un cran entier hors du cadre
     à cinq crans illisibles. */
  .seg > * { flex: none; scroll-snap-align: end; }
}

/* Un bouton de barre réduit à son icône (2026-09-20).
   /!\ Le rembourrage horizontal d'un `.btn` est calculé pour un LIBELLÉ : gardé tel
   quel autour d'une icône de 16 px, le bouton reste large de 80 px et ne fait donc
   rien gagner — c'est tout l'objet du passage à l'icône. On le ramène au carré, à la
   même taille que l'export voisin, sinon deux icônes côte à côte n'ont pas la même
   boîte et la barre paraît bancale. */
/* /!\ **CARRÉ PAR `aspect-ratio`, PLUS PAR 32 px FIGÉS** (TVNI-121, 2026-09-21) :
   posé sur le détail d'une analyse à côté d'« Analyser », un carré de 32 px
   paraissait plus petit que son voisin de 2,5 em. La largeur suit maintenant la
   hauteur du `.btn` (ou du `.btn-sm`), donc la rangée garde une seule hauteur. */
.btn.btn-icone-seule {
  padding: 0; aspect-ratio: 1;
  display: inline-flex; align-items: center; justify-content: center;
}


/* Les deux gestes d'une consigne dans la colonne du journal (2026-09-20).
   /!\ **CÔTE À CÔTE, ET C'EST UN RETOUR EN ARRIÈRE ASSUMÉ.** Ils étaient empilés,
   pour épargner de la largeur au texte. Vu à l'écran : sur une consigne COURTE — deux
   lignes — la pile de gestes est plus haute que le texte qu'elle accompagne, et c'est
   elle qui fixe la hauteur de la rangée ; l'interrupteur tombait sous le crayon, en
   décalé, et chaque consigne brève gagnait vingt pixels de vide. En ligne, ils
   prennent 66 px et laissent plus de 230 px au texte : la largeur perdue ne coûte
   presque rien, la hauteur gagnée se voit sur toute la colonne. */
.t-consigne-gestes {
  flex: none; display: flex; align-items: center; gap: 6px;
}
.t-consigne-crayon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--muted-foreground); cursor: pointer;
  transition: color 160ms var(--ease-out), background 160ms var(--ease-out);
}
.t-consigne-crayon:hover { color: var(--foreground); background: var(--muted); }

/* L'échéance d'une consigne, quand elle en a une.
   /!\ Elle est posée APRÈS le corps et occupe la ligne entière (`flex-basis`) : le
   `<li>` est une rangée, et sans cela la date se serait glissée entre le texte et
   l'interrupteur, en les comprimant tous les deux. */
.t-consigne-echeance {
  flex: 1 1 100%; order: 3;
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 4px;
  font-size: var(--text-caption); color: var(--muted-foreground);
}
.t-consigne--eteinte .t-consigne-echeance { opacity: 0.45; }

/* La phrase qui explique un champ, sous lui, dans une fenêtre.
   /!\ Elle vit dans `.champ` : c'est ce qui la garde collée au champ qu'elle
   explique quand la fenêtre se resserre. */
.champ .champ-aide {
  display: block; margin-top: 5px;
  font-size: var(--text-caption); line-height: var(--leading-normal);
  color: var(--muted-foreground);
}

/* Le refus d'un geste de consigne, dans la colonne du journal (2026-09-20).
   /!\ `margin: 0` : `.drawer-flash` porte une marge pensée pour un tiroir ; la
   colonne est une pile `flex` avec son propre `gap`, et les deux se cumulaient. */
.t-consignes-refus { margin: 0; }

/* La confirmation d'un geste de consigne, dans la colonne du journal (2026-09-20).
   /!\ Elle est VERTE et discrète : un bandeau plein aurait pesé plus que la
   consigne qu'il annonce, et cette ligne se lit une fois puis disparaît au
   prochain chargement. */
.t-consignes-dit {
  display: flex; align-items: flex-start; gap: 7px;
  margin: 0; padding: 9px 11px;
  border: 1px solid color-mix(in srgb, var(--success) 35%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--success) 10%, transparent);
  font-size: var(--text-caption); line-height: var(--leading-normal);
  color: var(--foreground);
}
.t-consignes-dit svg { flex: none; margin-top: 1px; color: var(--success); }

/* ============================================================
   .jr-du-jour — LA LIGNE DU JOUR, EN HAUT DU JOURNAL (22/10/2026)
   ------------------------------------------------------------
   Ce qui reste de la carte « Aujourd'hui » en frise (2026-09-21, maquette V6) une
   fois la frise retirée par le fondateur : le graphique de la journée, sous le
   calendrier, montre déjà chaque analyse à son heure. Une ligne sans cadre : la
   phrase de la séance, le résultat du jour, « + Une consigne ».
   ============================================================ */
.jr-du-jour {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 18px; margin: 0 0 16px;
}
.frise-phrase { margin: 0; flex: 1 1 auto; min-width: 0; }
.frise-phrase strong { color: var(--foreground); font-weight: 600; }
.frise-rappel { color: var(--disabled-foreground); white-space: nowrap; }
.jr-du-jour-chiffre { margin: 0; display: inline-flex; align-items: baseline; gap: 8px; }
.jr-du-jour-chiffre > span { display: inline; margin: 0; }
.frise-consignes { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-left: auto; }
.pilote-mot { margin: -8px 0 16px; }
@media (max-width: 720px) {
  .frise-phrase { flex-basis: 100%; }
  .frise-consignes { margin-left: 0; }
}

/* La carte à onglets Historique / Synthèse. */
.onglets-carte {
  padding: 6px 18px 18px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg);
}
.onglets-carte-nav { margin: 8px 0 16px; }
/* La tête de la carte (2026-09-21) : les onglets à gauche, l'échelle à droite. Elle
   pilote les deux onglets, d'où sa place au-dessus d'eux. */
.onglets-carte-tete { display: flex; align-items: center; justify-content: space-between;
                      gap: 10px 16px; flex-wrap: wrap; margin: 8px 0 16px; }
.onglets-carte-tete > .onglets-carte-nav { margin: 0; }
/* Les deux graphiques de la Synthèse, l'un sous l'autre, chacun sous son titre. */
.graphe-titre { margin: 4px 0 12px; }
.courbe + script + .graphe-titre, .courbe ~ .graphe-titre { margin-top: 26px; }
.onglets-carte .barres-legende { margin-bottom: 22px; }
.onglets-carte > [hidden] { display: none; }
/* Les encadrés du journal, posés DANS la carte, ne font plus carte dans la carte. */
.onglets-carte .encadre { background: transparent; border: 0; padding: 0; box-shadow: none; }
@media (max-width: 720px) {
  .onglets-carte { padding: 4px 12px 14px; }
  .onglets-carte-nav { display: flex; width: 100%; }
  .onglets-carte-echelle { display: flex; width: 100%; }
  .onglets-carte-echelle > * { flex: 1 1 0; justify-content: center; padding-left: 4px; padding-right: 4px; }
  .onglets-carte-nav > * { flex: 1 1 0; justify-content: center; }
}
/* Onglets et échelle SANS rechargement (2026-09-22, TVNI-198) : pendant que la carte
   est demandée, elle s'estompe — après un dixième de seconde seulement, pour qu'une
   réponse rapide ne clignote pas. Les enveloppes `.zone-suit` (commandes remises
   d'accord avec la carte) ne comptent pas dans la mise en page. */
.onglets-carte[aria-busy="true"] { opacity: .55; transition: opacity .15s .1s; }
.zone-suit { display: contents; }

/* ══ LA CARTE D'EN-TÊTE DES REVENUS (2026-09-21, maquettes parrainage-b et gains-b)
   Même charpente que la carte « Aujourd'hui » du journal : un surtitre, une phrase,
   les gestes à droite ; dessous, le dessin à gauche et la bande de chiffres à droite.
   Deux écrans s'en servent (Parrainage, Mes gains) : elle vit donc ici. */
.revenu-carte { margin: 0 0 20px; }
.revenu-tete { display: flex; align-items: center; justify-content: space-between;
           gap: 10px 16px; flex-wrap: wrap; margin-bottom: 18px; }
.revenu-phrase { margin-top: 4px; }
.revenu-phrase strong { color: var(--foreground); font-weight: 600; }
.revenu-gestes { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.revenu-gestes .copiable { display: contents; }
.revenu-corps { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 20px 36px; align-items: center; }
.revenu-carte .bande { margin: 0; }
.revenu-raison { margin: 10px 0 0; }
/* Parrainage : l'entonnoir en trois marches, chaque jauge proportionnelle au total. */
.revenu-marches { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.revenu-marche { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.revenu-marche .jauge { height: 8px; border-radius: 999px; background: var(--muted); overflow: hidden; }
.revenu-marche .jauge i { display: block; height: 100%; border-radius: 999px; background: rgba(167,139,250,0.55); }
.revenu-marche--fin .jauge i { background: var(--success); }
.revenu-marche .n { font-family: var(--font-mono); font-size: var(--text-xl); font-weight: 600; color: var(--foreground); }
.revenu-marche .l { font-size: var(--text-caption); color: var(--muted-foreground); }
/* Mes gains : une barre empilée par mois de gain. */
.revenu-barres { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
             gap: 12px; align-items: end; height: 150px; }
.revenu-mois { display: flex; flex-direction: column; align-items: center; gap: 6px;
           height: 100%; justify-content: flex-end; min-width: 0; }
.revenu-mois-col { width: 100%; max-width: 46px; height: 100px; display: flex;
               flex-direction: column; justify-content: flex-end; }
.revenu-mois-col i { display: block; width: 100%; }
.revenu-mois-col i:first-child { border-radius: 6px 6px 0 0; }
.revenu-verse { background: rgba(167,139,250,0.45); }
.revenu-reserve { background: rgba(96,165,250,0.55); }
.revenu-disponible { background: var(--success); }
.revenu-en_attente { background: rgba(251,191,36,0.55); }
.revenu-mois-v { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--foreground); white-space: nowrap; }
.revenu-mois-l { font-size: var(--text-caption); color: var(--muted-foreground); white-space: nowrap; }
.revenu-leg { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px;
          font-size: var(--text-caption); color: var(--muted-foreground); }
.revenu-leg i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 6px; }
@media (max-width: 720px) {
  .revenu-corps { grid-template-columns: 1fr; }
  .revenu-gestes { width: 100%; }
  .revenu-carte .bande { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 10px; }
  .revenu-carte .bande > div { padding-left: 12px; }
  .revenu-carte .bande > div:nth-child(odd) { padding-left: 0; border-left: 0; }
  .revenu-barres { gap: 6px; }
  .revenu-mois-v { font-size: var(--text-2xs); }
}

/* ── Le champ date-heure a la francaise (22/09/2026) ─────────────────────────
   `partials/date_heure.html`. Deux `.saisie` courtes, « a » entre les deux.
   /!\ (0,2,0) : bat la `width: 100%` de `.saisie` (0,1,0), declaree plus haut.
   Le plancher de 16 px au telephone s'applique toujours (il est en !important). */
.date-heure { display: inline-flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.date-heure .saisie { width: auto; padding: 7px 10px; font-size: var(--text-body-sm); }
/* (0,3,0) : bat aussi `.fc-ensuite .saisie { width: auto }` de quotidien.css,
   chargee APRES cette feuille (mesure a 390 points : l'heure s'etirait a 240). */
.date-heure .saisie.date-heure-jour { width: 12ch; }
.date-heure .saisie.date-heure-heure { width: 7ch; text-align: center; }
.date-heure .date-heure-a { color: var(--muted-foreground); font-size: var(--text-body-sm); }
/* Habille par `champs-a-la-charte.js` (TVNI-241) : le bouton du calendrier et de
   l'horloge vit DANS le champ, a droite ; le champ s'elargit d'autant, sinon
   « jj/mm/aaaa » se coupait en « jj/mm/aaa ». */
.date-heure > .champ-maison { flex: none; }
/* /!\ Cinq classes, et c'est voulu : `admin_base.html` redonne `9ch` a l'heure
   par `.field-tight .date-heure .saisie.date-heure-heure`, a quatre classes et
   APRES cette feuille -- a egalite, c'est elle qui gagnait (mesure a 390 px). */
.date-heure .champ-maison.champ-maison--date > .saisie.date-heure-jour { width: calc(12ch + 30px); }
.date-heure .champ-maison.champ-maison--heure > .saisie.date-heure-heure { width: calc(8ch + 34px); text-align: left; }

/* ============================================================
   .chat-budget — LA JAUGE DE L'ASSISTANT IA (A4c2, 23/09/2026)
   ------------------------------------------------------------
   Dans le composeur, au-dessus de la barre d'état. VIDE (compte sans
   abonnement), elle ne prend aucune place ; pleine, UNE ligne discrète :
   un libellé, un trait fin, un pourcentage. Jamais un montant.
   ============================================================ */
.chat-budget:empty { display: none; }
.chat-budget { margin: 0 0 6px; }
.chat-budget-jauge {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--text-2xs); line-height: 1.3;
  color: var(--muted-foreground);
}
.chat-budget-libelle { flex: 0 1 auto; min-width: 0; }
.chat-budget-barre {
  flex: 1 1 60px; height: 4px; min-width: 40px; border-radius: 999px;
  background: var(--muted); overflow: hidden;
}
.chat-budget-barre i {
  display: block; height: 100%; border-radius: 999px; background: var(--primary);
}
.chat-budget-pourcent { flex: none; font-variant-numeric: tabular-nums; }
.chat-budget-atteint .chat-budget-barre i { background: var(--warning, #FBBF24); }
.chat-budget-atteint .chat-budget-libelle { color: var(--foreground); }
/* Le refus « une réponse est déjà en cours » : une ligne sobre, liseré d'accent. */
.chat-budget-refus {
  margin: 0 0 6px; padding: 8px 10px;
  border: 1px solid var(--border); border-left: 3px solid var(--primary);
  border-radius: 8px; font-size: calc(12px * var(--echelle-texte)); line-height: 1.4;
  color: var(--muted-foreground);
}
/* ── A3 (23/09/2026) : les RÉFÉRENCES « @ » d'une consigne ─────────────────────────
   Une donnée citée se lit comme une ÉTIQUETTE, au nom que le client voit sur la fiche
   (« Série 3 »), jamais sous sa balise. Absente : barrée d'une alerte, rien ne se tait.
   /!\ L'étiquette ne se coupe pas en fin de ligne (insécable) : un nom coupé en deux
   se lirait comme deux données. */
.ref-etiquette {
  display: inline-block; white-space: nowrap; vertical-align: baseline;
  padding: 0 7px; margin: 0 1px; border-radius: 999px;
  font-size: .92em; line-height: 1.5; font-weight: 500;
  color: var(--primary);
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
}
.ref-etiquette.ref-absente {
  color: var(--warning-text);
  background: color-mix(in srgb, var(--warning) 12%, transparent);
  border-color: color-mix(in srgb, var(--warning) 40%, transparent);

}
.ref-alerte {
  display: inline-flex; align-items: center; gap: 5px; margin: 4px 0 0;
  font-size: var(--text-sm, .82rem); color: var(--warning-text);
}
/* Consignes respectées (13/10/2026) : ce qui se règle ailleurs. Une PHRASE, pas une
   ligne d'icônes : le texte coule sur toute la largeur et le lien le suit, au lieu de
   s'écraser en colonne à côté de lui sur un téléphone. */
.ref-alerte.consigne-ailleurs { display: block; line-height: 1.45; }
.ref-alerte.consigne-ailleurs svg { display: inline; vertical-align: -2px; margin-right: 2px; }
.ref-alerte.consigne-ailleurs .cg-lien { display: inline; margin-left: 4px; white-space: nowrap; }

/* ── A3 (livraison 2) : le CHAMP À ÉTIQUETTES d'une consigne ───────────────────────
   Servi par `partials/champ_consigne.html` et `static/js/champ-consigne.js`. La zone
   source reste dans le formulaire (c'est elle qui part), cachée à l'oeil seulement. */
.champ-ref-cadre { position: relative; }
.champ-ref-source--cachee {
  position: absolute !important; width: 1px !important; height: 1px !important;
  padding: 0 !important; border: 0 !important; opacity: 0; pointer-events: none;
  overflow: hidden; clip: rect(0 0 0 0); left: 0; top: 0;
}
.champ-ref-zone {
  display: block; width: 100%; box-sizing: border-box; cursor: text;
  white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55;
  padding-right: 38px;             /* le micro de la dictée, en coin */
  max-height: calc(var(--fenetre) * .4); overflow-y: auto;
}
.champ-ref-zone:focus { outline: none; }
.champ-ref-zone--vide::before {
  content: attr(data-exemple); color: var(--muted-foreground); pointer-events: none;
}
.champ-ref-zone .ref-etiquette { cursor: default; user-select: all; }
.champ-ref-bas {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  margin: 6px 0 12px;
}
.champ-ref-bas[hidden] { display: none; }
.champ-ref-citer {
  font: inherit; font-size: var(--text-sm, .82rem); font-weight: 500;
  color: var(--primary); background: none; border: 0; padding: 2px 0; cursor: pointer;
}
.champ-ref-citer:hover { text-decoration: underline; }
.champ-ref-citer:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.champ-ref-aide { font-size: var(--text-xs, .75rem); color: var(--muted-foreground); }

/* /!\ DANS LE FLUX, sous le champ, et pas en surimpression : une fenêtre (`dialog`)
   rogne ce qui la déborde, et la liste s'y coupait à mi-hauteur. */
.champ-ref-liste {
  position: static; margin-top: 4px;
  max-height: 240px; overflow-y: auto; padding: 6px;
  background: var(--card); color: var(--foreground);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md, 8px);
  box-shadow: var(--shadow-md);
}
.champ-ref-liste[hidden] { display: none; }
.champ-ref-liste-tete {
  padding: 4px 8px 6px; font-size: var(--text-xs, .75rem); color: var(--muted-foreground);
}
.champ-ref-liste-groupe {
  padding: 8px 8px 2px; font-size: var(--text-xs, .75rem); font-weight: 600;
  letter-spacing: .02em; text-transform: uppercase; color: var(--muted-foreground);
}
.champ-ref-liste-rien { padding: 8px; font-size: var(--text-sm, .82rem); }
.champ-ref-option {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 8px; border-radius: var(--radius-sm, 4px); cursor: pointer;
}
.champ-ref-option[aria-selected="true"], .champ-ref-option:hover {
  background: color-mix(in srgb, var(--primary) 16%, transparent);
}
.champ-ref-option-nom { font-weight: 500; font-size: var(--text-sm, .88rem); }
.champ-ref-option-detail {
  font-size: var(--text-xs, .75rem); color: var(--muted-foreground); text-align: right;
}
/* Au téléphone, la liste est une FEUILLE pleine largeur, en bas de l'écran : sous le
   champ, elle passerait sous le clavier. */
.champ-ref-liste.champ-ref-liste--feuille {
  position: fixed; left: 0; right: 0; top: auto; bottom: 0; max-height: calc(var(--fenetre) * .45); z-index: 60;
  border-radius: 14px 14px 0 0; padding: 10px 10px calc(10px + env(safe-area-inset-bottom));
}
.champ-ref-liste--feuille .champ-ref-option { padding: 11px 10px; }

/* ══ L'ÉCRAN D'UNE ANALYSE (A5, 23/09/2026) ════════════════════════════════════════
   La LIGNE DE TÊTE collante : verdict, ordre, conviction, résultat, P&L ouvert, puis
   ce qui a déclenché, l'heure et le coût. Même vêtement que l'en-tête de séance du
   journal (`.jr-seance-tete`) : une barre posée, translucide, qui reste en haut.
   /!\ Au téléphone (D8), le verdict tient en deux lignes au plus : un motif plus
   long se coupe d'une ellipse ; il est écrit en entier dans « Ce que ça a donné ». */
.an-tete {
  position: sticky; top: 0; z-index: 25;
  display: flex; flex-direction: column; gap: 4px;
  padding: 10px 14px; margin: 0 0 16px;
  background: rgba(16,18,26,0.94); backdrop-filter: blur(6px);
  border: 1px solid var(--border); border-radius: var(--radius-md);
}
.an-tete-l1 { display: flex; align-items: baseline; gap: 6px 14px; flex-wrap: wrap; }
.an-tete-l2 { display: flex; align-items: center; gap: 4px 12px; flex-wrap: wrap; }
.an-tete-verdict {
  margin: 0; font-size: var(--text-lg); font-weight: 600; line-height: 1.3;
}
.an-tete-motif { font-weight: 500; color: var(--muted-foreground); }
.an-tete--refus .an-tete-verdict, .an-tete--non_analysee .an-tete-verdict { color: var(--foreground); }
.an-tete-ordre { font-size: var(--text-body-sm); }
.an-tete-ordre b, .an-tete-resultat {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.an-tete-solde { margin-left: auto; }
.an-tete-resultat { font-weight: 600; }
.an-tete-resultat--mot { font-family: inherit; font-weight: 500; color: var(--muted-foreground); }
.an-tete-cout { display: inline-flex; align-items: center; gap: 4px; }
.an-tete-pause { color: var(--accent); }
@media (max-width: 640px) {
  .an-tete { padding: 8px 12px; }
  .an-tete-verdict {
    font-size: var(--text-base); flex: 1 1 100%;
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    overflow: hidden;
  }
  .an-tete-resultat { margin-left: 0; }
}

/* La CONVICTION en points (D6) : trois depuis le 26/09/2026, pleins jusqu'à la valeur. Jamais une jauge
   de chance : aucune couleur de gain ni de perte. */
.conv { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.conv-points { display: inline-flex; gap: 3px; }
.conv-point {
  width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--accent); background: transparent;
}
.conv-point--plein { background: var(--accent); }

/* « Ce qui était en vigueur » : une liste de réglages à deux colonnes, puis les
   consignes lues dans leur version. */
.an-vigueur {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 6px 18px; margin: 0 0 14px;
}
.an-vigueur div { display: flex; flex-direction: column; min-width: 0; }
.an-vigueur dt { font-size: var(--text-caption); color: var(--muted-foreground); }
.an-vigueur dd { margin: 0; font-size: var(--text-body-sm); overflow-wrap: anywhere; }
.an-vigueur-titre { font-size: var(--text-body-sm); margin: 6px 0; }
.an-vigueur-consignes { margin: 0; padding-left: 18px; }
.an-vigueur-consignes p { margin: 2px 0 8px; white-space: pre-wrap; }

/* Les FILTRES D'UN GESTE du journal (A5, T3) : des puces qui posent ou retirent une
   pastille. Sur une ligne au bureau, à la ligne au téléphone, jamais de défilement
   horizontal. */
.puces-geste { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0 0 12px; }
.puces-geste-groupe { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.puces-geste-titre {
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted-foreground); margin-right: 2px;
}
.puce-geste {
  display: inline-flex; align-items: center; min-height: 28px; padding: 3px 10px;
  border: 1px solid rgba(180,168,255,0.22); border-radius: 999px;
  background: rgba(180,168,255,0.06); color: var(--foreground);
  font-size: var(--text-sm); text-decoration: none; white-space: nowrap;
}
.puce-geste:hover { background: rgba(180,168,255,0.12); border-color: rgba(180,168,255,0.45); }
.puce-geste[aria-current="true"] {
  background: var(--primary); border-color: var(--primary); color: var(--primary-foreground, #fff);
}

/* « Regrouper par » de la synthèse (A5, T4). Le tableau défile SEUL au téléphone,
   jamais la page. */
.regrouper-choix { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 10px; }
.regrouper-perimetre { font-size: var(--text-xs); margin: 0 0 10px; }
.regrouper-table-cadre { overflow-x: auto; }
.regrouper-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.regrouper-table th, .regrouper-table td {
  padding: 7px 10px; text-align: right; border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.regrouper-table th:first-child, .regrouper-table td:first-child { text-align: left; }
.regrouper-table thead th {
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted-foreground);
}
.regrouper-notes { margin: 10px 0 0; }

@media (max-width: 640px) {
  .regrouper-table { font-size: var(--text-xs); }
  .regrouper-table th, .regrouper-table td { padding: 6px 5px; white-space: normal; }
  /* « Trades soldés » se lit déjà dans « x sur n » de la colonne des gagnants. */
  .regrouper-table th:nth-child(5), .regrouper-table td:nth-child(5) { display: none; }
  /* A5b : deux colonnes de plus (abstentions, refus) ; l'en-tête perd son espacement
     de lettres et ses capitales pour que le tableau tienne sans défilement. */
  .regrouper-table thead th { letter-spacing: 0; text-transform: none; padding: 6px 3px; }
  .regrouper-table td { padding: 6px 3px; }
}

/* ============================================================
   LE STYLE DE RÉDACTION : cinq cartes et le champ « Personnalisé »
   (recette du 24/09/2026, fiche d'un trader)
   ------------------------------------------------------------
   /!\ Une rangée EMPILÉE (`.reglage-ligne--pile`) : la question en haut, les
   cartes dessous sur toute la largeur. Côte à côte, cinq cartes et un grand
   champ de texte se tassaient dans la colonne étroite d'une valeur.
   /!\ La carte est un `<label>` qui porte un vrai bouton radio, masqué à l'oeil
   mais pas au clavier ni au lecteur d'écran : les flèches changent de style. */
.reglage-ligne--pile { grid-template-columns: minmax(0, 1fr); align-items: start; }
.style-choix {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
}
.style-carte {
  position: relative; display: flex; flex-direction: column; gap: 4px;
  padding: 12px 14px; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--card);
  transition: border-color var(--motion-base) var(--ease-out),
              background var(--motion-base) var(--ease-out);
}
.style-carte:hover { border-color: var(--border-strong); }
.style-carte > input {
  position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none;
}
.style-carte:has(> input:checked) {
  border-color: var(--accent); background: var(--accent-wash);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.style-carte:has(> input:focus-visible) { outline: 2px solid var(--ring); outline-offset: 2px; }
.style-carte-nom {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: var(--text-sm); font-weight: 600; color: var(--foreground);
}
.style-carte:has(> input:checked) .style-carte-nom { color: var(--accent); }
.style-carte-defaut {
  font-size: var(--text-xs); font-weight: 500; color: var(--muted-foreground);
  padding: 1px 7px; border: 1px solid var(--border); border-radius: var(--radius-pill);
}
.style-carte-quoi { font-size: var(--text-body-sm); color: var(--muted-foreground); line-height: 1.4; }

/* Le champ « Personnalisé » : juste sous les cartes, grand et confortable. */
.style-perso { display: flex; flex-direction: column; gap: 6px; }
.style-perso[hidden] { display: none; }
.style-perso-titre { font-size: var(--text-sm); font-weight: 600; color: var(--foreground); }
.style-perso .reglage-quoi { margin: 0 0 4px; }
.style-perso .dictee-cadre { width: 100%; }
.style-perso-zone {
  display: block; width: 100%; min-height: 128px; resize: vertical;
  padding: 12px 14px; line-height: 1.55; font-size: var(--text-sm);
}
.style-perso-compte {
  margin: 0; align-self: flex-end;
  font-size: var(--text-xs); color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
  .style-choix { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   .accord — UNE CARTE DE CONSENTEMENT (TVNI-249, 25/09/2026)
   ------------------------------------------------------------
   Demande du fondateur, sur l'inscription : « je n'aime pas trop la coche
   pour le consentement, il faudrait la rendre plus esthetique ». Le
   composant est PARTAGE (`partials/accord.html`) : inscription, bulle, et
   demain evenements et rendez-vous.

   /!\ **TOUTE LA CARTE EST LE CLIC** : un `<label>` qui porte une vraie case,
   masquee. Le voyant a droite suit la case par `~`, et la carte par `:has()`.
   Aucun script : une case qui dependrait d'un script serait morte le jour ou
   il ne se charge pas.

   /!\ **Dans les ecrans `.auth-socle`**, la regle du fragment
   `form label > span:not(.saisie-oeil)` (trois classes) touche les enfants
   DIRECTS du label : `.accord-corps` et `.accord-voyant`. On ne leur confie
   donc ni taille de titre ni couleur de coche : le titre et la phrase sont
   des petits-enfants, et le trait de la coche porte sa couleur lui-meme
   (`stroke`), pas `currentColor`.
   ============================================================ */
.accord {
  position: relative;
  display: flex; align-items: flex-start; gap: 14px;
  padding: 13px 14px 13px 15px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  background: rgba(255,255,255,0.02);
  cursor: pointer; text-align: left;
  transition: border-color var(--motion-base), background var(--motion-base),
              box-shadow var(--motion-base);
}
.accord > input {
  position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0;
  pointer-events: none;
}
.accord-corps { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.accord-titre {
  font-size: var(--text-base); font-weight: 600; line-height: 1.35;
  color: var(--foreground);
}
.accord-phrase {
  font-size: var(--text-sm); line-height: 1.5; color: var(--muted-foreground);
}
.accord-phrase a { color: var(--accent); text-decoration: underline;
  text-underline-offset: 2px; text-decoration-color: rgba(180,168,255,.45); }
.accord-phrase a:hover { text-decoration-color: var(--accent); }
.accord-mention {
  margin-top: 3px;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--disabled-foreground);
}
.accord--requis .accord-mention { color: var(--accent); }
.accord-voyant {
  flex: 0 0 22px; width: 22px; height: 22px; margin-top: 1px;
  display: grid; place-items: center;
  border-radius: 6px; border: 1.5px solid rgba(245,244,240,0.28);
  background: transparent;
  transition: background var(--motion-base), border-color var(--motion-base);
}
.accord-voyant svg { display: block; width: 14px; height: 14px; max-width: none;
  stroke: var(--on-accent); opacity: 0; transform: scale(.6);
  transition: opacity var(--motion-base), transform var(--motion-base); }
.accord:hover { border-color: rgba(255,255,255,0.22); background: rgba(255,255,255,0.035); }
.accord:hover .accord-voyant { border-color: rgba(245,244,240,0.5); }
.accord > input:checked ~ .accord-voyant { background: var(--accent); border-color: var(--accent); }
.accord > input:checked ~ .accord-voyant svg { opacity: 1; transform: none; }
.accord:has(> input:checked) {
  border-color: rgba(180,168,255,0.55); background: var(--accent-wash);
}
.accord:has(> input:focus-visible) { box-shadow: 0 0 0 3px var(--accent-ring-strong); }
.accord--erreur:not(:has(> input:checked)) { border-color: rgba(248,113,113,0.6); }
/* Une pile de cartes : l'espace entre deux accords, et rien d'autre. */
.accords { display: flex; flex-direction: column; gap: 10px; }

/* /!\ LES ACCORDS EN LIGNES (05/10/2026, fondateur : « trop indigeste ») : une
   case facultative n'est plus une carte encadree, c'est une petite ligne, la
   case a GAUCHE (on la cherche la ou on lit), le titre et une phrase courte.
   L'accord obligatoire est une phrase discrete au-dessus du bouton. */
.accords:has(.accord--ligne, .accord-info) { gap: 8px; }
.accord.accord--ligne {
  gap: 10px; padding: 2px 0; border: 0; border-radius: 0;
  background: none; align-items: flex-start;
}
.accord--ligne:hover, .accord--ligne:has(> input:checked) {
  background: none; border-color: transparent;
}
.accord--ligne:has(> input:focus-visible) { box-shadow: none; }
.accord--ligne:has(> input:focus-visible) .accord-voyant {
  box-shadow: 0 0 0 3px var(--accent-ring-strong);
}
.accord--ligne .accord-voyant { flex-basis: 18px; width: 18px; height: 18px; border-radius: 5px; }
.accord--ligne .accord-voyant svg { width: 12px; height: 12px; }
.accord--ligne .accord-corps { display: block; gap: 0; }
.accord--ligne .accord-titre {
  display: inline; font-size: var(--text-sm); font-weight: 500;
}
.accord--ligne .accord-phrase { display: inline; font-size: var(--text-xs); }
.accord--ligne .accord-phrase::before { content: " · "; }
.accord-info {
  margin: 2px 0 0; font-size: var(--text-xs); line-height: 1.5;
  color: var(--muted-foreground); text-align: left;
}
.accord-info a { color: var(--accent); text-decoration: underline;
  text-underline-offset: 2px; text-decoration-color: rgba(180,168,255,.45); }

/* ============================================================
   MON JOURNAL (prefixe `jp-`) : la note du client (TVNI-259)
   ============================================================
   Les etiquettes (puces, panneau, page « Mes étiquettes ») sont retirees le 23/10/2026
   (decision du fondateur). */
.jp-erreur { color: var(--error); }

/* Le bloc du DETAIL. */
.jp-bloc { display: flex; flex-direction: column; gap: 14px; }
.jp-note-form { display: flex; flex-direction: column; gap: 8px; margin: 0; }
/* /!\ La note est du TEXTE, pas un nombre : la chasse fixe de `.saisie` se retire. */
.jp-note {
  font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.55;
  min-height: 84px; resize: vertical;
}
.jp-note-pied { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.jp-note-pied [data-note-etat] { color: var(--muted-foreground); }
.jp-note-pied .btn { margin-left: auto; }

/* La rangee d'une LIGNE du journal, posee seulement quand le client a ecrit une note :
   sous la ligne, alignee sur son corps (la colonne de l'heure fait 3,4 em en taille de
   legende, plus l'ecart de la grille). */
.jp-ligne {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: 4px 13px 2px calc(13px + 3.4 * var(--text-sm) + 12px);
}
.jp-a-une-note {
  display: inline-flex; align-items: center; color: var(--muted-foreground);
  padding: 2px 4px; border-radius: var(--radius-sm);
}
.jp-a-une-note:hover { color: var(--foreground); }
[hidden] + .jp-ligne { display: none; }

/* ── « MON IA » : Claude, ChatGPT branchés sur l'espace (26/09/2026) ───────────
   Le menu (`partials/menu_ia.html`) reprend `.menu` ; seules ses pastilles et
   ses marges vivent ici, UNE fois pour la bulle, les traders, les analyses et le
   profil. La couleur d'une IA n'est pas une marque de TVNI : un repère, rien de plus. */
.ia-marque { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: calc(11px * var(--echelle-texte)); font-weight: 700; color: #0A0B10; }
.ia-marque--claude { background: #D97757; }
.ia-marque--chatgpt { background: #E5E7EB; }
.menu-ia { display: inline-block; }
.menu-ia > summary { display: inline-flex; align-items: center; gap: 5px; }
/* /!\ LARGEUR FLUIDE ET BORNÉE (TVNI-280) : 264 points, jamais plus que l'écran. Le
   texte « Branchez… » avait une largeur FIXE de 250 : le panneau ne se resserrait
   pas sur un téléphone. Elle vit sur le panneau, et le texte la suit. */
.menu-ia-panneau { min-width: 0; width: 264px;
  max-width: calc(100vw / var(--echelle) - 32px); }
.menu-ia-titre { padding: 8px 11px 4px; font-size: var(--text-2xs); letter-spacing: .12em;
  text-transform: uppercase; color: var(--disabled-foreground); }
.menu-ia-vide { margin: 0; padding: 4px 11px 10px; white-space: normal;
  font-size: var(--text-sm); line-height: 1.5; color: var(--muted-foreground); }
.menu-ia-fin { margin-left: auto; display: inline-flex; color: var(--disabled-foreground); }
.menu-ia-doux { color: var(--muted-foreground); }
.menu-ia-limite { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 8px; }
/* « DEMANDER À L'IA », dans la barre du haut (« Mon IA » jusqu'au 27/09/2026).
   /!\ EN DORÉ, la teinte de `.an-bouton` (l'ancien « Analyser ») et PAS une couleur neuve
   (fondateur, 27/09/2026 : « il faudrait le mettre en jaune ») : les deux gestes
   ouvrent la même IA, ils se reconnaissent. La forme reste celle de la barre
   (hauteur de « Parrainez un proche »). */
.menu-ia--barre { flex-shrink: 0; }
.menu-ia-pastille { display: inline-flex; align-items: center; gap: .5em; height: 2.5em;
  padding: 0 .9em; border-radius: 8px; border: 1px solid rgba(251, 191, 36, 0.32);
  background: rgba(251, 191, 36, 0.16); color: var(--warning-text); font-size: var(--text-sm);
  font-weight: 600; letter-spacing: -.005em; transition: all .16s ease; }
.menu-ia-pastille:hover { background: rgba(251, 191, 36, 0.26); border-color: rgba(251, 191, 36, 0.52);
  color: #FDE68A; }
.menu-ia-pastille:hover .an-bouton-etincelle { filter: drop-shadow(0 0 7px rgba(252, 211, 77, 0.85)); }
.menu-ia--barre[open] > .menu-ia-pastille { background: rgba(251, 191, 36, 0.26);
  border-color: rgba(251, 191, 36, 0.52); }
.menu-ia--barre .menu-panneau { top: calc(100% + 8px); }
@media (max-width: 640px) { .menu-ia-pastille-mot { display: none; } }
/* « DEMANDER À L'IA », AU PIED DU MENU DE GAUCHE (fondateur, 01/10/2026 : « en violet
   transparent, comme Parrainez un proche »). /!\ La teinte est CELLE de
   `.topbar-invite-btn`, recopiée valeur pour valeur : les deux gestes se lisent
   comme une même famille. Le panneau s'ouvre VERS LE HAUT (le bouton est en bas),
   et à DROITE de la barre quand elle est repliée. */
.menu-ia--cote { width: 100%; }
.menu-ia-cote { display: flex; align-items: center; gap: .6em; padding: 9px 12px;
  list-style: none; cursor: pointer; border-radius: 8px;
  background: linear-gradient(180deg, rgba(200,190,255,.18), rgba(139,127,224,.14));
  border: 1px solid rgba(180,168,255,.34); color: #E4DEFF;
  font-size: var(--text-sm); font-weight: 600; letter-spacing: -.005em;
  box-shadow: 0 1px 0 rgba(255,255,255,.06) inset, 0 4px 14px -8px rgba(139,127,224,.7);
  transition: all .16s ease; }
.menu-ia-cote::-webkit-details-marker { display: none; }
/* ⚠️ Plus précis que `.menu > summary` (en ligne), sinon le bouton ne s'étire pas
   sur la largeur de la barre comme ses voisins. */
.menu-ia--cote > .menu-ia-cote { display: flex; width: 100%; box-sizing: border-box; }
/* Sur téléphone, centré comme « Parrainez un proche », juste au-dessus. */
.sidebar-mobile .menu-ia-cote { justify-content: center; }
.menu-ia-cote:hover, .menu-ia--cote[open] > .menu-ia-cote {
  background: linear-gradient(180deg, rgba(200,190,255,.30), rgba(139,127,224,.22));
  border-color: rgba(180,168,255,.55); color: #FFFFFF; }
.menu-ia--cote .menu-panneau { top: auto; bottom: calc(100% + 8px); left: 0; right: auto; }
.menu-ia--cote .menu-ia-q--premiere { color: var(--accent-hi); }
/* « Brancher mon IA », dans le panneau : violet comme son bouton, plus doré. */
.menu-ia--cote .menu-ia-brancher .an-bouton {
  background: linear-gradient(180deg, rgba(200,190,255,.18), rgba(139,127,224,.14));
  border-color: rgba(180,168,255,.34); color: #E4DEFF; }
.menu-ia--cote .menu-ia-brancher .an-bouton:hover {
  background: linear-gradient(180deg, rgba(200,190,255,.30), rgba(139,127,224,.22));
  border-color: rgba(180,168,255,.55); color: #FFFFFF; }
.menu-ia--cote .menu-ia-brancher .an-bouton-etincelle { color: inherit; filter: none; }
.barre-repliee .menu-ia-cote { justify-content: center; gap: 0; padding-left: 0;
  padding-right: 0; font-size: 0; }
.barre-repliee .menu-ia--cote .menu-panneau { bottom: 0; left: calc(100% + 10px); }
/* Les QUESTIONS TOUTES PRÊTES (27/09/2026) : un libellé court, sur deux lignes au
   plus ; la question entière est dans l'infobulle. Le panneau s'élargit pour elles. */
.menu-ia--barre .menu-ia-panneau { width: 296px; }
.menu-ia--barre .menu-item { font-size: var(--text-sm); }
.menu-ia-q { white-space: normal; line-height: 1.35; }
.menu-ia-q-texte { flex: 1 1 auto; min-width: 0; }
.menu-ia-q--premiere { font-weight: 600; color: var(--warning-text); }
.menu-ia-q.menu-item--inerte { padding-top: 7px; padding-bottom: 7px; }
.menu-ia-brancher { padding: 0 11px 8px; }
.menu-ia-brancher .an-bouton { width: 100%; justify-content: center; display: inline-flex;
  align-items: center; gap: 6px; }
/* Deux IA branchées : « Claude | ChatGPT », en tête du menu, retenu (`demander-ia.js`). */
.menu-ia-choix { display: flex; gap: 4px; padding: 4px; margin: 2px 2px 4px;
  border: 1px solid var(--border); border-radius: var(--radius-md); }
.menu-ia-choix-ia { flex: 1 1 0; display: inline-flex; align-items: center; justify-content: center;
  gap: 6px; padding: 5px 6px; border: 0; border-radius: 6px; background: none; cursor: pointer;
  font: inherit; font-size: var(--text-sm); font-weight: 500; color: var(--muted-foreground); }
.menu-ia-choix-ia .ia-marque { width: 16px; height: 16px; flex-basis: 16px; font-size: calc(9px * var(--echelle-texte)); }
.menu-ia-choix-ia:hover { color: var(--foreground); }
.menu-ia-choix-ia[aria-pressed="true"] { background: var(--muted); color: var(--foreground); }
/* La mise en route : l'étape FACULTATIVE « Brancher votre IA », hors décompte. */
.mer-facultatif { margin-top: 18px; padding-top: 16px; border-top: 1px dashed var(--border-strong); }
.mer-facultatif-tete { font-size: var(--text-2xs); letter-spacing: .14em; text-transform: uppercase;
  color: var(--disabled-foreground); margin: 0 0 10px; }
.mer-etape .mer-puce.mer-puce--ia { color: var(--accent); background: rgba(139,127,224,.14);
  border: 1px solid rgba(180,168,255,.35); }
.mer-etape .mer-puce.mer-puce--ia svg { width: 13px; height: 13px; }
.tuto-corps.tuto-corps--sans-dessin { display: block; }
.tuto-corps--sans-dessin .tuto-texte { max-width: 68ch; }
/* « Construire avec l'IA » (09/10/2026, `partials/porte_ia.html`) : avec DEUX IA
   branchées, la carte porte un bouton par IA (elle n'est plus un lien entier). */
.porte-ia-choix { cursor: default; }
.porte-ia-boutons { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }

/* ============================================================
   Une RESSOURCE de la bibliothèque, lue par un humain (28/09/2026)
   ============================================================
   Le HTML vient de `bibliotheque/rendu.py` : paragraphes, titres h3/h4,
   listes. Une ligne de lecture bornée : un article d'aide se lit, il ne
   s'étale pas sur toute la largeur d'un grand écran. */
.ressource-texte { max-width: 72ch; line-height: 1.6; }
.ressource-texte p { margin: 0 0 12px; }
.ressource-texte h3 { margin: 22px 0 8px; font-size: var(--text-lg); font-weight: 600; }
.ressource-texte h4 { margin: 18px 0 6px; font-size: var(--text-base); font-weight: 600; }
.ressource-texte h3:first-child, .ressource-texte h4:first-child { margin-top: 0; }
.ressource-texte ul, .ressource-texte ol { margin: 0 0 12px; padding-left: 22px; }
.ressource-texte li { margin: 4px 0; }
/* La liste des ressources : une grille, une carte par fiche. Quarante cartes
   empilées l'une sous l'autre ne se parcourent plus. */
.cartes.ressources-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px; margin-bottom: 28px;
}
.ressources-grille .carte { padding: 16px 18px; }

/* La plateforme d'un trader, dans les gestes de son en-tête (28/09/2026) : le logo de
   SA plateforme, un point orange quand son plugin est à mettre à jour. */
.pf-icone { position: relative; }
.pf-icone img { display: block; width: 18px; height: 18px; object-fit: contain; }
.pf-icone--maj { border-color: rgba(251,191,36,.55); }
.pf-icone--maj::after {
  content: ''; position: absolute; top: -3px; right: -3px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--warning); box-shadow: 0 0 0 2px var(--background);
}
/* Le bandeau « plugin à mettre à jour », en tête des pages du trader. */
.alerte-plugin { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  justify-content: space-between; margin: 0 0 18px; }
.alerte-plugin > div { flex: 1 1 320px; }
.tr-icone.pf-icone img { width: 1.15em; height: 1.15em; }

/* Un champ qui n'apparait qu'avec sa case (`accord.js`, `data-si-coche`) :
   `hidden` doit gagner sur le `display` de son conteneur. */
[data-si-coche][hidden] { display: none !important; }

/* ============================================================
   LE LIEN À PARTAGER (11/10/2026, promu depuis la fenêtre de parrainage)
   ============================================================
   Un lien tronqué sur une ligne, son bouton de copie COLLÉ à droite, puis les
   boutons d'envoi (WhatsApp, e-mail, SMS, « Autre »). Né dans
   `partials/invite_panel.html`, repris par la carte d'une offre
   (`partials/offre_carte.html`) puis par « Mon programme » : la règle vit ICI,
   une seule fois, et le balisage dans `partials/partage_lien.html`.
   /!\ La copie et le partage natif sont tenus par `js/copier.js`, pour toute
   la page : aucun écran ne repose son propre écouteur.
   /!\ Ce n'est pas `.copiable` : celui-là passe à la ligne pour montrer une clé
   ENTIÈRE ; un lien à partager se reconnaît à son début, et son bouton ne doit
   jamais tomber sous lui. */
/* Le lien : le même objet que la clé d'un trader, en plus étroit. */
.mp-lien{
  display:flex;align-items:center;gap:10px;padding:10px 10px 10px 13px;
  background:var(--muted);border:1px solid var(--border-strong);border-radius:9px;
}
.mp-lien-tag{
  flex-shrink:0;font-size: var(--text-2xs);font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--disabled-foreground);
}
.mp-lien code{
  flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size: var(--text-sm);color:var(--foreground);
}
.mp-lien-copier{
  flex-shrink:0;width:30px;height:30px;display:inline-flex;align-items:center;
  justify-content:center;background:rgba(255,255,255,.05);
  border:1px solid var(--border-strong);border-radius:7px;
  color:var(--muted-foreground);cursor:pointer;transition:all .14s ease;
}
.mp-lien-copier:hover{border-color:var(--accent);color:var(--accent)}
.mp-lien-copier.est-copie{border-color:rgba(74,222,128,.42);color:#7CE7A6}

.mp-partages{display:flex;gap:7px;margin-top:10px;flex-wrap:wrap}
.mp-partage{
  flex:1;min-width:96px;display:inline-flex;align-items:center;justify-content:center;
  gap:7px;padding:8px 10px;font-family:inherit;font-size: var(--text-sm);font-weight:500;
  background:transparent;border:1px solid var(--border-strong);border-radius:8px;
  color:var(--muted-foreground);cursor:pointer;text-decoration:none;
  transition:all .14s ease;
}
.mp-partage:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-wash)}
.mp-partage svg{opacity:.8}
.mp-presentation{
  display:flex;align-items:center;gap:12px;margin-top:12px;padding:11px 13px;
  border:1px solid var(--border-strong);border-radius:10px;text-decoration:none;color:var(--foreground);
  transition:all .14s ease;
}
.mp-presentation:hover{border-color:var(--accent);background:var(--accent-wash)}
.mp-presentation svg{flex:none;color:var(--accent)}
.mp-presentation b{display:block;font-size: var(--text-sm);font-weight:600}
.mp-presentation small{display:block;font-size: var(--text-xs);color:var(--muted-foreground);line-height:1.4}
/* Au téléphone, un doigt et non une souris : 40 points au moins. */
@media (max-width: 720px) {
  .mp-lien-copier { width: 40px; height: 40px; }
  .mp-partage { min-height: 40px; }
}

/* ============================================================
   LA BARRE DE FAITS (11/10/2026, promue depuis « Vos places »)
   ============================================================
   Trois ou quatre faits côte à côte, chacun son libellé au-dessus de sa valeur,
   et au plus UN geste à droite. Née sur la page Abonnement (TVNI-259), reprise
   par « Mon programme ». Au téléphone, chaque fait devient une ligne
   « libellé … valeur ». */
/* La barre du haut : trois faits et UN bouton. */
.vp-barre { display: flex; align-items: stretch; gap: 0; flex-wrap: wrap;
            background: var(--muted); border: 1px solid var(--border);
            border-radius: 14px; padding: 6px 8px 6px 4px; }
.vp-barre--essai { border-color: rgb(250 204 21 / 0.35); }
.vp-barre--refuse { border-color: rgb(248 113 113 / 0.40);
                    background: rgb(248 113 113 / 0.05); }
.vp-fait { display: flex; flex-direction: column; justify-content: center; gap: 3px;
           padding: 8px 18px; border-right: 1px solid var(--border); min-width: 0; }
.vp-fait:last-of-type { border-right: none; }
.vp-fait .l { font-size: var(--text-xs); color: var(--muted-foreground); }
.vp-fait .v { font-size: var(--text-base); font-weight: 600; color: var(--foreground);
              font-variant-numeric: tabular-nums; white-space: nowrap; }
.vp-fait .vp-ensuite { display: block; margin-top: 3px; font-size: var(--text-xs);
                       font-weight: 400; color: var(--muted-foreground); }
.vp-fait .v--alerte { color: var(--warning-text, var(--warning)); }
.vp-fait .v--refus { color: var(--error); }
.vp-fait .v small { font-weight: 400; color: var(--muted-foreground);
                    font-size: var(--text-xs); }
.vp-barre-geste { margin-left: auto; display: flex; align-items: center;
                  padding: 6px 4px 6px 12px; }
@media (max-width: 720px) {
  .vp-barre { flex-direction: column; padding: 6px; }
  .vp-fait { border-right: none; border-bottom: 1px solid var(--border);
             flex-direction: row; justify-content: space-between;
             align-items: baseline; padding: 10px 12px; }
  .vp-fait:last-of-type { border-bottom: none; }
  .vp-fait .v { white-space: normal; text-align: right; }
  /* Sur un téléphone, le code et « ensuite … » passent chacun sur leur ligne,
     sous le montant : coupés au milieu, ils se lisaient de travers. */
  .vp-fait .v small { display: block; margin-top: 3px; }
  .vp-barre-geste { margin: 4px 0 0; padding: 6px; }
  .vp-barre-geste .btn { width: 100%; justify-content: center; }
}

/* ══ LE JOURNAL D'UN TRADER, REVU DU POINT DE VUE DU CLIENT (fondateur, 11/10/2026) ══
   Trois onglets (Calendrier, Trades, Statistiques), une barre réduite à Réel / Rejeu,
   « Filtrer » et le partage. */
.jr-barre { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 12px; }
.jr-barre > .basc { margin: 0; }
.jr-barre > .cmd-outils { margin-left: auto; }
.jr-filtrer { gap: 7px; }
.jr-filtrer[aria-expanded="true"] { border-color: var(--accent-ring-strong); color: var(--accent-hi); }
.jr-filtrer-pose { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); }
.jr-filtres { margin-bottom: 14px; }

/* Le calendrier : douze mois, le mois ouvert, sa grille. */
.cal { margin: 0 0 22px; }
.cal-annee { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 14px; }
.cal-annee-n { font-weight: 600; font-variant-numeric: tabular-nums; min-width: 3em; text-align: center; }
.cal-fleche {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 30px; height: 30px; border-radius: var(--radius-md);
  border: 1px solid var(--border); color: var(--muted-foreground);
}
.cal-fleche:hover { color: var(--foreground); border-color: var(--border-strong); }
.cal-fleche[aria-disabled="true"] { opacity: .3; pointer-events: none; }
.cal-mois-rangee { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 6px; flex: 1 1 520px; }
.cal-mois {
  display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 5px 2px;
  border-radius: var(--radius-md); border: 1px solid var(--border);
  font-size: var(--text-xs); color: var(--muted-foreground); text-decoration: none;
}
.cal-mois:hover { border-color: var(--border-strong); color: var(--foreground); }
.cal-mois--ouvert { border-color: var(--accent); color: var(--foreground); background: var(--accent-wash); }
.cal-mois-v { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; }
.cal-mois-v--rien { opacity: .4; font-weight: 400; }
.cal-tete { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap; margin: 0 0 12px; }
.cal-titre { margin: 0; font-size: var(--text-lg); font-weight: 600; min-width: 8.5em; text-align: center; }
.cal-bilan {
  margin-left: auto; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: flex-end;
  gap: 2px 14px; font-size: var(--text-sm); color: var(--muted-foreground);
}
.cal-bilan-v { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; font-variant-numeric: tabular-nums; }
/* La refonte du journal (22/10/2026) : UNE navigation de date. Le titre du mois ouvre
   la rangée des douze mois (`<details>`), posée par-dessus la grille. */
.cal-choix-mois { position: relative; }
.cal-choix-mois > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; justify-content: center; }
.cal-choix-mois > summary::-webkit-details-marker { display: none; }
.cal-choix-mois > summary .chev { font-size: var(--text-xs); color: var(--muted-foreground); }
/* ⚠️ Il porte aussi `.menu-panneau`, qui le met en COLONNE : sans `flex-direction: row`,
   les flèches de l'année s'empilaient et la rangée des mois prenait ses 520px de base
   EN HAUTEUR (fondateur, 22/10/2026 : « bug d'affichage »). Ancré à GAUCHE du titre :
   centré, il débordait de l'écran du côté de la flèche du mois précédent. */
.cal-choix-mois > .cal-annee {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: auto; transform: none;
  flex-direction: row; justify-content: center;
  width: min(420px, calc(100vw - 32px)); margin: 0; padding: 10px;
  background: var(--card); border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  box-shadow: 0 12px 32px rgba(0,0,0,.45);
}
.cal-choix-mois .cal-mois-rangee { flex: 1 1 100%; grid-template-columns: repeat(6, minmax(0, 1fr)); margin-top: 4px; }
/* Au téléphone, le titre est déjà décalé par la flèche du mois : quatre mois par rang. */
@media (max-width: 640px) {
  .cal-choix-mois > .cal-annee { width: min(290px, calc(100vw - 96px)); }
  .cal-choix-mois .cal-mois-rangee { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* La journée sous le calendrier : sa tête, son graphique repliable. */
.jr-journee { margin: 4px 0 18px; scroll-margin-top: 90px; }
/* « Toute la période », en tête de l'onglet Statistiques (22/10/2026). */
.stats-periode { display: flex; align-items: baseline; gap: 6px 14px; flex-wrap: wrap; margin: 0 0 14px; }
.stats-periode-v { font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; font-variant-numeric: tabular-nums; }
.jr-journee-tete { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0 0 10px; }
.jr-journee-titre { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.jr-journee-tete [data-journee-basculer] { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
/* Sous l'en-tête de la journée (qui porte déjà la date) : le bouton se pose à droite
   de la ligne de légende du graphique, sans prendre une ligne à lui. */
.jr-journee { position: relative; }
.jr-journee-tete--seule { position: absolute; right: 0; top: -4px; margin: 0; z-index: 2; }
.jr-journee-tete--seule ~ .jr-journee-corps .jr-journee-dit { padding-right: 190px; }
.jr-journee-tete--seule ~ .jr-journee-corps[hidden] ~ * { margin-top: 0; }
.jr-journee:has(.jr-journee-corps[hidden]) { min-height: 34px; }
@media (max-width: 640px) {
  .jr-journee-tete--seule { position: static; margin: 0 0 6px; }
  .jr-journee-tete--seule ~ .jr-journee-corps .jr-journee-dit { padding-right: 0; }
}
.jr-journee-corps[hidden] { display: none; }
.jr-journee-dit { margin: 0 0 8px; }
.jr-journee-marche + .jr-journee-marche { margin-top: 18px; }
.jr-journee-charge, .jr-journee-vide { color: var(--muted-foreground); font-size: var(--text-body-sm); margin: 0; padding: 18px 0; }
/* Les comptes en pastilles, à côté de Réel / Rejeu (même habit que `.basc`). */
.jr-comptes { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.jr-compte {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  font-size: var(--text-sm); color: var(--muted-foreground); text-decoration: none;
}
.jr-compte:hover { color: var(--foreground); border-color: var(--border-strong); }
.jr-compte[aria-current="page"] { color: var(--foreground); border-color: var(--accent); background: var(--accent-wash); }
.jr-compte .cmd-n { font-size: var(--text-xs); opacity: .7; font-variant-numeric: tabular-nums; }
/* Une ligne de la journée : la flèche ouvre l'analyse, la ligne centre le graphique. */
/* « Voir l'analyse » (22/10/2026, fondateur : « la flèche ce n'est pas très
   explicite ») : un clic sur la ligne met l'analyse en évidence sur le graphique, ce
   mot-là l'ouvre. Un petit bouton, plus une flèche seule. */
.rangee-ouvrir {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; white-space: nowrap;
  padding: 3px 9px; border: 1px solid var(--border-strong); border-radius: 999px;
  font-size: var(--text-xs); color: var(--foreground);
}
.rangee-ouvrir:hover { border-color: var(--accent); color: var(--accent-hi, var(--foreground)); }
/* « Voir l'analyse » au PIED de sa colonne, au bureau (fondateur, 22/10/2026 : le
   bouton de l'étiquette le cachait ; l'étiquette est retirée le 23/10/2026, la mise en
   page reste). Sous 720 px, « Voir l'analyse » passe à gauche sous le texte. */
@media (min-width: 721px) {
  .jr-seance .rangee:has(.rangee-ouvrir) { align-items: stretch; }
  .jr-seance .rangee-argent:has(.rangee-ouvrir) {
    display: flex; flex-direction: column; align-items: flex-end;
  }
  .jr-seance .rangee-argent .rangee-ouvrir { margin-top: auto; }
}
a.rangee[data-centrer][aria-current] { border-color: var(--accent); }
.cal-ligne {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)) minmax(0, 1.1fr);
  gap: 6px; margin: 0 0 6px;
}
.cal-ligne--tete > span {
  font-size: var(--text-xs); color: var(--muted-foreground); text-align: center;
  letter-spacing: .03em; padding: 0 0 2px;
}
.cal-ligne--tete > .cal-sem-tete { text-align: right; padding-right: 10px; }
.cal-case {
  position: relative; display: flex; flex-direction: column; justify-content: flex-end; gap: 1px;
  min-height: 64px; padding: 22px 9px 7px; border-radius: var(--radius-md);
  border: 1px solid var(--border); background: rgba(255,255,255,0.015);
  color: var(--foreground); text-decoration: none; min-width: 0;
  transition: border-color .15s ease, background-color .15s ease;
}
a.cal-case:hover { border-color: var(--border-strong); background-color: rgba(255,255,255,0.04); }
.cal-case--hors { border-color: transparent; background: transparent; }
.cal-case--gain { background: rgba(74,222,128,0.09); border-color: rgba(74,222,128,0.26); }
a.cal-case--gain:hover { background: rgba(74,222,128,0.14); border-color: rgba(74,222,128,0.4); }
.cal-case--perte { background: rgba(248,113,113,0.09); border-color: rgba(248,113,113,0.26); }
a.cal-case--perte:hover { background: rgba(248,113,113,0.14); border-color: rgba(248,113,113,0.4); }
.cal-case--calme { background: rgba(255,255,255,0.04); }
.cal-case--futur { opacity: .4; }
.cal-case[aria-current="date"] { box-shadow: 0 0 0 2px var(--accent); }
.cal-num {
  position: absolute; top: 6px; left: 9px; font-size: var(--text-xs);
  color: var(--muted-foreground); font-variant-numeric: tabular-nums;
}
.cal-case--aujourdhui .cal-num {
  color: var(--card); background: var(--accent); border-radius: var(--radius-pill);
  padding: 0 6px; left: 6px; font-weight: 700;
}
.cal-v {
  font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600;
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-case--gain .cal-v { color: var(--success); }
.cal-case--perte .cal-v { color: var(--error); }
.cal-n {
  font-size: calc(11px * var(--echelle-texte)); line-height: 1.35; color: var(--muted-foreground);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-n--doux { opacity: .75; }
.cal-v-court { display: none; }
.cal-sem {
  display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 1px;
  padding: 8px 10px; border-radius: var(--radius-md); background: rgba(255,255,255,0.02); min-width: 0;
}
.cal-sem-v { font-family: var(--font-mono); font-size: var(--text-sm); font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cal-note { margin: 8px 0 0; font-size: var(--text-xs); color: var(--muted-foreground); }
.cal-vide { margin: 0 0 18px; color: var(--muted-foreground); font-size: var(--text-sm); }
@media (max-width: 720px) {
  .cal-mois-rangee { grid-template-columns: repeat(6, minmax(0, 1fr)); flex-basis: 100%; }
  .cal-tete { gap: 8px; }
  .cal-titre { min-width: 0; flex: 1; font-size: var(--text-base); }
  .cal-bilan { margin-left: 0; width: 100%; justify-content: flex-start; }
  .cal-ligne { grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; margin-bottom: 4px; }
  .cal-sem, .cal-sem-tete { display: none; }
  .cal-case { min-height: 50px; padding: 17px 2px 5px; align-items: center; }
  .cal-num { top: 3px; left: 5px; font-size: calc(10px * var(--echelle-texte)); }
  .cal-case--aujourdhui .cal-num { left: 3px; padding: 0 4px; }
  .cal-v-long, .cal-case .cal-n { display: none; }
  .cal-v-court { display: inline; }
  .cal-v { font-size: calc(11px * var(--echelle-texte)); }
  .cal-case--calme::after {
    content: ""; width: 5px; height: 5px; border-radius: 50%;
    background: var(--muted-foreground); opacity: .7;
  }
}

/* Les statistiques : six essentiels, puis le reste replié. */
.jr-essentiels { margin: 18px 0 0; }
.jr-toutes { margin-top: 20px; border-top: 1px solid var(--border); }
.jr-toutes > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 14px 2px 6px; font-weight: 600;
}
.jr-toutes > summary::-webkit-details-marker { display: none; }
.jr-toutes > summary > svg { transition: transform .15s ease; color: var(--muted-foreground); }
.jr-toutes[open] > summary > svg { transform: rotate(180deg); }
.jr-toutes > summary .t-caption { font-weight: 400; }
.jr-toutes-corps { margin-top: 14px; }
/* Trois onglets sur un téléphone : sans leurs icônes, ils tiennent dans 375 px. */
@media (max-width: 480px) {
  .onglets-carte-nav > a > svg { display: none; }
  .onglets-carte-nav > a { padding-left: 6px; padding-right: 6px; min-width: 0; }
}
/* Une journée où aucune analyse n'a abouti (panne, crédit, budget). */
.cal-case--alerte { background: rgba(251,191,36,0.07); border-color: rgba(251,191,36,0.28); }
.cal-n--alerte { color: var(--warning); opacity: 1; }
@media (max-width: 720px) {
  .cal-case--alerte::after {
    content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--warning);
  }
}

/* ══ LE PARTAGE DU JOURNAL : LA PÉRIODE ET LE FORMAT, DANS LA FENÊTRE (11/10/2026) ══ */
.pa-pilules { display: flex; flex-wrap: wrap; gap: 6px; }
.pa-pilule { position: relative; cursor: pointer; }
.pa-pilule input { position: absolute; opacity: 0; pointer-events: none; }
.pa-pilule > span {
  display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 0 12px;
  border-radius: var(--radius-pill); background: var(--muted); border: 1px solid var(--border);
  font-size: var(--text-sm); color: var(--muted-foreground);
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.pa-pilule:hover > span { color: var(--foreground); }
.pa-pilule input:checked + span {
  background: var(--accent-ring); border-color: var(--accent-ring-strong); color: var(--foreground);
}
.pa-pilule input:focus-visible + span { outline: 2px solid var(--ring); outline-offset: 2px; }
/* Le format : sa forme dessinée, son nom, et où il sert. */
.pa-pilule--format > span {
  flex-direction: column; align-items: flex-start; gap: 2px; padding: 8px 12px 8px 40px;
  border-radius: var(--radius-md); position: relative; min-width: 104px;
}
.pa-pilule--format em { font-style: normal; font-size: calc(11px * var(--echelle-texte)); opacity: .75; }
.pa-forme {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  border: 1.5px solid currentColor; border-radius: 3px; opacity: .85;
}
.pa-forme--carre { width: 18px; height: 18px; }
.pa-forme--large { width: 20px; height: 12px; }
.pa-forme--story { width: 12px; height: 20px; }
.pa-dates { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; font-size: var(--text-sm);
            color: var(--muted-foreground); }
.pa-dates label { display: inline-flex; align-items: center; gap: 6px; }
.pa-dates input {
  background: var(--muted); color: var(--foreground); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: 5px 8px; font: inherit; font-size: var(--text-sm);
}
.pa-periodes .pa-bloc-dit { margin-top: 8px; color: var(--foreground); }
/* Le calendrier, sur une période trop longue : la case reste, éteinte. */
.pa-case--muette label { opacity: .5; }
/* Une période sans trade : les gestes se taisent. */
[data-partage-reseau][data-vide] { pointer-events: none; opacity: .45; }
.pa-pilules:has(.pa-pilule--format) { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.pa-pilule--format > span { min-width: 0; width: 100%; box-sizing: border-box; }
/* Une séance rejouée, dite sur la carte du salon (12/10/2026). */
.cm-ct-rejeu { color: var(--warning-text); }
/* Sur un téléphone (12/10/2026) : l'aperçu, PUIS les réglages (période, format,
   cases), PUIS « Où l'envoyer ». Avant, les réglages tombaient sous les boutons
   d'envoi, et personne ne les trouvait. */
@media (max-width: 860px) {
  .pa-grille { display: flex; flex-direction: column; }
  .pa-rendu { display: contents; }
  .pa-rendu > * { order: 1; }
  .pa-rendu > .pa-apercu-cadre { order: -1; }
  .pa-choix { order: 0; }
}
/* La fiche de versement (13/10/2026, `partials/fiche_versement.html`) : le profil
   et la fenêtre de retrait l'incluent tous les deux. Le code postal et la ville
   sur une ligne, la ville prenant la place ; empilés sur un téléphone. */
.fv-paire { display: flex; gap: 8px; width: 100%; }
.fv-paire > input:first-child { flex: 0 0 8.5em; min-width: 0; }
.fv-paire > input:last-child { flex: 1 1 auto; min-width: 0; }
@media (max-width: 520px) {
  .fv-paire { flex-direction: column; }
  .fv-paire > input:first-child { flex-basis: auto; }
}
.fiche-versement .saisie[aria-invalid="true"] { border-color: var(--destructive, #e5484d); }
/* Le récapitulatif d'une demande de retrait : ce qui part, où, et le document. */
.retrait-recap { display: grid; gap: 10px; padding: 14px 16px; margin: 0 0 14px;
                 border: 1px solid var(--border); border-radius: var(--radius-lg); }
.retrait-recap-ligne { display: flex; justify-content: space-between; gap: 12px;
                       font-size: var(--text-base); }
.retrait-recap-ligne > span:first-child { color: var(--muted-foreground); }
.retrait-recap-ligne > span:last-child { text-align: right; overflow-wrap: anywhere; }
.retrait-recap-montant { font-size: var(--text-xl, 1.35rem); font-weight: 600;
                         font-variant-numeric: tabular-nums; }
