/* IBM Plex auto-hébergée (S3.4, 30/08/2026) : l'import distant vers Google
   Fonts forçait une résolution DNS + poignée de main TLS séquentielle vers
   une 2ᵉ origine avant le premier rendu, et empêchait de retirer ce
   fournisseur tiers du CSP. Sous-ensemble "latin" uniquement (U+0000-00FF
   couvre les accents français à-ÿ, plus €, tirets, guillemets) : le
   "latin-ext" du fournisseur ne sert que l'Europe centrale, hors périmètre
   ici. IBM Plex Sans 500/600/700 pointent vers le MÊME fichier variable
   (`ibm-plex-sans-variable.woff2`) : c'est exactement ce que servait la
   source distante (vérifié dans sa réponse CSS) — un seul fichier à axe
   `wght`, le navigateur choisit l'instance. Licence : SIL OFL 1.1, voir
   static/fonts/OFL.txt. */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/ibm-plex-mono-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/ibm-plex-mono-500.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-mono-600.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-variable.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-variable.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/ibm-plex-sans-variable.woff2') format('woff2');
}
/* ===== Design tokens ===== */
/* Échelle de breakpoints (S6.2, 31/08/2026) : 3 paliers documentés, à ne pas
   dépasser sans revue. Remplace 11 valeurs ad hoc (520/560/640/680/720/760/
   780/820/860/900/1080px) qui ne correspondaient à aucune intention déclarée
   — chaque nouveau composant ajoutait la sienne au pixel près où il cassait
   visuellement, sans qu'aucune ne soit réutilisée ailleurs.
     560px : mobile étroit — colonnes empilées, nav condensée.
     720px : tablette / mobile large — bascule du multi-colonnes au 1-colonne
             pour les tableaux et grilles de cartes.
     900px : desktop étroit / tablette large — dernier point où les panneaux
             latéraux (filtres, détail) repassent sous le contenu principal.
   Un besoin de valeur intermédiaire est un signal qu'un composant a une
   largeur de contenu propre à revoir, pas qu'il faut une 4e valeur. */
:root {
  color-scheme: light dark;
  /* Thème « Encre & Laiton » (2026).
     Le sujet de cette app, c'est la MESURE : des cours, des poids, des écarts.
     Le thème est donc construit comme un instrument, pas comme une brochure —
     plan de fond neutre-froid (encre sur papier technique), filets d'un pixel,
     chiffres en monospace, et UNE couleur chaude qui sert de repère.

     ⚠️ Règle de lisibilité inchangée : l'ACCENT (chrome : nav, boutons, liens)
     est un LAITON — une teinte tierce, volontairement HORS du vert/rouge
     sémantiques. Ainsi le vert (--pos) reste réservé aux GAINS et le rouge
     (--neg) aux PERTES : dans une app boursière, la décoration ne doit jamais
     se confondre avec un signal de performance.

     Le prédécesseur (« Or & Graphite ») posait un fond crème chaud (#f7f5ef).
     Le crème tire vers le papier vieilli : joli, mais il réchauffe les rouges
     et écrase la lisibilité des teintes froides des graphiques. Le plan est
     désormais neutre-froid, ce qui laisse le laiton être la seule chose chaude
     de l'écran — donc la seule qui attire l'œil. */
  --bg: #eceff3;
  --bg-tint: radial-gradient(120% 90% at 50% -10%, rgba(138, 101, 18, 0.05), transparent 60%);
  --surface: #ffffff;
  --surface-2: #f4f6f9;
  --surface-3: #e6ebf1;
  --text: #10151b;
  --text-muted: #5b6875;
  --border: #dce2e9;
  --border-strong: #bcc7d3;
  --accent: #8a6512;
  --accent-hover: #6d500a;
  --accent-weak: rgba(138, 101, 18, 0.12);
  /* Texte POSÉ SUR l'accent. Longtemps codé en dur à #fff : lisible en clair
     mais catastrophique en sombre, où l'accent s'éclaircit — blanc sur laiton
     tombe sous le seuil 3:1 des composants d'interface. Le défaut ne se voyait
     qu'APRÈS bascule de thème, d'où sa longévité. */
  --on-accent: #fdfbf6;
  --pos: #0d7a45;
  --pos-weak: rgba(13, 122, 69, 0.12);
  --neg: #c02b26;
  --neg-weak: rgba(192, 43, 38, 0.11);
  --warn: #9a5c08;

  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.05);
  --shadow: 0 1px 3px rgba(16, 24, 40, 0.07), 0 1px 2px rgba(16, 24, 40, 0.04);
  --shadow-lg: 0 12px 32px -8px rgba(16, 24, 40, 0.18), 0 4px 10px rgba(16, 24, 40, 0.06);
  --shadow-pop: 0 20px 50px -12px rgba(16, 24, 40, 0.28);
  /* Trame des modales (S6.3) : volontairement identique en clair et en sombre
     — un fond de scène assombri doit rester lisible quel que soit le thème,
     ce n'est pas --bg qui varie ici mais un noir de studio fixe. */
  --scrim: #05070c;

  /* Rayons resserrés (7/12/16 -> 5/9/13). Un instrument de mesure a des angles
     nets ; les grands arrondis lisaient « application grand public ». */
  --radius-sm: 5px;
  --radius: 9px;
  --radius-lg: 13px;

  /* Rythme d'espacement : tout dérive de cette échelle, plus de valeurs au hasard */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --fast: 0.14s var(--ease);
  --med: 0.24s var(--ease);

  /* Typographie — le pari du thème.
     Le corps reste sur la pile système (aucun reflow des tableaux, chargement
     instantané). Deux faces s'ajoutent, chacune pour un rôle :
       --font-display : IBM Plex Sans, grotesque technique, pour les TITRES.
       --font-mono    : IBM Plex Mono, pour tout ce qui est un NOMBRE — valeur
                        héros, tuiles de KPI, tickers, graduations d'axes.
     C'est le geste signature : dans un terminal de marché, un chiffre se lit en
     chasse fixe. Les colonnes s'alignent d'elles-mêmes, les ordres de grandeur
     se comparent à l'œil, et un « 1 » ne fait plus la moitié d'un « 8 ».
     Space Grotesk (prédécesseur) faisait les deux à la fois et ne faisait bien
     ni l'un ni l'autre : trop typé pour un tableau, pas assez pour un titre. */
  --font-display: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --fs-display: clamp(2.3rem, 4.6vw, 3.2rem);
  --fs-xl: 1.55rem;
  --fs-lg: 1.15rem;
  --fs-md: 0.95rem;
  --fs-sm: 0.82rem;
  --fs-xs: 0.72rem;
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* ===== Palette catégorielle (donuts, barres, légendes) =====
     REFAITE et VALIDÉE (skill dataviz, `scripts/validate_palette.js`).
     L'ancienne (or/bleu/violet/turquoise…) échouait dès 4 teintes ensemble :
     --c2 et --c3 mesuraient ΔE 1,5 en vision deutan (seuil : 8) et AUCUNE
     n'atteignait 3:1 contre le fond. Concrètement, deux secteurs d'un donut
     étaient indiscernables pour ~8 % des hommes, et pâles pour tout le monde.

     Le passage validé, 8 fentes, ORDRE COMPRIS (l'ordre est le mécanisme de
     sécurité daltonienne : ce sont les paires ADJACENTES qui se retrouvent
     côte à côte dans un empilement ou une légende) :
       clair (fond #ffffff) : tous les contrôles PASS —
         bande de clarté, plancher de chroma, séparation CVD (pire paire
         adjacente violet↔cyan ΔE 9,3 deutan), vision normale (pire paire
         ΔE 16,8), contraste ≥ 3:1 sur les 8.
       sombre (fond #16181b) : tous les contrôles PASS —
         pire paire adjacente ΔE 8,6 deutan, vision normale ΔE 16,3,
         contraste ≥ 3:1 sur les 8.

     Le VERT et le ROUGE en sont toujours absents : réservés aux gains/pertes.
     L'OR aussi, désormais : il était la 1re teinte, donc le plus gros segment
     de chaque donut portait exactement la couleur du chrome (boutons, liens,
     onglet actif) — impossible de savoir si une pastille dorée désignait une
     catégorie ou un élément d'interface. Le laiton ne dit plus qu'une chose.

     Au-delà de 8 catégories, on NE génère PAS une 9e teinte : le reste se
     replie dans « Autres » (--c-other, neutre, hors palette). */
  --c1: #2b62c9;   /* bleu */
  --c2: #dd6a12;   /* orange */
  --c3: #0f92ae;   /* cyan */
  --c4: #8a4fc0;   /* violet */
  --c5: #d95093;   /* magenta */
  --c6: #4a92dc;   /* azur */
  --c7: #b5732f;   /* terre */
  --c8: #6f5bd6;   /* indigo */
  --c-other: #7b8794;
  /* Compat : deux fichiers référencent encore --c9/--c10 (badges de source).
     Ce sont des NEUTRES, pas des fentes catégorielles — ne pas les ajouter à
     une palette de graphique, elles n'ont pas passé la validation. */
  --c9: #6b7684;
  --c10: #8d7a5c;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
    --bg: #0d0f12;
    --bg-tint: radial-gradient(120% 90% at 50% -10%, rgba(217, 169, 78, 0.09), transparent 60%);
    --surface: #16181b;
    --surface-2: #1d2126;
    --surface-3: #262b31;
    --text: #e9edf1;
    --text-muted: #949fac;
    --border: #262b31;
    --border-strong: #39414a;
    --accent: #d9a94e;
    --accent-hover: #eec473;
    --accent-weak: rgba(217, 169, 78, 0.16);
    --on-accent: #0d0f12;   /* le blanc sur laiton clair tombait à 2,17:1 */
    --pos: #35c07f;
    --pos-weak: rgba(53, 192, 127, 0.15);
    --neg: #f0685e;
    --neg-weak: rgba(240, 104, 94, 0.14);
    --warn: #e0973a;
    --c1: #4d80e2;
    --c2: #d7701e;
    --c3: #149ab6;
    --c4: #9a5fdb;
    --c5: #e05f9b;
    --c6: #4f91d8;
    --c7: #b57c31;
    --c8: #7b6ade;
    --c-other: #7f8a97;
    --c9: #8a95a3;
    --c10: #a08c68;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 12px 32px -8px rgba(0, 0, 0, 0.7);
    --shadow-pop: 0 20px 50px -12px rgba(0, 0, 0, 0.8);
  }
}
@media (prefers-reduced-motion: reduce) {
* {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
*::before {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
*::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
* {
  box-sizing: border-box;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 1.5rem 3rem;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
h1 {
  font-size: 1.6rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0.5rem 0 1.25rem;
}
h2 {
  font-size: 1.15rem;
  font-weight: 650;
}
a {
  color: var(--accent);
}
.indices {
  display: flex;
  gap: 1.1rem;
  flex-wrap: wrap;
}
.market-status .dot.open {
  background: var(--pos);
}
.topnav a.active {
  color: var(--accent);
  background: var(--accent-weak);
}
#prospect-form {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: center;
}
input {
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--text);
}
select {
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--text);
}
button {
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--text);
}
input {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface);
  min-height: 38px;
}
select {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface);
  min-height: 38px;
}
input::placeholder {
  color: var(--text-muted);
}
input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-weak);
}
button[type="submit"] {
  padding: 0.5rem 1.1rem;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  cursor: pointer;
  min-height: 38px;
  transition: filter 0.15s;
}
button[type="submit"]:hover {
  filter: brightness(1.08);
}
button.secondary {
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  font-weight: 550;
  min-height: 38px;
}
table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
th {
  text-align: right;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
td {
  text-align: right;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
thead th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  font-weight: 600;
  border-bottom: 1px solid var(--border-strong);
  position: sticky;
  top: 0;
}
th:first-child {
  text-align: left;
}
td:first-child {
  text-align: left;
}
/* S7.7 : sur écran tactile, `:hover` reste « allumé » après le tap tant que le
   doigt ne touche pas ailleurs — une ligne restait surlignée en permanence
   (Rubis, FDJ United…) sans qu'aucune interaction ne l'explique. `pointer: fine`
   exclut le tactile, `hover: hover` exclut les appareils qui ne peuvent pas
   survoler : ensemble ils ne couvrent que la souris/trackpad. */
@media (hover: hover) and (pointer: fine) {
tbody tr:hover td {
  background: var(--surface-2);
}
}
.pos {
  color: var(--pos);
}
/* ===== Synthèse par compte ===== */
.summary-table {
  max-width: 960px;
}
.mgr-mvt-type.pos { color: var(--pos); }
/* ===== Prospects ===== */
.prospect-layout {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
}
.prospect-list-wrap {
  flex: 0 0 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
  box-shadow: var(--shadow);
}
.recap-panel {
  flex: 1;
  min-width: 0;
}
.range-tabs button.active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
}
.ind-window-btn.active {
  border-color: var(--accent);
  color: var(--text);
  font-weight: 600;
}
.tabs .tab { flex: none; white-space: nowrap; }
.tabs .tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font-size: 0.92rem;
  font-weight: 600;
  padding: 0.6rem 0.9rem;
  cursor: pointer;
  border-radius: 0;
}
.tabs .tab:hover {
  color: var(--text);
}
.tabs .tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}
.layer-tabs button.active {
  background: var(--accent-weak);
  border-color: var(--accent);
  color: var(--accent);
}
.signal polygon {
  fill: var(--pos);
}
.signal.sig-divergence polygon {
  fill: var(--accent);
}
.signal {
  cursor: help;
}
.signal:hover polygon {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(1.3);
}
/* ===== Marqueurs déclarés : socle commun initiés + gérants =====
   Trois défauts se cumulaient et rendaient ces marqueurs illisibles :
   ils étaient trop petits (10 px de côté), ils flottaient sans lien visible
   avec la séance qu'ils annotent, et ils étaient peints dans la même encre
   que la MM20 qu'ils croisent. D'où la tige, l'ancre sur le cours, et le
   halo couleur fond qui les détache de tout ce qui passe dessous. */
.insider polygon {
  /* `paint-order: stroke` peint le contour SOUS le remplissage : le halo
     déborde donc vers l'extérieur sans ronger la forme. */
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: 3;
  stroke-linejoin: round;
}
/* Survol : la forme grossit ET la tige s'allume, pour que le lien avec la
   séance saute aux yeux au moment précis où on lit l'infobulle. */
.insider.mk-on .mk-tige {
  opacity: 1;
  stroke-width: 1.5;
}
.insider.mk-on polygon {
  transform-box: fill-box;
  transform-origin: center;
  transform: scale(1.25);
}
/* Marqueurs d'initiés (SEC Form 4). Ni vert ni rouge : ce couple appartient aux
   gains et aux pertes, et le graphe s'en sert DÉJÀ pour les bougies — un
   triangle vert au-dessus d'une bougie verte aurait dit deux choses avec la
   même encre. Le sens tient à l'orientation du triangle (haut = achat, sous le
   cours ; bas = vente, au-dessus) ; le plein/creux le redouble, parce qu'à
   10 px deux triangles opposés se ressemblent beaucoup. */
.insider {
  cursor: help;
}
.insider polygon {
  fill: var(--accent);
}
/* Vente : creux. `fill: none` laissait voir la courbe À TRAVERS le triangle, et
   un contour or de 1,6 px s'y noyait complètement. Le remplissage prend donc la
   couleur du fond — la forme reste creuse à l'œil, mais elle masque ce qui
   passe dessous — et le trait est épaissi pour tenir à cette taille. */
.insider.ins-vente polygon {
  fill: var(--surface);
  stroke: var(--accent);
  stroke-width: 2.2;
}
.etf-sheet-actions button.secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border-strong);
}
/* ============================================================
   PASSE UX/UI — surcharges volontairement en fin de fichier
   ============================================================ */
body {
  background-color: var(--bg);
  background-image: var(--bg-tint);
  background-repeat: no-repeat;
  background-attachment: fixed;
  font-size: 14px;
  letter-spacing: -0.006em;
  max-width: 1560px;
  padding: 0 var(--s5) var(--s6);
}
h1 {
  font-size: 1.75rem;
  font-weight: 750;
  letter-spacing: -0.028em;
  margin: var(--s5) 0 var(--s4);
}
h2 { letter-spacing: -0.018em; }
h3 { letter-spacing: -0.012em; }
::selection { background: var(--accent-weak); }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* La liste d'indices cède la place à la recherche quand la fenêtre se resserre :
   c'est la recherche qui doit rester à taille utilisable, pas l'inverse. */
.indices { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.market-status .dot.open { background: var(--accent); color: var(--accent); animation: pulse 2.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
.topnav a.active { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent); }
/* ===== Formulaires ===== */
input { transition: border-color var(--fast), box-shadow var(--fast), background var(--fast), transform var(--fast); }
select { transition: border-color var(--fast), box-shadow var(--fast), background var(--fast), transform var(--fast); }
button { transition: border-color var(--fast), box-shadow var(--fast), background var(--fast), transform var(--fast); }
input:hover { border-color: var(--border-strong); }
select:hover { border-color: var(--border-strong); }
button { border-radius: var(--radius-sm); }
button:not(:disabled):active { transform: translateY(1px); }
button[type="submit"] {
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}
button[type="submit"]:hover { background: var(--accent-hover); filter: none; }
thead th {
  background: var(--surface);
  z-index: 2;
  padding-top: var(--s3);
  padding-bottom: var(--s2);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
}
tbody tr { transition: background var(--fast); }
@media (hover: hover) and (pointer: fine) {
tbody tr:hover td { background: var(--surface-2); }
}
tbody tr:last-child td { border-bottom: none; }
td { padding: 0.6rem; }
@keyframes shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
.toast.err::before { content: "!"; color: var(--neg); }
.toast.err { border-color: color-mix(in srgb, var(--neg) 35%, var(--border)); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(0.97); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(4px); } }
@keyframes fade { from { opacity: 0; } }
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(0.985); } }
.tabs .tab { transition: color var(--fast), border-color var(--fast); }
@media (max-width: 720px) {
body { padding: 0 var(--s3) var(--s5); }
h1 { font-size: 1.4rem; }
}
.view-tabs button.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.mode-tabs button.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.chip-preset.on { background: var(--accent); border-color: var(--accent); border-style: solid; color: var(--on-accent); }
/* Item actif : surlignage gris clair neutre (pas de teinte verte) ; le repère
   vert reste porté par la barre ::before en accent sapin. */
.sidebar nav a.active { background: var(--surface-3); color: var(--text); font-weight: 600; }
.sidebar nav a.active::before {
  content: ""; position: absolute; left: -var(--s3); left: -12px;
  top: 50%; transform: translateY(-50%);
  width: 3px; height: 18px; border-radius: 0 3px 3px 0;
  background: var(--accent);
}
/* ===== Hero ===== */
.hero {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s5);
  flex-wrap: wrap;
  padding: var(--s5) var(--s5) var(--s4);
  margin-bottom: var(--s4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  position: relative;
  overflow: hidden;
}
.hero::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(90% 140% at 100% 0%, var(--accent-weak), transparent 55%);
  pointer-events: none;
}
.hero-delta .chip.pos { background: var(--pos-weak); color: var(--pos); }
h1 { font-family: var(--font-display); letter-spacing: -0.02em; }
kbd {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) {
.hero { padding: var(--s4); }
}
.traj-perf.pos { color: var(--pos); }
.traj-range.on { background: var(--accent-weak); border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
/* Carte du portefeuille (treemap : taille = poids, couleur = variation du jour) */
.mmap-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: var(--s4) var(--s5) var(--s5);
  margin-bottom: var(--s4); box-shadow: var(--shadow);
}
.mmap-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
.mmap-head h2 { font-size: var(--fs-lg); margin: 0; }
.mmap-hint { font-size: var(--fs-xs); color: var(--text-muted); }
.mmap-sym.sm { font-size: 0.66rem; }
/* ============================================================
   THÈME FORÇABLE — le media query seul ne laisse aucun choix.
   [data-theme] doit gagner dans les deux sens.
   ============================================================ */
:root[data-theme="dark"] {
  --bg: #0d0f12;
  --bg-tint: radial-gradient(120% 90% at 50% -10%, rgba(217, 169, 78, 0.09), transparent 60%);
  --surface: #16181b; --surface-2: #1d2126; --surface-3: #262b31;
  --text: #e9edf1; --text-muted: #949fac;
  --border: #262b31; --border-strong: #39414a;
  --accent: #d9a94e; --accent-hover: #eec473; --accent-weak: rgba(217, 169, 78, 0.16);
  --on-accent: #0d0f12;
  --pos: #35c07f; --pos-weak: rgba(53, 192, 127, 0.15);
  --neg: #f0685e; --neg-weak: rgba(240, 104, 94, 0.14);
  --warn: #e0973a;
  /* Mêmes 8 fentes catégorielles, re-pas-à-pas pour le fond sombre — validées
     comme un JEU contre #16181b, pas éclaircies une à une. */
  --c1: #4d80e2; --c2: #d7701e; --c3: #149ab6; --c4: #9a5fdb;
  --c5: #e05f9b; --c6: #4f91d8; --c7: #b57c31; --c8: #7b6ade;
  --c-other: #7f8a97; --c9: #8a95a3; --c10: #a08c68;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4); --shadow: 0 1px 3px rgba(0,0,0,.5);
  --shadow-lg: 0 12px 32px -8px rgba(0,0,0,.7); --shadow-pop: 0 20px 50px -12px rgba(0,0,0,.8);
  color-scheme: dark;
}
/* Forcer le CLAIR sur une machine en sombre : il faut réécrire toutes les
   valeurs, sinon le media query ci-dessus gagne encore. */
:root[data-theme="light"] {
  --bg: #eceff3;
  --bg-tint: radial-gradient(120% 90% at 50% -10%, rgba(138, 101, 18, 0.05), transparent 60%);
  --surface: #ffffff; --surface-2: #f4f6f9; --surface-3: #e6ebf1;
  --text: #10151b; --text-muted: #5b6875;
  --border: #dce2e9; --border-strong: #bcc7d3;
  --accent: #8a6512; --accent-hover: #6d500a; --accent-weak: rgba(138, 101, 18, 0.12);
  --on-accent: #fdfbf6;
  --pos: #0d7a45; --pos-weak: rgba(13, 122, 69, 0.12);
  --neg: #c02b26; --neg-weak: rgba(192, 43, 38, 0.11);
  --warn: #9a5c08;
  --c1: #2b62c9; --c2: #dd6a12; --c3: #0f92ae; --c4: #8a4fc0;
  --c5: #d95093; --c6: #4a92dc; --c7: #b5732f; --c8: #6f5bd6;
  --c-other: #7b8794; --c9: #6b7684; --c10: #8d7a5c;
  --shadow-sm: 0 1px 2px rgba(16,24,40,.05); --shadow: 0 1px 3px rgba(16,24,40,.07), 0 1px 2px rgba(16,24,40,.04);
  --shadow-lg: 0 12px 32px -8px rgba(16,24,40,.18), 0 4px 10px rgba(16,24,40,.06);
  --shadow-pop: 0 20px 50px -12px rgba(16,24,40,.28);
  color-scheme: light;
}
kbd {
  font-family: inherit; font-size: 0.68rem; font-weight: 650;
  padding: 0.08rem 0.32rem; border-radius: 5px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-muted);
}
.rtabs button.active { color: var(--accent); border-bottom-color: var(--accent); }
.recap-summary.open { -webkit-line-clamp: unset; }
/* ===== Boutons ===== */
.btn-primary {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 0.45rem 0.9rem;
  border-radius: var(--radius-sm);
  font-weight: 640; font-size: 0.84rem;
  cursor: pointer; min-height: 36px;
  border: 1px solid transparent;
}
.btn-primary { background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
.fp-fill.hi { background: var(--pos); }
.fp-fill.lo { background: var(--neg); }
.fp-val.hi { color: var(--pos); }
.fp-val.lo { color: var(--neg); }
.fp-drivers .pos { color: var(--pos); }
@keyframes tick-up {
  0% { background: var(--pos-weak); color: var(--pos); }
  100% { background: transparent; }
}
@keyframes tick-down {
  0% { background: var(--neg-weak); color: var(--neg); }
  100% { background: transparent; }
}
/* ===== Typographie plus affirmée ===== */
body {
  font-family: "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", Roboto, sans-serif;
  font-feature-settings: "kern" 1, "liga" 1, "cv11" 1;
  text-rendering: optimizeLegibility;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) :is(.summary > div, .form-section, .table-wrap, .filter-panel, .chart-card,
  .hero, .modal, .pal, .toast, .sidebar) {
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  }
:root:not([data-theme="light"]) .hero { background: linear-gradient(180deg, var(--surface-2), var(--surface)); }
}
:root[data-theme="dark"] .hero {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
:root[data-theme="dark"] .hero { background: linear-gradient(180deg, var(--surface-2), var(--surface)); }
th { padding: 0.7rem 0.7rem; }
td { padding: 0.7rem 0.7rem; }
.hero { padding: var(--s6) var(--s6) var(--s5); }
.indices { gap: var(--s1); }
.idx .v { font-weight: 640; font-variant-numeric: tabular-nums; }
.idx .c { font-size: 0.72rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.mk {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.22rem 0.55rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 0.72rem;
  color: var(--text-muted);
  cursor: default;
}
.mk b { font-weight: 640; color: var(--text-muted); }
.mk.is-open { border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: var(--accent-weak); }
/* Le NOM de la place reste en pleine encre : mesuré, le laiton clair tombe à
   3,96:1 sur son propre fond pâle, sous le seuil AA d'un texte de 0,72 rem.
   C'est le point pulsé qui porte le statut (3,96:1 suffit pour un élément
   non textuel, seuil 3:1), pas le mot « New York ». */
.mk.is-open b { color: var(--text); }
.mk .dot { width: 6px; height: 6px; margin: 0; }
/* Zébrure très légère : suffit à tenir la ligne sur 18 colonnes, sans quadriller */
tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--surface-2) 45%, transparent); }
@media (hover: hover) and (pointer: fine) {
tbody tr:hover td { background: var(--surface-2); }
tbody tr:nth-child(even):hover td { background: var(--surface-2); }
}
/* Hiérarchie : les chiffres de contexte s'effacent, les décisifs ressortent */
td { color: var(--text-muted); }
td .pos { color: inherit; }
thead th {
  font-size: 0.65rem;
  letter-spacing: 0.07em;
  color: var(--text-muted);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--border-strong);
  background: var(--surface);
}
.type-tabs button.active { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }
/* ============================================================
   FLUIDITÉ — transitions de page & réglages fins
   ============================================================ */
/* Transition native entre pages : lisse le flash blanc du rechargement complet.
   Ignoré par les navigateurs qui ne la supportent pas — dégradation propre. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
::view-transition-old(root) { animation: vt-out 0.16s var(--ease); }
::view-transition-new(root) { animation: vt-in 0.22s var(--ease); }
}
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(6px); } }
::view-transition-group(sidebar) { animation: none; }
/* Apparition douce du contenu au premier chargement (hors transition de vue) */
.hero {
  animation: rise 0.4s var(--ease) both;
}
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }
/* Défilement lissé quand on clique une ancre ou qu'on remonte */
html { scroll-behavior: smooth; }
/* Le focus clavier doit rester net et cohérent partout */
a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.tab { cursor: pointer; }
/* Scrollbars discrètes et cohérentes avec le thème */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 99px; border: 3px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: padding-box; }
.leg-item:not(.active) { opacity: 0.4; text-decoration: line-through; }
/* ===== Page de connexion ===== */
.login-body {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; padding: var(--s5); margin: 0;
}
.login-card {
  width: 100%; max-width: 400px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; padding: var(--s6, 2rem) var(--s5);
  box-shadow: var(--shadow-pop);
  position: relative; overflow: hidden;
  animation: modal-in 0.32s var(--ease);
}
/* Filet laiton en tête de carte : même geste que l'accent de nav, pour que la
   page de connexion se sente déjà « dans » l'app avant même la connexion. */
.login-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
}
.login-brand {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--s3); margin-bottom: var(--s4);
}
.login-logo {
  width: 56px; height: 56px; border-radius: 15px; flex: none;
  /* #000 ici n'est pas une couleur affichée : c'est le point noir pur vers
     lequel color-mix() assombrit --accent, indépendant du thème (S6.3). */
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #000));
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 700; font-size: 1.5rem;
  color: var(--on-accent); box-shadow: var(--shadow);
}
.login-brand h1 {
  margin: 0; font-size: 1.5rem; letter-spacing: 0.01em;
  font-family: var(--font-display); font-weight: 700;
}
.login-sub { margin: 0 0 var(--s5); color: var(--text-muted); font-size: 0.86rem; line-height: 1.5; text-align: center; }
.login-field { display: flex; flex-direction: column; gap: 0.3rem; margin-bottom: var(--s4); font-size: 0.8rem; color: var(--text-muted); font-weight: 600; }
.login-field input { width: 100%; }
.login-error {
  margin: 0 0 var(--s3); font-size: 0.82rem; color: var(--neg);
  background: var(--neg-weak); border-radius: 8px; padding: 0.5rem 0.65rem;
}
.login-links {
  display: flex; justify-content: space-between; gap: var(--s3);
  margin: var(--s4) 0 0; font-size: 0.8rem;
}
.login-links a { color: var(--text-muted); }
.login-links a:hover { color: var(--accent); }
.login-legal { margin: var(--s3) 0 0; font-size: 0.74rem; text-align: center; }
.login-legal a { color: var(--text-muted); }
.login-legal a:hover { color: var(--accent); }
/* Connexion Google. Le bouton reste NEUTRE (surface + bordure) : l'or est
   l'accent du thème, réservé à l'action principale du formulaire — deux
   boutons dorés côte à côte ne diraient plus lequel est le chemin normal. */
.login-ou {
  display: flex; align-items: center; gap: var(--s3);
  margin: var(--s4) 0; font-size: 0.75rem; color: var(--text-muted);
}
.login-ou::before {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.login-ou::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.btn-google {
  display: flex; align-items: center; justify-content: center; gap: var(--s3);
  width: 100%; padding: 0.6rem 0.9rem; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  color: var(--text); font-size: 0.88rem; font-weight: 600; text-decoration: none;
}
.btn-google:hover { border-color: var(--accent); color: var(--text); box-shadow: var(--shadow-sm); }
.btn-google svg { width: 17px; height: 17px; flex: none; }
.pres-actions .btn-primary {
  background: var(--accent); color: var(--on-accent); padding: 0.5rem 1rem;
  border-radius: var(--radius); text-decoration: none;
}
.pres-actions .btn-primary:hover { background: var(--accent-hover); }
.pres-actions a:not(.btn-primary) { color: var(--text-muted); }
.pres-actions a:not(.btn-primary):hover { color: var(--accent); }
@media (max-width: 720px) {
.pres-actions .btn-primary { padding: 0.45rem 0.75rem; font-size: 0.82rem; }
.pres-actions a:not(.btn-primary) { font-size: 0.82rem; }
}
/* ===== Documents légaux (publics, lisibles sans compte) ===== */
.legal-body { margin: 0; padding: var(--s5); }
.legal {
  max-width: 760px; margin: 0 auto; padding: var(--s6, 2rem) 0;
  line-height: 1.65; font-size: 0.92rem;
}
.legal h1 { font-size: 1.6rem; margin: 0 0 var(--s2); }
.legal h2 { font-size: 1.02rem; margin: var(--s6, 2rem) 0 var(--s3); }
.legal p { color: var(--text-muted); }
.legal li { color: var(--text-muted); }
.legal b { color: var(--text); font-weight: 600; }
.legal ul { padding-left: 1.15rem; }
.legal li { margin-bottom: var(--s2); }
.legal-maj { font-size: 0.8rem; margin: 0 0 var(--s5); }
.legal-retour {
  margin-top: var(--s6, 2rem); padding-top: var(--s4);
  border-top: 1px solid var(--border); font-size: 0.85rem;
}
.src-badge.s-sec { color: var(--c1);  border-color: color-mix(in srgb, var(--c1) 34%, transparent); }
/* ===== Veille sectorielle ===== */
.sector-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--s4);
}
.sector-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius, 14px);
  padding: var(--s4) var(--s5);
}
.sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); }
.sec-head h2 { margin: 0; font-size: 1.05rem; }
.sec-count { font-size: 0.78rem; color: var(--text-muted); white-space: nowrap; }
.sec-chip.pos { color: var(--pos); border-color: color-mix(in srgb, var(--pos) 30%, transparent); }
@media (max-width: 560px) {
.sector-grid { grid-template-columns: 1fr; }
}
.lead-dot.on { background: var(--pos); }
.sd-note { font-size: 0.74rem; color: var(--text-muted); margin: var(--s2) 0 0; line-height: 1.5; }
.cv-flag.pos { color: var(--pos); border-color: color-mix(in srgb, var(--pos) 30%, transparent); }
.dur-court { color: var(--pos); border-color: color-mix(in srgb, var(--pos) 30%, transparent); }
.dur-mid { color: var(--text-muted); }
.dur-long { color: var(--neg); border-color: color-mix(in srgb, var(--neg) 30%, transparent); }
.sf-yoy.pos { color: var(--pos); }
.ins-type.pos { color: var(--pos); background: var(--pos-weak); }
.ini-onglet.on {
  color: var(--text); font-weight: var(--fw-semibold);
  border-bottom-color: var(--accent);
}
.ini-block-label {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); font-weight: var(--fw-semibold);
  margin: var(--s5) 0 var(--s2);
  display: flex; align-items: baseline; gap: var(--s2);
}
.ini-corp.seul { font-size: var(--fs-sm); color: var(--text); }
.ini-sens.pos { color: var(--pos); background: var(--pos-weak); }
/* Un montant MESURÉ s'affirme : chiffres pleins, couleur du texte. */
.ini-amt.exact { color: var(--text); font-weight: var(--fw-semibold); }
@media (max-width: 720px) {
/* Densité : titres et cartes plus compacts, jamais de min-width qui déborde */
h1 { font-size: 1.55rem; }
.hero { padding: var(--s4); }
.stat { min-width: 0; }
/* Garde-fou : aucune page ne doit scroller latéralement (le tiroir est fixed,
     donc non affecté). Le raccourci clavier ⌘K n'a pas de sens sur mobile. */
body { overflow-x: hidden; }
/* S7.3 : sous 720px, CAC/S&P/Nasdaq/DAX/Nikkei ne tiennent plus sur une ligne.
     `flex-wrap: wrap` + `overflow: hidden` (règle plus bas) les empilait ou les
     tronquait sans aucun signe qu'il restait des indices à droite — DAX et
     Nikkei devenaient inatteignables. Le défilement horizontal remplace le
     retour à la ligne, et le dégradé en bord droit dit qu'il y a plus à voir. */
.indices {
    flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent 100%);
  }
.indices::-webkit-scrollbar { display: none; }
.idx { flex: none; white-space: nowrap; }
}
.tabbar a.active { color: var(--accent); }
.tabbar a.active::before {
  content: "";
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 26px; height: 2px; border-radius: 0 0 2px 2px;
  background: var(--accent);
}
@media (max-width: 720px) {
.btn-primary {
    min-height: 44px;
  }
/* iOS zoome sur tout champ dont la police est < 16px : ça décale la page
     entière et on ne revient jamais au bon niveau de zoom. */
input { font-size: 16px; }
select { font-size: 16px; }
textarea { font-size: 16px; }
/* Les onglets de la fiche récap défilent au doigt plutôt que de se tasser */
.recap-tabs {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    flex-wrap: nowrap; scrollbar-width: none;
  }
.recap-tabs::-webkit-scrollbar { display: none; }
/* Confort de lecture : titres qui ne cassent pas n'importe où, chiffres
     alignés, pas de surlignage bleu au tap. */
h1 { text-wrap: balance; }
h2 { text-wrap: balance; }
a { -webkit-tap-highlight-color: transparent; }
button { -webkit-tap-highlight-color: transparent; }
/* Le rebond élastique de Safari sous une position:fixed donne un effet cassé */
body { overscroll-behavior-y: none; }
}
/* Lissage des polices sur macOS/iOS : sans ça le texte fin paraît gras et sale */
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.sim-range.on { background: var(--accent-weak); border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); }
.sim-area.tre { fill: color-mix(in srgb, var(--c3) 22%, transparent); }
.sim-line.tre { stroke: var(--c3); }
.sim-end.tre { fill: var(--c3); }
.sim-swatch.tre { background: var(--c3); }
