/* =========================================================================
   Crypto Master — thème « Aurora »
   Feuille AUTONOME : elle ne dépend pas de app.css et ne la surcharge pas.
   `header.php` sert l'une OU l'autre (voir inc/ui_theme.php).

   POURQUOI AUTONOME PLUTÔT QU'UNE SURCHARGE
   -------------------------------------------------------------------------
   Une feuille de surcharge aurait fait dépendre chaque règle d'ici de l'ordre
   de cascade et de la spécificité de son vis-à-vis dans app.css : retirer une
   ligne là-bas aurait changé le rendu ici, sans que rien ne le signale. Et
   surtout, la COMPARAISON n'aurait plus porté sur deux thèmes mais sur un
   thème et un empilement — ce qu'on ne peut pas juger.
   C'est le même raisonnement qui a donné à l'administration sa propre feuille.

   D'OÙ VIENNENT CES COULEURS
   -------------------------------------------------------------------------
   De `site/public/index.php`, la vitrine : même fond #060a14, même triade
   cyan → indigo → magenta, mêmes polices Inter + Sora. Un utilisateur qui
   passe de crypto-master.io à app.crypto-master.io ne doit pas changer de
   marque. Les valeurs du mode CLAIR en sont dérivées, mais ASSOMBRIES : le
   cyan #22d3ee de la vitrine donne 1,8:1 sur blanc, soit illisible.
   ========================================================================= */

/* ------------------------------------------------------------- tokens --- */
/* Le mode CLAIR est le défaut : `:root` sans attribut porte les mêmes
   valeurs que `[data-theme="light"]`. app.css annonçait l'inverse dans un
   commentaire (« Par défaut, utiliser le thème sombre ») en posant des
   valeurs claires — on ne reprend pas l'écart. */
:root,
:root[data-theme="light"] {
  color-scheme: light;

  --bg:          #f6f8fc;
  --bg-2:        #eef2f9;
  --card:        #ffffff;
  --card-2:      #fbfcff;
  --card-flou:   none;

  --text:        #0f172a;
  --muted:       #5b6b85;
  --line:        #e6ecf6;
  --line-fort:   #d5dfee;

  --accent:      #4f46e5;
  --accent-ink:  #4338ca;
  --accent-doux: #eef2ff;
  --c1:          #0891b2;
  --c2:          #4f46e5;
  --c3:          #c026d3;

  --up:          #047857;
  --up-doux:     #dcfce7;
  --down:        #be123c;
  --down-doux:   #ffe4e6;
  --warn:        #b45309;
  --warn-doux:   #fef3c7;
  --info:        #0369a1;
  --info-doux:   #e0f2fe;

  --input:       #f1f5fb;
  --header-bg:   rgba(255, 255, 255, .72);
  --link:        #4338ca;

  --halo-1:      rgba(34, 211, 238, .30);
  --halo-2:      rgba(99, 102, 241, .26);
  --halo-3:      rgba(214, 71, 226, .16);
  --quadrillage: rgba(100, 116, 139, .09);

  --ombre-sm:    0 1px 2px rgba(15, 23, 42, .05);
  --ombre:       0 1px 2px rgba(15, 23, 42, .05),
                 0 14px 38px -22px rgba(30, 41, 59, .35);
  --ombre-lg:    0 2px 4px rgba(15, 23, 42, .06),
                 0 24px 56px -28px rgba(30, 41, 59, .45);

  --r:      18px;
  --r-sm:   11px;
  --r-xs:   8px;
  --r-pill: 999px;

  --grad: linear-gradient(120deg, var(--c1) 0%, var(--c2) 52%, var(--c3) 100%);
  --police: Inter, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu,
            Cantarell, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --police-titre: Sora, Inter, system-ui, sans-serif;
  /* LA PILE DU SYSTÈME, PAS UNE POLICE TÉLÉCHARGÉE. « Roboto Mono » ne
     servait qu'à trois endroits et coûtait une famille entière à rapatrier.
     Deux besoins distincts se cachaient derrière un seul token :
       - ALIGNER DES MONTANTS en colonne, ce que `font-variant-numeric:
         tabular-nums` fait avec Inter, dont les chiffres tabulaires sont de
         même chasse — aucune police monospace n'est nécessaire pour cela ;
       - lire une ADRESSE ou un hachage, où il faut vraiment distinguer le 0
         de l'O et le 1 du l : là, une monospace s'impose, et celle du système
         est déjà sur la machine. */
  --police-num: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
                "Liberation Mono", "Courier New", monospace;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:          #060a14;
  --bg-2:        #0a1020;
  --card:        rgba(15, 23, 42, .62);
  --card-2:      rgba(15, 23, 42, .42);
  --card-flou:   blur(14px);

  --text:        #e6edf7;
  --muted:       #94a3b8;
  --line:        rgba(148, 163, 184, .16);
  --line-fort:   rgba(148, 163, 184, .30);

  /* PAS l'indigo #6366f1 de la vitrine, et la raison est MESURÉE : ce token
     porte DEUX rôles qui tirent en sens inverse. Il est une SURFACE sous du
     blanc (case à cocher, bouton du bandeau de comparaison), ce qui le veut
     foncé — #6366f1 y donne 4,47:1, sous le seuil de 4,5. Et il est un TRAIT
     sur fond sombre (bordure de focus), ce qui le veut clair — #4f46e5, la
     valeur du mode clair, n'y donne que 2,92:1.
     #5457ee tient les deux : 5,28:1 sous le blanc, 3,48:1 contre la carte.
     Un SECOND token aurait été plus simple à écrire et impossible à employer :
     le bandeau de comparaison est stylé dans `header.php` parce qu'il doit
     s'afficher sous LES DEUX feuilles, et `app.css` ne connaîtrait pas ce
     second nom. */
  --accent:      #5457ee;
  --accent-ink:  #818cf8;
  --accent-doux: rgba(99, 102, 241, .16);
  --c1:          #22d3ee;
  --c2:          #6366f1;
  --c3:          #d647e2;

  --up:          #34d399;
  --up-doux:     rgba(52, 211, 153, .14);
  --down:        #fb7185;
  --down-doux:   rgba(251, 113, 133, .14);
  --warn:        #fbbf24;
  --warn-doux:   rgba(251, 191, 36, .13);
  --info:        #38bdf8;
  --info-doux:   rgba(56, 189, 248, .13);

  --input:       rgba(148, 163, 184, .10);
  --header-bg:   rgba(6, 10, 20, .68);
  --link:        #7dd3fc;

  --halo-1:      rgba(6, 182, 212, .42);
  --halo-2:      rgba(37, 99, 235, .40);
  --halo-3:      rgba(192, 38, 211, .28);
  --quadrillage: rgba(148, 163, 184, .07);

  --ombre-sm:    0 1px 2px rgba(0, 0, 0, .4);
  --ombre:       0 1px 2px rgba(0, 0, 0, .4),
                 0 18px 46px -24px rgba(0, 0, 0, .85);
  --ombre-lg:    0 2px 6px rgba(0, 0, 0, .5),
                 0 28px 64px -30px rgba(0, 0, 0, .95);
}

/* ------------------------------------------- transitions de page -------- */
/* Une navigation classique produit un écran blanc entre deux pages. Cette
   déclaration la fait FONDRE à la place, sans une ligne de JavaScript et sans
   rien changer au HTML : le navigateur garde une image de l'ancienne page et
   l'enchaîne avec la nouvelle.
   Elle est IGNORÉE en silence là où elle n'est pas comprise — il n'y a donc
   rien à détecter ni à replier.

   `prefers-reduced-motion` EST TRAITÉ PLUS BAS, ET IL LUI FAUT SA PROPRE
   RÈGLE : les pseudo-éléments de transition vivent dans une arborescence
   séparée (`::view-transition` > `::view-transition-group` > …) que
   `*, *::before, *::after` N'ATTEINT PAS. Le raccourci « la règle générale
   s'en charge » était faux, et c'est le genre d'affirmation qui dispense
   d'aller vérifier. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation-duration: .16s; }
::view-transition-new(root) { animation-duration: .16s; }

/* ---------------------------------------------------------------- base --- */

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  padding: 0;                     /* app.css posait 24px, ce qui décalait le
                                     header sticky de la même valeur. */
  background: var(--bg);
  color: var(--text);
  font: 14px/1.55 var(--police);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;             /* les halos débordent volontairement */
}

a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 {
  font-family: var(--police-titre);
  letter-spacing: -.022em;
  font-weight: 700;
}
h1 { margin: 0 0 14px; font-size: 23px; }
/* LA TAILLE EST ÉCRITE ICI, PAS LAISSÉE AUX ATTRIBUTS. `IconeActif::img()` pose
   `width="20" height="20"` — la taille d'une cellule de tableau — et le modèle
   de boîte est `border-box` : les 5 px de rembourrage se prenaient SUR ces
   vingt pixels, et le glyphe sortait à dix pixels au milieu d'une pastille de
   vingt. Trente-huit moins deux fois sept : un glyphe de vingt-quatre. */
h1 img {
  vertical-align: middle; width: 38px; height: 38px; margin-right: 10px;
  background: var(--card-2); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 7px;
}

/* Les CHIFFRES sont tabulaires partout : sans cela une colonne de montants
   danse d'une ligne à l'autre, le « 1 » étant plus étroit que le « 0 ». */
.amount, .num, .mono, td.num, th.num, .kpi__value, .tabnum {
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------- une image PAR THÈME ------- */
/* LE BON LOGO SANS JAVASCRIPT. `app.js` échangeait la source une fois le DOM
   prêt : le mauvais logo s'affichait donc le temps d'un rendu — exactement le
   défaut que le script de thème ferme en tête de `<head>`. Les deux images
   sont servies et le CSS masque celle qui ne convient pas.
   LE NAVIGATEUR TÉLÉCHARGE LES DEUX, et ce paragraphe a dit le contraire : un
   `<img>` masqué par `display:none` est chargé quand même — seule une image de
   FOND ne l'est pas. C'est ce qui oblige les deux fichiers à rester légers :
   sept kilo-octets chacun depuis le 08/10/2026, contre 121 avant.
   Attention au nom du fichier : `logo-light.png` est le logo POUR le thème
   clair, pas un logo de couleur claire.

   LES SÉLECTEURS SONT PRÉFIXÉS, ET C'EST LA CORRECTION D'UN DÉFAUT RÉEL.
   Écrites `.pour-sombre { display: none }`, ces règles pèsent (0,1,0) quand
   `.brand img { display: block }` pèse (0,1,1) : la seconde gagnait, et LES
   DEUX logos s'affichaient. Le défaut ne se voyait qu'en thème CLAIR — en
   sombre, la règle de masquage porte `:root[data-theme="dark"]`, pèse (0,2,0)
   et l'emportait. Un thème sur deux masquait donc l'erreur de l'autre, ce
   qu'aucun contrôle de MARKUP ne pouvait voir : les deux images sont bien
   servies, c'est le CSS APPLIQUÉ qui décide. `verify_theme.php` compare
   désormais les spécificités. */
.brand .pour-sombre,
.acces-marque .pour-sombre { display: none; }
:root[data-theme="dark"] .brand .pour-sombre,
:root[data-theme="dark"] .acces-marque .pour-sombre { display: block; }
:root[data-theme="dark"] .brand .pour-clair,
:root[data-theme="dark"] .acces-marque .pour-clair  { display: none; }

/* ------------------------------------------------------------ backdrop --- */
/* Les halos de la vitrine, en fond FIXE derrière toute la page. Ils sont
   portés par des pseudo-éléments de <body> : aucun markup à ajouter dans les
   110 pages, et rien à retirer le jour où l'on change d'avis. */

body::before {
  content: "";
  position: fixed; inset: 0; z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(620px 420px at 12% -8%,  var(--halo-1), transparent 68%),
    radial-gradient(560px 400px at 88% -4%,  var(--halo-2), transparent 68%),
    radial-gradient(520px 460px at 52% 108%, var(--halo-3), transparent 68%),
    var(--bg);
  animation: mc-derive 34s ease-in-out infinite alternate;
}

body::after {
  content: "";
  position: fixed; inset: 0; z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(var(--quadrillage) 1px, transparent 1px),
    linear-gradient(90deg, var(--quadrillage) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(ellipse 72% 52% at 50% 0%,
                                      #000 16%, transparent 100%);
          mask-image: radial-gradient(ellipse 72% 52% at 50% 0%,
                                      #000 16%, transparent 100%);
}

@keyframes mc-derive {
  from { transform: translate3d(0, 0, 0)        scale(1); }
  to   { transform: translate3d(-24px, 18px, 0) scale(1.06); }
}

/* Une animation de fond n'a aucune valeur d'information : elle est la
   première chose à couper pour qui la demande. */
@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
  /* Sa propre règle : voir « transitions de page » plus haut. Le fondu est
     coupé, la navigation reste instantanée. */
  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none !important; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* -------------------------------------------------------------- header --- */

.site-header {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 14px;
  padding: 10px 22px;
  background: var(--header-bg);
  border-bottom: 1px solid var(--line);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
          backdrop-filter: blur(14px) saturate(140%);
}

.brand { display: flex; align-items: center; flex-shrink: 0; }
/* LE LOGO A GRANDI (22/09/2026) : 38 px le rendaient plus petit que le menu
   qui le suit, alors qu'il est la seule marque de la page. Il reste borné par
   la HAUTEUR et non par la largeur — l'image n'est pas carrée, et fixer la
   largeur la déformerait au premier changement de fichier.

   CINQUANTE PIXELS PARTOUT, TÉLÉPHONE COMPRIS, et les paliers qui le
   rapetissaient ont été RETIRÉS plutôt que rapprochés : une seule valeur vaut
   mieux que trois à tenir d'accord.
   L'ancien fichier faisait 652 × 213, un rapport de 3,06 : à 50 px de haut il
   en occupait 153 de large. C'est ce qui a obligé à resserrer l'écart et le
   rembourrage de l'en-tête sous 480 px — la barre y dispose de 300 px pour la
   marque, la bascule de thème, la pastille et le hamburger, et elle ne tient
   plus au jeu d'avant.

   QUARANTE-HUIT DEPUIS LE NOUVEAU LOGO (08/10/2026), ET C'EST CETTE LARGEUR
   QUI L'A DÉCIDÉ. Il fait 455 × 144, un rapport de 3,16 : à 50 px il en
   aurait pris 158, cinq de plus que le budget mesuré ci-dessus — et le
   surplus ne se voit pas, il pousse le hamburger sous le bord. À 48 px il en
   prend 152.

   `width: auto` N'EST PAS DÉCORATIF : les balises portent `width` et `height`
   pour réserver la place avant l'arrivée de l'image, et l'attribut `width`
   vaut une largeur CSS de 455 px tant qu'aucune règle ne la reprend. */
.brand img { height: 48px; width: auto; display: block; }
.brand .title {
  font-family: var(--police-titre);
  font-weight: 700; letter-spacing: -.02em;
}

.site-nav {
  display: flex; align-items: center; gap: 3px;
  flex-wrap: wrap; flex: 0 1 auto; min-width: 0;
}

/* L'entrée de menu N'EST PLUS UNE BOÎTE BORDÉE. Dix boîtes côte à côte
   pèsent autant que le contenu de la page ; ici le repos est nu, et seule
   l'entrée active porte un fond. */
.nav-item {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 11px;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font-size: 12.5px; font-weight: 500;
  white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.nav-item:hover {
  background: var(--card-2);
  color: var(--text);
  text-decoration: none;
}
.nav-item.active {
  background: var(--accent-doux);
  color: var(--accent-ink);
  font-weight: 600;
}
.nav-item .emoji { font-size: 15px; line-height: 1; }
/* Les icônes servies par header.php en variante Aurora. `currentColor` est
   ce qui leur fait suivre l'état de l'entrée sans une règle par état. */
.nav-item svg { flex-shrink: 0; display: block; }

.theme-switch {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0; padding: 6px 11px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--card-2);
  color: var(--text);
  font-size: 12px; font-weight: 600;
  cursor: pointer; flex-shrink: 0;
  transition: border-color .15s ease, background .15s ease;
}
.theme-switch:hover { border-color: var(--line-fort); background: var(--card); }
.theme-switch-icon { font-size: 14px; margin: 0; line-height: 1; }

.hamburger {
  display: none;
  background: none; border: 0; cursor: pointer;
  font-size: 22px; color: var(--text);
  padding: 4px 8px; border-radius: var(--r-xs);
}
.hamburger:hover { background: var(--card-2); }

/* --------------------------------------------------------------- main --- */

.site-main {
  position: relative;
  max-width: 1560px;
  margin: 22px auto 0;
  padding: 0 22px;
}

.site-footer {
  color: var(--muted);
  padding: 22px;
  border-top: 1px solid var(--line);
  text-align: center;
  margin-top: 48px;
  font-size: 12.5px;
}

/* -------------------------------------------------------------- cartes --- */

.card {
  position: relative;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 20px;
  margin: 0 auto 16px;
  box-shadow: var(--ombre);
  -webkit-backdrop-filter: var(--card-flou);
          backdrop-filter: var(--card-flou);
}
.card h2 {
  margin: 0 0 12px;
  font-size: 16px;
  text-align: left;              /* app.css centrait tous les h2 de carte */
}
.card .header {
  padding: 0 0 14px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.card .header h1 { margin: 0; font-size: 19px; color: var(--text); }

/* Le vocabulaire BEM de admin.css, repris ici : une dizaine de pages de
   app/public/ l'emploient déjà (`card__head`, `kpi`, `tbl`, `num`…) alors
   qu'aucune ne charge admin.css — ces classes n'y faisaient rien. */
.card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 0 0 12px; margin-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.card__head h2 { margin: 0; font-size: 15px; }
.card__head .muted { font-size: 12.5px; }
.card__head-actions { display: flex; align-items: center; gap: 8px; }
.card__body { padding: 0; }
.card__body--flush { padding: 0; margin: 0 -20px -20px; }
.card__foot {
  padding: 12px 0 0; margin-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--muted);
}

.container { max-width: 1160px; margin: 0 auto; }
.stack > * + * { margin-top: 12px; }
.split { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* minmax(0, 1fr) et non la colonne implicite `auto` : le minimum de `auto`
   est le min-content de son contenu, et un `<input type="file">` (Nexo, 350 px)
   élargissait la grille au-delà d'un écran de 320 px. `.cols-2` et `.cols-3`
   pèsent (0,2,0) et gardent la main. */
.grid { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr); }
.grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* ------------------------------------------------------------- chiffre --- */
/* Le chiffre de tête, en dégradé de marque. C'est le seul endroit où le
   dégradé porte du TEXTE : au-delà il devient une texture, et une texture
   sur un montant se lit moins bien qu'une couleur pleine. */
.kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}
.kpi {
  position: relative;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 14px 16px;
  box-shadow: var(--ombre);
  overflow: hidden;
  -webkit-backdrop-filter: var(--card-flou);
          backdrop-filter: var(--card-flou);
}
.kpi--accent::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: var(--grad);
}
.kpi__label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
.kpi__value {
  font-family: var(--police-titre);
  font-size: 24px; font-weight: 800; letter-spacing: -.03em;
  color: var(--text); margin-top: 2px;
}
.kpi--accent .kpi__value {
  background: var(--grad);
  -webkit-background-clip: text; background-clip: text;
  color: transparent;
}
.kpi__hint { font-size: 11.5px; color: var(--muted); margin-top: 3px; }

/* ------------------------------------------------------------ tableaux --- */

table { width: 100%; border-collapse: collapse; margin-top: 8px; }

.table th, .table td,
.tbl th, .tbl td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  font-size: 13px;
}
.table th, .tbl th {
  color: var(--muted);
  font-size: 10.5px; font-weight: 700;
  letter-spacing: .07em; text-transform: uppercase;
  background: var(--card-2);
  white-space: nowrap;
}
.table tr:last-child td, .tbl tr:last-child td { border-bottom: 0; }
.table tbody tr, .tbl tbody tr { transition: background .12s ease; }
.table tbody tr:hover, .tbl tbody tr:hover { background: var(--card-2); }
/* Une ADRESSE se lit caractère par caractère : la monospace y est
   indispensable. Un MONTANT se lit en colonne : les chiffres tabulaires
   suffisent, et gardent la police du texte — donc une famille de moins à
   télécharger. */
.table .addr, .mono { font-family: var(--police-num); }
td.num, th.num, .num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* CES UTILITAIRES SONT QUALIFIÉS PAR `td`/`th`, ET C'EST INDISPENSABLE.
   `.right` seul pèse (0,1,0) quand `.table td` pèse (0,1,1) : dans un
   tableau — c'est-à-dire là où ces classes servent — le sélecteur contextuel
   gagne et l'alignement demandé est IGNORÉ, en silence. Rien dans la console,
   juste une colonne de montants alignée à gauche.
   C'est le TROISIÈME défaut de cette famille dans le même chantier, après le
   logo en double et le `[hidden]` des blocs dépliants : une classe utilitaire
   perd toujours contre un sélecteur contextuel, et il faut l'écrire.
   `verify_theme.php` (partie 1 bis) le vérifie désormais. */
td.num, th.num, td.right, th.right, .right { text-align: right; }
td.center, th.center, .center { text-align: center; }
.nowrap { white-space: nowrap; }
.tiny { font-size: 11px; }
.sub  { font-size: 11.5px; color: var(--muted); }
.dim  { color: var(--muted); }
.table-sm td, .table-sm th { padding: 7px 10px; font-size: 12px; }

/* LES VIGNETTES NE SONT PLUS SUR FOND BLANC FORCÉ. app.css imposait
   `background:#FFF` à toute image de tableau : en thème sombre, chaque
   logo devenait une pastille blanche. Le fond suit désormais la surface. */
td img {
  max-height: 25px;
  vertical-align: middle;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px;
  margin-right: 4px;
}

.tablewrap, .portfolio-table-wrapper {
  width: 100%;
  overflow-x: auto;
  position: relative;
  border-radius: var(--r-sm);
}
.portfolio-table-wrapper table { width: 100%; border-collapse: collapse; }
.portfolio-table-wrapper table td { font-size: 12px; padding: 8px 10px; }

/* POURQUOI IL N'Y A PAS DE `content-visibility` ICI, et pourquoi il ne faut
   pas l'y remettre.
   L'idée est juste sur le principe : `tx_all.php` sert cent une lignes pour
   236 Ko, et le navigateur calcule la position de chacune avant de peindre la
   première. Mais `content-visibility: auto` n'agit qu'en appliquant
   `contain: layout paint size style`, et la spécification CSS Containment
   EXCLUT expressément les éléments internes de tableau — `table-row`,
   `table-cell`, `table-row-group`. La règle est donc ignorée : aucun effet,
   aucune erreur, aucune trace. Écrite sur `tbody tr`, elle aurait été du CSS
   mort qui se présente comme une optimisation.
   Ce qui marcherait vraiment demande de sortir les lignes du modèle de
   tableau (`display: block`), ce qui casse l'alignement des colonnes, ou de
   réduire la taille de page — qui est un choix fonctionnel, pas de thème. */
.portfolio-table-wrapper table thead th {
  position: sticky; top: 0; z-index: 3;
  background: var(--card-2);
}
/* La première colonne reste visible au défilement horizontal. Son fond doit
   être OPAQUE, faute de quoi les colonnes glissent visiblement dessous —
   d'où `--bg-2` plutôt que `--card`, translucide en thème sombre. */
.portfolio-table-wrapper table th:first-child,
.portfolio-table-wrapper table td:first-child {
  position: sticky; left: 0; z-index: 2;
  background: var(--bg-2);
  white-space: nowrap;
}
.portfolio-table-wrapper table thead th:first-child { z-index: 4; }

.transaction-table { position: relative; }
.transaction-table thead {
  position: sticky; top: 0; z-index: 5;
  background: var(--card-2);
}
.transaction-table thead::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--line-fort);
}

/* ------------------------------------------------------------- boutons --- */

button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  background: var(--card-2);
  color: var(--text);
  border: 1px solid var(--line);
  padding: 8px 13px;
  border-radius: var(--r-pill);
  font: 600 12.5px/1 var(--police);
  cursor: pointer;
  text-decoration: none;
  transition: border-color .15s ease, background .15s ease,
              transform .12s ease, box-shadow .15s ease;
}
button:hover, .btn:hover {
  border-color: var(--line-fort);
  background: var(--card);
  text-decoration: none;
  transform: translateY(-1px);
  box-shadow: var(--ombre-sm);
}
button:active, .btn:active { transform: translateY(0); }
.btn[disabled], button[disabled] {
  opacity: .5; cursor: not-allowed; transform: none; box-shadow: none;
}

/* Le bouton d'action principal porte le dégradé de marque. Il est le SEUL :
   deux dégradés côte à côte ne désignent plus rien. */
.btn--primary, .btn.primary, .btn-primary {
  background: var(--grad);
  border-color: transparent;
  color: #fff;
}
.btn--primary:hover, .btn.primary:hover, .btn-primary:hover {
  filter: brightness(1.08);
  border-color: transparent;
}
.btn--sm, .btn-sm { padding: 6px 11px; font-size: 11.5px; }

.btn-danger, .btn.danger {
  background: var(--down); border-color: transparent; color: #fff;
}
.btn-danger:hover, .btn.danger:hover {
  filter: brightness(1.08); border-color: transparent; text-decoration: none;
}
.btn-danger a:hover { text-decoration: none; }

/* Action discrète en fin de ligne de tableau : une icône nue, qui ne prend sa
   couleur qu'au survol. Le rouge plein est réservé aux actions de page. */
.btn-ligne {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; gap: 0;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--r-pill); color: var(--muted);
  opacity: .7; vertical-align: middle; cursor: pointer; text-decoration: none;
}
.btn-ligne:hover {
  opacity: 1; background: var(--card-2); border-color: var(--line);
  transform: none; box-shadow: none; text-decoration: none;
}
.btn-ligne--danger:hover { color: var(--down); border-color: var(--down); }

/* --------------------------------------------------------- formulaires --- */

/* `max-width: 100%` N'EST PAS DÉCORATIF : un `<input type="file">` a une
   largeur INTRINSÈQUE que le navigateur fabrique à partir du libellé de son
   bouton et du nom du fichier — 330 px sur les pages d'import — et rien ne la
   borne. Sur un écran de 320 px, la page entière s'en trouvait élargie.
   La règle vaut pour les trois : un `<select>` dont une option est longue se
   comporte de la même façon. */
input, select, textarea {
  background: var(--input);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  font: 400 13px/1.4 var(--police);
  max-width: 100%;
}
input:focus, select:focus, textarea:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-doux);
}
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--accent);
  width: 14px; height: 14px;
  padding: 0; box-shadow: none;
}

.field { margin-bottom: 12px; }
.field__label {
  display: block;
  font-size: 12px; font-weight: 600; color: var(--muted);
  margin-bottom: 5px;
}
.field__hint { font-size: 11.5px; color: var(--muted); margin-top: 4px; }

.filter-form {
  margin-bottom: 18px;
  padding: 14px 16px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.filter-container {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end;
}
.filter-group { display: flex; flex-direction: column; min-width: 150px; }
.filter-group label {
  margin-bottom: 5px;
  font-size: 12px; font-weight: 600; color: var(--muted);
}
/* app.css imposait ici `border:1px solid #ced4da` — un gris fixe qui ne
   suivait pas le thème et cerclait les champs de clair en mode sombre. */
.filter-group select, .filter-group input {
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}

.import-form { text-align: center; max-width: 820px; margin: 0 auto; }
.import-form input, .import-form select, .import-form button {
  font-size: 16px; margin: 10px;
  /* Les deux marges de 10 px s'ajoutent au `max-width: 100%` général :
     sans ce retrait, un champ borné à son conteneur en dépasse de 20 px. */
  max-width: calc(100% - 20px);
}
/* ---------------------------------------------- tutoriel d'import -------
   CES RÈGLES ÉTAIENT SOUS `.import-form .tuto` ET NE S'APPLIQUAIENT À RIEN :
   le bloc vit APRÈS le `</form>`, pas dedans. Seul le `style="width:100%"`
   écrit en dur sur chaque image agissait — d'où six captures de 300 à 420 Kio
   servies en pleine largeur. Même famille que le logo en double et que le
   `margin-right: auto` annulé : une règle parsée sans erreur, qui ne fait
   rien, et que rien ne signale.

   LE CHEMIN EST CE QU'ON VIENT LIRE, la capture ne sert qu'à lever un doute.
   Le premier est donc mis en avant, la seconde est repliée — et dans un
   `<details>` fermé le navigateur ne la télécharge pas. */

.tuto { text-align: left; max-width: 820px; margin: 40px auto 0; }
.tuto > h2 { font-size: 18px; margin: 0 0 12px; }

.tuto-rapport {
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card-2);
  margin-bottom: 8px;
}
.tuto-rapport > summary {
  cursor: pointer; padding: 11px 14px; font-weight: 600;
  display: flex; align-items: center; gap: 10px;
}
.tuto-rapport > summary::-webkit-details-marker { display: none; }
.tuto-num {
  flex: 0 0 auto;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent); color: #fff;
  font-size: 12px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.tuto-corps { padding: 0 14px 14px; }

/* Le chemin dans les menus de l'exchange : la seule ligne qu'on relit. */
.tuto-chemin {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; line-height: 1.5;
  padding: 8px 11px; margin: 0 0 10px;
  border-left: 3px solid var(--accent);
  background: var(--card);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  /* Un chemin ne se coupe pas au milieu d'un mot, mais il doit pouvoir
     passer à la ligne : sinon il sort du cadre sur un téléphone. */
  overflow-wrap: anywhere;
}
.tuto-alerte {
  font-size: 13px; line-height: 1.5; margin: 0 0 10px;
  padding: 9px 12px; border-radius: var(--r-sm);
  border: 1px solid var(--down); background: var(--down-doux);
}
.tuto-etapes { margin: 0; padding-left: 22px; font-size: 14px; line-height: 1.7; }
.tuto-etapes li { padding-left: 2px; }

.tuto-capture { margin-top: 10px; }
.tuto-capture > summary {
  cursor: pointer; font-size: 13px; color: var(--muted-fg, inherit); opacity: .8;
}
.tuto-capture img {
  /* PAS EN PLEINE LARGEUR : une capture d'écran d'exchange lue à 820 px est
     illisible ET écrasante. On la montre en vignette, et le lien l'ouvre à
     sa taille réelle. `height:auto` avec les dimensions déclarées garde le
     rapport sans faire sauter la page à l'ouverture. */
  display: block; margin-top: 8px;
  width: 100%; max-width: 420px; height: auto;
  border-radius: var(--r-sm); border: 1px solid var(--line);
}

/* -------------------------------------------------- messages et badges --- */

.notice, .flash, .alert {
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card-2);
  font-size: 13px;
}
.notice.ok, .flash--ok, .alert.success, .alert-success {
  border-color: var(--up); background: var(--up-doux); color: var(--up);
}
.flash--danger, .alert.danger, .alert-danger, .alert.error {
  border-color: var(--down); background: var(--down-doux); color: var(--down);
}
.flash--warn, .alert.warning, .alert-warning {
  border-color: var(--warn); background: var(--warn-doux); color: var(--warn);
}
.flash--info, .alert.info, .alert-info {
  border-color: var(--info); background: var(--info-doux); color: var(--info);
}

.muted, .meta { color: var(--muted); }
.meta { font-size: 12.5px; }
.bar {
  display: flex; gap: 12px; align-items: center;
  justify-content: space-between; flex-wrap: wrap;
}
/* `flex-wrap` : sans lui, quatre boutons côte à côte sortaient de la carte
   sur `account.php` dès 390 px — un flex ne rétrécit pas un bouton sous son
   contenu, il le pousse dehors. */
.actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Même parade, et pour la même raison : les QUATRE boutons de pagination
   (« Première », « Précédente », « Suivante », « Dernière ») font 389 px à
   eux seuls dans un écran de 320. Sans `flex-wrap`, le dernier passait sous
   le bord droit — et `body` portant `overflow-x: hidden`, il n'était pas
   simplement hors-champ, il était INATTEIGNABLE : pas même une barre de
   défilement pour le signaler. C'est `tx_table.php` qui les sert, donc
   `tx_all.php` et les cinq listes de transactions d'un coup. */
.pager { margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap; }

.badge, .etiq {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--card-2);
  color: var(--text);
  font-size: 11px; font-weight: 600;
  width: max-content;
}

/* LE SOLDE, SOUS LE SYMBOLE. Cette règle a porté le COURS du jour jusqu'au
   22/09/2026, sous le nom `.symbol_current_price` ; la page « Performances »
   y sert désormais le solde, et une classe qui ment sur son contenu finit par
   être réutilisée de travers. Un seul fichier la nommait. */
.symbole-solde { font-size: 12px; color: var(--muted); }

/* La variation en pourcentage : vert ou rouge, sur fond teinté. */
.prc_badge {
  display: inline-block;
  padding: 3px 8px;
  font-size: 10.5px; font-weight: 600; line-height: 1.2;
  text-align: center; vertical-align: baseline;
  border-radius: var(--r-pill);
  width: max-content;
}
.prc_badge_green { background: var(--up-doux);   color: var(--up); }
.prc_badge_red   { background: var(--down-doux); color: var(--down); }
/* La flèche est un TRAIT, à la taille du texte et à sa couleur : le triangle
   plein de 16 px qu'elle remplace dépassait le texte du badge et gardait un
   vert figé, étranger aux deux thèmes. Une seule flèche, retournée pour la
   baisse. */
.prc_fleche {
  width: .9em; height: .9em;
  margin-right: .3em; vertical-align: -.1em;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.prc_fleche--bas { transform: rotate(180deg); }

/* app.css figeait ici `#EEE` sur `#555` : une pastille grise en plein thème
   sombre, et un contraste de 3,4:1 en thème clair. */
.usd_quote {
  display: inline-block;
  background: var(--input);
  border: 1px solid var(--line);
  padding: 3px 7px;
  border-radius: var(--r-xs);
  font-size: 10.5px; font-style: normal; font-weight: 600;
  color: var(--muted);
  margin-top: 4px;
}

.amount {
  text-align: right !important;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 12px !important;
}

/* ------------------------------------------- états d'une position (C-020) --- */

/* Position qu'aucune source ne sait coter. Elle reste AFFICHÉE — la masquer
   reviendrait à taire un avoir réel — mais le libellé dit « inconnu », pas
   « zéro », et elle n'entre pas dans le total. */
.prix-indisponible {
  color: var(--warn);
  font-style: italic;
  white-space: nowrap;
}
.total-exclusions {
  margin: -4px 0 12px;
  text-align: center;
  font-size: 12px;
  color: var(--warn);
}

/* Jeton dont le contrat n'a pas encore été examiné (C-003). AMBRE : une
   vérification est attendue de NOUS. */
.badge-en-revue {
  display: inline-block;
  margin-left: 6px; padding: 1px 7px;
  border: 1px solid var(--warn);
  border-radius: var(--r-pill);
  background: var(--warn-doux);
  color: var(--warn);
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  vertical-align: middle; white-space: nowrap;
}

/* Jeton POUSSÉ : reçu sans l'avoir demandé, jamais échangé, jamais approuvé,
   et qu'aucun marché ne cote (MC\Service\ReglesDeSpam).
   LA LIGNE EST GRISÉE, PAS SUPPRIMÉE, et redevient pleinement lisible au
   survol : la règle est une PRÉSOMPTION, et une présomption qui effacerait
   sa cible du document la rendrait irréfutable. */
.ligne-indesirable,
.ligne-nft-poussee { opacity: .48; transition: opacity .12s ease-in-out; }
.ligne-indesirable:hover,
.ligne-indesirable:focus-within,
.ligne-nft-poussee:hover,
.ligne-nft-poussee:focus-within { opacity: 1; }

/* Gris NEUTRE, délibérément : l'ambre de `.badge-en-revue` signale qu'une
   vérification est attendue, et il n'y a rien à vérifier ici — juste du
   bruit dont on ne veut pas. Deux alertes de même couleur pour deux sens
   différents finiraient par n'en faire qu'une. */
/* `.badge-nft` : le même badge, pour la carte « NFT » de `tx_by_account.php`
   (lot 84). Une classe À PART parce que les bancs des jetons non sollicités
   comptent `badge-indesirable` sur cette page même. */
.badge-indesirable,
.badge-nft {
  display: inline-block;
  margin-left: 6px; padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  vertical-align: middle; white-space: nowrap;
  cursor: help;
}

.encart-indesirable {
  margin: 0 0 14px; padding: 11px 15px;
  border: 1px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--warn-doux);
  color: var(--warn);
  font-size: 13px;
}
.encart-indesirable p { margin: 0 0 8px; }
.encart-indesirable.encart-neutre {
  border-color: var(--line); background: var(--card-2); color: var(--muted);
}
.encart-actions { margin: 0; }
.encart-actions .btn { font-size: 11.5px; padding: 5px 11px; }
.encart-discret { margin: 0 0 14px; opacity: .6; }

/* La carte « NFT » (lot 84). L'image est RÉENCODÉE chez nous (MC\View\ImageNft),
   jamais servie depuis le fournisseur. */
.nft-image {
  width: 40px; height: 40px; object-fit: cover;
  border-radius: var(--r-xs); vertical-align: middle;
}
.nft-actions { display: inline-block; margin-left: 6px; }
.encart-discret:hover { opacity: 1; }

/* EMPOISONNEMENT D'ADRESSE PRÉSUMÉ — rouge, et non le gris du jeton poussé :
   celui-ci ne demande rien, celui-là met en garde contre un geste à venir
   (recopier la mauvaise adresse). */
.badge-sosie {
  display: inline-block;
  margin-left: 6px; padding: 1px 7px;
  border: 1px solid var(--down);
  border-radius: var(--r-pill);
  background: var(--down-doux);
  color: var(--down);
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  vertical-align: middle; white-space: nowrap;
  cursor: help;
}
.badge-saisie {
  display: inline-block;
  margin-left: 6px; padding: 1px 7px;
  border: 1px solid var(--accent);
  border-radius: var(--r-pill);
  background: var(--accent-doux);
  color: var(--accent-ink);
  font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  vertical-align: middle; white-space: nowrap;
  cursor: help;
}
.encart-sosie {
  margin: 0 0 14px; padding: 11px 15px;
  border: 1px solid var(--down);
  border-radius: var(--r-sm);
  background: var(--down-doux);
  font-size: 13px;
}
.encart-sosie p { margin: 0 0 8px; }
.encart-sosie .sosie-paire {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 2px 10px; margin: 0 0 10px; align-items: baseline;
}
/* Une adresse ne se coupe pas à la césure : elle se coupe N'IMPORTE OÙ, sans
   quoi elle sort de l'écran d'un téléphone. */
.encart-sosie code { font-size: 12px; overflow-wrap: anywhere; word-break: break-all; }
.encart-sosie mark {
  background: transparent; color: var(--down);
  font-weight: 700; text-decoration: underline; text-underline-offset: 2px;
}

/* Une JAMBE poussée dans une liste : on grise la CELLULE du jeton, jamais la
   ligne — la date, le compte et les frais restent des faits à lire, et
   l'autre jambe peut être parfaitement légitime. */
.cellule-indesirable {
  opacity: .45; transition: opacity .12s ease-in-out; cursor: help;
}
tr:hover .cellule-indesirable { opacity: 1; }

/* ------------------------------------------------------- menu déroulant --- */

.dropdown { position: relative; display: inline-block; }
.dropdown-content {
  display: none; position: absolute;
  background: var(--card); min-width: 190px;
  box-shadow: var(--ombre-lg);
  z-index: 40;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  overflow: hidden;
  -webkit-backdrop-filter: var(--card-flou);
          backdrop-filter: var(--card-flou);
}
.dropdown-content a {
  color: var(--text); padding: 10px 14px;
  text-decoration: none; display: block; font-size: 13px;
}
.dropdown-content a:hover { background: var(--card-2); text-decoration: none; }
.dropdown:hover .dropdown-content { display: block; }

/* ------------------------------------------------------------ colonnes --- */

/* `minmax(0, 1fr)` ET NON `1fr`, et c'est la seule différence qui compte ici.
   `1fr` vaut `minmax(auto, 1fr)`, et ce minimum `auto` est le MIN-CONTENT de
   la colonne : un enfant qui ne sait pas rétrécir — le `<canvas>` de Chart.js,
   dont la largeur est celle que la bibliothèque lui écrit — élargit alors la
   grille, donc la carte, donc la page. Mesuré sur `index.php` : 597 px de
   contenu sur un écran de 390, à tous les paliers d'écran. */
.cards-2col {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px; align-items: start;
}
.cards-2col > * { min-width: 0; }
/* UN `margin: 0 auto` NE CENTRE PAS UN ENFANT DE GRILLE, IL LE RÉTRÉCIT.
   `.card` porte `margin: 0 auto 16px` — utile quand la carte est seule dans
   le flux, ruineux dans une cellule de grille : des marges automatiques
   l'emportent sur le `stretch` par défaut, et la carte se réduit à son
   contenu. Mesuré : le tableau des soldes de `tx_by_coin.php` occupait 350 px
   d'une colonne de 690, décalé vers le milieu. On BAISSE le concurrent plutôt
   que d'empiler des qualificatifs — deux longhands, pas un raccourci, pour ne
   pas réécrire la marge du bas au passage (C-187, même famille que le
   `margin: 0` de `.controls form`). */
.cards-2col > .card { margin-left: 0; margin-right: 0; }

/* ======================================================= l'anneau de répartition ===
   `index.php`. Le graphe était un camembert Chart.js, servi depuis un CDN, à
   qui l'on ne passait qu'une liste de pourcentages : il ne pouvait donc porter
   ni le nom ni l'icône d'un jeton. Il est désormais du SVG rendu par la page,
   à partir de `MC\View\DonutRepartition` — et TOUT ce qui bouge est ici. Pas
   une ligne de JavaScript : ni l'apparition, ni l'épaississement du secteur
   visé, ni l'effacement des autres, ni le cœur qui change de contenu.

   `:has()` PLUTÔT QU'UN `:hover` SUR LE `<svg>` — et la différence n'est pas
   théorique : le second se déclencherait en passant sur le trou du beignet, sur
   une étiquette ou sur le blanc des angles, et le graphe ENTIER s'effacerait
   sans qu'aucun secteur ne soit désigné. Là où `:has()` n'est pas compris, il
   ne se passe rien — le graphe reste lisible, il ne réagit pas.
   ================================================================================= */
.repartition {
  display: flex; flex-direction: column; align-items: center;
  gap: 12px; padding: 6px 0 2px;
}
.donut {
  /* L'ÉPAISSEUR DE L'ANNEAU EST REPOSÉE PAR LA PAGE, depuis
     `DonutRepartition::epaisseur()` — la valeur ci-dessous n'est qu'un repli,
     pour que la feuille reste lisible seule et pour que le contrôle des
     variables trouve sa définition. Deux écritures d'un même nombre divergent,
     et c'est l'attribut en ligne qui gagne. */
  --donut-ep: 46px;
  --donut-cx: 300px;
  --donut-cy: 180px;
  display: block; width: 100%; max-width: 640px; height: auto;
}

/* ------------------------------------------------ l'état d'un secteur ----
   `--pop` VAUT 1 QUAND UN SECTEUR EST DÉSIGNÉ, 0 SINON, et tout ce qui le met
   en valeur en DÉRIVE : l'épaisseur de l'arc, sa lueur, la force du trait de
   rappel, l'opacité de sa pastille, de son étiquette et de son cœur.

   POURQUOI UNE VARIABLE PLUTÔT QUE DES RÈGLES : un secteur se désigne de DEUX
   façons — en le survolant, ou en survolant SA LIGNE dans le tableau — et la
   seconde ne se dit qu'avec un sélecteur PAR POSITION, écrit par la page parce
   qu'il dépend des données. Six déclarations par position pesaient neuf
   kilo-octets sur chaque rendu ; en les faisant toutes dériver d'un seul
   nombre, la page n'a plus qu'à poser `--pop: 1`. Mesuré : 9,3 Ko → 2,2.

   Les transitions portent sur les propriétés DÉRIVÉES et non sur `--pop` : une
   variable personnalisée ne s'interpole pas, mais la valeur calculée qui en
   dépend, si. */
.donut__part { --pop: 0; transition: opacity .18s ease; }
.donut__part:hover { --pop: 1; }

/* LE VOILE PORTE SUR `:not(:hover)`, ET C'EST TOUT L'ENJEU. La première
   écriture l'appliquait à TOUS les secteurs, quitte à rattraper le survolé par
   une seconde règle — or `:has()` prend le poids de son ARGUMENT : la règle de
   voile pesait (0,4,0) contre (0,3,0) pour le rattrapage, donc elle gagnait, et
   le secteur visé s'éteignait AVEC les autres. Le défaut se voyait à l'écran et
   pas à la relecture : c'est la famille « une classe utilitaire perd contre un
   sélecteur contextuel », vue depuis `:has()`. */
.donut:has(.donut__part:hover) .donut__part:not(:hover) { opacity: .16; }

/* SURVOLER UNE LIGNE DU TABLEAU ÉTEINT L'ANNEAU ; c'est la règle par position,
   écrite par la page, qui rallume le secteur apparié — elle porte un
   IDENTIFIANT, donc elle l'emporte sur ce voile sans qu'on ait à empiler des
   qualificatifs. */
.cards-2col--repartition:has(tbody tr[data-a]:hover) .donut__part { opacity: .16; }
.donut:has(.donut__part:hover) .donut__coeur--defaut,
.cards-2col--repartition:has(tbody tr[data-a]:hover) .donut__coeur--defaut { opacity: 0; }

/* LA LIGNE SURVOLÉE PREND LA COULEUR DE SON SECTEUR, sur toute sa largeur —
   c'est ce qui remplace la colonne de ronds : la ligne DIT sa couleur au lieu
   de la faire chercher. La teinte est posée en valeur par la page, la règle
   n'a qu'à la lire.

   Elle pèse (0,3,2) contre (0,2,2) pour le survol générique des tableaux :
   c'est la seule raison pour laquelle elle l'emporte, et la baisser d'un cran
   la rendrait silencieusement sans effet. Une ligne SANS secteur — sans cours
   connu, ou poussée — n'a pas de teinte et garde le survol ordinaire.

   LE REPLI N'EST PAS DÉCORATIF : une propriété dont la variable manque est
   ignorée EN SILENCE, et la ligne n'aurait alors AUCUN fond au survol — pas
   même celui des autres tableaux. Il vaut donc la couleur ordinaire. */
.portfolio-table-wrapper tbody tr { transition: background-color .15s ease; }
.portfolio-table-wrapper tbody tr[data-a]:hover { background-color: var(--teinte, var(--card-2)); }

.donut__arc {
  stroke-width: calc(var(--donut-ep) + var(--pop) * 10px);
  /* Une lueur de rayon NUL ne se voit pas : c'est la forme qui permet de la
     faire dériver du même nombre que l'épaisseur, au lieu d'une règle à part. */
  filter: drop-shadow(0 0 calc(var(--pop) * 8px) currentColor);
  transition: stroke-width .18s ease, filter .18s ease;
}

/* LES ÉTIQUETTES SONT LÀ EN PERMANENCE sur un écran qui a la place. Elles ne
   se sont montrées qu'au survol pendant une demi-journée, au motif qu'un
   résumé cerné de douze libellés n'en est plus un — mais un anneau muet oblige
   à promener la souris pour savoir ce qu'on regarde, et c'est cher payé pour
   un peu de calme. Sous 620 px, en revanche, elles sont RETIRÉES : le texte
   d'un SVG suit l'échelle de son cadre, et le tableau juste en dessous porte
   déjà les mêmes noms.

   C'est le voile du survol — l'opacité du GROUPE — qui les efface quand un
   autre secteur est désigné : elles n'ont donc pas de règle à elles. */

/* Le trait de rappel et la pastille portent la couleur du secteur, héritée par
   `currentColor` : c'est une donnée, pas un token de thème — la même position
   garde la même couleur en clair comme en sombre. Ici on ne règle que leur
   force, et elle aussi dérive de `--pop`. */
.donut__trait {
  stroke-width: 1.25;
  stroke-opacity: calc(.5 + var(--pop) * .5);
  transition: stroke-opacity .18s ease;
}
.donut__pastille {
  fill-opacity: calc(.16 + var(--pop) * .14);
  stroke-opacity: calc(.5 + var(--pop) * .5);
  stroke-width: 1.5;
  transition: fill-opacity .18s ease, stroke-opacity .18s ease;
}

/* LE TEXTE PORTE LES TOKENS DE TEXTE, JAMAIS LA COULEUR DU SECTEUR : c'est la
   pastille à côté qui porte l'identité. Un libellé teinté serait illisible sur
   la moitié de la palette, et changerait de contraste d'une ligne à l'autre. */
.donut__sym { font: 600 14px/1 var(--police-titre); fill: var(--text); }
.donut__pct { font: 500 12px/1 var(--police-num);   fill: var(--muted); }

/* LE CŒUR NE PREND JAMAIS LE POINTEUR. Il est dessiné dans le trou, donc
   au-dessus de rien ; s'il le prenait, survoler le vide central désignerait le
   secteur dont le cœur se trouve tracé en dernier. */
.donut__coeur { pointer-events: none; text-anchor: middle; transition: opacity .18s ease; }
.donut__coeur--survol { opacity: var(--pop); }

.donut__coeur-label {
  font: 600 10.5px/1 var(--police); fill: var(--muted);
  letter-spacing: .12em; text-transform: uppercase;
}
.donut__coeur-total { font: 700 21px/1 var(--police-num);   fill: var(--text); }
.donut__coeur-sym   { font: 700 17px/1 var(--police-titre); fill: var(--text); }
.donut__coeur-pct   { font: 600 12.5px/1 var(--police-num); fill: var(--muted); }

.repartition__reste { margin: 0; font-size: 12px; color: var(--muted); text-align: center; }

/* ------------------------------------------------- l'apparition -----------
   C'EST LA PREMIÈRE CHOSE QU'ON VOIT après une actualisation. Les parts
   ARRIVENT DE TROP LOIN — une fois et demie leur taille, tournées d'un quart
   de seizième de tour — et se posent l'une après l'autre dans le sens horaire :
   l'anneau se dézoome et les camemberts tombent en place.

   `backwards` ET NON `both`, et c'est la seule subtilité : `forwards` fige la
   valeur animée, qui l'emporte ensuite sur toute règle ordinaire — le survol
   n'épaissirait plus jamais l'arc, et la mise à l'échelle du petit écran ne
   s'appliquerait plus. Avec `backwards`, l'état de départ ne vaut que pendant
   le délai, et la cascade reprend la main une fois l'animation finie.

   L'IMAGE D'ARRIVÉE N'EST PAS ÉCRITE, et c'est ce qui rend l'animation
   compatible avec le téléphone : sans `to`, le navigateur prend l'état que la
   CASCADE donne à l'élément — `none` sur grand écran, `scale(1.5)` sous
   620 px. L'écrire en dur aurait fait rapetisser l'anneau d'un coup à la
   dernière image, sur mobile seulement.

   LE RETARD EST BORNÉ PAR `min()` : un portefeuille de quarante lignes ferait
   sinon deux secondes et demie d'attente, et une animation qu'on attend n'est
   plus une animation, c'est une lenteur. */
@keyframes donut-chute     { from { opacity: 0; transform: scale(1.5) rotate(-22deg); } }
@keyframes donut-coeur     { from { opacity: 0; } }
@keyframes familles-monte  { from { opacity: 0; transform: translateY(-8px); } }

.donut__part {
  /* Le repère est celui du `viewBox` et le centre est celui de l'anneau :
     sans cela, chaque secteur se mettrait à l'échelle autour de SA propre
     boîte et l'anneau éclaterait au lieu de se dézoomer. */
  transform-box: view-box;
  transform-origin: var(--donut-cx, 300px) var(--donut-cy, 180px);
  animation: donut-chute .8s cubic-bezier(.16, 1, .3, 1) backwards;
  animation-delay: calc(min(var(--i, 0), 14) * 50ms + 60ms);
}
.donut__coeur--defaut { animation: donut-coeur .5s ease backwards; animation-delay: .55s; }
.familles { animation: familles-monte .45s cubic-bezier(.22, 1, .36, 1) backwards; }

/* ------------------------------------------- les trois poches -------------
   LES COULEURS SONT ÉCRITES ICI, EN DUR, ET NON EN TOKENS DE THÈME : ce sont
   des données — « cette part est du bitcoin » — pas des surfaces, et elles ne
   doivent pas changer avec le thème. Les trois passent les cinq contrôles du
   validateur de palettes sur LES DEUX fonds, toutes paires confrontées
   (ΔE daltonisme 13,2 · vision normale 15,1 · contraste au-dessus de 3:1).
   L'ambre du bitcoin est aussi la convention du marché.

   LE TEXTE NE S'ÉCRIT PAS DESSUS, il s'écrit à CÔTÉ d'un point de couleur :
   `#bd7800` ne donne que 3,1:1 sous du blanc, soit moins que le seuil d'un
   petit texte. La couleur porte l'identité, les tokens de texte portent la
   lisibilité — c'est la même règle que les étiquettes de l'anneau. */
.familles { display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 640px; }
.familles__barre {
  display: flex; gap: 3px; height: 9px;
  border-radius: var(--r-pill); overflow: hidden;
}
.famille-seg { display: block; min-width: 3px; border-radius: var(--r-pill); opacity: .85; }
.familles__pastilles { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }

.famille {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--card-2);
  font-size: 12.5px; color: var(--text); text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}
.famille:hover { border-color: var(--line-fort); }
.famille.est-active { border-color: var(--f, var(--accent)); background: var(--accent-doux); }
.famille__point { width: 9px; height: 9px; border-radius: 50%; background: var(--f, var(--muted)); }
.famille__part { font-family: var(--police-num); color: var(--muted); }
.famille.est-active .famille__part { color: var(--text); }

/* LA COULEUR VOYAGE DANS `--f`, PAS DANS `color` — et ce n'est pas un détail
   de style : posée en `color`, elle serait héritée par le LIBELLÉ, qui
   s'afficherait en ambre sur la carte. `#bd7800` ne donne que 3,1:1 sous du
   blanc, soit moins que le seuil d'un petit texte. La couleur porte
   l'identité, les tokens de texte portent la lisibilité. */
.famille--bitcoin, .famille-seg--bitcoin { --f: #bd7800; }
.famille--ether,   .famille-seg--ether   { --f: #8f49a5; }
.famille--alt,     .famille-seg--alt     { --f: #2e8de2; }
.famille--stable,  .famille-seg--stable  { --f: #009b8c; }

/* ------------------------------------------ un même actif, plusieurs tickers
   (14/10/2026). L'en-tête du groupe porte la somme ; ses membres suivent,
   en retrait et en plus petit — ils restent des lignes ordinaires, avec leur
   lien et leur solde exact. */
.ligne-groupe td { font-weight: 600; border-top: 1px solid var(--line); }
.ligne-membre td:first-child { padding-left: 26px; }
.ligne-membre td { font-size: .93em; color: var(--muted); }
.ligne-membre td:first-child strong { color: var(--text); font-weight: 500; }
.groupe__tickers {
  margin-left: 6px; padding: 1px 7px; border-radius: var(--r-pill);
  border: 1px solid var(--line); font-size: 11px; font-weight: 400; color: var(--muted);
}

.famille-seg { background: var(--f, var(--muted)); }
.famille-seg.est-active { opacity: 1; }

/* ------------------------------------------ sur un téléphone -------------
   LES ÉTIQUETTES SONT RETIRÉES, PAS RÉTRÉCIES — et l'anneau prend leur place.
   Le texte d'un SVG suit l'échelle du cadre : à 390 px de large, un libellé
   de 14 unités est servi à moins de huit pixels, c'est-à-dire illisible, et
   le grossir aurait fait déborder les colonnes hors du cadre. Or le TABLEAU
   suit immédiatement, avec les mêmes icônes, les mêmes symboles et les mêmes
   parts : une légende de plus n'aurait rien appris à personne.

   On agrandit donc l'ANNEAU, autour du centre du cadre. Le facteur est borné
   par la hauteur : 116 × 2 × 1,5 = 348 unités pour un cadre de 364.

   `transform-box: view-box` fait porter la mise à l'échelle sur le repère du
   `viewBox` et non sur la boîte de chaque groupe — sans lui, chaque secteur
   se mettrait à l'échelle autour de SON propre centre et l'anneau éclaterait.
   Là où il n'est pas compris, rien ne bouge : l'anneau reste petit, il ne se
   disloque pas. */
@media (max-width: 620px) {
  .donut__etiquette, .repartition__reste { display: none; }
  .donut__part, .donut__coeur--defaut {
    /* `.donut__part` porte déjà le repère et le centre, pour son apparition ;
       le cœur, lui, ne s'anime pas et doit les poser. */
    transform-box: view-box;
    transform-origin: var(--donut-cx, 300px) var(--donut-cy, 180px);
    transform: scale(1.5);
  }
}

@media (prefers-reduced-motion: reduce) {
  .donut__part, .donut__coeur, .familles { animation: none; }
  .donut__part, .donut__arc, .donut__coeur, .donut__etiquette, .famille { transition: none; }
}

/* LA BARRE DE FILTRES D'UN TABLEAU. Elle n'avait aucune règle : ses formulaires
   se plaçaient par `float: right`, qui ne revient pas à la ligne et ne dit rien
   de la largeur qu'il lui faut. Un flex qui ENVELOPPE remplace les trois. */
.controls {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 10px 14px;
  margin-bottom: 10px;
}
/* LA MARGE EST REMISE À ZÉRO SUR LES SEULS AXES VERTICAUX, et ce n'est pas
   du zèle : le raccourci `margin: 0` pèse (0,1,1) là où `.controls__tri` pèse
   (0,1,0). Il écrasait donc le `margin-right: auto` posé juste en dessous —
   la déclaration était parsée sans erreur et ne faisait RIEN, et les deux
   cases s'arrêtaient 92 px avant le bord droit du tableau. C'est le piège que
   `verify_theme.php` (partie 1 bis) garde ailleurs : une classe utilitaire
   perd toujours contre un sélecteur contextuel. On baisse le concurrent
   plutôt que d'empiler des qualificatifs. */
.controls form { margin-top: 0; margin-bottom: 0; }
/* L'ORDRE VISUEL EST TENU PAR `order`, ET LE MARKUP N'A PAS BOUGÉ. Les deux
   cases étaient placées par `float: right`, qui les met à droite DANS L'ORDRE
   INVERSE de leur écriture ; le tri, lui, reste à gauche. Reproduire cela en
   flex sans `order` aurait demandé de déplacer deux formulaires dans le
   fichier — donc un diff illisible pour un changement qui ne se voit pas.
   `margin-right: auto` plutôt que `justify-content: space-between` : les deux
   cases restent COLLÉES l'une à l'autre à droite, comme le faisaient les
   `float`, au lieu d'être réparties sur toute la ligne. Mesuré au
   `getBoundingClientRect` : elles s'arrêtent à 708 px sur un écran de 1440,
   exactement où les plaçaient les `float`. */
.controls__tri  { order: 0; margin-right: auto; }
#hideSpamForm   { order: 2; }
#hideZeroForm   { order: 3; }

/* ------------------------------------------------------------- mobile --- */

/* SUR UN TRÈS GRAND ÉCRAN, LE GRAPHE PREND LA PLACE. Les deux colonnes valent
   1fr chacune, ce qui est le bon partage tant que l'écran est étroit : le
   tableau a besoin de quatre colonnes lisibles. Au-delà, il n'a plus besoin de
   rien — ses libellés sont courts et ses nombres alignés à droite — alors que
   l'anneau, lui, gagne à chaque pixel : ses étiquettes grossissent avec le
   cadre, puisque le texte d'un SVG suit son échelle.

   Le modificateur ne porte QUE la page d'accueil : `.cards-2col` sert aussi
   `tx_by_coin.php`, où les deux cartes se valent. */
@media (min-width: 1400px) {
  .cards-2col--repartition { grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); }
  .donut, .familles { max-width: 820px; }
}
@media (min-width: 1800px) {
  .cards-2col--repartition { grid-template-columns: minmax(0, .7fr) minmax(0, 1.3fr); }
  .donut, .familles { max-width: 940px; }
}

@media (max-width: 900px) {
  .cards-2col, .grid.cols-2, .grid.cols-3 { grid-template-columns: minmax(0, 1fr); }

  /* SUR UNE COLONNE, LE GRAPHE PASSE DEVANT LA LISTE. Il tient dans un écran,
     la liste non : reléguée sous quatre-vingts lignes de tableau, la
     répartition n'était atteignable qu'après un long défilement — c'est-à-dire
     jamais, alors que c'est le premier écran servi après une actualisation.

     `order` plutôt qu'un déplacement dans le balisage : l'ordre du DOM reste
     celui de la lecture à deux colonnes, où le tableau est à gauche, et les
     deux blocs gardent leur ordre de tabulation sur grand écran. */
  .cards-2col > .repartition { order: -1; }
}

@media (max-width: 768px) {
  .hamburger { display: block; }

  .site-nav {
    display: none;
    flex-direction: column; align-items: stretch;
    position: absolute; top: 100%; left: 0; right: 0;
    padding: 8px;
    background: var(--bg);
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--ombre-lg);
    z-index: 100;
  }
  .site-nav.show { display: flex; }
  .nav-item { padding: 11px 14px; border-radius: var(--r-sm); }

  .site-header { padding: 9px 14px; gap: 10px; }
  .site-main { padding: 0 12px; margin: 14px auto 0; }
  .card { padding: 14px; border-radius: var(--r-sm); }
  .kpis { grid-template-columns: 1fr; gap: 10px; }
  h1 { font-size: 20px; }

  #themeSwitch { font-size: 11px; padding: 5px 9px; }
  #hideZeroForm { text-align: left; font-size: 12px; font-style: italic; }

  .table th, .table td, .tbl th, .tbl td {
    padding: 8px; font-size: 12px;
  }
}

/* L'ORDRE DES ÉLÉMENTS DU HEADER, sans toucher au markup.
   Le HTML les pose dans l'ordre marque / bascule de thème / hamburger / nav,
   que la variante actuelle arrange avec un `margin-left:auto`. `order` fait
   cela sans qu'une seule ligne de `header.php` ait à distinguer les deux
   variantes — ce qui aurait fait deux en-têtes à tenir d'accord, exactement
   ce que ce dépôt a payé cher. */
.site-header .brand        { order: 1; }
/* `margin-left:auto` COLLE le sélecteur au bord droit, quelle que soit la
   place que prend le reste. Il vit sur la NAVIGATION, pas sur la bascule de
   thème : la nav, le thème et l'avatar forment un seul bloc à droite, la
   marque seule à gauche — pas la marque et la nav collées ensemble avec un
   grand vide avant la bascule de thème, ce que portait la version
   précédente. */
.site-header .site-nav     { order: 2; margin-left: auto; }
.site-header .theme-switch { order: 3; }
.site-header .hamburger    { order: 4; }

@media (max-width: 768px) {
  /* Repliée, la navigation se place SOUS l'en-tête : son `order` ne doit
     plus la faire passer devant le sélecteur de thème. */
  .site-header .site-nav { order: 9; }

  /* LA MARQUE À GAUCHE, TOUT LE RESTE À DROITE. Le `margin-left: auto` qui
     tenait ce rôle vit sur la NAVIGATION — or sur petit écran elle est
     `display: none` tant qu'on n'a pas ouvert le menu, et un élément retiré
     du flux n'y pousse plus rien : la bascule de thème, la pastille et le
     hamburger se collaient donc à la marque, laissant la moitié droite de la
     barre vide. La marge passe sur la MARQUE, qui est toujours là.

     Longhand et non `margin: 0 auto 0 0` : le raccourci écraserait une marge
     verticale posée ailleurs, et ce dépôt a déjà payé deux fois cette
     famille de défaut (C-187). */
  .site-header .brand { margin-right: auto; }
}

/* --------------------------------------------- écrans étroits (≤480px) --- */
/* L'EN-TÊTE NE TENAIT PLUS SUR UN PETIT TÉLÉPHONE, et ce n'est pas une
   impression : marque, bascule de thème, pastille et hamburger font 337 px de
   contenu minimal, là où un iPhone SE en offre 320. Le surplus ne se voyait
   pas sous la forme d'une barre de défilement — `body` porte
   `overflow-x: hidden`, donc la page n'est pas élargie, elle est COUPÉE — mais
   le hamburger, dernier de la ligne, passait sous le bord droit. C'est-à-dire
   que le seul accès à la navigation devenait inatteignable.

   CE QU'ON RETIRE EST LE MOT DE LA BASCULE, JAMAIS LE BOUTON. Son icône reste,
   et son `aria-label` porte déjà le libellé pour qui ne lit pas les icônes ;
   `app.js` continue d'écrire dans `#themeName`, qui existe toujours. Retirer
   le bouton aurait coûté le choix du thème à qui n'a qu'un téléphone. */
@media (max-width: 480px) {
  /* L'ÉCART ET LE REMBOURRAGE SE RESSERRENT PARCE QUE LE LOGO A GRANDI. À 50 px
     de haut il en prenait 153 de large (152 aujourd'hui, à 48 px) ; avec 20 px de rembourrage et trois
     écarts de 8, il ne restait plus assez pour la bascule, la pastille et le
     hamburger — et le surplus ne se voit PAS comme une barre de défilement,
     `body` portant `overflow-x: hidden` : le dernier élément passe simplement
     sous le bord. C'est le défaut que ce palier existe pour éviter. */
  .site-header { padding: 8px 8px; gap: 6px; }
  .theme-switch { padding: 5px 8px; }
  #themeName    { display: none; }
  .site-main   { padding: 0 10px; }
}

/* =========================================================================
   PAGES HORS APPLICATION — connexion, inscription, mots de passe, retours
   Servies par `inc/layout_hors_app.php`. Une carte centrée sur le fond
   aurora, sans menu : ces pages s'adressent à quelqu'un qui n'est pas encore
   entré, et la navigation de l'application n'y mènerait nulle part.
   ========================================================================= */

body.page-hors-app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 18px;
  gap: 18px;
}

.carte-acces {
  width: 100%;
  max-width: 408px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--ombre-lg);
  padding: 30px 30px 26px;
  -webkit-backdrop-filter: var(--card-flou);
          backdrop-filter: var(--card-flou);
}
.carte-acces--large { max-width: 560px; }

.acces-marque {
  display: block;
  margin: 0 auto 18px;
  width: max-content;
}
.acces-marque img { height: 46px; width: auto; display: block; }



.carte-acces h1 {
  margin: 0 0 6px;
  font-size: 22px;
  text-align: center;
  letter-spacing: -.025em;
}
.acces-sous-titre {
  margin: 0 0 20px;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
.carte-acces h1 + form,
.carte-acces h1 + .alert,
.carte-acces h1 + p { margin-top: 18px; }

/* Les champs occupent toute la largeur : une carte de 408 px n'a pas de
   raison de laisser un champ à moitié vide, et deux champs de largeurs
   différentes se lisent comme deux natures différentes. */
.carte-acces label {
  display: block;
  margin: 14px 0 6px;
  font-size: 12.5px; font-weight: 600;
  color: var(--muted);
}
.carte-acces input[type="text"],
.carte-acces input[type="email"],
.carte-acces input[type="password"],
.carte-acces input[type="tel"],
.carte-acces select,
.carte-acces textarea { width: 100%; padding: 11px 13px; font-size: 14px; }

.carte-acces button[type="submit"],
.carte-acces .btn--bloc {
  width: 100%;
  margin-top: 20px;
  padding: 12px 16px;
  font-size: 14px;
  background: var(--grad);
  border-color: transparent;
  color: #fff;
}
.carte-acces button[type="submit"]:hover { filter: brightness(1.08); }

.carte-acces .alert,
.carte-acces .notice { margin: 14px 0 0; }
.carte-acces .alert p { margin: 6px 0 0; }

/* Les cases à cocher des conditions générales : la case et son texte sur une
   ligne, le texte alignés à gauche même sur deux lignes. */
.carte-acces .chk,
.carte-acces label.inline {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 16px 0 0;
  font-weight: 400; font-size: 12.5px; color: var(--text);
}
.carte-acces .chk input,
.carte-acces label.inline input { margin-top: 2px; flex-shrink: 0; }

.acces-liens {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 6px 18px;
  margin-top: 22px; padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
}

.acces-pied {
  display: flex; align-items: center; gap: 14px;
  font-size: 11.5px; color: var(--muted);
}
.acces-pied a { color: var(--muted); }

.acces-bascule {
  position: fixed; top: 18px; right: 18px; z-index: 5;
  width: 36px; height: 36px; padding: 0;
  border-radius: 50%;
  background: var(--card);
  border: 1px solid var(--line);
  color: var(--muted);
}
.acces-bascule:hover { color: var(--text); transform: none; }
/* Une seule des deux icônes est tracée : celle qui désigne le thème COURANT.
   Montrer celle vers laquelle on va est l'autre convention, aussi défendable,
   mais elle oblige à lire l'infobulle pour savoir où l'on est. */
.acces-bascule .icone-lune   { display: none; }
:root[data-theme="dark"] .acces-bascule .icone-lune   { display: inline; }
:root[data-theme="dark"] .acces-bascule .icone-soleil { display: none; }

/* Le widget reCAPTCHA est servi dans un iframe de largeur fixe (304 px) :
   il ne peut pas suivre la carte, on le centre. */
.carte-acces .g-recaptcha { margin: 18px 0 0; display: flex; justify-content: center; }

@media (max-width: 480px) {
  body.page-hors-app { padding: 20px 14px; }
  .carte-acces { padding: 22px 18px 20px; border-radius: var(--r-sm); }
  .acces-bascule { top: 12px; right: 12px; }
}

/* ------------------------------------- étiquettes de type de mouvement --- */
/* Une étiquette par FAMILLE, pas par type : `deposit_fiat` et
   `deposit_crypto` sont deux entrées, et leur donner deux couleurs voisines
   n'apprend rien tout en occupant deux crans de la palette. Le libellé
   technique dit lequel des deux c'est.

   Fond TEINTÉ et texte coloré, jamais fond saturé et texte noir : c'est ce
   qui permet à la même règle de tenir dans les deux thèmes. `tx_table.php`
   écrivait `gold`, `pink`, `darkblue` et `lightgreen` sur du `#000`. */
.badge-tx {
  gap: 6px;
  padding: 2px 9px;
  font-size: 11px; font-weight: 600;
  border-color: transparent;
  white-space: nowrap;
}
.badge-tx__signe { font-size: 12px; line-height: 1; opacity: .85; }
.badge-tx__mot { margin-left: 5px; }

.badge-tx--entree    { background: var(--up-doux);     color: var(--up); }
.badge-tx--sortie    { background: var(--down-doux);   color: var(--down); }
.badge-tx--gain      { background: var(--warn-doux);   color: var(--warn); }
.badge-tx--echange   { background: var(--accent-doux); color: var(--accent-ink); }
.badge-tx--liquide   { background: var(--info-doux);   color: var(--info); }
.badge-tx--frais,
.badge-tx--neutre    { background: var(--input); color: var(--muted); }

/* ------------------------------------------ blocs qui se déplient -------- */
/* Ce que `slideToggle()` de jQuery faisait, en CSS. `grid-template-rows`
   anime une hauteur qu'on ne connaît pas d'avance — ce qu'aucune transition
   sur `height` ne sait faire sans mesurer en JavaScript.

   `[hidden]` DOIT REPRENDRE LA MAIN, et c'est la leçon du logo en double :
   `display: grid` sur `.token-details` pèse (0,1,0) et écraserait le
   `display: none` que le navigateur attache à `[hidden]`. Le sélecteur
   ci-dessous pèse (0,2,0) et gagne. Sans lui, un bloc « replié » resterait
   visible — et le défaut ne se verrait qu'au premier chargement, avant tout
   clic. */
.token-details {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  margin-top: 8px;
  transition: grid-template-rows .22s ease, opacity .18s ease;
}
.token-details[hidden] { display: none; }
.token-details > * { overflow: hidden; min-height: 0; }
.token-details.deplie { grid-template-rows: 1fr; opacity: 1; }

.portfolio-cell { cursor: pointer; }
.portfolio-cell:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
.portfolio-toggle-icon {
  display: inline-block;
  margin-left: 6px;
  color: var(--muted);
  transition: transform .2s ease;
}
.portfolio-cell.open .portfolio-toggle-icon { transform: rotate(180deg); }

#toggle-tab-summary { cursor: pointer; }
#toggle-tab-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ======================================================================
   MENU DE COMPTE, ONGLETS, ET CE QUE LE MINIMALISME RETIRE
   ====================================================================== */

/* ------------------------------------------------ menu de compte -------- */
/* Un `<details>`, pas un survol : un menu qui s'ouvre au passage de la souris
   est inatteignable au clavier et s'ouvre par accident en visant autre chose.
   `<details>` donne le clavier, l'échappement et l'état ouvert sans script. */
/* PAS DE `position: relative` ICI. Le panneau s'ancrait sur l'avatar
   lui-même — juste tant qu'aucun élément ne le suit dans l'en-tête. Sur
   petit écran le hamburger vient après lui (`order: 4` à égalité, mais
   après dans le DOM) : l'avatar n'est plus au bord droit, et `right: 0`
   collait le panneau à SON bord à lui, qui pendait vers la gauche au lieu
   de toucher le bord de l'écran — visible seulement en OUVRANT le menu sur
   téléphone, ce que la relecture ne fait jamais.
   En laissant `.menu-compte` non positionné, le panneau prend pour bloc
   englobant `.site-header` (sticky, donc positionné) — exactement ce que
   fait déjà `.site-nav` en dessous. */
.menu-compte { flex-shrink: 0; }
.menu-compte > summary {
  display: flex; align-items: center; gap: 5px;
  padding: 3px 7px 3px 3px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--card-2);
  color: var(--muted);
  cursor: pointer; list-style: none;
  transition: border-color .15s ease, background .15s ease;
}
.menu-compte > summary::-webkit-details-marker { display: none; }
.menu-compte > summary:hover { border-color: var(--line-fort); background: var(--card); }
.menu-compte > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.menu-compte[open] > summary { border-color: var(--line-fort); }
.menu-compte[open] > summary svg { transform: rotate(180deg); }
.menu-compte > summary svg { transition: transform .18s ease; }

.menu-compte__pastille {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--grad); color: #fff;
  font-size: 10.5px; font-weight: 700; letter-spacing: -.02em;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

.menu-compte__liste {
  position: absolute; right: 0; top: calc(100% + 7px);
  min-width: 208px; padding: 6px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r-sm); box-shadow: var(--ombre-lg);
  z-index: 60;
  display: flex; flex-direction: column; gap: 1px;
  -webkit-backdrop-filter: var(--card-flou);
          backdrop-filter: var(--card-flou);
}
.menu-compte__item {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: var(--r-xs);
  color: var(--text); font-size: 13px; text-decoration: none;
}
.menu-compte__item:hover { background: var(--card-2); text-decoration: none; }
.menu-compte__item.active { color: var(--accent-ink); font-weight: 600; }
.site-header .menu-compte { order: 4; }

/* ------------------------------------------------------- onglets -------- */
/* De VRAIS liens : chaque vue est une page, avec son URL et son bouton
   précédent. Le trait actif est porté par un pseudo-élément et non par une
   bordure, pour qu'il ne déplace rien en apparaissant. */
.entete-portefeuille {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px;
  margin-bottom: 18px;
}
.entete-portefeuille h1 { margin: 0; }

.onglets {
  display: flex; gap: 2px;
  padding: 3px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
}
.onglets__item {
  padding: 6px 14px; border-radius: var(--r-pill);
  color: var(--muted); font-size: 12.5px; font-weight: 600;
  text-decoration: none; white-space: nowrap;
  transition: color .15s ease, background .15s ease;
}
.onglets__item:hover { color: var(--text); text-decoration: none; }
.onglets__item.is-actif {
  background: var(--card); color: var(--text);
  box-shadow: var(--ombre-sm);
}

/* ------------------------------ le fond, ATTÉNUÉ sur les données -------- */
/* Les halos sont à leur place derrière une page de connexion ou un titre.
   Derrière un tableau de quatre-vingts lignes, ils entrent en concurrence
   avec les chiffres — et c'est bien le tableau qu'on est venu lire.
   On garde l'identité, on baisse l'intensité, et le quadrillage disparaît :
   une grille sous une grille de données, ce sont deux grilles. */
body.page-donnees::before { opacity: .45; }
body.page-donnees::after { display: none; }

/* ------------------------------ étiquettes de type ---------------------- */
/* LE MOT EST TOUJOURS LÀ. Il s'est déplié au survol pendant un lot, et c'était
   une mauvaise idée : `max-width` fait partie du FLUX, si bien que chaque
   passage de souris élargissait la colonne et poussait toutes les suivantes.
   Le tableau bougeait en permanence pendant qu'on le parcourait.
   La leçon vaut au-delà de ce cas : une animation déclenchée par le survol
   d'une LIGNE ne doit jamais toucher une propriété qui change la mise en page
   — largeur, hauteur, marge, `display`. Seules `opacity`, `color`,
   `background` et `transform` sont sans effet sur les voisins. */
.badge-tx { padding: 2px 8px; }

@media (max-width: 768px) {
  /* CINQ ONGLETS NE TIENNENT PAS SUR UNE LIGNE : ceux du module fiscal
     sortaient de 630 px à 320 et à 390 px (mesuré le 16/10/2026, la première
     fois que le banc mobile a mesuré ces pages). Ils reviennent à la ligne ;
     les trois du portefeuille tiennent toujours sur une seule. Le rayon passe
     à celui d'une carte : une pilule sur deux lignes ne ressemble à rien. */
  .onglets { width: 100%; justify-content: space-between; flex-wrap: wrap; border-radius: 16px; }
  .onglets__item { flex: 1 1 auto; text-align: center; padding: 7px 8px; }
  /* IL EST ANCRÉ À DROITE, COMME AILLEURS. Cette règle a porté
     `right: auto; left: 0` depuis sa création, et c'était juste l'inverse de
     ce qu'il fallait : ouvrir le panneau vers la droite le faisait sortir de
     l'écran — 92 px dehors sur un écran de 390. `right: 0; left: auto` a
     réparé cette sortie d'écran SANS réparer l'ancrage : tant que le bloc
     englobant restait l'avatar lui-même, le panneau collait au bord DE
     L'AVATAR, qui n'est pas le bord de l'écran sur petit écran — le
     hamburger le suit. C'est la règle ci-dessus, `.menu-compte` non
     positionné, qui fait maintenant porter `right: 0` par `.site-header`.
     Le `max-width` reste la ceinture : il borne le panneau à l'écran quelle
     que soit la longueur des libellés, que la traduction anglaise allonge. */
  .menu-compte__liste {
    right: 0; left: auto;
    max-width: calc(100vw - 20px);
  }
}

/* ----------------------------------------- la colonne des tickers ------- */
/* ELLE EST SÉPARÉE DE CELLE DES MONTANTS, et l'essai inverse a montré
   pourquoi : réunis dans une cellule, le ticker démarrait après un nombre de
   longueur variable, donc à une abscisse différente à chaque ligne. Une
   colonne de tickers en escalier se lit plus mal que deux colonnes, même si
   l'information est logiquement la même.
   Les deux restent ADJACENTES — quantité puis actif — ce qui garde la
   proximité : chaque colonne a son alignement, et les deux bords sont
   droits. */
.cell-ticker {
  white-space: nowrap;
  font-size: 11.5px; font-weight: 600;
}
.cell-ticker a { display: inline-flex; align-items: center; gap: 3px; }
.cell-ticker img { margin-right: 1px; }

/* L'UNITÉ D'UN NOMBRE N'EST PAS UNE DONNÉE, c'est ce qui la rend lisible :
   elle suit le nombre, en plus petit et en gris, et ne concurrence pas la
   colonne de chiffres qu'on balaie du regard. */
.unite {
  margin-left: 4px;
  font-size: 10px; font-weight: 600;
  color: var(--muted);
  text-transform: uppercase; letter-spacing: .02em;
}

/* --------------------------------- la bande d'indicateurs de l'accueil -- */
/* Un chiffre qui ne peut PAS être calculé ne s'écrit pas en gros et en
   couleur : il se dit, en gris, de la même taille que les autres pour ne pas
   trouer la bande. C'est la règle des trois états appliquée à un indicateur —
   « non chiffrable » n'est ni zéro ni une perte. */
.kpi__value--muet {
  color: var(--muted);
  font-size: 17px; font-weight: 600;
  letter-spacing: -.01em;
}
.kpi__value.est-hausse { color: var(--up); }
.kpi__value.est-baisse { color: var(--down); }
/* La carte accentuée porte le dégradé sur son chiffre ; les deux autres
   prennent une couleur pleine. Deux dégradés côte à côte ne désignent plus
   rien. */
.kpi--accent .kpi__value.est-hausse,
.kpi--accent .kpi__value.est-baisse {
  background: none; color: var(--text); -webkit-text-fill-color: currentColor;
}

/* ======================================================================
   PAGE D'UN JETON — l'en-tête, le cours du jour, les deux graphiques
   ====================================================================== */

/* UN COURS CENTRÉ DANS LE VIDE. `.card .live-price` portait
   `text-align: center` sur un bloc de PLEINE LARGEUR : le titre restait collé
   à gauche, le cours flottait au milieu, et il y avait un mètre de rien entre
   les deux — sur 1 440 px, une bande vide de 600. Les deux vivent désormais
   sur la même ligne, par `.bar`, dont le `flex-wrap` les fait passer l'un sous
   l'autre plutôt que de pousser le second hors de l'écran.

   `.current_balances` est partie avec : elle figeait le tableau des soldes à
   500 px en `inline-block`, donc à 40 % de la carte, le reste vide. Sa jumelle
   `.intro_summary` visait un `id`, pas une classe, si bien que les deux ne se
   sont JAMAIS placées côte à côte comme la règle le prévoyait. Une paire dont
   une moitié ne s'applique nulle part ne se répare pas, elle se remplace : la
   grille `.cards-2col` fait ce que l'auteur avait en tête. */
.coin-tete { align-items: baseline; }
.coin-cours { display: flex; align-items: baseline; gap: 8px; }
/* LE COURS DU JOUR N'EST PAS UNE PLUS-VALUE : il n'a pas de SENS, donc pas de
   couleur de sens. Il était vert (`var(--up)`), ce qui se lit « en hausse »
   sur un chiffre qui ne dit rien de tel — les deux seules couleurs qui
   signifient quelque chose dans cette application sont celles des
   `kpi__value.est-hausse` et `est-baisse`, juste en dessous. */
.coin-cours__valeur {
  font-family: var(--police-titre);
  font-size: 22px; font-weight: 800; letter-spacing: -.02em;
  color: var(--text);
}
/* « Prix indisponible » se dit en gris et plus petit : la règle des trois
   états appliquée à un chiffre absent, comme `.kpi__value--muet`. */
.coin-cours__valeur--muet { color: var(--muted); font-size: 15px; font-weight: 600; }
.coin-cours__libelle {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}

/* LE SOCLE D'UN GRAPHIQUE DOIT SAVOIR RÉTRÉCIR — c'est C-175. ApexCharts
   ÉCRIT la largeur de son SVG : sans `min-width: 0`, le socle impose son
   min-content à la carte, donc à la page, et la page déborde de l'écran sans
   qu'aucune barre de défilement ne le signale (`body` porte
   `overflow-x: hidden`). La hauteur est posée par la bibliothèque. */
.graphique-jeton { width: 100%; min-width: 0; margin-top: 6px; }
.graphique-reserves { margin: 8px 0 0; }
.explication-comptabilite { margin: 12px 0 0; }

/* ======================================================================
   PORTEFEUILLE » ÉVOLUTION — le tableau mensuel et le détail d'un mois
   ====================================================================== */

/* Le `<style>` qui vivait dans `portfolio_performance.php` est ici : il était
   servi à chaque rendu de la page et ne suivait pas le thème. */
.tbl-evolution .month-row.a-du-detail { cursor: pointer; }
.tbl-evolution .month-row.a-du-detail:hover { background: var(--card-2); }
.tbl-evolution .month-row:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
.tbl-evolution .mois {
  font-weight: 600;
  text-transform: capitalize;
  white-space: nowrap;
}
.tbl-evolution .chevron {
  display: inline-block;
  color: var(--muted);
  transition: transform .18s ease;
}
.tbl-evolution .month-row.open .chevron { transform: rotate(180deg); }
/* Une ligne ouverte et son détail se lisent comme un seul bloc : la première
   perd sa bordure basse, et les deux prennent le même fond. */
.tbl-evolution .month-row.open > td { border-bottom-color: transparent; }
.tbl-evolution .month-row.open,
.tbl-evolution .ligne-detail { background: var(--card-2); }

/* La CONVERSION, sous le montant : plus petite et grise, elle ne concurrence
   pas le chiffre qu'elle éclaire. C'est ce que `render_usd_quote()` fait
   partout ailleurs — ici la seconde monnaie occupait une COLONNE entière. */
.tbl-evolution .sub { font-size: 10.5px; color: var(--muted); }

/* --------------------------------------- le détail d'un mois ------------ */
/* CE N'EST PLUS UN TABLEAU DANS UNE CELLULE. Une grille, sur une ligne à part :
   le sous-tableau élargissait la colonne qui le portait, et toutes les lignes
   du tableau se déformaient à chaque ouverture. */
.tbl-evolution .ligne-detail > td { padding: 0 14px 14px; border-bottom: 1px solid var(--line); }

.detail-actifs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 2px 18px;
}
.detail-actif {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 10px;
  padding: 5px 9px;
  border-radius: var(--r-xs);
  font-size: 12px;
}
.detail-actif:hover { background: var(--card); }
.detail-actif__nom {
  display: inline-flex; align-items: center; gap: 5px;
  font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.detail-actif__solde {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.detail-actif__valeur {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  min-width: 6.5rem; text-align: right;
}

/* Un intitulé réservé aux lecteurs d'écran : la colonne du chevron n'a pas de
   titre visible, mais elle en a besoin d'un pour être annoncée. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* La ligne qui RÉSUME les actifs sans cours d'un mois : elle compte, elle
   n'énumère pas — les symboles sont dans son infobulle. Discrète, parce
   qu'elle dit une absence et non une valeur. */
.detail-actif--resume { color: var(--muted); font-style: italic; cursor: help; }
.detail-actif--resume .detail-actif__nom { font-weight: 500; }


/* ═══════════════ Sélecteur de langue du menu de compte ═══════════════
   Il vit DANS le menu de compte, sous les entrées de réglage : changer de
   langue est un réglage, pas une vue de données. Le mettre dans la barre lui
   aurait donné le même poids qu'au portefeuille.

   Chaque langue non courante est un FORMULAIRE (POST + jeton), parce que le
   choix est enregistré sur le compte — et ce dépôt n'écrit jamais sur un GET.
   Le bouton est donc remis à plat pour ne pas ressembler à un bouton. */
.menu-compte__langues{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
    padding:10px 14px;margin-top:6px;border-top:1px solid var(--line)}
.menu-compte__langues-titre{font-size:12px;color:var(--muted);margin-right:auto}
.menu-compte__langues .lang-form{display:inline;margin:0}
.menu-compte__langues .lang-on,
.menu-compte__langues .lang-off{font-size:12px;font-weight:600;letter-spacing:.04em;
    padding:3px 8px;border-radius:999px;line-height:1.6}
.menu-compte__langues .lang-on{background:var(--accent);color:#fff}
.menu-compte__langues .lang-off{background:none;border:1px solid var(--line);
    color:var(--muted);cursor:pointer;font-family:inherit}
.menu-compte__langues .lang-off:hover{color:var(--text);border-color:var(--accent)}

/* Le même, sur les huit pages hors application : elles n'ont pas de menu de
   compte — il n'y a pas encore de compte — donc le sélecteur est en pied de
   carte. */
.hors-app__langues{display:flex;justify-content:center;align-items:center;gap:8px;margin-top:18px}
.hors-app__langues .lang-form{display:inline;margin:0}
.hors-app__langues .lang-on,
.hors-app__langues .lang-off{font-size:12px;font-weight:600;padding:3px 9px;border-radius:999px}
.hors-app__langues .lang-on{background:var(--accent);color:#fff}
.hors-app__langues .lang-off{background:none;border:1px solid var(--line);color:var(--muted);
    cursor:pointer;font-family:inherit}
.hors-app__langues .lang-off:hover{color:var(--text);border-color:var(--accent)}

/* ── L'icône d'information et sa bulle (`inc/info.php`) ───────────────────
   Un `popover` natif : ouverture au clic et au clavier, fermeture par Échap
   ou par un clic à côté, sans script. La bulle vit dans la couche
   supérieure, centrée à l'écran — elle ne peut donc être ni coupée par une
   enveloppe de tableau qui défile, ni poussée hors du cadre sur un
   téléphone.

   LE FOND EST OPAQUE, et il faut le fabriquer : `--card` est translucide en
   thème sombre, et une bulle transparente posée au-dessus d'un tableau serait
   illisible. On pose la teinte de la carte sur le fond de page. */
.info-i{display:inline-flex;align-items:center;justify-content:center;
    width:16px;height:16px;margin-left:5px;padding:0;vertical-align:1px;
    border:1px solid var(--line-fort);border-radius:999px;background:none;
    color:var(--muted);font:600 10.5px/1 var(--police);font-style:normal;
    cursor:pointer}
.info-i:hover,.info-i:focus-visible{color:var(--accent-ink);border-color:var(--accent)}
.info-i--alerte{color:var(--warn);border-color:var(--warn);background:var(--warn-doux)}
.info-pop{width:min(440px, calc(100vw - 32px));max-height:min(70vh, 560px);overflow:auto;
    margin:auto;padding:16px 36px 16px 16px;border:1px solid var(--line-fort);
    border-radius:var(--r-sm);color:var(--text);box-shadow:var(--ombre-lg);
    background:var(--bg);background-image:linear-gradient(var(--card), var(--card));
    font:400 13.5px/1.55 var(--police);font-style:normal;letter-spacing:normal;text-transform:none;
    text-align:left;white-space:normal}
.info-pop::backdrop{background:rgba(15, 23, 42, .28)}
.info-pop:not(:popover-open){display:none}
.info-pop:popover-open{display:block}
.info-pop__p{display:block}
.info-pop__p + .info-pop__p{margin-top:8px}
.info-pop__fermer{position:absolute;top:6px;right:8px;border:0;background:none;
    color:var(--muted);font-size:20px;line-height:1;cursor:pointer;padding:2px 4px}
.info-pop__fermer:hover{color:var(--text)}

/* ----------------------------------------- « À valider » : son nombre ---- */
/* § 36.3. Le NOMBRE est dans l'entrée du menu ; un POINT le signale sur
   l'avatar, parce que le menu est fermé et qu'un nombre qu'il faut ouvrir le
   menu pour voir ne prévient de rien. Le point est posé en `position:
   absolute` sur le `<summary>` : il ne prend aucune place dans l'en-tête, qui
   n'en a plus sous 480 px (voir le logo à 48 px, plus haut). */
.menu-compte > summary { position: relative; }
.menu-compte__signal {
  position: absolute; top: 1px; left: 23px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--warn);
  box-shadow: 0 0 0 2px var(--card-2);
}
.menu-compte__nombre {
  margin-left: auto;
  min-width: 20px; padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--warn-doux); color: var(--warn);
  font-size: 11.5px; font-weight: 600; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------------
   LE BANDEAU DE LA DÉMONSTRATION (lot 63) — servi par `header.php` à la seule
   session ouverte par `/demo.php`. Il revient à la ligne sur un téléphone :
   un bouton poussé hors de l'écran serait l'inscription elle-même perdue.
   ------------------------------------------------------------------------ */
.bandeau-demo {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px;
  margin: 0 auto 16px; max-width: 1400px; padding: 12px 16px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--accent); border-radius: 12px;
}
.bandeau-demo__texte { flex: 1 1 320px; min-width: 0; margin: 0; }
.bandeau-demo__cta {
  flex: 0 0 auto; padding: 8px 14px; border-radius: var(--r-pill);
  background: var(--accent); color: #fff; font-weight: 700; text-decoration: none;
}
.bandeau-demo__refus { flex: 1 1 100%; margin: 0; color: var(--warn); font-weight: 600; }

/* ── La sécurité d'une clé d'API (inc/securite_cle_api.php) ────────────────
   Un <details> : déplié tant qu'aucune clé n'est enregistrée, replié ensuite.
   Le liseré vert dit « rassurant », pas « attention » : ce bloc n'avertit de
   rien, il explique. */
.secu-cle {
  max-width: 720px; margin: 0 auto 1rem; text-align: left;
  border-left: 4px solid var(--up);
}
.secu-cle > summary { cursor: pointer; font-weight: 700; }
.secu-cle-etapes {
  margin: .8rem 0 0; padding-left: 1.3rem;
  display: grid; gap: .55rem; font-size: .93rem; line-height: 1.55;
}
.secu-cle-lien { margin: .8rem 0 0; font-size: .9rem; }
.ip-sortante { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
/* Un clic sélectionne l'adresse ENTIÈRE : c'est le repli sans presse-papiers,
   et le geste naturel sur un téléphone. */
.ip-sortante code {
  user-select: all; -webkit-user-select: all;
  padding: .1rem .45rem; border-radius: 6px;
  background: rgba(127,127,127,.12); font-size: .95rem;
}
.ip-sortante__copier {
  padding: .2rem .7rem; border-radius: var(--r-pill); cursor: pointer;
  border: 1px solid var(--accent); background: transparent; color: var(--text);
  font: inherit; font-size: .85rem;
}

/* ── La saisie manuelle (saisie.php, lot 70) ─────────────────────────────
   Une grille qui se replie toute seule : aucune largeur fixe, rien qui sorte
   d'un écran de 320 px (verify_mobile.php). */
.saisie-lead { color: var(--muted); max-width: 52rem; }
.saisie .card { margin-bottom: 1rem; }
.saisie-liste-comptes { list-style: none; padding: 0; margin: 0 0 .8rem; display: flex; flex-wrap: wrap; gap: .5rem; }
.saisie-liste-comptes li { border: 1px solid var(--line); border-radius: var(--r-pill); padding: .3rem .8rem; }
.saisie-liste-comptes li.actif { border-color: var(--accent); }
.saisie-nouveau-compte, .saisie-csv, .saisie-gabarits { display: flex; flex-wrap: wrap; gap: .6rem; align-items: flex-end; }
.saisie-nouveau-compte label { display: grid; gap: .3rem; flex: 1 1 16rem; }
.saisie-form {
  display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
}
.saisie-form label { display: grid; gap: .3rem; min-width: 0; }
.saisie-form .saisie-note, .saisie-form .saisie-actions { grid-column: 1 / -1; }
.saisie-actions { display: flex; flex-wrap: wrap; gap: .6rem; }
.saisie-aide { font-size: .9rem; margin: .6rem 0 0; }
.saisie-geste-ligne { white-space: nowrap; }
.saisie-geste-ligne form { display: inline; margin-left: .6rem; }
.lien-bouton {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--down); font: inherit; text-decoration: underline;
}
