/* ===========================================================================
   MSB — STRUCTURE ET COMPOSANTS
   ---------------------------------------------------------------------------
   ⚠️ Ne PAS mettre de valeur en dur ici (couleur, rayon, taille, espacement).
   Tout vient de `theme.css` via var(). Si une valeur manque, ajoutez-la
   d'abord comme variable dans theme.css : c'est ce qui garantit qu'un
   changement d'apparence reste un changement d'UN SEUL fichier.

   Plan : réinitialisation → utilitaires → marque → site public → coquille
   applicative → cartes/KPI → tableaux → pastilles → boutons → formulaires →
   onglets → authentification → graphiques → divers → responsive.
   =========================================================================== */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { color: var(--ink); letter-spacing: var(--tracking); }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }

/* ---------- Utilitaires ---------- */
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.small { font-size: var(--fs-small); }
.r { text-align: right; }
.pos { color: var(--pos); }
.neg { color: var(--neg); }
.nowrap { white-space: nowrap; }
.row-between { display: flex; justify-content: space-between; align-items: center; gap: var(--gap-sm); }
.stack { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); align-items: start; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); align-items: start; }

/* ---------- Marque ---------- */
.brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: var(--w-bold); font-size: 16.5px; color: var(--ink); letter-spacing: -.02em;
}
/* ⚠️ Les quatre blancs en dur d'ici (#fff et trois rgba blancs) étaient les
   SEULES valeurs de couleur en dur du fichier, sur le seul composant présent
   sur toutes les pages. Ils sont devenus --accent-text, qui est précisément le
   jeton « l'encre qui se pose sur l'accent » : blanc en clair, FONCÉ en sombre,
   où l'accent devient un lilas clair. Un #fff en dur y aurait éclairci un carré
   déjà clair. */
.brand-mark {
  width: 27px; height: 27px; border-radius: var(--radius-sm); flex-shrink: 0; position: relative;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 72%, var(--accent-text)), var(--accent));
  box-shadow: 0 2px 6px color-mix(in srgb, var(--accent) 35%, transparent),
              inset 0 1px 0 color-mix(in srgb, var(--accent-text) 45%, transparent);
}
.brand-mark::after {                       /* la diagonale « courbe qui monte » */
  content: ""; position: absolute; inset: 0; border-radius: var(--radius-sm);
  background: linear-gradient(to top right, transparent 46%,
              color-mix(in srgb, var(--accent-text) 90%, transparent) 46%,
              color-mix(in srgb, var(--accent-text) 90%, transparent) 54%, transparent 54%);
}
.brand-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* =====================  SITE PUBLIC  ===================== */
.pub-header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(180%) blur(10px);
  border-bottom: var(--border) solid var(--line);
}
.pub-header .inner { max-width: var(--page-max); margin: 0 auto; padding: 14px 24px; display: flex; align-items: center; gap: 26px; }
.pub-nav { display: flex; gap: 22px; margin-left: 12px; }
.pub-nav a { color: var(--muted); font-weight: var(--w-medium); font-size: 14px; }
.pub-nav a:hover { color: var(--ink); }
.pub-header .right { margin-left: auto; display: flex; align-items: center; gap: var(--gap-sm); }

.wrap { max-width: var(--page-max); margin: 0 auto; padding: 0 24px; }

.hero { padding: 76px 0 44px; text-align: center; }
.hero h1 { font-size: var(--fs-hero); line-height: 1.1; margin: 0 0 18px; letter-spacing: var(--tracking-hero); }
.hero p.lead { font-size: 18px; color: var(--muted); max-width: 640px; margin: 0 auto 28px; }
.hero .cta-row { display: flex; gap: var(--gap-sm); justify-content: center; flex-wrap: wrap; }
.hero-note { margin-top: 16px; font-size: var(--fs-small); color: var(--faint); }
.eyebrow {
  display: inline-flex; align-items: center; gap: 7px; margin-bottom: 20px;
  background: var(--accent-soft); color: var(--accent);
  border: var(--border) solid color-mix(in srgb, var(--accent) 22%, transparent);
  padding: 5px 13px; border-radius: var(--radius-pill); font-size: var(--fs-small); font-weight: var(--w-semi);
}

.section { padding: 64px 0; }
.section h2 { font-size: 27px; margin: 0 0 10px; letter-spacing: -.026em; }
.section .sub { color: var(--muted); margin: 0 0 34px; font-size: 15.5px; }
.section-center { text-align: center; }
.section-center .sub { max-width: 620px; margin-left: auto; margin-right: auto; }

.features { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); text-align: left; }
.feature {
  background: var(--surface); border: var(--border) solid var(--line);
  border-radius: var(--radius); padding: var(--card-pad); box-shadow: var(--shadow);
}
.feature .ic {
  width: 34px; height: 34px; border-radius: var(--radius-sm);
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 16px; margin-bottom: 13px;
}
.feature h3 { font-size: var(--fs-h2); margin: 0 0 6px; }
.feature p { margin: 0; font-size: 13.5px; color: var(--muted); }

.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; text-align: left; }
.step { position: relative; padding-left: 46px; }
.step .num {
  position: absolute; left: 0; top: 0; width: 32px; height: 32px; border-radius: var(--radius-pill);
  background: var(--ink); color: var(--bg); display: flex; align-items: center; justify-content: center;
  font-weight: var(--w-bold); font-size: 13.5px;
}
.step h3 { font-size: var(--fs-h2); margin: 4px 0 6px; }
.step p { margin: 0; font-size: 13.5px; color: var(--muted); }

.plans { display: grid; grid-template-columns: repeat(2, minmax(0, 340px)); gap: 20px; justify-content: center; text-align: left; }
.plan {
  background: var(--surface); border: var(--border) solid var(--line); border-radius: var(--radius);
  padding: 26px; box-shadow: var(--shadow); display: flex; flex-direction: column;
}
.plan.featured { border-color: var(--accent); box-shadow: var(--shadow-lg); position: relative; }
.plan .tag {
  position: absolute; top: -11px; right: 20px; background: var(--accent); color: var(--accent-text);
  font-size: var(--fs-micro); font-weight: var(--w-bold); padding: 3px 10px; border-radius: var(--radius-pill);
}
.plan h3 { font-size: 16px; margin: 0 0 4px; }
.plan .price { font-size: 32px; font-weight: 750; color: var(--ink); letter-spacing: -.03em; margin: 10px 0 2px; }
.plan .price span { font-size: 14px; font-weight: var(--w-medium); color: var(--muted); letter-spacing: 0; }
.plan ul { list-style: none; padding: 0; margin: 18px 0 22px; display: flex; flex-direction: column; gap: 9px; font-size: 13.5px; }
.plan li::before { content: "✓"; color: var(--pos); font-weight: var(--w-bold); margin-right: 9px; }
.plan li.off { color: var(--faint); }
.plan li.off::before { content: "—"; color: var(--line-strong); }
.plan .btn { margin-top: auto; }

.faq { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap) 34px; text-align: left; }
.faq h3 { font-size: 14.5px; margin: 0 0 5px; }
.faq p { margin: 0; font-size: 13.5px; color: var(--muted); }

.pub-footer { border-top: var(--border) solid var(--line); padding: 34px 0 46px; margin-top: 30px; }
.pub-footer .cols { display: flex; gap: 60px; flex-wrap: wrap; }
.pub-footer h4 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--faint); margin: 0 0 10px; }
.pub-footer a { display: block; color: var(--muted); font-size: 13.5px; padding: 3px 0; }
.pub-footer a:hover { color: var(--ink); }
.pub-footer .legal { margin-top: 28px; font-size: var(--fs-small); color: var(--faint); }

/* ---------- Aperçu produit sur la page d'accueil ---------- */
.shot {
  background: var(--surface); border: var(--border) solid var(--line); border-radius: 18px;
  box-shadow: var(--shadow-lg); overflow: hidden; text-align: left; margin: 44px auto 0; max-width: 960px;
}
.shot .bar {
  height: 38px; border-bottom: var(--border) solid var(--line); background: var(--surface-2);
  display: flex; align-items: center; gap: 7px; padding: 0 14px;
}
.shot .bar i { width: 9px; height: 9px; border-radius: var(--radius-pill); background: var(--line-strong); display: block; }
.shot .body { padding: 20px; }
/* L'aperçu de la page publique emploie le vocabulaire RÉEL (.kpi-strip) : la
   vitrine doit montrer l'application telle qu'elle est. On n'y règle que
   l'échelle, la maquette étant une réduction. */
.shot .kpi-strip { margin-bottom: 16px; border: var(--border) solid var(--line);
                   border-radius: var(--radius-sm); overflow: hidden; }
.shot .kpi-strip > div { padding: 13px 15px; }
.shot .kpi-strip .k-num { font-size: 19px; }
.shot .kpi-strip > div.hero .k-num { font-size: 21px; }

/* =====================  COQUILLE APPLICATIVE  ===================== */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar-w); flex-shrink: 0;
  background: var(--surface); border-right: var(--border) solid var(--line);
  padding: 20px 12px; display: flex; flex-direction: column; gap: 2px;
  position: sticky; top: 0; height: 100vh;
}
.sidebar .brand { padding: 4px 10px 20px; }
.sidebar nav { display: flex; flex-direction: column; gap: 2px; }
.sidebar nav a, .sidebar nav .off, .sidebar .nav-bottom {
  display: flex; align-items: center; gap: 11px; color: var(--muted);
  padding: var(--field-pad-y) var(--field-pad-x); border-radius: 10px;
  font-weight: var(--w-medium); font-size: 14px;
  transition: background var(--speed), color var(--speed);
}
/* `:not(.off)` sur l'entrée du bas : une entrée SANS route s'éclairerait au
   survol comme un vrai lien, et le mensonge serait plus fort que la convention
   qu'il est censé servir. C'est « Mon compte » qui l'a révélé — mais il a sa
   route depuis le 27/08/2026 et n'est plus concerné ; la garde reste pour la
   prochaine entrée grisée. */
.sidebar nav a:hover, .sidebar .nav-bottom:not(.off):hover { background: var(--bg); color: var(--ink); }
.sidebar nav a.on, .sidebar .nav-bottom.on { background: var(--accent-soft); color: var(--accent); font-weight: var(--w-semi); }
/* ⚠️ UNE ENTRÉE À VENIR SE GRISE PAR LA COULEUR, JAMAIS PAR `opacity`.
   Mesuré le 22/08/2026 sur les blocs « bientôt » de la page publique :
   `opacity: .55` fait tomber le contraste de 3,42:1 à 1,84:1, très en dessous
   des 4,5:1 de WCAG AA. La mention devient illisible pour ceux qui en ont le
   plus besoin. `--faint` est un jeton du thème, contrasté dans les deux modes.
   Ces entrées sont des <span> et non des <a> : l'invariant n° 5 ferait d'un
   href="#" un lien mort, et tests/test_liens_vues.php le refuse. */
/* `.sidebar .off` et non `.sidebar nav .off` : l'entrée du bas (« Mon compte »)
   vit HORS du <nav>, et n'était donc pas grisée du tout. */
.sidebar .off { color: var(--faint); cursor: default; }
.sidebar .ico { width: 17px; text-align: center; opacity: .9; font-size: 14px; }
.sidebar .nav-sep {
  margin: 14px 12px 6px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--faint); font-weight: var(--w-bold);
}
.sidebar .nav-bottom { margin-top: auto; }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* La barre du haut : le fond (flou, filet) prend TOUTE la largeur, mais son
   CONTENU est plafonné et centré comme le contenu de la page — sans quoi le
   titre resterait collé à gauche pendant que la page, elle, est centrée, et le
   décalage se verrait. */
.topbar {
  height: var(--topbar-h);
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(180%) blur(8px);
  border-bottom: var(--border) solid var(--line); position: sticky; top: 0; z-index: 10;
}
.topbar-inner {
  max-width: var(--content-max); margin: 0 auto; height: 100%;
  display: flex; align-items: center; gap: 16px; padding: 0 var(--content-pad-x);
}
/* ⚠️ LE TITRE DU BANDEAU PORTE UNE DONNÉE DE L'UTILISATEUR — « Performance —
   <nom du portefeuille> », et ce nom, c'est lui qui l'écrit, sans limite utile.
   Sans troncature, le bandeau poussait LA PAGE ENTIÈRE : mesuré le 01/09/2026 à
   375 px, `.topbar-right` (thème + pastille + « Se déconnecter », 187 px large)
   finissait à 407 pour un écran de 375 — 32 px de défilement horizontal sur
   /performance, 29 sur /portefeuilles. Et un défilement horizontal sur
   téléphone, personne ne le lit comme un bogue : on croit l'application mal
   faite.
   Le TITRE cède, jamais les commandes : lui seul peut être abrégé sans rien
   perdre, puisque la page dit déjà où l'on est. Même trio que `.brand-name`, et
   `min-width: 0` est indispensable — sans lui, une case de flex ne descend pas
   sous la largeur de son texte et l'ellipse ne se déclenche jamais. */
.page-title {
  font-size: var(--fs-page); font-weight: var(--w-strong); margin: 0;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.topbar-right {
  margin-left: auto; flex-shrink: 0;
  display: flex; align-items: center; gap: var(--gap-sm);
}
/* Le bouton de déconnexion porte SES DEUX FORMES dans le balisage — le libellé et
   l'icône — et c'est le repli qui choisit. Aucun script n'est en jeu (la politique
   de sécurité de contenu l'interdirait), et le nom accessible reste le même dans
   les deux cas : il vit sur l'`aria-label` du bouton, pas sur ce qu'on voit.
   ⚠️ L'ICÔNE EST « ⇥ » ET PAS « ⏻ ». Le symbole « power », qui serait le choix
   naturel, est un TOFU dans la police de ce produit — mesuré le 01/09/2026 en
   comparant sa largeur à celle d'un caractère de la zone privée. On ne pose pas un
   glyphe sans vérifier qu'il est dessiné : c'est le « ¹3 » de la page publique,
   resté trois jours en production, qui a appris ça. */
.topbar-right .deco-ico { display: none; }
.icon-btn {
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  border: var(--border) solid var(--line); background: var(--surface); color: var(--muted);
  cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px;
}
.icon-btn:hover { color: var(--ink); border-color: var(--line-strong); }
.avatar {
  width: 32px; height: 32px; border-radius: var(--radius-pill); flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: var(--w-bold);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.content { padding: 26px var(--content-pad-x) 72px; width: 100%; max-width: var(--content-max);
  margin-inline: auto; }

/* ---------- Cartes et KPI ---------- */
.card {
  background: var(--surface); border: var(--border) solid var(--line);
  border-radius: var(--radius); padding: var(--card-pad); box-shadow: var(--shadow);
  margin-bottom: var(--gap);
}
.card.no-pad { padding: 0; overflow: hidden; }
.card > h2 { font-size: var(--fs-h2); margin: 0 0 14px; font-weight: var(--w-semi); }
.card-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; margin-bottom: 16px; }
.card-head h2 { margin: 0; font-size: var(--fs-h2); font-weight: var(--w-semi); }
.card-head .hint, .hint { margin: 4px 0 0; font-size: var(--fs-small); color: var(--muted); }
.card-title-row { padding: var(--card-pad) var(--card-pad) 14px; }   /* entête d'une carte .no-pad */
.card-title-row h2 { margin: 0; font-size: var(--fs-h2); font-weight: var(--w-semi); }
.card-title-row .hint { margin: 4px 0 0; }

/* ⚠️ `.kpis` / `.kpi` / `.kpi-label` / `.kpi-value` / `.kpi-sub` / `.kpi.hero`
   ONT ÉTÉ SUPPRIMÉES le 26/08/2026, et c'est l'aboutissement de tout ce travail.
   C'était le vocabulaire RIVAL : des pavés séparés, bordés et ombrés, quand
   `.kpi-strip` pose les mêmes chiffres dans UNE carte séparée de filets. Les
   deux ont cohabité dans ce fichier, et chaque écran figeait celui qui lui
   passait par la tête au moment où on l'écrivait — d'où des pages qui divergent,
   et la sensation de foutoir. Tant que les deux existaient, la divergence
   pouvait revenir par simple copier-coller.
   Le remplaçant vit plus bas, section « LE SYSTÈME », avec ses variantes k3, k4,
   k6 et sa case à contre-jour `.hero`.
   ⚠️ NE PAS LES RÉINTRODUIRE. Si un écran a besoin d'une disposition que
   `.kpi-strip` ne sait pas faire, la bonne réponse est de lui ajouter UNE
   variante, jamais un second composant. */
.info {
  display: inline-flex; width: 14px; height: 14px; border-radius: var(--radius-pill); cursor: help;
  border: var(--border) solid var(--line-strong); color: var(--faint);
  align-items: center; justify-content: center; font-size: 9.5px; font-weight: var(--w-bold);
}

/* ---------- Tableaux ---------- */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th, .tbl td { text-align: left; padding: var(--row-pad-y) var(--row-pad-x); border-bottom: var(--border) solid var(--line-soft); }
.tbl thead th {
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .05em;
  color: var(--faint); font-weight: var(--w-semi);
  border-bottom: var(--border) solid var(--line); white-space: nowrap;
}
.tbl tbody tr:hover { background: var(--row-hover); }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl tfoot td {
  border-top: var(--border) solid var(--line); border-bottom: none;
  font-weight: var(--w-strong); color: var(--ink); background: var(--surface-2);
}
.tbl a { color: var(--ink); font-weight: 550; }
.tbl a:hover { color: var(--accent); }
.tbl .sub { display: block; font-size: 11.5px; color: var(--faint); font-weight: 400; }
.empty { text-align: center; padding: 44px 24px; color: var(--muted); }

/* Conteneur de défilement pour les tableaux LARGES. Sans lui, un tableau plus
   large que l'écran pousse la PAGE ENTIÈRE horizontalement : l'en-tête, les
   cartes et le pied se décalent, et le défilement latéral devient la seule
   façon de lire. C'est le tableau ligne à ligne de l'import qui l'a révélé —
   dix colonnes, 1 035 px, contre 1 200 px de fenêtre. */
.scroll-x { overflow-x: auto; }

/* ---------- Pastilles ---------- */
.badge {
  display: inline-block; padding: 2px 9px; border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: var(--w-semi); background: var(--chip-bg); color: var(--text);
}
/* ⚠️ FOND OPAQUE, ENCRE DISTINCTE — corrigé le 26/08/2026, voir theme.css §4 bis.
   Ces quatre règles prenaient --cN pour le fond ET pour le texte, avec un fond en
   color-mix transparent. .badge.av mesurait 1,81:1, SOUS le 1,84:1 qui a fait
   bannir `opacity` ici ; .badge.crypto 2,67:1, et il est servi en production par
   le tableau de bord. Et comme un fond transparent se compose sur ce qu'il y a
   derrière, le rapport se dégradait encore au survol d'une ligne de tableau.
   Un fond OPAQUE rend la mesure indépendante de la surface porteuse. */
/* Par ENVELOPPE — les quatre que le domaine sait teinter (PF_BADGES). */
.badge.pea    { background: var(--c1-soft); color: var(--c1-ink); }
.badge.cto    { background: var(--c2-soft); color: var(--c2-ink); }
.badge.av     { background: var(--c3-soft); color: var(--c3-ink); }
.badge.crypto { background: var(--c4-soft); color: var(--c4-ink); }
/* Par TEINTE DE SÉRIE — employées pour la classe d'actif d'une ligne, dont la
   couleur vient de inst_teinte_classe() (domain/instruments.php). Ce sont les
   mêmes paires : une action porte donc la même teinte dans son camembert, dans
   sa barre d'allocation et dans la pastille de sa ligne de tableau. Un actif de
   classe « autre » ne reçoit aucune de ces classes et garde la pastille neutre. */
.badge.c1 { background: var(--c1-soft); color: var(--c1-ink); }
.badge.c2 { background: var(--c2-soft); color: var(--c2-ink); }
.badge.c3 { background: var(--c3-soft); color: var(--c3-ink); }
.badge.c4 { background: var(--c4-soft); color: var(--c4-ink); }
.badge.c5 { background: var(--c5-soft); color: var(--c5-ink); }
.badge.c6 { background: var(--c6-soft); color: var(--c6-ink); }
.badge.ok     { background: var(--pos-soft); color: var(--pos); }
.badge.ko     { background: var(--neg-soft); color: var(--neg); }
.dot { display: inline-block; width: 9px; height: 9px; border-radius: 3px; margin-right: 8px; vertical-align: -1px; }

/* ---------- Boutons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  border-radius: 10px; border: var(--border) solid var(--line-strong); background: var(--surface);
  color: var(--ink); font: inherit; font-weight: 550; font-size: 14px;
  padding: var(--field-pad-y) 16px; cursor: pointer;
  transition: background var(--speed), border-color var(--speed), transform .06s;
}
.btn:hover { border-color: var(--muted); color: var(--ink); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); color: var(--accent-text); }
.btn-ghost { background: transparent; }
.btn-lg { padding: 12px 24px; font-size: 15px; border-radius: 11px; }
.btn-xs { padding: 5px 11px; font-size: var(--fs-small); border-radius: 8px; }
.btn-block { width: 100%; }
.btn-danger { color: var(--neg); border-color: color-mix(in srgb, var(--neg) 35%, transparent); }
.btn-danger:hover { background: var(--neg-soft); border-color: var(--neg); color: var(--neg); }
.btn-link { background: none; border: none; color: var(--accent); padding: 0; font: inherit; cursor: pointer; }

/* ---------- Formulaires ---------- */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 15px; }
.field label { font-size: var(--fs-small); font-weight: var(--w-semi); color: var(--text); }
.field .help { font-size: 12px; color: var(--faint); }
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=number], select, textarea {
  font: inherit; font-size: 14px; color: var(--ink); background: var(--surface);
  border: var(--border) solid var(--line-strong); border-radius: var(--radius-sm);
  padding: var(--field-pad-y) var(--field-pad-x); width: 100%;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
input::placeholder { color: var(--faint); }
.form-row { display: flex; gap: var(--gap-sm); flex-wrap: wrap; }

/* ---------- Renommage inline (portefeuilles) ----------
   <details> natif : l'ouverture au clic ne coûte AUCUN JavaScript, donc reste
   compatible avec « script-src 'self' ». Le triangle de déroulement par défaut
   est masqué au profit d'un crayon discret. */
.renommer { display: inline-block; vertical-align: middle; margin-left: 6px; }
.renommer > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: var(--radius-sm);
  color: var(--faint); font-size: 12px; user-select: none;
}
.renommer > summary::-webkit-details-marker { display: none; }
.renommer > summary::marker { content: ""; }
.renommer > summary:hover { color: var(--accent); background: var(--accent-soft); }
.renommer[open] > summary { color: var(--accent); background: var(--accent-soft); }
.renommer-form { display: flex; gap: var(--gap-sm); align-items: center; margin: 8px 0 2px; }
.renommer-form input[type=text] { width: 220px; }
.form-row > .field { flex: 1; min-width: 130px; }
.checkline { display: flex; gap: 9px; align-items: flex-start; font-size: 13px; color: var(--muted); margin-bottom: 15px; }
.checkline input { width: auto; margin-top: 3px; }

/* ---------- Onglets ---------- */
.tabs { display: flex; gap: 4px; border-bottom: var(--border) solid var(--line); margin-bottom: 20px; }
.tabs a {
  padding: var(--field-pad-y) 14px; font-size: 14px; font-weight: 550; color: var(--muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs a:hover { color: var(--ink); }
.tabs a.on { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------- Authentification ---------- */
.auth-page { min-height: 100vh; display: flex; }
.auth-side {
  flex: 1; background: var(--hero-bg); color: var(--hero-label);
  padding: 46px; display: flex; flex-direction: column;
}
.auth-side .brand { color: var(--hero-text); }
.auth-side blockquote {
  margin: auto 0 0; font-size: 19px; line-height: 1.45; color: var(--hero-text);
  letter-spacing: var(--tracking); max-width: 420px;
}
.auth-side .who { margin-top: 16px; font-size: 13px; color: var(--hero-sub); }
.auth-side .sell {
  list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column;
  gap: 11px; font-size: 14.5px; color: color-mix(in srgb, var(--hero-text) 92%, var(--hero-label));
}
.auth-side .sell-title {
  font-size: 13px; color: var(--hero-sub);
  text-transform: uppercase; letter-spacing: .06em; font-weight: var(--w-bold); margin: 0 0 18px;
}
.auth-main {
  width: 520px; flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  padding: 40px 46px; background: var(--surface);
}
.auth-box { width: 100%; max-width: 360px; }
.auth-box h1 { font-size: 25px; margin: 0 0 6px; }
.auth-box .sub { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
.sep { display: flex; align-items: center; gap: var(--gap-sm); color: var(--faint); font-size: 12px; margin: 20px 0; }
.sep::before, .sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.strength { display: flex; gap: 4px; margin-top: 7px; }
.strength i { flex: 1; height: 3px; border-radius: 2px; background: var(--line); }
.strength i.on { background: var(--pos); }

/* ---------- Graphiques ---------- */
.chart { width: 100%; height: auto; display: block; }
.legend { display: flex; flex-wrap: wrap; gap: 8px 20px; font-size: var(--fs-small); color: var(--muted); margin-top: 10px; }
.legend b { color: var(--ink); font-weight: var(--w-semi); }

.donut-row { display: flex; gap: 26px; align-items: center; flex-wrap: wrap; }
.donut-row .donut { width: 178px; flex-shrink: 0; }
.donut-row .donut-tbl { flex: 1; min-width: 240px; }
.donut-tbl table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.donut-tbl td { padding: 6px 0; border-bottom: var(--border) solid var(--line-soft); }
.donut-tbl tr:last-child td { border-bottom: none; }

.perfbar { display: flex; align-items: center; gap: 11px; margin: 7px 0; font-size: 13px; }
.perfbar .nm { width: 190px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perfbar .val { width: 64px; text-align: right; font-variant-numeric: tabular-nums; }
/* La jauge est un SVG et non deux <div> imbriqués : sa largeur DÉPEND D'UNE
   DONNÉE, et `style="width:86%"` — la forme de la maquette — est bloqué par la
   politique de sécurité de contenu. Un attribut de présentation SVG, lui, passe.
   Voir svg_jauge() dans lib/svg.php. */
.perfbar .jauge { flex: 1; height: 13px; }
.perfbar .jauge .piste { fill: color-mix(in srgb, var(--muted) 16%, transparent); }
.perfbar .jauge .remplissage.hausse { fill: var(--pos); }
.perfbar .jauge .remplissage.baisse { fill: var(--neg); }

.bars { display: flex; gap: 3px; height: 104px; align-items: stretch; }
.bars .b { flex: 1; display: flex; flex-direction: column; }
.bars .b .up { flex: 1; display: flex; align-items: flex-end; }
.bars .b .dn { flex: 1; display: flex; align-items: flex-start; }
.bars .b i { display: block; width: 100%; border-radius: 3px; }
.bars .b .up i { background: var(--pos); }
.bars .b .dn i { background: var(--neg); }
.bars-axis { display: flex; justify-content: space-between; font-size: var(--fs-micro); color: var(--faint); margin-top: 6px; }

.spark { width: 76px; height: 22px; vertical-align: middle; }

/* ---------- Divers ---------- */
.notice {
  display: flex; gap: 11px; align-items: flex-start; padding: 13px 16px; border-radius: var(--radius-sm);
  background: var(--accent-soft); border: var(--border) solid color-mix(in srgb, var(--accent) 20%, transparent);
  font-size: 13.5px; color: var(--text); margin-bottom: var(--gap);
}
.notice.warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.notice .ic { font-size: 15px; line-height: 1.3; }
.notice b { color: var(--ink); }

.choice {                                  /* carte cliquable (choix d'enveloppe) */
  margin: 0; cursor: pointer; display: block;
}
.choice.on { border-color: var(--accent); box-shadow: var(--focus-ring); }
.choice h3 { font-size: 14.5px; margin: 12px 0 4px; }
.choice p { margin: 0; }
.choice input { width: auto; }

/* ============================================================================
   TABLEAU DE BORD DENSE — porté de la maquette d'atelier, le 24/08/2026.
   ----------------------------------------------------------------------------
   La page « faisait 5 km de long » : tout empilé sur une seule colonne. Ces
   classes reprennent la STRUCTURE de la maquette — une rangée d'indicateurs
   compacte, deux colonnes, des barres d'allocation, des opérations en lignes —
   sans toucher à la palette ni aux autres écrans.
   ⚠️ Ce commentaire disait, le 24/08, que « les anciennes (.kpis, .card-head…)
   restent pour le reste du produit ». C'ÉTAIT LE DÉFAUT, énoncé sans être vu :
   laisser deux vocabulaires cohabiter garantit que les écrans divergeront. Les
   anciennes .kpis ont été supprimées le 26/08 ; il n'en reste qu'un.
   Aucune valeur en dur : tout vient des jetons de theme.css.
   ============================================================================ */

/* Micro-label en capitales, réutilisable partout. */
.cap { font-size: var(--fs-micro); letter-spacing: .12em; text-transform: uppercase;
       color: var(--faint); font-weight: var(--w-semi); }

/* Entête de carte : micro-label à gauche, note discrète à droite, fond teinté. */
.card-cap {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; background: var(--head);
  border-bottom: var(--border) solid var(--line);
}
/* Le bandeau est FRANC, donc son texte est celui du bandeau — jamais --faint,
   qui suppose un fond blanc, et jamais --accent en dur, qui deviendrait
   illisible sur le bandeau sombre du thème sombre. --head-ink bascule. */
.card-cap .cap  { color: var(--head-ink); }
/* La note s'efface par une COULEUR mêlée au bandeau, pas par `opacity` — la
   règle du fichier (l.183) vaut aussi pour le code écrit le jour même. Un
   color-mix reste calculable et ne s'applique pas au sous-arbre entier. */
.card-cap .note { font-size: var(--fs-small); font-weight: var(--w-medium);
                  color: color-mix(in srgb, var(--head-ink) 80%, var(--head)); }
/* Corps padté sous un .card-cap, quand la carte est en .no-pad. */
.card-body { padding: 13px 16px; }

/* Les indicateurs dans UNE carte, séparés d'un filet — non cinq pavés empilés. */
.kpi-strip { display: grid; grid-template-columns: repeat(5, 1fr); }
/* ⚠️ LES VARIANTES SE DÉCLARENT ICI, AVANT LES @media DE LA MÊME FEUILLE.
   Écrites en fin de fichier le 25/08, elles battaient les règles responsives en
   SPÉCIFICITÉ (.kpi-strip.k3 = 0,2,0 contre .kpi-strip = 0,1,0) : une bande k3
   ou k4 ne se repliait JAMAIS sur téléphone, et les filets calculés en
   nth-child(3n+1) tombaient au mauvais endroit. Les @media plus bas les
   reprennent nommément pour égaliser la spécificité. */
.kpi-strip.k3 { grid-template-columns: repeat(3, 1fr); }
.kpi-strip.k4 { grid-template-columns: repeat(4, 1fr); }
.kpi-strip.k6 { grid-template-columns: repeat(6, 1fr); }
.kpi-strip .k-num.accent { color: var(--accent); }
/* Une case À CONTRE-JOUR, pour le chiffre qui commande les autres. Elle emploie
   les jetons --hero-*, qui basculent en thème sombre : là-bas le bloc ne peut
   plus être « plus foncé » et ressort par une teinte bleutée. */
.kpi-strip > div.hero { background: var(--hero-bg); }
.kpi-strip > div.hero + div { border-left-color: var(--hero-bg); }
.kpi-strip > div.hero .cap   { color: var(--hero-label); }
.kpi-strip > div.hero .k-num { color: var(--hero-text); font-size: var(--fs-kpi-hero); }
.kpi-strip > div.hero .k-sub { color: var(--hero-sub); }
.kpi-strip > div { padding: 15px 16px; }
.kpi-strip > div + div { border-left: var(--border) solid var(--line-soft); }
.kpi-strip .k-num {
  font-size: var(--fs-kpi); font-weight: var(--w-semi); letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; line-height: 1; margin-top: 8px;
}
.kpi-strip .k-num.pos { color: var(--pos); }
.kpi-strip .k-num.neg { color: var(--neg); }
.kpi-strip .k-sub { font-size: var(--fs-small); color: var(--muted); margin-top: 6px; line-height: 1.5; }

/* Deux colonnes : le gros à gauche, l'appoint à droite. C'est ce qui tue le
   déroulé interminable. */
.grid-lede { display: grid; grid-template-columns: 1.4fr .6fr; gap: var(--gap); align-items: start; }
.grid-lede > div { min-width: 0; display: flex; flex-direction: column; gap: var(--gap); }

/* Opérations en lignes-grille (date · type+titre · montant), pas un tableau large. */
.oplist { display: flex; flex-direction: column; }
.oprow {
  display: grid; grid-template-columns: 84px 1fr auto; gap: 14px; align-items: center;
  padding: 11px 16px; border-bottom: var(--border) solid var(--line-soft);
}
.oprow:last-child { border-bottom: 0; }
.oprow .d { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* ⚠️ `min-width: 0` — LE MÊME PIÈGE QUE `.kpi-strip`, UNE TROISIÈME FOIS. La
   colonne du milieu est déclarée `1fr`, ce qui laisse croire qu'elle cède : mais
   `1fr` a pour minimum implicite `min-content`, et « AM.M.WOR.ETF » ne se coupe
   nulle part. La colonne se figeait donc à 110 px, et la TROISIÈME — le montant,
   en `nowrap` — sortait de la ligne : mesuré le 01/09/2026 à 375 px, « -1 902,48 € »
   finissait à 389 px pour une ligne s'arrêtant à 356, rognée sans bruit par
   `.card.no-pad { overflow: hidden }`. Le montant est ce que l'œil vient chercher
   dans un journal ; c'était lui qui disparaissait.
   Le nom s'abrège en ellipse, la sous-ligne continue de passer à la ligne : on
   abrège ce qui se devine, jamais un chiffre. */
.oprow .t { min-width: 0; }
.oprow .t b {
  font-weight: var(--w-semi); color: var(--ink);
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠️ `:not(.kind)` — SANS LUI LA PASTILLE DE NATURE EST AVALÉE. `.oprow .t span`
   pèse 0,2,1 et bat `.kind.vente` qui ne pèse que 0,2,0 : la pastille héritait
   de la sous-ligne, donc gris --muted au lieu de son encre, 12 px au lieu de
   --fs-nano, et surtout `display: block`, qui la jetait sur sa propre ligne.
   Trouvé le 26/08/2026 en MESURANT le rendu réel dans le navigateur — un audit
   des feuilles ne pouvait pas le voir, il faut la cascade appliquée au DOM. */
.oprow .t span:not(.kind) { color: var(--muted); font-size: var(--fs-small); display: block; margin-top: 1px; }
.oprow .a { font-size: 13px; font-variant-numeric: tabular-nums; font-weight: var(--w-semi); text-align: right; white-space: nowrap; }
.oprow .a.pos { color: var(--pos); } .oprow .a.neg { color: var(--neg); }

/* Allocation en barre EMPILÉE (svg_barre_empilee). Les segments sont des rects
   SVG : leur largeur dépend d'une donnée, ce que la CSP interdit en style=. */
.alloc { padding: 16px; }
.alloc-bar {
  width: 100%; height: 12px; display: block; border-radius: 6px; overflow: hidden;
  background: var(--line-soft); margin: 10px 0 12px;
}
.alloc-bar .seg.c1 { fill: var(--c1); } .alloc-bar .seg.c2 { fill: var(--c2); }
.alloc-bar .seg.c3 { fill: var(--c3); } .alloc-bar .seg.c4 { fill: var(--c4); }
.alloc-bar .seg.c5 { fill: var(--c5); } .alloc-bar .seg.c6 { fill: var(--c6); }
.alloc-bar .seg.muted { fill: var(--line-strong); }
.alloc-keys { display: flex; flex-wrap: wrap; gap: 9px 16px; }
.alloc-key { display: flex; align-items: center; gap: 8px; font-size: var(--fs-small); color: var(--muted); }
.alloc-key b { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: var(--w-semi); }
/* ⚠️ `.filet` EST LE NOM GÉNÉRIQUE, et `.alloc-sep` n'en est plus qu'un alias.
   Le séparateur d'allocation a été réemployé le 27/08 pour couper une carte de
   réglages — un nom spécifique détourné vers un autre rôle, exactement le
   mécanisme que le post-mortem du système a voulu tuer : le prochain écran
   hésite, puis invente un troisième nom, et la divergence revient par
   copier-coller. Employer `.filet` partout ailleurs.
   (⚠️ `.sep` était pris : c'est le séparateur « ou » des écrans d'authentification.) */
.filet, .alloc-sep { height: var(--border); background: var(--line-soft); margin: 16px 0; }

/* Note éditoriale, en serif — le seul endroit où Newsreader paraît. */
.lede { font-family: var(--font-serif); font-size: 15px; line-height: 1.7; color: var(--text); padding: 14px 16px; margin: 0; }
.lede em { font-style: italic; color: var(--accent); }

/* Une mesure : label + valeur sur une ligne, explication dessous. Plus lisible
   qu'un tableau à trois colonnes dans une colonne étroite (écran performance). */
.mrow { padding: 12px 16px; border-bottom: var(--border) solid var(--line-soft); }
.mrow:last-child { border-bottom: 0; }
.mrow-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.mrow-l { font-weight: var(--w-semi); color: var(--ink); font-size: 13.5px; }
.mrow-v { font-variant-numeric: tabular-nums; font-weight: var(--w-semi); font-size: 15px; color: var(--ink); white-space: nowrap; }
.mrow-d { margin: 4px 0 0; font-size: var(--fs-small); color: var(--muted); line-height: 1.55; }

/* Sous-titre d'un bloc de répartition (par classe, par devise…). */
.repart-t { margin: 16px 0 7px; }
.repart-t:first-child { margin-top: 4px; }

/* Fil d'ariane + en-tête d'un portefeuille. */
.crumb { font-size: var(--fs-small); color: var(--muted); margin: 0 0 8px; }
.crumb a:hover { color: var(--accent); }
/* Le chevron par la COULEUR et non par `opacity` — mais en --faint, pas en
   --line-strong : mesuré à 1,72:1, un jeton de BORDURE n'est pas un jeton de
   texte, et il ne devient pas lisible parce que le caractère est petit. */
.crumb span { margin: 0 7px; color: var(--faint); }
.pf-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: var(--gap); }
.pf-head .sub { font-size: var(--fs-small); color: var(--muted); }

/* Carte d'enveloppe PEA : deux volets (plafond, antériorité). */
.pea-grid { display: grid; grid-template-columns: 1fr 1fr; }
.pea-grid > div { padding: 16px; }
.pea-grid > div + div { border-left: var(--border) solid var(--line-soft); }
.pea-gauge-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; margin: 10px 0 8px; font-size: var(--fs-small); color: var(--muted); }
.pea-gauge-top b { font-size: 16px; font-weight: var(--w-semi); color: var(--ink); font-variant-numeric: tabular-nums; }
.pea-grid .hint { margin-top: 10px; }
.pea-ok { color: var(--pos); font-weight: var(--w-semi); }

/* ---------- Responsive ----------
   ⚠️ Les variantes .k3 / .k4 sont NOMMÉES dans chaque règle de repli. Sans
   elles, `.kpi-strip.k3` (0,2,0) l'emporterait sur `.kpi-strip` (0,1,0) et la
   bande resterait figée à trois ou quatre colonnes sur téléphone. */
@media (max-width: 1100px) {
  .grid-lede { grid-template-columns: 1fr; }
  .kpi-strip, .kpi-strip.k3, .kpi-strip.k4, .kpi-strip.k6 { grid-template-columns: repeat(3, 1fr); }
  .kpi-strip > div:nth-child(3n+1) { border-left: 0; }
  .kpi-strip > div:nth-child(n+4) { border-top: var(--border) solid var(--line-soft); }
}
@media (max-width: 640px) {
  .kpi-strip, .kpi-strip.k3, .kpi-strip.k4, .kpi-strip.k6 { grid-template-columns: 1fr 1fr; }
  .kpi-strip > div { border-left: 0; }
  .kpi-strip > div:nth-child(2n) { border-left: var(--border) solid var(--line-soft); }
  .kpi-strip > div:nth-child(n+3) { border-top: var(--border) solid var(--line-soft); }
  /* ⚠️ SUR TÉLÉPHONE, LE CHIFFRE ÉTAIT COUPÉ — ET PERSONNE NE POUVAIT LE VOIR.
     Une case de grille ne descend pas sous la largeur de son contenu
     (`min-width: auto` par défaut), et « 70 495,48 € » à --fs-kpi mesure 126 px
     pour 105 px disponibles. Les deux colonnes réclamaient donc 332 px dans une
     bande de 231 : la colonne de DROITE dépassait de cent pixels, et
     `.card.no-pad { overflow: hidden }` la rognait en silence — ni barre de
     défilement, ni page poussée. C'est le piège n° 4 de ce fichier (« overflow
     MASQUE un débordement »), repayé sur un autre composant.
     MESURÉ à 375 px le 31/08/2026 : tableau de bord 332 > 231, portefeuille
     347 > 275. Il fallait les DEUX règles — `min-width: 0` autorise la case à
     rétrécir, la taille réduite évite que le chiffre déborde alors de sa case. */
  .kpi-strip > div { min-width: 0; }
  .kpi-strip .k-num,
  .kpi-strip > div.hero .k-num { font-size: var(--fs-page); }
  /* La carte d'enveloppe PEA est EN PRODUCTION (views/portefeuille.php) et
     n'avait aucun repli : ses deux volets restaient côte à côte, et
     `.card.no-pad { overflow: hidden }` MASQUAIT les 137 px qui débordaient —
     ni barre de défilement, ni page poussée, donc invisible en test. */
  .pea-grid { grid-template-columns: 1fr; }
  .pea-grid > div + div { border-left: 0; border-top: var(--border) solid var(--line-soft); }

  /* ⚠️ L'EN-TÊTE DE CARTE PASSE À LA LIGNE. `.card-cap` est un flex en
     `space-between` : titre à gauche, commandes à droite. À 375 px, « Mes
     portefeuilles » plus « Exporter CSV » plus « + Nouveau Portefeuille »
     réclamaient 308 px dans une carte de 275, et `.card.no-pad
     { overflow: hidden }` rognait le bouton de droite EN SILENCE — ni barre, ni
     page poussée. MESURÉ le 01/09/2026 sur le tableau de bord. C'est le piège
     n° 4 de ce fichier, sur un troisième composant : partout où un flex tient
     dans une carte qui masque, il faut lui dire quoi faire quand la place
     manque. */
  .card-cap { flex-wrap: wrap; }

  /* La ligne « période + sélecteur de fenêtre » passe à la ligne : côte à côte à
     375 px, la phrase s'étranglait sur cinq lignes contre le sélecteur. */
  .row-between { flex-wrap: wrap; gap: 10px; }
  .row-between .fenetre { margin-left: auto; }

  /* La glose de la légende disparaît sur téléphone : dans une case de 220 px
     elle se disloquait en trois lignes à côté du nom de la référence. Rien n'est
     perdu — la carte « Et si j'avais suivi la référence ? », juste dessous, dit
     la même chose en entier. */
  .legende .cle .muted { display: none; }

  /* ⚠️ LE BANDEAU N'A QUE 241 px UTILES À 375 — le rail latéral en prend 62, les
     marges 36. Titre + thème + pastille + « Se déconnecter » réclamaient 187 px de
     commandes : il restait 72 px au titre, et « Tableau de bord » se réduisait à
     « T. ». L'ellipse posée plus haut empêche bien la page d'être poussée, mais un
     titre illisible n'est pas un progrès : il faut AUSSI faire de la place.

     Deux choses s'effacent, et AUCUNE FONCTION avec elles : la PASTILLE
     d'initiales, seul élément purement décoratif du bandeau (elle rappelle à
     l'utilisateur qui il est, ce qu'il sait déjà) ; et le LIBELLÉ du bouton de
     déconnexion, qui devient une icône carrée comme le bouton de thème à côté —
     le bouton passe de 94 px à 32. MESURÉ le 01/09/2026 : le titre récupère
     157 px pour les 123 qu'il réclame à sa taille PLEINE. C'est ce qui permet de
     ne PAS rapetisser le titre, contrairement à une première rédaction.
     Le nom accessible ne bouge pas : `aria-label` et `title` portent
     « Se déconnecter » quelle que soit la largeur. */
  .topbar-right .avatar { display: none; }
  .topbar-right .deco-lbl { display: none; }
  .topbar-right .deco-ico { display: block; }
  .topbar-right .btn-xs {
    width: 32px; height: 32px; padding: 0; font-size: 15px; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 1000px) {
  .features, .steps, .grid-3, .grid-2, .faq { grid-template-columns: 1fr; }
  .plans { grid-template-columns: 1fr; }
  .auth-side { display: none; }
  .auth-main { width: 100%; }
  .hero h1 { font-size: calc(var(--fs-hero) * .74); }
  /* ⚠️ LE LIBELLÉ EST MASQUÉ À L'ŒIL, PAS AU LECTEUR D'ÉCRAN. `display: none`
     retire du DOM ACCESSIBLE autant que de l'écran : sous 1 000 px, les douze
     entrées du rail n'avaient plus AUCUN nom — un lecteur d'écran n'annonçait
     que l'icône, « ▦ », « ▩ », « ↗ ». Mesuré le 02/09/2026.
     Le motif ci-dessous (le « visually hidden » classique) laisse le texte dans
     l'arbre d'accessibilité et hors de la mise en page : le rail garde ses
     62 px, et chaque lien retrouve son nom. Le `title` posé par la vue rend le
     même service à la souris, entre 620 et 1 000 px.
     ⚠️ Ne PAS remplacer par `visibility: hidden` ni `width: 0` : les deux
     masquent aussi pour l'assistance. */
  .sidebar { width: 62px; }
  .sidebar .brand-name, .sidebar nav a span:not(.ico),
  .sidebar nav .off span:not(.ico),
  .sidebar .nav-bottom span:not(.ico), .sidebar .nav-sep {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .sidebar nav a, .sidebar nav .off, .sidebar .nav-bottom { position: relative; }
  .content, .topbar { padding-left: 18px; padding-right: 18px; }
}
@media (max-width: 620px) {
  .pub-nav { display: none; }
  .perfbar .nm { width: 110px; }
}

/* ---------- Pages hors coquille applicative (accueil provisoire, erreurs) ----
   Aucune valeur en dur : tout vient de theme.css, comme le reste. Ces classes
   existent pour que les vues n'aient AUCUN attribut style= — la politique de
   sécurité de contenu interdit les styles en ligne. */
.wrap-accueil { padding: 72px 24px 96px; max-width: var(--colonne-etroite); }
.wrap-accueil h1 { font-size: 32px; margin: 26px 0 10px; letter-spacing: var(--tracking-hero); }
.wrap-accueil > p.muted { font-size: 16px; margin: 0 0 34px; max-width: 620px; }
.wrap-accueil .card { margin-top: 0; }
.wrap-accueil .tbl { margin: 0 calc(var(--card-pad) * -1); width: calc(100% + var(--card-pad) * 2); }
.wrap-accueil code {
  font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
  font-size: 12.5px; background: var(--chip-bg); padding: 1px 5px; border-radius: 4px;
}

.wrap-erreur { padding: 96px 24px; max-width: 560px; text-align: center; }
.wrap-erreur .brand { justify-content: center; margin-bottom: 34px; }
.erreur-code {
  font-size: 64px; font-weight: var(--w-bold); letter-spacing: var(--tracking-hero);
  margin: 0; color: var(--muted);
}
.erreur-message { font-size: 16px; color: var(--text); margin: 6px 0 26px; }
.etat-ok { color: var(--pos); font-weight: var(--w-semi); }
.etat-ko { color: var(--neg); font-weight: var(--w-semi); }

/* ---------- Écrans d'authentification (compléments) ---------- */
.auth-argument { margin: auto 0 0; }
.auth-pied { text-align: center; margin: 26px 0 0; }
/* Même puce que `.plan li::before` plus haut : coche U+2713, en vert positif.
   Elle a valu « ¹3 » — U+00B9 puis le chiffre 3 — du 19 au 22/08/2026, sur la
   page PUBLIQUE de création de compte, devant les cinq arguments de vente.
   Jamais correcte depuis son introduction, et invisible pour tout le reste de
   l'outillage : rien ici ne lit le CSS. D'où `tests/test_css_content.php`. */
.auth-side .sell li::before { content: "✓"; color: var(--pos); font-weight: var(--w-bold); margin-right: 9px; }

/* --- Courbes SVG rendues par le serveur -------------------------------------
   Les couleurs viennent des jetons du thème : aucune valeur en dur ici, comme
   partout ailleurs dans cette feuille. */
.courbe            { width: 100%; height: auto; display: block; margin: 8px 0 4px; }
.courbe .grille    { stroke: var(--line); stroke-width: 1; }
.courbe .axe       { fill: var(--muted); font-size: 11px; }
.courbe .unite     { font-size: 10px; }
.courbe .ligne     { stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.courbe .ligne.hausse { stroke: var(--pos); }
.courbe .ligne.baisse { stroke: var(--neg); }
.courbe .aire      { opacity: .12; stroke: none; }
.courbe .aire.hausse  { fill: var(--pos); }
.courbe .aire.baisse  { fill: var(--neg); }
/* ⚠️ LA COURBE DE LA RÉFÉRENCE NE PORTE NI SENS NI AIRE. Elle n'est pas jugée —
   c'est un repère, pas une performance à vous : lui donner le vert ou le rouge
   ferait croire à un second portefeuille. D'où l'ACCENT, et un TIRETÉ : deux
   traits pleins de couleurs voisines se confondent sur un écran de téléphone, et
   la légende serait le seul recours. Plus fin que la courbe principale, qui
   reste le sujet. */
.courbe .ligne.reference { stroke: var(--accent-ink); stroke-width: 1.6;
                           stroke-dasharray: 5 4; opacity: .85; }

/* La légende des deux courbes — en HTML et non dans le SVG, pour qu'elle passe à
   la ligne sur téléphone et prenne ses couleurs aux jetons du thème. */
.legende { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 10px;
           font-size: var(--fs-small); color: var(--text); }
.legende .cle { display: inline-flex; align-items: center; gap: 7px; }
.legende .trait { width: 18px; height: 0; border-top: 2px solid var(--muted);
                  display: inline-block; }
.legende .trait.hausse { border-top-color: var(--pos); }
.legende .trait.baisse { border-top-color: var(--neg); }
.legende .trait.ref { border-top: 2px dashed var(--accent-ink); }
/* Le point final et sa valeur (maquette 09, validée le 28/08/2026) : l'œil
   atterrit sur le présent. Le disque prend la couleur du sens, la valeur reste
   à l'encre — un chiffre se lit avant de se juger. */
.courbe .point.hausse { fill: var(--pos); }
.courbe .point.baisse { fill: var(--neg); }
.courbe .val-fin      { fill: var(--ink); font-size: 12px; font-weight: var(--w-semi); }
/* Le pied de courbe : période à gauche, lien vers le détail à droite. */
.courbe-pied { display: flex; justify-content: space-between; align-items: baseline;
               gap: 12px; flex-wrap: wrap; margin-top: 6px; }

/* L'étincelle — même code couleur que la grande courbe, pour que le sens se
   lise pareil des deux côtés de l'écran. */
.spark .ligne        { stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.spark .ligne.hausse { stroke: var(--pos); }
.spark .ligne.baisse { stroke: var(--neg); }

/* Le camembert évidé. Les six teintes sont celles de theme.css ; `muted` est
   réservée au non-renseigné, qui n'est PAS une catégorie d'allocation. */
.donut-svg { width: 100%; height: auto; display: block; }
.donut-svg .part.c1 { stroke: var(--c1); }
.donut-svg .part.c2 { stroke: var(--c2); }
.donut-svg .part.c3 { stroke: var(--c3); }
.donut-svg .part.c4 { stroke: var(--c4); }
.donut-svg .part.c5 { stroke: var(--c5); }
.donut-svg .part.c6 { stroke: var(--c6); }
.donut-svg .part.muted { stroke: var(--line-strong); }
.donut-svg .centre-haut { fill: var(--muted); font-size: 11px; }
.donut-svg .centre-bas  { fill: var(--ink); font-size: 15px; font-weight: var(--w-bold); }
/* Le donut de répartition d'un portefeuille (maquette 09) : borné et centré
   dans la colonne étroite — `.donut-svg` fait 100 % de son conteneur, et 100 %
   d'une colonne en ferait une roue de charrette. */
.repart-donut { width: 150px; margin: 2px auto 12px; }
.dot-c1 { background: var(--c1); } .dot-c2 { background: var(--c2); }
.dot-c3 { background: var(--c3); } .dot-c4 { background: var(--c4); }
.dot-c5 { background: var(--c5); } .dot-c6 { background: var(--c6); }
.dot-muted-fort { background: var(--line-strong); }

/* --- Page publique d'accueil ------------------------------------------------
   Ces huit règles remplacent les huit attributs style= que portait
   `maquettes/01-accueil.html` : la politique de sécurité de contenu bloque
   « style-src 'self' » en ligne, et tests/test_escape_views.php le vérifie sur
   le HTML émis. Aucune valeur de couleur en dur, comme partout ailleurs. */
.dot-accent { background: var(--accent); }
.dot-muted  { background: var(--muted); }
.shot-note  { margin: 14px 0 0; font-size: var(--fs-small); color: var(--faint); text-align: center; }
.cta-final  { margin-top: 38px; }
.pub-footer-marque   { flex: 1; min-width: 220px; }
.pub-footer-marque .brand { margin-bottom: 10px; }
.pub-footer-baseline { max-width: 280px; margin: 0; }

/* --- Ce qui n'est pas encore vrai -------------------------------------------
   `bientot` grise un bloc SANS le retirer : la promesse reste lisible, mais
   elle s'annonce comme à venir. Pour sortir un bloc de cet état : enlever la
   classe et le badge, rien d'autre.

   ⚠️ CE GRISÉ N'EST PAS UNE OPACITÉ, ET C'EST MESURÉ. La forme évidente —
   `opacity: .55` sur le bloc — fait tomber le paragraphe d'une carte de
   3,42:1 à 1,84:1 sur fond blanc, soit très en dessous des 4,5:1 exigés par
   WCAG AA, et même des 3:1 tolérés pour du texte large. Le titre y perd
   autant, de 18:1 à 4,0:1. Autrement dit : la mention « bientôt » devient
   illisible pour ceux qui en ont le plus besoin, et le texte grisé cesse
   d'informer alors qu'il est justement là pour ça.

   L'état se marque donc par le FOND, la BORDURE et le BADGE — trois signaux
   qui n'ont pas de contraste à perdre — et la couleur du texte ne bouge pas.
   Seule l'icône, purement décorative, est désaturée.

   (Relevé au passage, et non corrigé ici parce que le design est centralisé :
   `.feature p` en `--muted` sur `--surface` plafonne déjà à 3,42:1, sous le
   seuil AA. C'est un choix global de la palette, pas de ce bloc.) */
.bientot {
  background: var(--chip-bg);
  border-style: dashed;
  box-shadow: none;
}
.bientot .ic { background: var(--chip-bg); color: var(--faint); }
/* Les blocs de la section « Vos données » n'ont ni carte ni bordure : le même
   état s'y marque par un filet à gauche, seul repère disponible. */
.faq .bientot {
  background: none; border-style: none;
  border-left: 2px dashed var(--line-strong); padding-left: 14px;
}
/* ⚠️ --muted et non --faint. Mesuré le 26/08/2026 sur le rendu réel : --faint
   sur --chip-bg ne donnait que 4,13:1, sous les 4,5:1 de WCAG AA — et sur le
   seul élément dont TOUT le propos est de rester lisible pour dire « pas
   encore ». --faint est calibré sur --surface, le blanc des cartes ; il ne
   l'est pas sur --chip-bg, qui est plus foncé. Un jeton de texte se vérifie
   contre le fond où on le pose, jamais contre celui pour lequel il a été
   choisi. */
.badge-bientot {
  display: inline-block; margin-left: 8px; padding: 2px 8px; vertical-align: middle;
  border-radius: var(--radius-pill); background: var(--chip-bg); color: var(--muted);
  font-size: var(--fs-micro); font-weight: var(--w-medium); letter-spacing: .02em;
  text-transform: uppercase; white-space: nowrap;
}

/* Un lien vers une page qui n'existe pas encore n'est pas un lien : l'invariant
   n° 5 en ferait un 404, et un href="#" ment sur ce qui va se passer. C'est donc
   un <span> — non cliquable, non focalisable, et le title dit pourquoi. */
.lien-inactif { color: var(--faint); cursor: not-allowed; font-size: var(--fs-label); display: block; padding: 3px 0; }

/* Un bouton désactivé s'annonce de lui-même aux technologies d'assistance ; il
   ne reste qu'à le montrer à l'œil. */
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:disabled:hover { border-color: var(--line-strong); color: var(--ink); }
.btn:disabled:active { transform: none; }

/* Rapport de division d'action : « 4 » pour « 1 », deux champs courts sur une
   ligne. Aucun style en ligne — la CSP les bloque. */
.rapport { display: flex; align-items: center; gap: 9px; }
.rapport input { width: 5.5em; }

/* ============================================================================
   LE SYSTÈME — le vocabulaire de composants, arrêté le 25/08/2026.
   ----------------------------------------------------------------------------
   Ce bloc existe parce que le design partait en morceaux. DEUX vocabulaires
   cohabitaient dans ce fichier — .kpis/.kpi contre .kpi-strip, .card-head contre
   .card-cap, .tbl contre .oplist — et chaque écran figeait, au moment où on
   l'écrivait, celui qui passait par la tête. D'où des pages qui divergent, et la
   sensation de foutoir : ce n'était pas une affaire de goût, c'était deux
   systèmes dans le même fichier.

   Ce qui suit est le vocabulaire RETENU. Il se juge sur /systeme.html — la
   galerie de composants — AVANT d'être posé sur un écran. C'est la règle qui a
   coûté le plus cher à apprendre : le système d'abord, les écrans ensuite.

   Trois principes, dont découle tout le reste :

     1. LA COULEUR VIT DANS LES SURFACES, pas seulement dans le texte. Le
        bandeau d'en-tête (--head), les surfaces internes (--surface-2) et le sol
        de la page (--bg) : c'est ce trio qui donne une couleur à l'interface.
        Teindre trois mots en indigo n'y suffit pas, et c'est exactement ce qui
        manquait.

     2. UN TABLEAU DE DONNÉES PORTE DE LA COULEUR À CHAQUE LIGNE — pastille
        d'enveloppe, barre de poids, sens de la variation. Un tableau monochrome
        est un tableau que personne ne lit.

     3. AUCUNE VALEUR EN DUR pour la couleur et la typographie : tout vient de
        theme.css. Et AUCUNE LARGEUR QUI DÉPEND D'UNE DONNÉE en CSS : celles-là
        sont des SVG (lib/svg.php), puisque la CSP interdit style=.
   ============================================================================ */

/* ---------- Le sélecteur de fenêtre (1 an / 3 ans / Tout) -------------------
   Des LIENS, pas des boutons : changer de fenêtre change l'adresse, donc la
   page est partageable et le retour arrière fonctionne. Il tient aussi bien
   dans un en-tête de page que dans un bandeau de carte.

   ⚠️ IL S'APPELAIT `.seg` LE 25/08, ET C'ÉTAIT UNE COLLISION. `svg_barre_empilee()`
   (lib/svg.php) émet `<rect class="seg cN">` pour CHAQUE segment de barre : un
   sélecteur `.seg` nu attrapait donc tous les segments de toutes les barres, y
   compris en production. Rien ne cassait tant que la règle ne déclarait que des
   propriétés inertes sur du SVG — mais la garantie était nulle, et le premier
   `display` ou `padding` ajouté ici aurait défiguré les barres sans qu'on fasse
   le lien. D'où `.fenetre`. Ne pas rebaptiser un composant `.seg`, `.part` ou
   `.piste` : ces trois noms appartiennent aux SVG. */
/* ⚠️ `flex-shrink: 0` N'EST PAS UN ORNEMENT ICI. `overflow: hidden` est là pour
   que les coins arrondis coupent le fond des segments — mais posé dans le flex
   de `.card-cap`, ce composant héritait de `flex-shrink: 1` et se laissait
   écraser : à 375 px, 110 px pour 142 de contenu, et « Tout » disparaissait.
   Une commande que l'utilisateur ne pouvait plus ni voir ni viser, sans barre
   de défilement ni page poussée pour le signaler. MESURÉ le 01/09/2026 sur
   /portefeuille : segments 41 + 48 + 54 = 143. Ce composant a la taille de son
   contenu, ou il ment. */
.fenetre {
  display: inline-flex; background: var(--surface); flex-shrink: 0;
  border: var(--border) solid var(--line-strong);
  border-radius: var(--radius-sm); overflow: hidden;
}
.fenetre a {
  padding: 7px 13px; font-size: var(--fs-small); font-weight: var(--w-semi);
  color: var(--muted); background: transparent;
}
.fenetre a + a { border-left: var(--border) solid var(--line-strong); }
/* Survol sur --row-hover et non sur --bg : le sol de la page étant clair, un
   survol en --bg serait invisible sur la carte blanche qui porte le sélecteur. */
.fenetre a:hover { background: var(--row-hover); color: var(--ink); }
.fenetre a.on { background: var(--accent); color: var(--accent-text); }

/* ---------- L'encart d'explication -----------------------------------------
   La bande teintée où vit l'HONNÊTETÉ : ce que le chiffre au-dessus ne dit pas,
   ce qu'une colonne exclut, pourquoi deux totaux diffèrent. Un écran qui affiche
   du patrimoine en a besoin plus souvent qu'il ne croit.

   ⚠️ NE PAS CONFONDRE AVEC `.info`, qui est la pastille ronde « ? » de 14 px.
   La maquette d'inspiration appelait cette bande `.info` ; le nom était pris. */
.encart {
  display: flex; gap: 11px; align-items: flex-start;
  background: var(--surface-2); border-top: var(--border) solid var(--line);
  padding: 12px 16px;
}
.encart.haut { border-top: 0; border-bottom: var(--border) solid var(--line); }
/* La pastille est un ::before : pas de balise vide à écrire dans chaque vue. */
.encart::before {
  content: ""; width: 7px; height: 7px; border-radius: var(--radius-pill);
  background: var(--accent); margin-top: 7px; flex: none;
}
.encart p { margin: 0; font-size: var(--fs-small); line-height: 1.6; color: var(--text); }
.encart b { color: var(--ink); font-weight: var(--w-semi); }

/* ---------- Le tableau de données, enrichi ---------------------------------
   Compléments à `.tbl`. Le principe : CHAQUE LIGNE PORTE DE LA COULEUR — une
   pastille d'enveloppe, une barre de poids, le sens de la variation. */

/* Le nom d'une ligne et sa référence en sous-ligne (ISIN, devise, nature). */
.t-nom { color: var(--ink); font-weight: var(--w-semi); }
.t-ref { display: block; font-size: var(--fs-micro); color: var(--faint); font-weight: var(--w-regular); letter-spacing: .02em; }

/* Le second chiffre d'une cellule : le pourcentage sous le montant. */
.sec { display: block; font-size: var(--fs-mini); font-weight: var(--w-regular); color: var(--faint); }

/* La colonne « poids » : une barre courte suivie du pourcentage. La barre est un
   svg_barre_empilee à un seul segment — sa largeur dépend d'une donnée. */
.poids { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.poids .alloc-bar { width: 44px; height: 7px; margin: 0; border-radius: 4px; flex: none; }
.poids b { font-weight: var(--w-semi); font-variant-numeric: tabular-nums; color: var(--ink); }

/* Une CELLULE porte le sens (+/-) — maquette 09, validée le 28/08/2026. Le
   domaine fournit la classe (m_signe, invariant n° 8), la feuille la colore.
   ⚠️ `.tbl td.pos` et non `.pos` seul : dans le PIED de tableau,
   `.tbl tfoot td` (0,1,2) battrait l'utilitaire `.pos` (0,1,0) et le total
   resterait à l'encre — exactement le genre de défaut qui ne se voit qu'au
   rendu. (0,2,1) passe devant dans les deux cas. */
.tbl td.pos { color: var(--pos); }
.tbl td.neg { color: var(--neg); }

/* ---------- Le tableau de COMPARAISON de /performance (maquette 10) ---------
   ⚠️ IL NE SE COMPRIME PAS, IL DÉFILE. `.tbl { width: 100% }` convient à cinq
   colonnes ; à huit, il les écrase toutes pour tenir, et « 49 552,25 € » sortait
   sur TROIS lignes — mesuré pendant la maquette, le 01/09/2026. Ici la table
   prend la largeur de son CONTENU, avec le conteneur pour plancher : elle
   remplit la carte quand elle y tient (983 px sur 983 à 1 280 de large), et
   `.scroll-x` prend le relais quand elle n'y tient plus. Aucun chiffre ne se
   casse en deux.
   Deux exceptions au `nowrap`, et deux seulement : la phrase qui explique une
   ligne non mesurable, et la mention de fenêtre du PIED — celle-ci tenait la
   colonne « Mesuré » à 268 px pour un contenu de 130. Un pied n'a pas à
   commander la largeur d'une colonne. */
.tbl.compare { width: auto; min-width: 100%; }
.tbl.compare th, .tbl.compare td { white-space: nowrap; }
/* Sauf la PREMIÈRE colonne : le nom y côtoie la pastille d'enveloppe, dont le
   libellé est celui de /portefeuilles — « Compte-titres ordinaire » et non un
   sigle, pour que les deux écrans nomment la même chose pareil. En nowrap, cette
   seule colonne réclamait 319 px et poussait « vs référence » hors champ ;
   autorisée à passer à la ligne, elle en prend 200 et la pastille descend sous
   le nom quand il le faut. MESURÉ le 01/09/2026. */
.tbl.compare td:first-child { white-space: normal; }
.tbl.compare td.refus { white-space: normal; min-width: 250px;
                        color: var(--muted); font-style: italic; }
.tbl.compare tfoot .t-ref { white-space: normal; }

/* Le nom et le chevron sont deux liens vers le MÊME écran : une ligne
   entièrement cliquable demanderait du JavaScript, que la politique de sécurité
   de contenu interdit. Le survol éclaire toute la ligne pour que la cible se
   devine sans l'avoir survolée. */
.tbl tbody tr.ouvrable:hover .t-nom { color: var(--accent-ink); }
.tbl td.chev { text-align: right; width: 26px; color: var(--line-strong); }
.tbl tbody tr.ouvrable:hover td.chev { color: var(--accent-ink); }

/* ---------- Les quatre axes de /allocation (maquette 11) --------------------
   ⚠️ EN DEUX COLONNES, ET C'EST LE SUJET. Empilés, il fallait quatre écrans de
   défilement pour les comparer — or c'est la COMPARAISON entre axes qui fait
   l'intérêt de la page : « 78 % en ETF, mais 61 % de secteur non renseigné » ne
   se voit que si les deux sont à portée d'œil. */
/* ⚠️ minmax(0, 1fr) ET NON 1fr — le piège n° 4, cette fois sur une PISTE DE
   GRILLE et non sur une case flex. Le minimum implicite d'une piste `1fr` est
   `auto`, c'est-à-dire le `min-content` de son contenu : une carte que rien ne
   peut comprimer élargit donc la colonne au-delà du conteneur, en silence.
   Mesuré le 02/09/2026, à 375 px, après que la référence de classement a rempli
   l'écran : le libellé « Diversifié (fonds ou ETF large) » — le plus long du
   vocabulaire, et désormais le plus fréquent — donnait à sa `.wrow` un
   min-content de 237 px, à la carte 314, et poussait la PAGE ENTIÈRE de 20 px.
   Invisible avant, parce qu'un écran sans aucun classement n'affiche aucune
   barre. `min-width: 0` sur `.wl` ne suffit pas : il laisse la case céder, pas
   la piste. */
.axes { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--gap); align-items: start; }
.axes > .card { margin: 0; min-width: 0; }
/* Le donut d'un axe est plus petit que celui d'un portefeuille : à quatre
   graphiques sur une page, ce sont les BARRES qu'on lit, le donut ne donne que
   la silhouette. */
.axes .repart-donut { width: 132px; margin: 2px auto 14px; }

/* Le pied d'ORIGINE d'un axe (maquette 12) : qui a classé, en décompte. Sous
   les barres, là où l'œil arrive après les avoir lues. */
.origine { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--line-soft);
           font-size: var(--fs-small); color: var(--muted); }
.origine .src { color: var(--text); font-weight: var(--w-semi); }

/* La pastille d'origine d'une LIGNE (écran portefeuille) : creuse = la source,
   pleine = vous. ⚠️ Elle DOUBLE un texte, elle ne le porte jamais seule — deux
   remplissages ne se distinguent pas de tout le monde, et le `title` de la
   cellule dit la même chose en toutes lettres. */
.orig-pt { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
           margin-right: 5px; border: 1.5px solid var(--accent); background: transparent; }
.orig-pt.vous { background: var(--accent); }
@media (max-width: 900px) { .axes { grid-template-columns: minmax(0, 1fr); } }

/* La note éditoriale — le seul texte en serif d'un écran de chiffres, réservé à
   ce que les nombres ne peuvent pas dire d'eux-mêmes. */
.edito { font-family: var(--font-serif); font-size: 15px; line-height: 1.65;
         color: var(--text); margin: 0; }
.edito + .edito { margin-top: 10px; }
.edito b { font-family: var(--font-sans); font-weight: var(--w-semi);
           font-size: 14px; color: var(--ink); }

/* Le déclencheur « Classer » vit sous la pastille de classe (maquette 09) : un
   petit texte à l'accent, même gabarit que `.t-ref`, qui ouvre la modale de
   classement. Le secteur renseigné s'affiche au même endroit — il reste
   cliquable, et sa couleur le dit.
   ⚠️ SPÉCIFICITÉ PLAFONNÉE À (0,1,1) : `.modale[open] > summary` (0,2,1), plus
   bas dans la feuille, doit CONTINUER de gagner quand la modale s'ouvre — c'est
   lui le voile plein écran. Une variante à deux classes ici l'emporterait et
   casserait la fermeture au clic à côté. */
.tbl .classer { display: block; }
.classer > summary {
  display: block; cursor: pointer; margin-top: 1px;
  font-size: var(--fs-micro); letter-spacing: .02em; font-weight: var(--w-medium);
  color: var(--accent);
}
.classer > summary:hover { color: var(--accent-ink); }

/* ---------- Déplier une ligne SANS JAVASCRIPT -------------------------------
   <details> ne peut pas envelopper un <tr> : le HTML ne l'autorise pas. Le
   dépliage vit donc dans une cellule qui s'étend sur toute la largeur, juste
   SOUS la ligne concernée. Le déclencheur n'est donc pas la ligne elle-même —
   c'est le prix de se passer de script, et il est honnête puisque le libellé
   dit ce qu'il ouvre (« les 4 achats qui composent ce PRU »). */
.tbl td.depli-cell { padding: 0; background: var(--surface-2); }
.depli > summary {
  list-style: none; cursor: pointer; padding: 8px 16px;
  font-size: var(--fs-small); font-weight: var(--w-semi); color: var(--accent);
}
.depli > summary::-webkit-details-marker { display: none; }
.depli > summary::marker { content: ""; }
/* Les triangles sont écrits EN CLAIR et non en « \25B8 » : tests/test_css_content.php
   contrôle caractère par caractère ce que le CSS imprime, et un échappement est
   cinq caractères ASCII qui traversent le contrôle sans être lus. Le garde-fou
   existe parce que « ¹3 » est resté trois jours sur la page publique. */
.depli > summary::before { content: "▸"; margin-right: 7px; }
.depli[open] > summary::before { content: "▾"; }
.depli > summary:hover { color: var(--accent-ink); background: var(--row-hover); }
.depli-corps { padding: 2px 16px 14px; }

/* Le tableau imbriqué des lots : une carte dans la carte, en plus serré. */
.tbl-lots { background: var(--surface); border: var(--border) solid var(--line);
            border-radius: var(--radius-sm); overflow: hidden; }
.tbl-lots .tbl th, .tbl-lots .tbl td { padding: 7px 11px; font-size: var(--fs-small); }
.tbl-lots .tbl tfoot td { font-size: var(--fs-small); }

/* ---------- La pastille de type d'opération ---------------------------------
   Le journal était monochrome : une colonne de dates, une colonne de libellés,
   une colonne de montants. La NATURE de l'opération est pourtant ce qu'on y
   cherche en premier. Une teinte par nature, prise dans la série des
   graphiques — donc cohérente avec les camemberts et les barres. */
.kind {
  font-size: var(--fs-nano); font-weight: var(--w-bold); letter-spacing: .07em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 4px;
  margin-right: 8px; background: var(--chip-bg); color: var(--text);
}
/* Mêmes jetons opaques que .badge, et pour la même raison : ces pastilles
   apparaissent AUSSI sur --surface-2 (ligne dépliée) et sur --row-hover, où un
   fond transparent faisait tomber .kind.versement à 2,49:1. */
.kind.achat     { background: var(--c1-soft); color: var(--c1-ink); }
.kind.vente     { background: var(--c5-soft); color: var(--c5-ink); }
.kind.dividende { background: var(--pos-soft); color: var(--pos); }
.kind.versement { background: var(--c6-soft); color: var(--c6-ink); }
.kind.retrait   { background: var(--warn-soft); color: var(--warn); }
.kind.frais     { background: var(--neg-soft); color: var(--neg); }
.kind.echange   { background: var(--c4-soft); color: var(--c4-ink); }
/* La division d'action et l'échange sont deux opérations SUR TITRES : elles ne
   déplacent pas d'argent, elles rebattent les quantités. Teintes voisines. */
.kind.division  { background: var(--c3-soft); color: var(--c3-ink); }

/* La barre de pied d'une liste : « afficher les plus anciennes ». Teintée comme
   les autres surfaces internes, pour qu'elle se lise comme un pied et non comme
   une ligne de plus. */
.oplist-plus {
  display: block; padding: 10px 16px; text-align: center;
  font-size: var(--fs-small); font-weight: var(--w-semi); color: var(--accent);
  background: var(--surface-2); border-top: var(--border) solid var(--line);
}
.oplist-plus:hover { background: var(--row-hover); color: var(--accent-ink); }

/* ---------- La ligne de poids (répartition) ---------------------------------
   Libellé, barre colorée, pourcentage. Employée dans le volet étroit, là où un
   camembert serait trop gros et un tableau trop bavard. */
.wrow { display: flex; align-items: center; gap: 10px; padding: 6px 0; }
.wrow .wl { font-size: var(--fs-small); color: var(--text); width: 42%; min-width: 0;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wrow .alloc-bar { flex: 1; height: 8px; margin: 0; border-radius: 4px; }
.wrow .wp { font-size: var(--fs-small); font-weight: var(--w-semi);
            font-variant-numeric: tabular-nums; width: 46px; text-align: right; color: var(--ink); }

/* ---------- La frise d'antériorité ------------------------------------------
   Une durée écoulée, un seuil daté, et où l'on se situe entre les deux. La
   PISTE dépend d'une donnée : c'est un SVG, étiré en largeur. Les ÉTIQUETTES,
   elles, restent du HTML — un <text> dans un SVG étiré par preserveAspectRatio
   se déformerait avec lui, et deviendrait illisible sur une carte étroite. */
/* Les bouts arrondis viennent du CSS et non d'un rx= : la frise est étirée par
   preserveAspectRatio="none", et un rx en unités SVG deviendrait une ellipse. */
/* ⚠️ LE REPÈRE DE SEUIL EST SOUS LA BARRE, ET CE N'EST PAS UN CAPRICE.
   Corrigé le 26/08/2026, après mesure. Deux défauts en thème sombre :
     · le rail en --line-soft ne valait que 1,11:1 contre le fond de carte : la
       frise paraissait TOUJOURS pleine, on ne voyait plus la part restante, qui
       est pourtant l'information ;
     · le repère était dessiné DANS la barre. Or il tombe tantôt sur la portion
       parcourue (plan de plus de cinq ans), tantôt sur le rail (plan plus
       jeune) — donc il lui faut contraster avec les DEUX. C'est impossible :
       en sombre, parcouru et rail ne sont séparés que de 3,58:1, si bien
       qu'aucune teinte ne peut être à 3:1 des deux à la fois. --accent tombait
       à 1,48:1 sur le parcouru : un trait de 1,4 % de large, invisible.
   D'où le repère SOUS la barre, où il ne se mesure plus que contre le fond de
   carte : --ink y donne environ 15:1 dans les deux thèmes, quel que soit l'état
   du plan. La barre perd ses bouts arrondis (un rx en unités SVG deviendrait
   une ellipse sous preserveAspectRatio="none") — une frise n'est pas une jauge. */
.frise { width: 100%; height: 14px; display: block; margin: 12px 0 0; }
.frise .piste  { fill: var(--line-strong); }
.frise .fait   { fill: var(--c2); }
.frise .seuil  { fill: var(--ink); }
.frise-etiq { display: flex; justify-content: space-between; gap: 10px; margin-top: 7px; }
.frise-etiq span { font-size: var(--fs-micro); color: var(--faint); line-height: 1.45; }
.frise-etiq span:last-child { text-align: right; }
.frise-etiq b { display: block; color: var(--ink); font-weight: var(--w-semi);
                font-size: var(--fs-small); font-variant-numeric: tabular-nums; }

/* Les variantes .k3 / .k4 vivaient ICI. Elles sont remontées auprès de
   `.kpi-strip`, avant les @media — voir l'avertissement là-bas. */

/* ---------- La liste des portefeuilles : PAS de composant nouveau -----------
   Le 25/08 une classe `.plist` avait été écrite ici, pour afficher chaque
   portefeuille avec SA VALEUR. Deux raisons de l'avoir retirée le 26/08 :
     · la liste EXISTE DÉJÀ — `nav_contexte()` rend `pf_liste()` et
       `views/layout.php` la dessine, en simples `<a>` du <nav>, sur chaque page
       connectée. Écrire un composant pour ce qui est déjà là, c'était le
       deuxième vocabulaire que ce fichier vient justement de finir de tuer ;
     · la VALEUR à côté du nom coûte une valorisation par portefeuille et par
       page. Elle a été écartée pour ce prix-là, décision du 26/08.
   Les entrées de portefeuille sont donc des `.sidebar nav a` comme les autres,
   et le panneau indigo les reteinte avec le reste du menu. */

/* ---------- La barre latérale en PANNEAU indigo -----------------------------
   Variante à l'essai : la colonne de gauche devient une surface pleine au lieu
   d'un rectangle blanc bordé. C'est le plus gros apport de couleur possible sur
   l'application, et il ne coûte qu'une classe.
   ⚠️ Les jetons employés sont ceux du bloc « à contre-jour » (--hero-*), qui
   BASCULENT en thème sombre. Écrire #fff en dur ici casserait le thème sombre,
   où le panneau ne peut plus être « plus foncé que le reste ». */
.sidebar.panneau {
  background: var(--hero-bg); border-right-color: var(--hero-bg); color: var(--hero-text);
}
.sidebar.panneau .brand { color: var(--hero-text); }
/* ⚠️ SANS CECI, LE LOGO DISPARAÎT. En thème clair --accent et --hero-bg valent
   la MÊME couleur (#26265e) : le carré de marque se fondait exactement dans le
   panneau, et il ne restait que la diagonale blanche flottant dans le vide. Sur
   le panneau, la pastille s'inverse — carré clair, diagonale foncée. */
.sidebar.panneau .brand-mark {
  background: linear-gradient(145deg, var(--accent-soft), var(--hero-label));
  box-shadow: none;
}
.sidebar.panneau .brand-mark::after {
  background: linear-gradient(to top right, transparent 46%,
              color-mix(in srgb, var(--hero-bg) 90%, transparent) 46%,
              color-mix(in srgb, var(--hero-bg) 90%, transparent) 54%, transparent 54%);
}
.sidebar.panneau .cap { color: var(--hero-label); padding: 0 10px; }
.sidebar.panneau nav a,
.sidebar.panneau .nav-bottom { color: var(--hero-label); }
.sidebar.panneau nav a:hover,
.sidebar.panneau .nav-bottom:not(.off):hover { background: color-mix(in srgb, var(--hero-text) 10%, transparent); color: var(--hero-text); }
.sidebar.panneau nav a.on,
.sidebar.panneau .nav-bottom.on { background: color-mix(in srgb, var(--hero-text) 17%, transparent); color: var(--hero-text); }
/* Grisé par la COULEUR, jamais par opacity — voir la note de .sidebar .off.
   Et hors du <nav> comme dedans : « Mon compte » est concerné. */
.sidebar.panneau .off { color: var(--hero-sub); }
.sidebar.panneau .nav-sep { color: var(--hero-sub); }

/* ---------- L'état vide -----------------------------------------------------
   Un vide dit ce qu'il faut faire ensuite, sinon c'est une impasse. */
.empty h3 { font-size: var(--fs-h2); color: var(--ink); margin: 0 0 6px; font-weight: var(--w-semi); }
.empty p { margin: 0 auto 16px; max-width: 34em; font-size: var(--fs-small); }

/* ---------- La modale, SANS UNE LIGNE DE JAVASCRIPT --------------------------
   La CSP pose `script-src 'self'` et le produit n'a aucun script en ligne : une
   modale pilotée par `onclick` est donc hors de question. Celle-ci est un
   `<details>`, comme le renommage inline — natif, clavier compris, et il garde
   son état ouvert/fermé sans qu'on l'écrive nulle part.

   ⚠️ LE `<summary>` JOUE DEUX RÔLES, ET C'EST LA CLÉ DU PROCÉDÉ.
     · fermé  : c'est le BOUTON qui ouvre (« Nouveau portefeuille ») ;
     · ouvert : il devient LE VOILE plein écran, derrière la boîte. Cliquer à
       côté referme donc la modale — le geste que tout le monde attend — alors
       qu'un `<details>` ne se referme normalement que par son propre résumé.
   Un seul élément, deux états, aucun script. Le « × » est son ::after, posé au
   coin de l'écran ; il n'est pas un second bouton mais la même surface.

   ⚠️ ROUVRIR APRÈS UNE ERREUR. Le serveur re-rend la page après un refus de
   saisie : sans l'attribut `open` posé par la vue, la modale se rouvrirait
   FERMÉE, emportant avec elle la saisie de l'utilisateur ET le message qui
   explique le refus. C'est le défaut classique des modales, et il se corrige
   ici par un attribut, pas par du script. */
.modale { display: inline-block; }

/* Fermée : un bouton ordinaire. `list-style: none` retire le triangle natif —
   il faut les deux écritures, WebKit ne suivant pas encore ::marker. */
.modale > summary { list-style: none; cursor: pointer; }
.modale > summary::-webkit-details-marker { display: none; }
.modale > summary::marker { content: ""; }

/* Ouverte : le résumé s'étale en voile plein écran, sous la boîte. */
.modale[open] > summary {
  position: fixed; inset: 0; z-index: 90;
  background: var(--voile);
  display: block; padding: 0; border: 0; border-radius: 0;
  cursor: default;
  /* Le libellé du bouton n'a plus de sens une fois ouvert : il disparaît à
     l'œil sans disparaître de l'arbre d'accessibilité, où il reste l'étiquette
     du contrôle qui referme. */
  font-size: 0; color: transparent;
}
.modale[open] > summary::after {
  content: "×";                         /* U+00D7, multiplication — pas la lettre x */
  position: fixed; top: 14px; right: 20px;
  font-size: 30px; line-height: 1; color: var(--hero-text);
  font-weight: var(--w-medium);
}

.modale-boite {
  position: fixed; z-index: 91;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(560px, 92vw); max-height: 88vh; overflow-y: auto;
  background: var(--surface); border: var(--border) solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  text-align: left;
}
/* Le bandeau d'une modale reste collé en haut quand le contenu défile : sur un
   téléphone, un formulaire long ferait sinon perdre de vue ce qu'on remplit. */
.modale-boite .card-cap { position: sticky; top: 0; z-index: 1; }
.modale-boite .card-body { padding: 16px; }
.modale-boite .field:last-of-type { margin-bottom: 18px; }

/* ---------- La page étroite --------------------------------------------------
   Pour un écran de RÉGLAGES (Mon compte) : des formulaires étirés sur les
   1420 px du contenu font des champs de saisie d'un mètre de long. On borne la
   COLONNE, pas le gabarit — la barre latérale et le bandeau gardent leur place. */
.page-etroite { max-width: var(--colonne-etroite); }

/* Un champ en LECTURE SEULE se distingue par ses jetons, pas par la feuille du
   navigateur : la surface interne douce dit « ce n'est pas à toi de l'éditer »,
   et le texte reste à --muted — lisible, calibré, dans les deux thèmes. Sans
   cette règle, l'habillage retombait sur le moteur : identique à un champ
   éditable sous Blink, opacité non calibrée sous WebKit. */
input[readonly] { background: var(--surface-2); color: var(--muted); cursor: default; }
input[readonly]:focus { border-color: var(--line-strong); box-shadow: none; }

/* ---------- L'en-tête d'un écran de liste -----------------------------------
   Titre à gauche, action principale à droite. Même rôle que `.pf-head` sur un
   écran de détail ; nom distinct parce qu'il ne porte ni sous-titre ni fil
   d'ariane. */
.liste-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: var(--gap);
}
.liste-head h2 { margin: 0; font-size: var(--fs-h2); font-weight: var(--w-semi); }
.liste-head .hint { margin: 3px 0 0; }

/* ===========================================================================
   PAGES LÉGALES — un gabarit de PROSE, le seul du site.
   ---------------------------------------------------------------------------
   Posé le 08/09/2026 avec /cgu, /confidentialite et /mentions-legales. Tout le
   reste du produit est fait de cartes, de tableaux et de chiffres ; ces trois
   pages-là sont du texte long, et un texte long se lit à une mesure étroite.
   D'où une largeur propre (68 caractères environ), et rien d'autre de nouveau :
   les couleurs, les bordures et les rythmes viennent des jetons existants.

   ⚠️ `.doc-incertain` N'EST PAS UN STYLE DÉCORATIF. Il marque ce que nous ne
   savons pas encore — une durée de conservation non confirmée, une mention
   d'identité à compléter, une clause qui attend le juriste. Sur une page
   légale, distinguer à l'œil ce qui est établi de ce qui ne l'est pas vaut
   mieux que de tout écrire du même ton : c'est la règle du projet, « ce qu'on
   ne sait pas, on le dit », appliquée à un document qui engage.
   =========================================================================== */

.doc { max-width: 74ch; padding-top: 48px; padding-bottom: 72px; }
.doc h1 { font-size: 34px; letter-spacing: -.028em; margin: 6px 0 10px; }
.doc h2 {
  font-size: 21px; letter-spacing: -.02em; color: var(--ink);
  margin: 40px 0 12px; padding-top: 18px;
  border-top: var(--border) solid var(--line-soft);
}
.doc h3 { font-size: 16px; color: var(--ink); margin: 26px 0 8px; }
.doc p { line-height: 1.68; margin: 0 0 14px; }
.doc .lead { font-size: 17px; color: var(--muted); margin-bottom: 26px; }

.doc-liste { margin: 0 0 16px; padding-left: 20px; }
.doc-liste li { line-height: 1.68; margin-bottom: 9px; }

/* L'encadré d'état, en tête de chaque page : il dit ce que le document est, et
   surtout ce qu'il n'est pas encore. */
.doc-etat {
  background: var(--accent-soft); border: var(--border) solid var(--line);
  border-radius: var(--radius); padding: 16px 18px; margin: 0 0 30px;
}
.doc-etat p { margin: 0; font-size: 14.5px; color: var(--text); }

.doc-table { width: 100%; border-collapse: collapse; margin: 4px 0 18px; font-size: 14.5px; }
.doc-table th, .doc-table td {
  text-align: left; vertical-align: top; padding: 10px 12px;
  border-bottom: var(--border) solid var(--line-soft); line-height: 1.55;
}
.doc-table thead th { color: var(--faint); font-size: 12.5px; text-transform: uppercase;
                      letter-spacing: .06em; background: var(--surface-2); }
.doc-table tbody th { color: var(--ink); font-weight: 600; width: 34%; }

.doc-note {
  font-size: 14px; color: var(--muted);
  border-left: 3px solid var(--line-strong); padding-left: 14px; margin: 0 0 18px;
}

/* Ce qui n'est PAS établi. Le ton de l'avertissement, pas celui de l'erreur :
   une information manquante n'est pas une panne. */
.doc-incertain {
  background: var(--warn-soft); color: var(--warn);
  border-radius: 4px; padding: 1px 6px;
}
td.doc-incertain, p.doc-incertain { display: block; padding: 8px 12px; }

.doc-pied {
  margin-top: 36px; padding-top: 16px;
  border-top: var(--border) solid var(--line-soft);
  font-size: 14px; color: var(--muted);
}

/* ⚠️ Une prose de 74ch tient déjà dans 375 px ; c'est le TABLEAU qui déborde.
   Il défile dans son propre cadre plutôt que de pousser la page — le piège n° 4
   du projet, `1fr` et son minimum implicite `min-content`. */
@media (max-width: 700px) {
  .doc h1 { font-size: 27px; }
  .doc-table { display: block; overflow-x: auto; }
}

/* ---------- Fiscalité — porté de la maquette 13, le 08/09/2026 -------------
   Trois classes seulement : le reste de l'écran se sert de `.card`, `.tbl`,
   `.kpi-strip`, `.badge`, `.encart` et `.kind`, qui existaient déjà. */

/* L'encart qui porte un FAIT À CONNAÎTRE — un retrait avant cinq ans, un report
   qui manque, un exercice sans barème. Le ton de l'avertissement, pas celui de
   l'erreur : une information manquante n'est pas une panne, et la traiter comme
   telle userait le rouge jusqu'à ce qu'on ne le voie plus. */
.encart-attention { border-top-color: var(--warn); background: var(--warn-soft); }
.encart-attention::before { background: var(--warn); }

/* ⚠️ `.encart` EST UN FLEX AVEC UNE PUCE EN ::before. Plusieurs paragraphes y
   deviendraient donc des colonnes côte à côte : la vue les enveloppe dans un
   <div>, et c'est cette règle qui les remet en pile. Défaut vu à l'œil sur la
   maquette 13 avant portage. */
.encart > div { min-width: 0; }
.encart > div p + p { margin-top: 7px; }

/* ⚠️ UN TABLEAU DE DÉFINITIONS N'EST PAS UN TABLEAU SUR TÉLÉPHONE. À 375 px,
   deux colonnes se partagent 275 px : le terme se replie sur trois lignes et sa
   définition devient un ruban de trois mots. Aucun débordement — et illisible.
   Relevé À L'ŒIL sur la maquette 13 ; les mesures du DOM disaient « rien ne
   déborde », ce qui était vrai et sans rapport avec la question posée. */
@media (max-width: 640px) {
  .tbl-def, .tbl-def tbody, .tbl-def tfoot, .tbl-def tr, .tbl-def td { display: block; width: auto; }
  .tbl-def tr { padding: 11px 0; border-bottom: var(--border) solid var(--line-soft); }
  .tbl-def tr:last-child { border-bottom: 0; }
  .tbl-def td { padding: 0 0 3px; border: 0; text-align: left; }
  .tbl-def td:first-child { color: var(--faint); font-size: var(--fs-micro); }
}

/* ⚠️ CES LARGEURS NE VALENT QUE SUR GRAND ÉCRAN. Une colonne de termes à 30 %
   laisse 85 px à 375 px : la cellule se vide et le texte s'écrase en ruban. Une
   largeur qui répare le bureau et casse le téléphone n'a rien réparé. */
@media (min-width: 900px) {
  .col-terme { width: 30%; }
}

/* Un montant ne se coupe jamais. « 115,50 € » rendu sur deux lignes — le nombre,
   puis l'euro tout seul — cesse d'être lisible d'un coup d'œil, ce qui est la
   seule chose qu'on demande à une colonne de chiffres. */
.tbl td.r.mono, .tbl tfoot td.r { white-space: nowrap; }
