
:root{
  color-scheme:light;
  /* Palette officielle NovaTitre v3 (2026-10-13, audit V7 §6 : "supprimer
     le jaune/doré comme couleur dominante" — capture d'écran à l'appui) :
     --accent (boutons primaires, CTA, états actifs — PARTOUT sur le
     site) était resté mappé sur --orange-500 malgré le commentaire
     ci-dessous (2026-09-23) annonçant déjà son remplacement par le rose —
     jamais appliqué à la variable elle-même, cause racine confirmée de
     "jaune partout" dans les captures. --accent passe maintenant sur
     --violet-500 (#805DFF), la couleur de marque NovaBot déjà établie
     (js/ui-primitives.js, .nb-page) — jamais une teinte inventée pour
     l'occasion, maquette validée par l'utilisateur (artifact
     XV25HLFvNiCeSRrzXm9hDa) avant ce changement. --orange-* reste défini
     (encore référencé nulle part après ce correctif, mais retiré
     seulement si un usage futur en dépendait par erreur serait pire
     qu'une variable inutilisée) — plus aucun usage actif après ce
     correctif (voir .btn-a/.nb-cta-a plus bas, corrigés pour ne plus
     référencer --orange-light directement). Vert/rouge (hausse/baisse) et
     l'ambre d'avertissement restent INCHANGÉS : couleurs sémantiques
     universelles en finance (gain/perte/alerte), jamais une couleur de
     marque décorative — seule l'étoile "favori" (une décoration, pas un
     avertissement) a été corrigée séparément pour ne plus emprunter
     --warn. Échelles à 6 crans (50/100/300/500/700/900) conservées. */
  --orange-50:#fef3e7;  --orange-100:#fce3c3; --orange-300:#f5b876;
  --orange-500:#e67e22; --orange-700:#b85f12; --orange-900:#7a3d08;
  --orange-light:#f39c12;
  --violet-50:#f1edff;  --violet-100:#e0d4ff; --violet-300:#b49cff;
  --violet-500:#805dff; --violet-700:#6b46f0; --violet-900:#4527a8;
  --violet-light:#9478ff;
  --cyan-50:#e5fbff;  --cyan-100:#ccf7ff; --cyan-300:#66e8ff;
  --cyan-500:#00d4ff; --cyan-700:#00a8cc; --cyan-900:#006b85;
  --green-50:#e7fbf4;  --green-100:#c3f5e3; --green-300:#6ee7b7;
  --green-500:#10b981; --green-700:#0d9268; --green-900:#065f46;
  /* CORRECTIF (2026-10-02, retour utilisateur : "fond soit seulement
     blanc et toute les écriture soit noire") : --bg/-2/-3/-4 et
     --ink/-2/-3/-4 passent en noir/blanc purs — demande explicite de
     "change uniquement ça et rien d'autre". La barre de navigation du
     haut (.top) n'est PAS concernée : elle n'utilise jamais ces tokens,
     son propre fond (rgba(255,255,255,...)/rgba(8,9,12,...) selon le
     thème) reste intact, volontairement non touché ici. */
  --bg:#ffffff;
  --bg-2:#ffffff;
  --bg-3:#ffffff;
  --bg-4:#ffffff;
  --ink:#000000;
  --ink-2:#000000;
  --ink-3:#000000;
  --ink-4:#000000;
  --accent:var(--violet-500);
  /* Même sens hover (plus clair, pas plus sombre) que l'ancien accent
     orange — seules les valeurs changent, --violet-light (#9478FF) joue
     le même rôle que jouait --orange-light. */
  --accent-hover:var(--violet-light);
  --accent-press:var(--violet-700);
  --accent-soft:var(--violet-50);
  --accent-ink:#ffffff;
  /* Bleu nuit officiel NovaTitre (#0A0E27) : utilisé pour le texte sur
     fond cyan clair (--blue-accent ci-dessous) — un texte blanc y serait
     illisible (contraste insuffisant). Défini ici (pas seulement en
     dark) car --blue-accent est identique en clair et en sombre. */
  --bg-dark-ink:#0a0e27;
  /* Cyan réservé aux boutons SECONDAIRES uniquement (jamais un accent
     général concurrent de l'orange) — voir .btn-blue plus bas (nom
     conservé, couleur désormais cyan — voir note de palette ci-dessus). */
  --blue-accent:var(--cyan-500); --blue-accent-soft:var(--cyan-50);
  --up:var(--green-500);   --up-soft:var(--green-50);
  --down:#ef4444; --down-soft:#fef0ef;
  --warn:#b45309; --warn-soft:#fdf4e7;
  --line:#e6e9f0;
  --line-2:#eef0f5;
  /* Tokens Nova (voir leur grand commentaire dans [data-theme="dark"]
     pour le contexte complet) : repli THÈME CLAIR — l'esthétique "verre
     bleu nuit" demandée est pensée pour le thème sombre, mais ces
     variables doivent exister ici aussi pour qu'un composant Nova reste
     lisible (contraste correct) si jamais consulté en thème clair,
     jamais une carte invisible/blanc sur blanc. */
  --nova-bg:#ffffff; --nova-bg-deep:#f4f5fb;
  --nova-surface:rgba(33,26,80,.045); --nova-surface-secondary:rgba(33,26,80,.03); --nova-surface-hover:rgba(33,26,80,.075);
  --nova-border:rgba(33,26,80,.10);
  --nova-text:#171334; --nova-text-secondary:rgba(23,19,52,.68); --nova-text-muted:rgba(23,19,52,.48);
  --nova-ai:#3458E8; --nova-bot:#6A3FE0; --nova-review:#C93E8A; --nova-news:#3E63D6; --nova-event:#0F8FBF;
  /* Glass header/nav/icon (§16, 2ᵉ passe) : définis aussi en clair pour que
     le système de tokens existe partout, mais seul le thème sombre les
     câble réellement dans .top/.dock/.nova-action-circle plus bas — le
     thème clair garde son propre traitement (verre blanc) déjà en place,
     jamais touché par cette refonte. */
  --glass-header:rgba(255,255,255,.72); --glass-navigation:rgba(255,255,255,.72); --glass-icon:rgba(33,26,80,.06);
  --r-xs:8px; --r-s:12px; --r-m:18px; --r-l:24px; --r-xl:32px; --r-pill:999px;
  --shadow-1:none;
  --shadow-2:0 1px 2px rgba(13,17,23,.04), 0 8px 24px rgba(13,17,23,.05);
  --shadow-3:0 2px 6px rgba(13,17,23,.06), 0 24px 48px rgba(13,17,23,.08);
  /* CORRECTIF (2026-10-13, cohérence avec le nouvel --accent violet) :
     cette ombre était restée teintée rose (halo d'une identité "rose
     primaire" antérieure jamais vraiment appliquée à --accent, voir le
     commentaire au-dessus de --violet-50) — un bouton violet avec un
     halo rose aurait été tout aussi incohérent que l'était le halo
     orange déjà corrigé ci-dessous pour le thème sombre. rgba(128,93,
     255,...) = --violet-500 en rgb, jamais une teinte indépendante du
     bouton qu'elle encadre. */
  --shadow-accent:0 8px 24px rgba(128,93,255,.24);
  --space-2:8px;  --space-3:12px; --space-4:16px; --space-5:24px;
  --space-6:32px; --space-7:48px; --space-8:64px; --space-9:96px;
  --sp-1:8px; --sp-2:16px; --sp-3:24px; --sp-4:32px; --sp-5:48px; --sp-6:80px;
  /* CORRECTIF dock mobile (confirmé sur Safari iPhone réel, pas seulement en
     test automatisé) : --dock-h = hauteur réelle du dock flottant (padding
     7px haut+bas + bouton 8px/22px icône/3px gap/~13px texte/7px ≈ 67px,
     arrondi par prudence) ; --dock-gap = son décalage du bas de l'écran
     (bottom:14px dans .dock, hors safe-area). Utilisées ensemble avec
     env(safe-area-inset-bottom) — jamais une valeur fixe devinée — pour que
     le padding-bottom du contenu s'adapte à la VRAIE zone sûre de l'appareil
     (elle varie selon le modèle d'iPhone/Android et l'orientation). */
  /* Recalculé une 4e fois le 2026-09-23 (passe 2, finition visuelle
     après comparaison directe avec la capture Revolut de référence) :
     la passe précédente visait délibérément "90 à 110px" (cible initiale
     du tout premier brief) et le résultat était trop haut/compact —
     Revolut est une capsule bien plus HORIZONTALE. Nouvelle cible
     "70-82px", et le padding vertical du dock réduit en conséquence
     (18px → 10px). Mesuré en direct sur le palier par défaut (≥390px,
     écran 414/430px) à 79.6px ; 82px retenu ici avec une petite marge de
     sécurité plutôt que la valeur pile mesurée. Les 2 paliers responsive
     plus étroits (≤390px, ≤360px) donnent un dock encore moins haut
     (mesuré 76.1px et 70.6px) ; --dock-h garde la valeur du palier le
     PLUS haut par sécurité (mieux vaut un peu de padding-bottom en trop
     sur petit écran qu'un pixel de contenu caché derrière le dock).
     Toujours couplé à env(safe-area-inset-bottom) dans .wrap ci-dessous
     — ne JAMAIS ajuster l'un sans l'autre, c'est ce qui a évité que ce
     changement (comme les 3 précédents) ne fasse recouvrir du contenu
     par le dock. */
  --dock-h:82px; --dock-gap:12px;
  --space-xs:8px; --space-sm:16px; --space-md:24px; --space-lg:32px;
  --t-display:clamp(38px,7vw,60px);
  --t-title:clamp(26px,4vw,36px);
  --t-h2:21px;
  --t-h3:17px;
  --t-body:15.5px;
  --t-small:13.5px;
  --t-label:12.5px;
  --t-micro:11.5px;
  --lh-tight:1.06; --lh-title:1.15; --lh-body:1.6;
  --icon-box:44px; --icon-gap:16px; --card-pad:24px;
  --font:'Inter',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,'SF Mono',Menlo,monospace;
  --ease:cubic-bezier(.32,.72,0,1);
  --spring:cubic-bezier(.32,.72,0,1);
  --fast:.14s; --slow:.28s;
  --tap:44px;
}
[data-theme="dark"]{
  color-scheme:dark;
  /* Échelle noire NovaTitre (2026-09-28, pivot marque "orange Hermès +
     cyan", palette exacte imposée) : --bg reprend EXACTEMENT le "noir
     profond domaine" #0A0E27 spécifié — un précédent passage (2026-09-23)
     s'en était délibérément éloigné (dominante bleu nuit jugée trop
     présente à l'époque, remplacée par un noir teinté rose/violet) ; ici
     c'est une décision de marque neuve et explicite, pas une régression.
     --bg-2/-3/-4 dérivés en éclaircissant progressivement la même teinte
     (même principe relatif que l'échelle précédente), --ink inchangé
     (déjà exactement #F3F4F6, la valeur demandée).
     CORRECTIF (2026-10-02, retour utilisateur : "fond completement en
     noire pour la session sombre et toute les écriture en blanc") :
     noir/blanc purs, même principe que le thème clair ci-dessus. .top
     (nav du haut) non concernée, voir sa note.
     CORRECTIF (2026-10-05, retour utilisateur : "a la place du noir
     dans le fond, du bleu marine") : #0A0E27, le "bleu nuit officiel
     NovaTitre" déjà utilisé ailleurs dans ce fichier (--bg-dark-ink,
     voir plus bas) — réutilisé ici plutôt qu'une nouvelle teinte
     arbitraire. Reste uniforme sur les 4 crans, comme demandé ("juste
     ça"). Texte blanc et boutons blancs (voir plus bas) non concernés,
     déjà conformes sur ce fond aussi.
     CORRECTIF (2026-10-07, refonte visuelle des modules Nova, retour
     utilisateur : "le fond actuel est trop proche du noir [...] je ne
     veux pas que le site paraisse noir") : #0A0E27 restait perceptible
     comme "quasi-noir" à l'œil/sur OLED malgré sa dominante bleue
     techniquement réelle (R10 G14 B39, très sombre) — remplacé par un
     indigo nettement plus clair et saturé, explicitement demandé cette
     fois. Échelle à 4 crans conservée (surfaces empilées de plus en
     plus claires, même principe que les palettes précédentes), teinte
     de base changée. --bg-2/-3/-4 dérivés par éclaircissement progressif
     de --bg plutôt qu'une 4e valeur arbitraire. */
  --bg:#19143E;
  /* CORRECTIF (2026-10-07, 2ᵉ passe de refonte, retour utilisateur :
     "le problème actuel est que seules les cartes Nova sont plus claires
     [...] je veux que ce langage visuel soit appliqué à l'ensemble de
     l'application") : --bg-2/-3/-4 n'étaient que des teintes indigo PLUS
     OPAQUES (donc visuellement une famille de couleurs différente de
     --nova-surface, un blanc translucide) — remplacées par la même
     famille "verre" que les cartes Nova (blanc translucide empilé sur le
     dégradé indigo de html), pour que TOUTE carte/surface du site
     (Ma position, Ma liste, Marché, Gratuit/Pro/Elite, inputs, chips,
     barres de progression...) appartienne visuellement à la même famille
     que les Nova — plus une seule carte "plus sombre et différente".
     3 crans progressifs (comme avant, même principe relatif), --bg-4
     ajouté comme palier le plus clair pour les surfaces imbriquées à 2
     niveaux (ex. un badge dans une carte déjà claire). */
  --bg-2:rgba(255,255,255,.075); --bg-3:rgba(255,255,255,.11); --bg-4:rgba(255,255,255,.14);
  --ink:#ffffff; --ink-2:#ffffff; --ink-3:#ffffff; --ink-4:#ffffff;
  /* CORRECTIF (2026-10-07, 2ᵉ passe) : "je ne veux plus d'orange dans le
     design général" (§3, portée explicitement élargie cette fois au-delà
     des seuls composants Nova — contrairement à la 1ʳᵉ passe) — l'accent
     décoratif du thème SOMBRE devient bleu électrique (même teinte que
     Nova AI, --nova-ai, pour ancrer "Nova AI" comme l'identité centrale
     du produit) au lieu de l'orange Hermès. Cascade automatique vers tout
     ce qui utilise var(--accent) sans redéfinition locale (eyebrows,
     coches d'abonnement, toggles, focus ring, badges...) — jamais
     réécrit composant par composant (§16 : "pas de couleurs hardcodées").
     Thème CLAIR non concerné (--accent y reste l'orange Hermès, protégé
     explicitement : "ne casse pas le mode clair"). Vert/rouge (gain/
     perte) INCHANGÉS : couleurs fonctionnelles, jamais décoratives. */
  --accent:#4D7CFF; --accent-hover:#6D94FF; --accent-press:#3458E8;
  --accent-soft:rgba(77,124,255,.16); --accent-ink:#ffffff;
  /* Cyan réservé aux boutons secondaires (même règle qu'en thème
     clair) — jamais un accent général. */
  --blue-accent:var(--cyan-500); --blue-accent-soft:#142a30;
  --up:#34d399; --up-soft:#0c2419; --down:#f87171; --down-soft:#2a1416;
  --warn:#e0a63a; --warn-soft:#2a2110;
  /* Même famille "verre" que les surfaces ci-dessus (2026-10-07, 2ᵉ passe) :
     bordures/séparateurs solides indigo -> bordure blanche translucide,
     cohérente avec --nova-border partout où une carte en dérive. */
  --line:rgba(255,255,255,.10); --line-2:rgba(255,255,255,.07);
  --shadow-2:0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35);
  --shadow-3:0 2px 6px rgba(0,0,0,.45), 0 24px 48px rgba(0,0,0,.45);
  /* ============================================================
     DESIGN TOKENS — MODULES NOVA (refonte visuelle, 2026-10-07)
     ============================================================
     CORRECTIF (2026-10-07, 2ᵉ passe) : cette famille --nova-* a été
     pensée à l'origine comme SÉPARÉE de la palette générale du site
     (--bg/-2/-3 restaient une échelle indigo opaque) — c'est précisément
     ce qui donnait l'impression que "seules les cartes Nova appartiennent
     à un autre design" (retour utilisateur explicite de cette 2ᵉ passe).
     --bg-2/-3/-4 ci-dessus REPRENNENT maintenant exactement les mêmes
     valeurs que --nova-surface/-hover (blanc translucide), donc les deux
     familles sont désormais volontairement ALIGNÉES — --nova-* reste une
     famille à part uniquement pour porter les 5 couleurs de module
     (ci-dessous), jamais plus pour la surface/le fond elle-même. */
  --nova-bg:#211A50; --nova-bg-deep:#19143E;
  --nova-surface:rgba(255,255,255,.075); --nova-surface-secondary:rgba(255,255,255,.05); --nova-surface-hover:rgba(255,255,255,.105);
  --nova-border:rgba(255,255,255,.08);
  --nova-text:#ffffff; --nova-text-secondary:rgba(255,255,255,.68); --nova-text-muted:rgba(255,255,255,.48);
  /* Une couleur par module (§4 de la demande) — mapping fixe partagé
     avec NOVA_FEATURES (ui-primitives.js), jamais réassigné au hasard
     si un module supplémentaire apparaît un jour (voir son commentaire). */
  --nova-ai:#4D7CFF; --nova-bot:#805DFF; --nova-review:#E653A5; --nova-news:#6D94FF; --nova-event:#45C8FF;
  /* Verre du header/de la nav basse (§12/13, 2ᵉ passe) : même teinte de
     base que --nova-bg-deep (rgb(25,20,62)) pour que header et bottom
     nav "semblent appartenir au même système visuel" — seule l'opacité
     diffère (header plus opaque : il défile en permanence sur du
     contenu, la nav flotte surtout sur du vide en bas d'écran). */
  --glass-header:rgba(25,20,62,.70); --glass-navigation:rgba(25,20,62,.55);
  --glass-icon:rgba(255,255,255,.09);
  /* CORRECTIF (2026-10-07, 2ᵉ passe) : .btn-a passe au blanc en thème
     sombre (voir plus bas) mais gardait ce halo ORANGE autour d'un
     bouton blanc — fuite orange décorative exactement du type visé par
     §3 ("CTA décoratifs"). Repris en bleu électrique (--accent). */
  --shadow-accent:0 8px 24px rgba(77,124,255,.3);
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
/* overflow-x:hidden ajouté ici le 2026-09-23 (bug réel trouvé en testant
   le temple sur une vraie largeur étroite, pas simulée) : body a déjà
   cette règle, mais le débordement symétrique de quelques px du temple
   (width:min(98vw,...) — vw inclut parfois la largeur de la scrollbar
   verticale selon le navigateur, un écart de quelques px avec
   document.documentElement.clientWidth est un artefact CSS connu, pas
   un vrai bug de layout) restait scrollable horizontalement au niveau
   du document malgré body{overflow-x:hidden}. */
html{-webkit-text-size-adjust:100%;overflow-x:hidden}
html{background:var(--bg)}
/* CORRECTIF (2026-10-07, §7 de la refonte Nova : "utiliser éventuellement
   un fond très subtil, un dégradé") : fond plat var(--bg) remplacé par
   un dégradé vertical TRÈS subtil entre --nova-bg (haut, légèrement plus
   clair) et --nova-bg-deep (bas) — uniquement en thème sombre, le thème
   clair reste un blanc plat comme avant (non concerné par cette
   demande). */
[data-theme="dark"] html{background:linear-gradient(180deg,var(--nova-bg) 0%,var(--nova-bg-deep) 100%) fixed}
[data-theme="xxx"] body::before{
  content:'';position:fixed;inset:0;pointer-events:none;z-index:0;
  background:
    radial-gradient(60% 40% at 50% -8%, rgba(120,140,255,.07), transparent 70%),
    radial-gradient(50% 35% at 90% 12%, rgba(0,200,140,.05), transparent 70%),
    radial-gradient(70% 50% at 8% 60%, rgba(140,120,255,.035), transparent 70%);
}
body{
  /* CORRECTIF DESIGN (2026-09-17) : transparent plutôt que var(--bg) — le
     fond réel de la page est désormais .aurora-bg (voir plus bas), un
     calque fixe positionné SOUS le contenu. Chaque surface qui doit
     rester pleinement opaque (cartes, feuilles, nav) porte déjà sa propre
     couleur explicite (--bg/--bg-2/--bg-3) : rien ne dépendait du fond
     uni de body pour son opacité, vérifié avant ce changement. */
  background:transparent;color:var(--ink);max-width:100vw;
  forced-color-adjust:none;font-family:var(--font);font-size:16px;line-height:1.5;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;letter-spacing:-.011em;
  transition:background .3s var(--spring),color .3s var(--spring);
}
/* Fond animé (§2 du PRD : "un fond qui change de couleur, de belles
   transitions") — vérifié en direct sur www.novabourse.site, en clair ET
   en sombre, avant d'être intégré ici : les mêmes teintes fonctionnent
   dans les deux thèmes sans variante séparée. Fixe (jamais affecté par le
   scroll), z-index:0 : tout le contenu réel (nav, cartes, feuilles) reste
   au-dessus car aucun n'a de z-index négatif. Flou et opacité volontai-
   rement faibles : visible dans les espaces vides entre les éléments,
   jamais au point de nuire à la lisibilité d'une donnée financière —
   c'est un fond d'ambiance, pas un habillage qui prend le pas sur le
   contenu. prefers-reduced-motion coupe l'animation, jamais l'effet
   entier (le dégradé reste visible, simplement figé). */
.aurora-bg{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none}
/* CORRECTIF (2026-10-02, retour utilisateur : "un vrai fond complètement
   et seulement noire/blanche sur l'ensemble du site, sans rien
   d'autre") : ces 3 taches orange/cyan (::before/::after/.aurora-3,
   juste en dessous) dérivent sur TOUTE page, au-dessus du noir/blanc
   pur de html/body — c'est exactement la "couleur en plus" qui
   empêchait un fond vraiment uni. Désactivées (display:none, pas
   supprimées : réversible d'un mot si redemandé un jour) plutôt que
   recolorées, car aucune teinte de dégradé n'aurait satisfait "sans
   rien d'autre" — seule leur disparition le fait. */
.aurora-bg::before,.aurora-bg::after,.aurora-bg .aurora-3{display:none}
/* CORRECTIF (bug visuel réel et grave, signalé par l'utilisateur — la
   barre de navigation du bas "décrochait" de sa position fixe pendant
   le défilement) : la rotation de teinte était animée sur .aurora-bg
   lui-même, un calque position:fixed;inset:0 couvrant TOUT le viewport
   — animer `filter` sur un calque fixe aussi grand crée une charge de
   composition énorme, connue pour perturber le rendu d'AUTRES éléments
   position:fixed sur la même page (notamment combinés à backdrop-filter,
   comme le dock). Déplacée sur chaque tache (déjà floutée, bien plus
   petite) : même effet visuel de "fond qui change de couleur", sans
   jamais animer `filter` sur l'intégralité du viewport. */
/* Flou réduit 110px -> 65px (2026-09-24, retour utilisateur : "le site
   est super lent et manque énormément de fluidité") : ces 3 taches
   sont PRÉSENTES SUR TOUTE PAGE (le fond de l'app entière) et animent
   déjà transform en continu — un blur() aussi large, recalculé à
   chaque déplacement de la tache (translate/scale, pas juste posé une
   fois), est probablement le plus gros poste de coût GPU permanent de
   tout le site. Les taches elles-mêmes restent énormes (55/50/38vw,
   inchangé) : à cette échelle, 65px de flou garde le même effet "fond
   d'ambiance diffus", vérifié en direct clair et sombre — jamais des
   bords nets visibles. */
.aurora-bg::before,.aurora-bg::after,.aurora-bg .aurora-3{content:"";position:absolute;border-radius:50%;
  filter:blur(65px);opacity:.32;
  animation:auroraDrift 26s ease-in-out infinite, auroraHue 60s linear infinite;
  will-change:transform}
/* CORRECTIF (2026-10-13, audit V7 §6 : "des effets lumineux jaunes") :
   ce fond animé plein écran (2 taches floutées + une 3e en accent cyan)
   était resté orange par défaut depuis le pivot du 2026-09-28, jamais
   mis à jour quand --accent est devenu violet (voir note de palette tout
   en haut du fichier) — très probablement la source la plus VISIBLE de
   "jaune partout" (grande tache floutée animée derrière le contenu sur
   la plupart des pages), plus que les petits composants déjà corrigés.
   aurora-1/1b/2/2b suivent maintenant --violet-500/--violet-light/
   --violet-700/--violet-500 — mêmes rôles structurels, cyan (accent
   secondaire, 3e touche) inchangé. Toujours personnalisable par section
   via body[data-bg=...] (paintNav()/render()). */
.aurora-bg::before{width:55vw;height:55vw;top:-18vw;left:-12vw;
  background:radial-gradient(circle,var(--aurora-1,#805dff) 0%,var(--aurora-1b,#9478ff) 45%,transparent 72%)}
.aurora-bg::after{width:50vw;height:50vw;bottom:-22vw;right:-14vw;animation-delay:-13s;
  background:radial-gradient(circle,var(--aurora-2,#6b46f0) 0%,var(--aurora-2b,#805dff) 40%,transparent 72%)}
.aurora-bg .aurora-3{width:38vw;height:38vw;top:38vh;left:32vw;animation-delay:-8s;
  animation-duration:32s;opacity:.24;
  background:radial-gradient(circle,var(--aurora-3,#00a8cc) 0%,var(--aurora-3b,#00d4ff) 45%,transparent 72%)}
body[data-bg="portfolio"]{--aurora-1:#00a8cc;--aurora-1b:#00d4ff;--aurora-2:#805dff;--aurora-2b:#9478ff;
  --aurora-3:#00a8cc;--aurora-3b:#00d4ff}
body[data-bg="account"]{--aurora-1:#8d95a4;--aurora-1b:#aeb4c0;--aurora-2:#646c7c;--aurora-2b:#9aa1b1;
  --aurora-3:#8d95a4;--aurora-3b:#aeb4c0}
@keyframes auroraDrift{
  0%,100%{transform:translate(0,0) scale(1)}
  33%{transform:translate(5vw,3vw) scale(1.06)}
  66%{transform:translate(-3vw,5vw) scale(.97)}
}
/* CORRECTIF (2026-09-21, palette officielle) : une rotation complète sur
   360° promenait chaque tache à travers TOUTES les teintes (le bleu
   devenait vert, puis jaune, puis rouge...) — "un fond qui change de
   couleur" au sens du PRD, mais qui abandonnait l'identité orange/bleu/
   vert au fil du cycle. Oscillation resserrée (±18°) : le fond continue
   de "vivre" sans jamais quitter la famille de teinte de sa tache. */
@keyframes auroraHue{
  0%,100%{filter:blur(65px) hue-rotate(-18deg)}
  50%{filter:blur(65px) hue-rotate(18deg)}
}
@media (prefers-reduced-motion:reduce){
  .aurora-bg::before,.aurora-bg::after,.aurora-bg .aurora-3{animation:none}
}
button,input,select{font:inherit;color:inherit}
button{border:none;background:none;cursor:pointer;transition:all .2s var(--spring)}
button:active{transform:scale(.97)}
.mono,.tabular-nums{font-variant-numeric:tabular-nums;letter-spacing:-.02em;
  font-feature-settings:'tnum' 1,'cv01' 1}
.mono{font-family:var(--mono)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* CONTRAT (motion design premium, phase 1, 2026-10-15) : la règle
   générale ci-dessous ne suffit PAS à elle seule pour toute NOUVELLE
   animation ajoutée à partir de maintenant — elle réduit la durée à
   quasi-zéro mais ne touche ni animation-delay ni fill-mode:backwards/
   forwards. Bug réel déjà rencontré avec cette seule règle : .page-in>*
   (fill-mode:backwards + delai) restait bloqué à opacity:0 indéfiniment
   sous réduction de mouvement (voir plus bas, correctif .page-in,
   .page-in>*{animation:none}). Toute animation d'entrée utilisant
   opacity:0 + backwards/forwards DOIT donc fournir SA PROPRE dérogation
   explicite (ex. .ma-classe{animation:none}), jamais compter sur cette
   seule règle générale. Une transition/animation purement décorative
   (couleur, ombre, fond) n'a besoin d'aucune dérogation supplémentaire,
   la règle générale suffit déjà pour elle. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}
}
.cap-i,.trio-i,.onb-i,.gate-i,.quick-ic,.empty-i,.ai-d,.flow-n,.onb-n,
.logo-m,.icon-btn,.star,.info,.x,.av,.sq-ic{
  display:flex;
  align-items:center;
  justify-content:center;
  line-height:0;
  flex-shrink:0;
  overflow:hidden;
  padding:0;
  text-align:center
}
.cap-i>svg,.trio-i>svg,.onb-i>svg,.gate-i>svg,.quick-ic>svg,.empty-i>svg,
.logo-m>svg,.icon-btn>svg,.star>svg,.x>svg,.sq-ic>svg,.ai-d>svg{
  display:block;
  margin:auto;
  flex:0 0 auto
}
.flow-n,.onb-n,.av,.ai-d,.info{line-height:1}
/* --top-fade (2026-09-23, "que le dessus s'efface progressivement en
   descendant dans la page") : custom property globale (posée sur
   documentElement par un seul écouteur de scroll, voir wireHeaderFade()
   en JS — pas par page, une fois pour toute l'app) qui va de 1 (haut de
   page) à ~.15 après ~160px de défilement. Seul le FOND du bandeau
   s'estompe — jamais le logo/la nav/les boutons, qui restent à opacité
   pleine : un header devenu illisible en scrollant serait un vrai
   défaut d'accessibilité, pas juste un détail esthétique. Le flou
   (backdrop-filter) reste constant pour garder le texte lisible même
   quand le fond est presque transparent. */
.top{
  position:sticky;top:0;z-index:50;background:rgba(255,255,255,calc(.72 * var(--top-fade,1)));
  /* Flou réduit 20px -> 14px (2026-09-24, retour utilisateur : "le site
     est super lent et manque énormément de fluidité") : le coût GPU
     d'un backdrop-filter croît avec le rayon de flou, et ce header
     sticky est présent sur TOUTE page — il recalcule ce flou à chaque
     frame de défilement, sur tout le contenu qui passe dessous.
     14px garde un effet "verre dépoli" net à l'œil (vérifié en direct),
     pour une fraction du coût. */
  backdrop-filter:saturate(180%) blur(14px);-webkit-backdrop-filter:saturate(180%) blur(14px);
  border-bottom:1px solid var(--line-2);
  padding-top:env(safe-area-inset-top);
  transition:background-color .1s linear;
  animation:topSlideIn .6s cubic-bezier(.22,1,.36,1) both;
}
/* Entrée au chargement (2026-09-23, retour utilisateur : "de la
   modernité et de l'animation, vraiment beaucoup" sur la nav) — courte
   et discrète (14px, pas un grand slide) pour ne pas retarder la
   lecture du contenu ; sticky+transform sont compatibles (le sticky ne
   régit que la position de scroll, pas le rendu du transform). */
@keyframes topSlideIn{ from{transform:translateY(-14px);opacity:0} to{transform:translateY(0);opacity:1} }
/* CORRECTIF (2026-10-07, 2ᵉ passe, §12 : "le header doit ressembler
   davantage à la navigation flottante du bas [...] même système visuel")
   : rgba(8,9,12,...) était un noir neutre sans lien avec le reste de la
   palette — repris sur la même teinte indigo que .dock (--glass-header,
   rgb(25,20,62)) ; calc() ne peut pas décomposer une rgba() stockée dans
   une variable, d'où les canaux R/G/B recopiés en dur ici (valeur de
   base identique à --glass-header, voir son commentaire). --top-fade
   (opacité dynamique au scroll, pilotée en JS) inchangée. */
[data-theme="dark"] .top{background:rgba(25,20,62,calc(.70 * var(--top-fade,1)));border-bottom-color:rgba(255,255,255,.10)}
.top-in{max-width:1120px;margin:0 auto;padding:10px 20px;display:flex;align-items:center;
  gap:12px;min-width:0}
.logo{display:flex;align-items:center;gap:9px;font-weight:700;font-size:17px;
  letter-spacing:-.03em;flex:0 0 auto;white-space:nowrap}
/* Retour utilisateur (2026-09-23) : "le logo sans la case blanche" —
   auparavant un badge plein (background:var(--ink), icône en
   color:var(--bg), donc une case CLAIRE en thème sombre) ; l'icône
   utilise déjà fill="currentColor" sur ses <path> (voir le HTML du
   header), donc color:var(--ink) directement sur ce conteneur suffit à
   la faire correspondre au texte "NovaTitre" juste à côté, sans fond
   ni coin arrondi. */
.logo-m{position:relative;width:40px;height:40px;display:flex;align-items:center;justify-content:center;color:var(--ink);
  transition:transform .35s cubic-bezier(.22,1,.36,1)}
.logo-m svg{width:40px;height:40px}
.logo:hover .logo-m{transform:rotate(-10deg) scale(1.1)}
.logo:active .logo-m{transform:rotate(-4deg) scale(.94)}
/* CORRECTIF PERFORMANCE (2026-09-24, retour utilisateur : "le site est
   super lent et manque énormément de fluidité") : cette respiration
   animait box-shadow directement sur .logo-m — une propriété qui force
   un repaint à CHAQUE frame (jamais composée par le GPU), en boucle
   infinie, dans le header STICKY présent sur TOUTE page de l'app.
   Remplacé par un ::after dont le box-shadow est fixe (calculé une
   seule fois) et dont seule l'opacity est animée : opacity est une des
   deux propriétés (avec transform) que le navigateur peut animer sur
   son propre calque GPU sans jamais repeindre — même rendu visuel,
   coût de rendu proche de zéro au lieu d'un repaint continu partout
   dans l'app. */
.logo-m::after{content:"";position:absolute;inset:-5px;border-radius:50%;
  box-shadow:0 0 0 5px rgba(236,72,153,.16);opacity:0;pointer-events:none;
  animation:logoGlow 4.5s ease-in-out infinite}
@keyframes logoGlow{ 0%,100%{opacity:0} 50%{opacity:1} }
@media (prefers-reduced-motion:reduce){ .logo-m::after{animation:none} }
@media (prefers-reduced-motion:reduce){ .top{animation:none} }
/* Nav desktop — pilule glissante (2026-09-23, même technique FLIP que
   .dock-pill plus bas dans ce fichier, voir positionTopNavPill() en JS)
   : jusqu'ici les liens n'avaient qu'un hover/actif statique (couleur +
   fond instantanés, sans transition). position:relative sur .top-nav
   sert de repère pour la pilule absolue ; les liens passent en z-index:1
   pour rester lisibles AU-DESSUS d'elle. */
.top-nav{display:flex;gap:2px;margin-left:var(--sp-3);position:relative}
.top-nav-pill{position:absolute;top:0;bottom:0;left:0;width:0;border-radius:100px;
  background:var(--bg-2);z-index:0;pointer-events:none;
  transition:transform .45s var(--spring),width .45s var(--spring)}
.top-nav-pill.no-anim{transition:none}
.top-nav a{position:relative;z-index:1;padding:8px 14px;border-radius:100px;font-size:14.5px;font-weight:500;
  color:var(--ink-3);transition:color .2s ease,transform .2s ease}
.top-nav a:hover{color:var(--ink);transform:translateY(-1px)}
.top-nav a[aria-current]{color:var(--ink);font-weight:600}
@media (prefers-reduced-motion:reduce){ .top-nav-pill{transition:none} }
.top-act{margin-left:auto;display:flex;align-items:center;gap:8px;min-width:0}
#srcBadge{display:flex;align-items:center;gap:6px;min-width:0;overflow:hidden}
.icon-btn{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--ink-3);
  transition:background .2s ease,color .2s ease,transform .2s cubic-bezier(.22,1,.36,1)}
.icon-btn:hover{background:var(--bg-2);color:var(--ink);transform:translateY(-1px) scale(1.08)}
.icon-btn:active{transform:scale(.9)}
.icon-btn svg{width:19px;height:19px}
.dock{display:none}
@media (max-width:900px){
  .top-nav{display:none}
  /* REFONTE (2026-09-23, référence Revolut) — historique complet des 4
     passes précédentes (géométrie générale, correction de surcorrection,
     verre dépoli) conservé dans l'historique git ; résumé utile ici :
     fond translucide rgba(10,14,35,.50) + backdrop-filter, bordure
     rgba(255,255,255,.10), dimensions validées par l'utilisateur
     ("la barre actuelle est bonne en dimensions, NE CHANGE PLUS sa
     taille") — hauteur/police/icônes de la PASSE 3 (font 11px, icône
     22px, dock padding 6px) restent donc la référence, INCHANGÉES ici.

     PASSE 5 (même jour, fusion Marchés/Explorer, 6→5 onglets) — deux
     changements structurels demandés explicitement, qui ANNULENT le
     comportement "capsule dynamique" des passes 2-3 :
     1) Explorer supprimée de la nav (fusionnée dans Marchés) : 6
        destinations → 5.
     2) Comportement de largeur inversé : "chaque position occupe 20%...
        aucun changement de largeur de l'onglet actif... aucun
        flex-grow dynamique" — flex:0 0 auto + padding élargi au clic
        (qui résolvait le débordement de "Portefeuille" à 6 colonnes
        égales) est donc retiré. flex:0 0 20% (colonnes strictement
        égales et FIXES, jamais recalculées) remplace flex:0 0 auto ;
        .dock button[aria-current] n'a plus de padding différent de ses
        voisins.

     Avec UNE destination de moins, "Portefeuille" retrouve assez de
     place dans une colonne à 20% fixe SANS avoir besoin de grandir —
     revérifié au canvas (mesure indépendante de la taille de fenêtre)
     et par simulation de largeur de conteneur à 320/340/350/360/375/
     390/393/414/430/768px (voir MOTION_SYSTEM.md) : la police de base
     (11px) suffit dès 386px environ, mais tombe juste en dessous du
     nécessaire à 360-375px avec seulement 5 colonnes fixes au lieu de
     6 colonnes qui s'auto-dimensionnaient avant — d'où les 2 paliers
     étroits ci-dessous (nouveaux, remplacent l'unique palier ≤350px de
     la passe 3), chacun avec sa propre marge de sécurité mesurée, pas
     une simple règle de trois.

     RÉTABLIE (2026-09-28, retour utilisateur : "remets l'ancienne barre
     de navigation") — la variante pill-shaped NovaTitre (dégradé bleu
     nuit, bordure cyan, 28px, icônes 28px) est retirée ; cette géométrie
     capsule verre dépoli reprend son rôle. Reste du rebrand (palette
     orange/cyan, onboarding, Nova Explain, fiche action) inchangé. */
  .dock{
    display:flex;align-items:stretch;
    position:fixed;left:50%;transform:translateX(-50%);
    bottom:calc(12px + env(safe-area-inset-bottom));z-index:60;
    width:calc(100% - 20px);max-width:440px;
    /* CORRECTIF (2026-10-07, refonte visuelle §8 "harmoniser la nav
       avec le nouvel univers") : fond rgba(10,14,35,...) (quasi-noir)
       remplacé par la teinte indigo --nova-bg-deep (rgb(25,20,62)), pour
       que la barre flotte visiblement dans le même univers bleu nuit
       que le reste de la page plutôt que de retomber sur du noir — même
       transparence/flou/dimensions, INCHANGÉS (voir note ci-dessus,
       "NE CHANGE PLUS sa taille"). */
    background:rgba(25,20,62,.55);
    backdrop-filter:blur(13px) saturate(120%);
    -webkit-backdrop-filter:blur(13px) saturate(120%);
    border:1px solid rgba(255,255,255,.10);border-radius:9999px;
    padding:6px 6px;
    box-shadow:0 8px 22px rgba(2,5,14,.45), 0 2px 6px rgba(10,20,45,.14)
  }
  /* 5 colonnes strictement égales et fixes (flex:0 0 20%, jamais
     recalculées selon l'onglet actif) — icône empilée sur libellé,
     c'est le PLUS LARGE des deux qui doit tenir dans 20% de la barre. */
  .dock button{
    position:relative;z-index:1;
    display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
    flex:0 0 20%;min-width:0;min-height:40px;padding:2px 3px;border-radius:9999px;
    color:rgba(255,255,255,.68);
    font-size:11px;font-weight:600;letter-spacing:-.01em;
    transition:color .25s ease-out
  }
  /* Priorité absolue : plus jamais de libellé coupé/en 2 lignes. nowrap
     + overflow visible (jamais overflow:hidden, qui masquerait un mot
     au lieu de le montrer en entier) — garanti par les colonnes à 20%
     dimensionnées pour "Portefeuille" (voir mesures ci-dessus et
     paliers responsive plus bas). */
  .dock button span{display:block;white-space:nowrap;overflow:visible}
  .dock button svg{width:22px;height:22px;color:inherit;
    transition:transform .25s cubic-bezier(.22,1,.36,1),opacity .25s ease-out}
  .dock button[aria-current]{color:#fff}
  .dock button[aria-current] svg{transform:scale(1.05);animation:dockIconPop .45s cubic-bezier(.22,1,.36,1)}
  /* Rebond quand une icône DEVIENT active (2026-09-23, "vraiment
     beaucoup d'animation") : une animation CSS démarre quand l'élément
     se met à correspondre à une règle qui en porte une — donc seulement
     au vrai changement d'onglet, jamais à un simple re-render, grâce à
     syncActiveAttr() en JS qui ne touche plus aria-current que lorsqu'il
     change réellement (voir sa note). Sans fill-mode: retombe proprement
     sur le transform:scale(1.05) statique juste au-dessus une fois finie,
     pas de saut visible. */
  @keyframes dockIconPop{
    0%{transform:scale(1)}
    45%{transform:scale(1.24)}
    100%{transform:scale(1.05)}
  }
  .dock button:focus-visible{outline:2px solid rgba(255,255,255,.85);outline-offset:2px;border-radius:9999px}
  /* 2 paliers étroits (voir note plus haut) : seule la police/icône/le
     padding du dock changent, jamais les colonnes à 20% ni le
     comportement "fixe" de la barre. */
  @media (max-width:385px){
    .dock{width:calc(100% - 16px);padding:5px 5px}
    .dock button{font-size:10px;gap:3px}
    .dock button svg{width:20px;height:20px}
  }
  @media (max-width:340px){
    .dock{width:calc(100% - 14px);padding:4px 4px}
    .dock button{font-size:9px;gap:3px}
    .dock button svg{width:18px;height:18px}
  }
  /* Pilule active glissante (technique inchangée depuis le 21/09, voir
     positionDockPill() en JS) : un seul élément partagé, jamais recréé,
     repositionné en transform/width à chaque changement d'onglet — la
     MÊME pilule glisse d'un onglet à l'autre, ne disparaît/réapparaît
     jamais. Sa largeur vaut désormais TOUJOURS 20% de la barre (les 5
     boutons ont maintenant tous la même largeur fixe) — positionDockPill()
     n'a pas eu besoin de changer, il lit toujours offsetLeft/offsetWidth
     du bouton actif, qui se trouve juste être constant maintenant.
     top/bottom:3px la garde clairement PLUS PETITE que la barre
     extérieure (respiration haut/bas, comme sur la référence). */
  .dock-pill{position:absolute;top:3px;bottom:3px;left:0;border-radius:9999px;
    background:rgba(255,255,255,.12);
    z-index:0;pointer-events:none;
    transition:transform .3s cubic-bezier(.22,1,.36,1), width .3s cubic-bezier(.22,1,.36,1);
    will-change:transform,width}
  .dock-pill.no-anim{transition:none}
}
@media (prefers-reduced-motion:reduce){
  .dock-pill{transition:none}
  .dock button svg{transition:none}
  .dock button{transition:none}
  .dock button[aria-current] svg{animation:none}
}
.wrap{max-width:1120px;margin:0 auto;padding:var(--sp-4) 22px calc(var(--sp-6) + 40px)}
.section{margin-top:96px}
.section:first-child{margin-top:0}
.section-h{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-2);margin-bottom:var(--sp-3)}
/* CORRECTIF DESIGN (2026-09-22, inspiration Revolut) : consulté
   revolut.com en direct — leurs plus gros titres ("Banking & Beyond")
   sont nettement plus massifs à taille égale que notre 700 (Inter en
   charge déjà 800/900, jamais utilisé). Réservé au SEUL .display (les
   accroches les plus visibles : accueil, écran de connexion) — .title,
   utilisé partout comme H1 de page, reste à 700 pour garder une vraie
   hiérarchie entre les deux. */
.display{font-size:var(--t-display);font-weight:800;letter-spacing:-.04em;line-height:var(--lh-tight)}
.title{font-size:var(--t-title);font-weight:700;letter-spacing:-.032em;line-height:var(--lh-title)}
.h2{font-size:var(--t-h2);font-weight:650;letter-spacing:-.024em}
.h3{font-size:var(--t-h3);font-weight:600;letter-spacing:-.018em}
.lead{font-size:17px;color:var(--ink-3);line-height:var(--lh-body);letter-spacing:-.01em}
.small{font-size:var(--t-small);color:var(--ink-3);line-height:var(--lh-body)}
.tiny{font-size:var(--t-micro);color:var(--ink-4);line-height:1.5}
.eyebrow{font-size:var(--t-label);font-weight:600;letter-spacing:.02em;color:var(--accent);text-transform:none}
.center{text-align:center}
.measure{max-width:34ch}
.measure-l{max-width:58ch}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  min-height:var(--tap);padding:13px 26px;border-radius:var(--r-pill);
  font-size:var(--t-body);font-weight:600;letter-spacing:-.012em;white-space:nowrap;
  transition:background var(--fast) ease,color var(--fast) ease,
    box-shadow var(--fast) ease,transform var(--fast) ease,opacity var(--fast) ease}
.btn:active{transform:scale(.985)}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.45;pointer-events:none}
.btn svg{width:17px;height:17px}
/* Dégradé rose animé (2026-09-22, spec exacte fournie ; recoloré
   2026-09-23 orange -> rose, voir note de palette tout en haut) :
   reproduit tel quel sur le bouton primaire — celui qui doit porter la
   personnalité de la marque, comme demandé ("Primary = Rose
   toujours"). background-size:200% 200% est nécessaire pour que
   l'animation de background-position soit réellement visible (un
   dégradé qui correspond exactement aux dimensions du bouton n'a nulle
   part où "se déplacer"). Coupée sous prefers-reduced-motion comme
   toutes les animations infinies de ce fichier (la leçon du fond
   aurora, plus haut, documente pourquoi une durée quasi nulle plutôt
   qu'un arrêt franc a déjà causé un vrai bug cette session). */
.btn-a{background:linear-gradient(135deg,var(--accent) 0%,var(--accent-hover) 100%);
  background-size:200% 200%;animation:gradientShift 4s ease-in-out infinite;
  color:var(--accent-ink);box-shadow:var(--shadow-accent)}
@keyframes gradientShift{
  0%{background-position:0% 50%}
  50%{background-position:100% 50%}
  100%{background-position:0% 50%}
}
@media (prefers-reduced-motion:reduce){ .btn-a{animation:none} }
.btn-a:hover{background:var(--accent-hover)}
.btn-a:active{background:var(--accent-press)}
.btn-s{background:var(--bg-2);color:var(--ink)}
.btn-s:hover{background:var(--bg-3)}
/* CORRECTIF (2026-10-02, retour utilisateur : "le fond soit entierement
   noire et sans autre couleur [...] tous les boutons soit tous blanc et
   tous les textes soit blanc", confirmé pour TOUS les boutons y compris
   les CTA orange) : en thème sombre, tout bouton plein devient blanc/
   texte noir, plus aucune couleur d'accent (orange/cyan) — survol/appui
   via opacity uniquement, jamais une autre teinte. */
[data-theme="dark"] .btn-a{background:#fff;color:#000;animation:none}
[data-theme="dark"] .btn-a:hover,[data-theme="dark"] .btn-a:active{background:#fff;opacity:.85}
[data-theme="dark"] .btn-s{background:#fff;color:#000}
[data-theme="dark"] .btn-s:hover{background:#fff;opacity:.85}
/* Bouton secondaire cyan (2026-09-22, composant demandé par le brief ;
   recoloré 2026-09-28 pour le pivot NovaTitre : violet -> cyan #00D4FF,
   voir note de palette tout en haut) — jamais utilisé comme accent
   général : réservé à une action secondaire précise qui mérite de se
   distinguer sans rivaliser avec l'orange primaire. Appliqué à "Vendre"
   sur la fiche action (voir PAGES.stock) : une vraie seconde action, pas
   juste "moins importante qu'Acheter" — le cyan porte ce sens mieux
   qu'un simple gris. color:var(--bg-dark-ink) plutôt que blanc : #00D4FF
   est trop clair pour porter un texte blanc lisible (conforme à la spec
   NovaTitre : texte #0A0E27 sur fond cyan). */
.btn-blue{background:var(--blue-accent);color:var(--bg-dark-ink)}
.btn-blue:hover{filter:brightness(1.1)}
.btn-blue-outline{background:transparent;color:var(--blue-accent);
  box-shadow:inset 0 0 0 1.5px var(--blue-accent)}
.btn-blue-outline:hover{background:var(--blue-accent-soft)}
.btn-g,.btn-ghost{background:transparent;color:var(--ink-2);
  box-shadow:inset 0 0 0 1px var(--line)}
.btn-g:hover,.btn-ghost:hover{background:var(--bg-2);color:var(--ink)}
.btn-p{background:var(--ink);color:var(--bg)}
.btn-p:hover{opacity:.88}
/* CORRECTIF (2026-10-02) : voir la note complète plus haut (.btn-a/
   .btn-s) — même principe pour les boutons secondaires/outline/ghost. */
[data-theme="dark"] .btn-blue{background:#fff;color:#000}
[data-theme="dark"] .btn-blue:hover{background:#fff;opacity:.85;filter:none}
[data-theme="dark"] .btn-blue-outline{color:#fff;box-shadow:inset 0 0 0 1.5px #fff}
[data-theme="dark"] .btn-blue-outline:hover{background:rgba(255,255,255,.12)}
[data-theme="dark"] .btn-g,[data-theme="dark"] .btn-ghost{color:#fff;box-shadow:inset 0 0 0 1px #fff}
[data-theme="dark"] .btn-g:hover,[data-theme="dark"] .btn-ghost:hover{background:rgba(255,255,255,.12);color:#fff}
.btn-sm{min-height:38px;padding:9px 18px;font-size:var(--t-small)}
.btns{display:flex;gap:10px;flex-wrap:wrap}
/* CORRECTIF DESIGN (2026-09-17) : vérifié en direct sur www.novabourse.site
   avant d'être intégré — depuis l'ajout du fond animé (.aurora-bg), une
   carte plate en background:var(--bg-2) (un gris très proche du fond
   clair) devenait presque indissociable de l'arrière-plan, surtout dans
   les zones les plus claires du dégradé. Traitement "verre dépoli" :
   fond semi-transparent + flou, comme le bandeau de navigation (.top) le
   fait déjà avec succès — une carte laisse transparaître une pointe de
   couleur du fond plutôt que de s'y noyer, tout en restant nettement plus
   contrastée que lui. background-color (jamais juste `background`) pour
   ne pas écraser un dégradé éventuel posé par une règle plus spécifique
   ailleurs (ex. .buy-card).
   Flou réduit 24px -> 16px (2026-09-24, retour utilisateur : "le site
   est super lent et manque énormément de fluidité") : .card est le
   composant le plus répété de tout le fichier — souvent plusieurs par
   page — un flou aussi large multiplié par carte est un vrai coût GPU
   cumulé. 16px garde le même effet "verre dépoli" à l'œil (vérifié en
   direct), pour une fraction du coût par carte. */
.card{background-color:rgba(255,255,255,.6);backdrop-filter:blur(16px) saturate(160%);
  -webkit-backdrop-filter:blur(16px) saturate(160%);border:1px solid rgba(255,255,255,.8);
  border-radius:var(--r-l);padding:var(--card-pad);
  box-shadow:0 1px 2px rgba(13,17,23,.05),0 16px 40px rgba(236,72,153,.10)}
/* CORRECTIF (2026-10-07, 2ᵉ passe, retour utilisateur : "seules les
   cartes Nova sont plus claires [...] ça donne l'impression qu'elles
   appartiennent à un autre design") : rgba(22,26,33,...) était un gris
   neutre quasi noir, sans AUCUN lien avec la teinte indigo du fond — la
   carte la plus utilisée du site (.card, 50+ emplacements : Ma liste,
   Marché, Radar, Comparateur, Portefeuille...) semblait donc "importée"
   d'un autre thème. Repris sur --bg-2/--line (même famille "verre blanc
   translucide sur fond indigo" que les cartes Nova, voir leur grand
   commentaire plus haut) : TOUTE carte du site appartient maintenant à
   la même famille visuelle. */
[data-theme="dark"] .card{background-color:var(--bg-2);border-color:var(--line);
  box-shadow:0 1px 2px rgba(0,0,0,.3),0 16px 40px rgba(0,0,0,.4)}
.card-flat{background:var(--bg-2);border-radius:var(--r-l);padding:var(--card-pad)}
.card-lift{transition:background var(--slow) var(--ease)}
.card-lift:hover{background:var(--bg-3)}
.card-white{background:var(--bg);box-shadow:var(--shadow-2)}
.grid{display:grid;gap:var(--space-4)}
.g-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
.g-3{grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
.g-4{grid-template-columns:repeat(auto-fit,minmax(165px,1fr))}
.field{width:100%;padding:15px 18px;border-radius:var(--r-s);background:var(--bg-2);
  border:1.5px solid transparent;min-height:var(--tap);font-size:var(--t-body);
  transition:border-color var(--fast) ease,background var(--fast) ease}
.field:focus{outline:none;border-color:var(--accent);background:var(--bg)}
.field::placeholder{color:var(--ink-4)}
.field[aria-invalid="true"]{border-color:var(--down)}
.seg{display:inline-flex;position:relative;padding:4px;background:var(--bg-2);border-radius:var(--r-pill);
  gap:3px;max-width:100%;overflow-x:auto;scrollbar-width:none}
.seg::-webkit-scrollbar{display:none}
.seg button{position:relative;z-index:1;padding:9px 18px;border-radius:var(--r-pill);font-size:var(--t-small);
  font-weight:550;color:var(--ink-3);white-space:nowrap;min-height:38px;
  transition:color var(--fast) ease}
.seg button[aria-pressed="true"]{color:var(--ink);font-weight:600}
/* Curseur glissant (motion system, 2026-09-21) : voir syncSegPill() (JS).
   Contrairement au dock (élément d'app-shell jamais recréé), un .seg vit
   dans une page entièrement reconstruite à chaque changement — donc pas
   d'élément "persistant" possible ici. Solution FLIP (First-Last-Invert-
   Play) à la place : syncSegPill() écrit explicitement la position de
   DÉPART en style inline (sans transition), force un reflow synchrone,
   puis écrit la position D'ARRIVÉE (avec transition) — le navigateur
   anime le changement de style qu'il vient d'observer, que l'élément qui
   le porte soit "nouveau" ou non ; ce n'est PAS le même besoin que le
   dock (élément persistant + transition passive), qui ne se serait pas
   appliqué ici. */
.seg-pill{position:absolute;top:4px;bottom:4px;left:0;border-radius:var(--r-pill);
  background:var(--bg);box-shadow:var(--shadow-2);z-index:0;pointer-events:none;
  transition:transform .35s var(--spring),width .35s var(--spring)}
@media (prefers-reduced-motion:reduce){ .seg-pill{transition:none} }
/* CORRECTIF (audit complet 2026-09-17) : .sw avait une première
   définition ici, entièrement écrasée par la seconde plus bas dans ce
   fichier (aucun balisage n'a de sélecteur plus spécifique) — code
   mort supprimé pour ne garder qu'une seule source de vérité. */
.divider{height:1px;background:var(--line-2);margin:var(--space-5) 0;border:none}
.sk{background:linear-gradient(90deg,var(--bg-2) 25%,var(--bg-3) 50%,var(--bg-2) 75%);
  background-size:200% 100%;animation:sk 1.4s linear infinite;border-radius:var(--r-s);display:block}
@keyframes sk{from{background-position:200% 0}to{background-position:-200% 0}}
:focus-visible{outline:2.5px solid var(--accent);outline-offset:3px;border-radius:var(--r-xs)}
.tag{display:inline-flex;align-items:center;gap:5px;padding:5px 11px;border-radius:100px;
  font-size:12.5px;font-weight:600;white-space:nowrap;letter-spacing:-.01em}
.tag-up{background:var(--up-soft);color:var(--up)}
.tag-down{background:var(--down-soft);color:var(--down)}
.tag-warn{background:var(--warn-soft);color:var(--warn)}
.tag-neutral{background:var(--bg-3);color:var(--ink-3)}
/* Liste d'actualités (Nova News, 2026-09-25, retour utilisateur : "pas
   très beau" + "on ne voit pas le texte entièrement") — .row/.row-t
   (white-space:nowrap + ellipsis) sont conçus pour UNE ligne courte (nom
   de société), pas pour un vrai titre d'article, souvent une phrase
   complète : les gros titres se retrouvaient tronqués "…" au lieu de se
   présenter sur 2-3 lignes. Composant dédié plutôt qu'un `.row` détourné
   de son usage — mêmes jetons de design (var(--bg-2)/--line-2/--r-s...)
   que le reste du fichier, pas un style isolé. */
.news-list{display:flex;flex-direction:column}
/* .news-item-img (2026-09-25, retour utilisateur : "possibilité d'ajouter
   des photos") : uniquement quand le flux source en fournit réellement
   une (Yahoo Finance systématiquement, CNBC jamais — voir imageRss()
   côté serveur) — un article sans image garde simplement le layout texte
   seul, jamais une image de remplacement inventée. */
.news-item{display:flex;align-items:flex-start;gap:14px;padding:16px 4px;
  border-bottom:1px solid var(--line-2);color:inherit;text-decoration:none;
  /* Motion design premium, phase 2 (2026-10-15) : survol sans transition
     (changement de fond instantané) et aucun état :active, lacune
     confirmée par audit — background-color uniquement, même principe que
     .row:hover (jamais padding/margin, qui restent du changement de mise
     en page). */
  transition:background-color var(--fast) ease}
.news-item:last-child{border-bottom:none}
.news-item:hover{background:var(--bg-2);border-radius:var(--r-s);padding-left:12px;padding-right:12px;
  margin-left:-12px;margin-right:-12px;border-color:transparent}
.news-item:active{transform:scale(.99)}
.news-item-img{width:96px;height:72px;object-fit:cover;border-radius:var(--r-s);
  flex:0 0 auto;background:var(--bg-3)}
.news-item-body{flex:1;min-width:0}
.news-item-title{display:block;font-size:16px;font-weight:650;letter-spacing:-.015em;
  line-height:1.4;color:var(--ink);white-space:normal;overflow:visible}
.news-item-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:9px}
@media (max-width:520px){
  .news-item-img{width:72px;height:72px}
}
.tag-accent{background:var(--accent-soft);color:var(--accent)}
/* CORRECTIF (audit de cohérence visuelle, 2026-09-21) : .tag-violet,
   jamais utilisé dans le balisage, référençait --violet/--cyan-soft —
   également jamais définis nulle part. Contrairement à .ai-dot ci-dessus,
   rien n'indique à quoi cette variante était destinée ; .tag-accent
   couvre déjà le seul cas "puce colorée avec la couleur de marque".
   Supprimée plutôt qu'inventée. */
.up-t{color:var(--up)} .down-t{color:var(--down)}
.hero{padding:var(--sp-5) 0 var(--sp-4);text-align:center}
.hero .display{margin:0 auto;max-width:16ch}
.hero .lead{margin:var(--sp-3) auto 0;max-width:52ch}
.hero-btns{margin-top:var(--sp-4);display:flex;gap:var(--sp-1);justify-content:center;flex-wrap:wrap}
.hero-note{margin-top:var(--sp-2);font-size:12.5px;color:var(--ink-4)}
.trio{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:var(--sp-2);
  margin-top:var(--sp-5)}
.trio-c{padding:var(--sp-3);border-radius:var(--r-m);background:var(--bg-2);text-align:center}
.trio-i{width:42px;height:42px;border-radius:13px;display:flex;align-items:center;justify-content:center;margin:0 auto var(--sp-1);
  background:var(--bg);box-shadow:var(--shadow-1);color:var(--accent)}
.trio-i svg{width:21px;height:21px}
.trio-c b{display:block;font-size:15px;font-weight:650;letter-spacing:-.02em}
.trio-c span{display:block;font-size:13px;color:var(--ink-3);margin-top:4px;line-height:1.45}
.search-btn{display:flex;align-items:center;gap:11px;width:100%;max-width:520px;margin:var(--sp-3) auto 0;
  padding:15px 20px;border-radius:100px;background:var(--bg-2);color:var(--ink-4);font-size:15.5px;
  text-align:left;border:1px solid transparent;
  transition:background .25s var(--spring),border-color .25s var(--spring),
    box-shadow .25s var(--spring),transform .25s var(--spring)}
.search-btn:hover{background:var(--bg);border-color:var(--line);box-shadow:var(--shadow-2);
  transform:translateY(-2px)}
.search-btn:active{transform:scale(.99)}
.search-btn svg{width:18px;height:18px;flex:0 0 auto}
.search-btn span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.search-btn kbd{font-family:var(--mono);font-size:12px;padding:3px 8px;border-radius:7px;
  background:var(--bg-3);color:var(--ink-4)}
.rows{display:flex;flex-direction:column}
.row{display:flex;align-items:center;gap:var(--sp-2);width:100%;padding:14px 0;text-align:left;
  border-bottom:1px solid var(--line-2);min-height:var(--tap);min-width:0;
  /* Motion design premium, phase 1 (2026-10-15) : .row:hover existait déjà
     (ligne suivante) mais sans transition -- le fond changeait d'un coup.
     background-color UNIQUEMENT (jamais padding/margin, qui restent du
     changement de mise en page) : une liste longue (Portefeuille, Watchlist)
     ne doit jamais animer une propriété de layout sur chaque ligne. */
  transition:background-color var(--fast) ease}
/* CORRECTIF (2026-09-25, trouvé en testant Nova News) : `.row` sert aussi
   bien à un <button> (navigation interne, pas de style de lien par
   défaut) qu'à un <a href> (article de presse externe, Nova News par
   entreprise ET accueil) — aucune règle de ce fichier ne réinitialisait
   color/text-decoration pour ce second cas, laissant le bleu souligné
   par défaut du navigateur transparaître, jamais voulu ici (le reste de
   .row/.row-t est clairement conçu pour un rendu "ligne de liste", pas
   un lien de texte classique). `.row` n'est utilisée nulle part ailleurs
   pour un lien de texte ordinaire dans ce fichier. */
a.row{color:inherit;text-decoration:none}
.row:last-child{border-bottom:none}
.row:hover{background:var(--bg-2);border-radius:var(--r-s);padding-left:10px;padding-right:10px;
  margin-left:-10px;margin-right:-10px;border-color:transparent}
/* .row-static (motion design premium, phase 2, 2026-10-15) : correctif de
   cohérence, pas une nouvelle animation -- certaines lignes (événements
   Nova Event, décisions passées Nova Review) utilisent .row comme simple
   conteneur sans aucun gestionnaire de clic, mais recevaient quand même
   le survol ci-dessus, laissant croire à tort qu'elles étaient
   cliquables. Neutralise uniquement le survol + le curseur, laisse tout
   le reste de .row (mise en page, bordure) intact. */
.row-static{cursor:default}
.row-static:hover{background:none;border-radius:0;padding-left:0;padding-right:0;
  margin-left:0;margin-right:0;border-color:var(--line-2)}
.av{width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;flex:0 0 auto;
  font-size:13px;font-weight:700;letter-spacing:-.03em;color:#fff;position:relative;overflow:hidden}
/* Plus compact dans une ligne de liste (Marchés, Suivi, Radar...) : 40px
   + 14px de gap y coûtait trop de largeur à côté de la sparkline et du
   prix, au point de faire tronquer jusqu'au TICKER lui-même ("AAPL" ->
   "AA…") sur les cartes de la grille — confirmé en test. Le grand format
   (40px) reste réservé à l'en-tête plein-largeur de la fiche action. */
.row .av{width:34px;height:34px;border-radius:10px;font-size:12px}
.row .av img{padding:4px}
/* Logo réel par-dessus les initiales (jamais à la place) : l'image est en
   position absolute, couvre tout le badge si elle charge ; onerror la
   retire du DOM (this.remove()) et les initiales déjà présentes en
   dessous redeviennent visibles sans aucun état JS à gérer — dégradation
   gracieuse garantie même si le logo n'existe pas pour ce ticker/place. */
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  background:#fff;padding:7px;box-sizing:border-box;border-radius:inherit}
.row-main{flex:1;min-width:0}
/* CORRECTIF (même bug que ci-dessus, vérifié en direct) : overflow:hidden
   et text-overflow:ellipsis n'ont AUCUN effet sur un élément inline (un
   <span> l'est par défaut) — cette règle ne pouvait donc jamais tronquer
   quoi que ce soit, même avant la double déclaration supprimée plus bas.
   display:block la fait fonctionner réellement (confirmé : "Vanguard
   Total Stock Market Index Fund ETF Shares" se tronque bien en "…" après
   ce correctif, plus de défilement horizontal). */
.row-t{display:block;font-size:16.5px;font-weight:600;letter-spacing:-.02em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* CORRECTIF (audit complet 2026-09-17) : .row-s/.row-end/.row-spark
   avaient chacun une PREMIÈRE définition ici (mise en ligne, sans
   troncature, min-width:0) qui ne correspondait à AUCUN balisage réel
   (prix + variation empilés verticalement) — la vraie définition,
   celle réellement utilisée, est plus bas dans ce fichier. Supprimées
   pour ne garder qu'une seule source de vérité par classe. */
.row-spark svg{display:block;width:100%;height:100%}
@media (max-width:560px){ .row-spark{display:none} }
.score-hero{display:flex;align-items:center;gap:var(--sp-4);flex-wrap:wrap}
.ring{position:relative;width:150px;height:150px;flex:0 0 auto}
.ring svg{transform:rotate(-90deg)}
.ring circle{fill:none;stroke-width:12;stroke-linecap:round}
.ring .bg{stroke:var(--bg-3)}
.ring .fg{transition:stroke-dashoffset 1.4s var(--spring)}
.ring-c{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center}
.ring-v{font-size:42px;font-weight:800;letter-spacing:-.05em;line-height:1;font-variant-numeric:tabular-nums}
.ring-l{font-size:11.5px;color:var(--ink-4);margin-top:3px}
.bars{margin-top:var(--sp-3)}
.bar{padding:13px 0;border-bottom:1px solid var(--line-2)}
.bar:last-child{border-bottom:none}
.bar-t{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-2)}
.bar-n{font-size:14.5px;font-weight:550;letter-spacing:-.02em}
.bar-v{font-variant-numeric:tabular-nums;font-size:14px;font-weight:600;flex:0 0 auto}
.bar-track{height:6px;border-radius:100px;background:var(--bg-3);margin-top:9px;overflow:hidden}
.bar-fill{display:block;height:100%;width:0;border-radius:100px;transition:width 1.1s var(--spring)}
.bar-h{font-size:12.5px;color:var(--ink-4);margin-top:7px;line-height:1.45}
.bar.off .bar-n,.bar.off .bar-v{color:var(--ink-4)}
.pts{list-style:none}
.pts li{display:flex;gap:10px;padding:8px 0;font-size:14.5px;color:var(--ink-2);line-height:1.5}
.pts li i{flex:0 0 auto;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-style:normal;font-size:13px;font-weight:700;margin-top:1px}
.pts.good li i{background:var(--up-soft);color:var(--up)}
.pts.bad li i{background:var(--down-soft);color:var(--down)}
.pts b{color:var(--ink);font-weight:600}
.plans{display:grid;grid-template-columns:repeat(auto-fit,minmax(258px,1fr));gap:var(--sp-2);
  margin-top:var(--sp-3)}
/* CORRECTIF DESIGN (2026-09-22, "mieux que Revolut") : "transition:all"
   (§61 du brief : à éviter explicitement — anime aussi des propriétés
   qui n'ont jamais changé, coût de calcul inutile) remplacé par les
   deux propriétés réellement concernées. Ajouté un vrai soulèvement au
   survol (offres = la décision la plus commerciale du site) plutôt que
   le simple changement de couleur de bordure d'avant — gardé derrière
   (hover:hover)/(pointer:fine), même raison qu'ailleurs dans ce fichier
   (jamais un survol "collé" après un tap tactile). */
.plan{position:relative;padding:var(--sp-3);border-radius:var(--r-l);border:1.5px solid var(--line);
  display:flex;flex-direction:column;background:var(--bg);
  transition:border-color .3s var(--spring),box-shadow .3s var(--spring),transform .3s var(--spring)}
/* CORRECTIF (2026-10-07, 2ᵉ passe, §2 : "Gratuit/Pro/Elite" citées en
   exemple) : background:var(--bg) posait ces cartes en thème sombre
   exactement dans la même teinte que la page — invisibles sans leur
   bordure, jamais "plus claires" comme le reste du nouveau système.
   --bg-2 (verre blanc translucide, même famille que .card/Nova) à la
   place ; l'offre la plus populaire (.plan.hot) passe à --bg-3, un cran
   plus clair, pour une hiérarchie visuelle nette entre les 3 offres. */
[data-theme="dark"] .plan{background:var(--bg-2)}
[data-theme="dark"] .plan.hot{background:var(--bg-3)}
.plan:hover{border-color:var(--ink-4)}
@media (hover:hover) and (pointer:fine){
  .plan:hover{transform:translateY(-4px);box-shadow:var(--shadow-3)}
}
.plan.hot{border-color:var(--ink);box-shadow:var(--shadow-2)}
.plan-b{position:absolute;top:-11px;left:50%;transform:translateX(-50%);padding:5px 14px;border-radius:100px;
  background:var(--ink);color:var(--bg);font-size:11.5px;font-weight:650;white-space:nowrap}
.plan-n{font-size:15px;font-weight:650;letter-spacing:-.02em}
.plan-p{font-size:40px;font-weight:700;letter-spacing:-.05em;margin-top:var(--sp-1);line-height:1}
.plan-p small{font-size:14.5px;font-weight:500;color:var(--ink-4);letter-spacing:-.01em}
.plan-d{font-size:13.5px;color:var(--ink-3);margin-top:8px;line-height:1.45;min-height:40px}
.plan ul{list-style:none;margin:var(--sp-3) 0;flex:1}
.plan li{display:flex;gap:9px;padding:6px 0;font-size:14px;color:var(--ink-2);line-height:1.45}
.plan li svg{width:16px;height:16px;flex:0 0 auto;margin-top:3px;color:var(--accent)}
.plan li.off{color:var(--ink-4)}
.plan li.off svg{color:var(--ink-4)}
.notice{padding:16px 18px;border-radius:var(--r-m);background:var(--warn-soft);
  font-size:13.5px;color:var(--ink-2);line-height:1.55}
.notice b{color:var(--warn)}
.sheet-bg{position:fixed;inset:0;z-index:90;background:rgba(10,12,15,.4);backdrop-filter:blur(4px);
  display:none;align-items:center;justify-content:center;padding:var(--sp-2)}
.sheet-bg.on{display:flex;animation:fade .22s ease}
@keyframes fade{from{opacity:0}to{opacity:1}}
.sheet{width:100%;max-width:520px;background:var(--bg);border-radius:var(--r-xl);padding:var(--sp-3);
  max-height:86vh;overflow-y:auto;box-shadow:var(--shadow-3);animation:sheetIn .42s var(--spring)}
@keyframes sheetIn{from{transform:translateY(24px) scale(.97);opacity:0}to{transform:none;opacity:1}}
/* Fermeture, symétrique de sheetIn (motion design premium, phase 1,
   2026-10-15 — jusqu'ici une feuille disparaissait instantanément, voir
   closeSheet(), js/ui-primitives.js). .closing s'ajoute SANS retirer .on
   tant que l'animation joue (closeSheet() retire les deux ensemble une
   fois terminée) -- la spécificité de .sheet-bg.closing l'emporte donc
   sur .sheet-bg.on ci-dessus pour la durée de la fermeture. */
@keyframes sheetOut{from{transform:none;opacity:1}to{transform:translateY(18px) scale(.97);opacity:0}}
.sheet-bg.closing{animation:fade .18s ease reverse forwards}
.sheet-bg.closing .sheet{animation:sheetOut .22s var(--ease) forwards}
@media (prefers-reduced-motion:reduce){
  .sheet-bg.closing,.sheet-bg.closing .sheet{animation:none}
}
/* Morph de la recherche : voir openSearch() (JS) pour --morph-from. Classe
   plus spécifique que .sheet seul -> l'emporte sur sheetIn/sheetUp sans
   !important. */
.sheet.sheet-morph{animation:searchMorph .5s var(--spring)}
@keyframes searchMorph{from{transform:var(--morph-from, none);opacity:.4}to{transform:none;opacity:1}}
@media (prefers-reduced-motion:reduce){ .sheet.sheet-morph{animation:none} }
.sheet-h{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-2);margin-bottom:var(--sp-2)}
.sheet h3{font-size:22px;font-weight:700;letter-spacing:-.035em}
.sheet p{font-size:15px;color:var(--ink-3);line-height:1.55;margin-top:10px}
.x{width:34px;height:34px;border-radius:50%;background:var(--bg-2);display:flex;align-items:center;justify-content:center;
  color:var(--ink-3);flex:0 0 auto}
.x:hover{background:var(--bg-3);color:var(--ink)}
.x svg{width:15px;height:15px}
@media (max-width:640px){
  .sheet-bg{align-items:flex-end;padding:0}
  .sheet{max-width:100%;border-radius:var(--r-xl) var(--r-xl) 0 0;max-height:92vh;
    padding-bottom:calc(var(--sp-3) + env(safe-area-inset-bottom));animation:sheetUp .42s var(--spring)}
  @keyframes sheetUp{from{transform:translateY(100%)}to{transform:none}}
}
.toast{position:fixed;left:50%;bottom:calc(96px + env(safe-area-inset-bottom));z-index:95;
  transform:translate(-50%,16px);background:var(--ink);color:var(--bg);padding:13px 22px;
  border-radius:100px;font-size:14.5px;font-weight:550;opacity:0;pointer-events:none;
  transition:opacity .35s var(--spring),transform .35s var(--spring);box-shadow:var(--shadow-3);max-width:90vw}
.toast.on{opacity:1;transform:translate(-50%,0)}
.chips{display:flex;gap:7px;flex-wrap:wrap}
/* Variante défilante (puces de filtre rapide Marchés) : une seule ligne
   qui défile horizontalement plutôt que de passer à la ligne — jamais de
   débordement visible ni de puce coupée, y compris sur les plus petits
   écrans (vérifié 375px). Scrollbar masquée mais navigable (molette,
   tactile, clavier via les flèches une fois un chip focus) : masquer la
   barre ne retire jamais la fonctionnalité de défilement elle-même. */
.chips-scroll{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;padding-bottom:2px;
  -webkit-overflow-scrolling:touch;scroll-behavior:smooth;scrollbar-width:none}
.chips-scroll::-webkit-scrollbar{display:none}
.chips-scroll .chip{flex:0 0 auto}
.chip{padding:9px 15px;border-radius:100px;background:var(--bg-2);font-size:13.5px;font-weight:550;
  color:var(--ink-3);min-height:38px;display:inline-flex;align-items:center;gap:6px}
/* CORRECTIF (bug visuel réel et grave, audit complet 2026-09-17) : un
   <svg> nu n'a AUCUNE taille par défaut dans un navigateur (taille de
   remplacement 300×150px) — chaque icône du site a jusqu'ici toujours
   sa taille fixée via un sélecteur dédié (.icon-btn svg, .btn svg…),
   sauf .chip, jamais utilisé avec une icône avant les suggestions du
   Radar ("Une grande entreprise américaine solide", etc.). Résultat
   vérifié en direct : un bouton normalement haut de 38px gonflait à la
   taille de l'icône géante et devenait un cercle énorme. */
.chip svg{width:15px;height:15px;flex:0 0 auto}
.chip:hover{background:var(--bg-3);color:var(--ink)}
.chip[aria-pressed="true"]{background:var(--ink);color:var(--bg)}
.check-box{width:22px;height:22px;border-radius:50%;border:1.5px solid var(--line-2);
  display:flex;align-items:center;justify-content:center;flex:0 0 auto;color:var(--bg);margin-left:auto}
.check-box svg{width:13px;height:13px}
.check-box[aria-pressed="true"]{background:var(--accent);border-color:var(--accent)}
.empty{text-align:center;padding:var(--sp-4) var(--sp-2)}
.empty-i{width:56px;height:56px;border-radius:18px;background:var(--bg-2);display:flex;align-items:center;justify-content:center;
  margin:0 auto var(--sp-2);color:var(--ink-4)}
.empty-i svg{width:26px;height:26px}
.empty h3{font-size:18px;font-weight:650;letter-spacing:-.025em}
.empty p{font-size:14.5px;color:var(--ink-3);margin:8px auto 0;max-width:38ch;line-height:1.5}
/* CORRECTIF (bug visuel réel, signalé par l'utilisateur : "on ne voit
   plus rien" sur la page Offres) : .rise démarre invisible et ne le
   redevient qu'après qu'un IntersectionObserver ajoute .seen — un point
   de défaillance de plus (timing, appareil, cycle de rendu) qu'une
   page de tarification n'a pas à courir. Retiré des .plan (voir
   pricingBlock ci-dessous) : ces cartes sont maintenant toujours
   visibles immédiatement, jamais conditionnées à un déclenchement JS
   après coup. La classe reste disponible pour du contenu non critique. */
.rise{opacity:0;transform:translateY(16px)}
.rise.seen{animation:rise .7s var(--spring) forwards}
@keyframes rise{to{opacity:1;transform:none}}
.page-in{animation:pageIn .38s var(--spring)}
@keyframes pageIn{from{opacity:0;transform:translateY(12px) scale(.995)}to{opacity:1;transform:none}}
.page-in>*{animation:stagger .5s var(--spring) backwards}
.page-in>*:nth-child(1){animation-delay:.02s} .page-in>*:nth-child(2){animation-delay:.06s}
.page-in>*:nth-child(3){animation-delay:.1s}  .page-in>*:nth-child(4){animation-delay:.14s}
.page-in>*:nth-child(5){animation-delay:.18s} .page-in>*:nth-child(6){animation-delay:.22s}
@keyframes stagger{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
/* Entrée échelonnée des 6 premières lignes (motion design premium, phase 2,
   2026-10-15) : .page-in>* ci-dessus ne couvre que les enfants DIRECTS de
   la page, jamais les lignes à l'intérieur de .rows/.news-list -- lacune
   confirmée par audit sur Nova Review/Nova News/Nova Event. Même keyframe
   (stagger) et MÊMES délais que .page-in>* ci-dessus, plafonnés aux 6
   premiers éléments (jamais une formule ouverte). Volontairement PAS
   appliqué à .rows/.news-list en général (seulement .rows-stagger,
   réservée à Nova Review/Nova Event) : Marchés/Portefeuille ont des
   listes longues ou non bornées, et un chiffre qui se rafraîchit ne doit
   jamais rejouer une animation d'apparition complète -- ces listes
   restent donc inchangées. */
.rows-stagger>.row,.news-list>.news-item{animation:stagger .5s var(--spring) backwards}
.rows-stagger>.row:nth-child(1),.news-list>.news-item:nth-child(1){animation-delay:.02s}
.rows-stagger>.row:nth-child(2),.news-list>.news-item:nth-child(2){animation-delay:.06s}
.rows-stagger>.row:nth-child(3),.news-list>.news-item:nth-child(3){animation-delay:.1s}
.rows-stagger>.row:nth-child(4),.news-list>.news-item:nth-child(4){animation-delay:.14s}
.rows-stagger>.row:nth-child(5),.news-list>.news-item:nth-child(5){animation-delay:.18s}
.rows-stagger>.row:nth-child(6),.news-list>.news-item:nth-child(6){animation-delay:.22s}
/* View Transitions natives (motion design premium, phase 1, 2026-10-15) :
   habille le fondu croisé par défaut du navigateur avec les jetons déjà
   utilisés partout ailleurs (--spring) plutôt que le réglage générique du
   navigateur (.25s ease) -- voir avecTransitionVue(), index.html, pour le
   déclenchement (go()/back()) et son repli déjà géré en JS. Dérogation
   reduced-motion ici aussi, en doublon volontaire du repli JS -- même
   principe "ceinture et bretelles" déjà utilisé ailleurs dans ce fichier
   (ex. le filet de sécurité JS du temple en plus de son CSS). */
::view-transition-old(root),::view-transition-new(root){
  animation-duration:.32s;animation-timing-function:var(--spring)}
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){animation:none!important}
}
/* CORRECTIF (bug visuel réel et grave, audit complet 2026-09-17) : sous
   prefers-reduced-motion, la règle générique plus haut ("*{animation-
   duration:.001ms!important}") réduisait la durée à presque zéro mais
   NE TOUCHAIT PAS animation-delay ni le fill-mode "backwards" de cette
   règle — combinaison qui, vérifiée en direct (navigateur avec réduction
   de mouvement activée), laissait le contenu de CHAQUE page (tout enfant
   direct de .page-in, donc le titre et tout le corps) bloqué à
   opacity:0, page entière invisible bien après la fin théorique de
   l'animation. Désactivée franchement ici, comme .aurora-bg plus haut :
   jamais de durée quasi nulle sur une animation "backwards", toujours
   animation:none. */
@media (prefers-reduced-motion:reduce){
  .page-in,.page-in>*{animation:none}
  .rows-stagger>.row,.news-list>.news-item{animation:none}
}
.chart-line{stroke-dasharray:2400;stroke-dashoffset:2400;animation:draw 1.3s var(--spring) forwards}
@keyframes draw{to{stroke-dashoffset:0}}
/* Sparkline Marchés (motion design premium, phase 2, 2026-10-15) : même
   principe que .chart-line (tracé progressif, stroke-dashoffset), mais un
   dasharray bien plus petit et une durée plus courte -- adapté à un tracé
   de 110×32 (2400 resterait quasi invisible à cette échelle). Classe
   ajoutée par spark() (js/ui-primitives.js) UNIQUEMENT quand l'appelant la
   demande explicitement (marketsRows(), 6 premières lignes seulement) --
   voir le commentaire de spark() pour le raisonnement sur les longues
   listes. */
.spark-line{stroke-dasharray:260;stroke-dashoffset:260;animation:sparkDraw .6s var(--spring) forwards}
@keyframes sparkDraw{to{stroke-dashoffset:0}}
@media (prefers-reduced-motion:reduce){ .spark-line{animation:none} }
.donut-svg{transform:rotate(-90deg);transform-origin:center;animation:donutIn .6s var(--spring)}
@keyframes donutIn{from{opacity:0;transform:rotate(-90deg) scale(.85)}to{opacity:1;transform:rotate(-90deg) scale(1)}}
@media (prefers-reduced-motion:reduce){ .donut-svg{animation:none} }
.build{display:inline-flex;align-items:center;padding:4px 8px;border-radius:100px;flex:0 0 auto;
  background:var(--bg-3);color:var(--ink-4);font-size:10.5px;font-weight:700;letter-spacing:.02em}
/* CORRECTIF (audit visuel, 2026-09-17) : text-overflow:ellipsis posé sur ce
   conteneur flex (point + texte) n'a jamais d'effet réel en CSS — cette
   propriété n'agit que sur un bloc de texte unique, pas sur les enfants
   d'un flex container. Constaté en direct sur www.novabourse.site : "Cours
   réels · 17:04" s'affichait tronqué en "Cours réels · 17:" SANS les
   points de suspension attendus, dès que la largeur de la barre de
   navigation devenait juste. La coupure et l'ellipse sont déplacées sur
   .src-txt (le texte lui-même) ci-dessous, là où text-overflow fonctionne
   réellement ; min-width:0 est nécessaire ici car un enfant flex refuse
   par défaut de rétrécir sous la largeur de son contenu (min-width:auto
   implicite), ce qui empêchait justement la coupure de s'appliquer. */
.src{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:100px;
  font-size:11.5px;font-weight:600;letter-spacing:-.01em;flex:0 1 auto;min-width:0;max-width:100%}
.src i{width:6px;height:6px;border-radius:50%;background:currentColor;flex:0 0 auto}
.src-txt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.src-live{background:var(--up-soft);color:var(--up)}
.src-live i{animation:srcPulse 2.2s ease-in-out infinite}
@keyframes srcPulse{0%,100%{opacity:1}50%{opacity:.3}}
.src-demo{background:var(--bg-3);color:var(--ink-3)}
.src-err{background:var(--warn-soft);color:var(--warn)}
@media (max-width:760px){
  .src{padding:6px;gap:0}
  .src-txt{display:none}
  .build{display:none}
  .top-in{padding:10px 14px;gap:8px}
  .logo span:last-child{font-size:16px}
}
@media (max-width:400px){ .top-act .btn{padding:7px 13px;font-size:13px} }
.cta-short{display:none}
/* CORRECTIF (bug visuel réel, signalé par l'utilisateur : "on ne voit
   plus les offres") : sur un vrai téléphone, le total logo + badge +
   icônes + bouton "Voir les offres" dépasse la largeur disponible dès
   qu'on descend sous ~424px (vérifié par calcul : rien dans .top-in ne
   rétrécit, body a overflow-x:hidden, donc le dernier élément — ce
   bouton, le plus important pour la conversion — sortait simplement de
   l'écran, invisible et inaccessible). Le texte raccourci libère assez
   de place pour que le bouton reste toujours visible. */
@media (max-width:600px){
  .cta-full{display:none}
  .cta-short{display:inline}
}
/* CORRECTIF (bug visuel réel, audit complet 2026-09-17) : ce second
   .logo-m orphelin (jamais utilisé par aucun balisage — .brand-mark a
   son propre style ci-dessous) écrasait silencieusement le badge sombre
   du logo d'en-tête (28px, fond --ink, coins arrondis) partout sur le
   site, le remplaçant par une icône nue 30px sans fond ni arrondi.
   Supprimé : la vraie définition est celle plus haut, ".logo-m{...
   background:var(--ink);border-radius:8px...}". */
/* Rétréci (2026-09-24, "page beaucoup plus courte... le bouton de
   connexion doit apparaître rapidement sans devoir énormément
   descendre") : 140px/86px prenait à lui seul près d'un quart d'un
   écran de téléphone en hauteur avant même le titre. 64px/38px reste
   parfaitement lisible en icône de marque, sans le poids d'un hero. */
/* Recoloré (2026-09-29, retour utilisateur : "je voudrais une animation
   stylée d'ouverture") : anneau gris neutre (--ink-4) -> couleurs de
   marque (orange primaire + cyan secondaire), même philosophie que le
   commentaire ci-dessus ("aucune nouvelle animation inventée" — reste
   le même .brand-ring partagé gate/loading, juste réaligné sur
   l'identité NovaTitre au lieu de rester gris comme avant le pivot).
   Halo doux ajouté (drop-shadow), même principe que le temple. */
.brand-orbit{position:relative;display:flex;align-items:center;justify-content:center;
  width:64px;height:64px;margin:0 auto;
  filter:drop-shadow(0 0 10px rgba(230,126,34,.25)) drop-shadow(0 0 16px rgba(0,212,255,.15))}
/* CORRECTIF (2026-10-07, 2ᵉ passe, §3) : le halo orange ci-dessus suit
   déjà --accent via .brand-ring circle:first-child (donc devient bleu
   électrique en thème sombre automatiquement) mais le drop-shadow lui-
   même est une rgba() figée, jamais un token — fuite orange décorative
   sur l'écran de connexion sinon. Thème clair non concerné (--accent y
   reste l'orange Hermès). */
[data-theme="dark"] .brand-orbit{filter:drop-shadow(0 0 10px rgba(77,124,255,.25)) drop-shadow(0 0 16px rgba(0,212,255,.15))}
.brand-ring{position:absolute;inset:0;width:100%;height:100%;opacity:.85;
  animation:ringSpin 9s linear infinite}
.brand-ring circle:first-child{color:var(--accent)}
.brand-ring circle:last-child{color:var(--blue-accent);
  animation:ringSpin 6s linear infinite reverse;transform-origin:60px 60px}
@keyframes ringSpin{to{transform:rotate(360deg)}}
.brand-mark{display:flex;align-items:center;justify-content:center;width:38px;height:38px;
  background:none;color:var(--ink);box-shadow:none;border-radius:0;
  animation:brandCycle 6s cubic-bezier(.65,0,.35,1) infinite}
.brand-mark svg{width:100%;height:100%}
@keyframes brandCycle{
  0%   {transform:rotate(0deg)}
  30%  {transform:rotate(360deg)}
  50%  {transform:rotate(360deg)}
  80%  {transform:rotate(0deg)}
  100% {transform:rotate(0deg)}
}
.brand-spin{display:inline-flex;width:44px;height:44px;color:var(--ink);
  animation:brandCycle 2.6s cubic-bezier(.65,0,.35,1) infinite}
.brand-spin svg{width:100%;height:100%}
.spin-wrap{display:flex;flex-direction:column;align-items:center;gap:16px;padding:36px 0}
.spin-txt{font-size:14px;color:var(--ink-3)}
@media (prefers-reduced-motion:reduce){
  .brand-mark,.brand-spin{animation:none}
  .brand-ring{animation-duration:24s}
}
.quota{margin-top:18px;padding:16px;border-radius:12px;background:var(--bg-2)}
.quota b{display:block;font-size:13px;font-weight:650;letter-spacing:.04em;text-transform:uppercase}
.quota span{display:block;font-size:14px;color:var(--ink-3);margin-top:6px}
.quota-bar{display:block;height:4px;border-radius:100px;background:var(--bg-3);margin-top:12px;overflow:hidden}
/* Entrée animée (motion design premium, phase 2, 2026-10-15) : même
   principe que .gauge i/.nsc-line em ci-dessus — width:0 + gaugeIn pour
   le premier affichage, la transition existante gère toujours les mises
   à jour suivantes (consommation qui change en direct). */
.quota-bar em{display:block;height:100%;width:0;background:var(--ink);border-radius:100px;
  transition:width .5s var(--spring);animation:gaugeIn 1.1s var(--spring) forwards}
/* ============================================================
   ACCUEIL — refonte "temple" (2026-09-23)
   ============================================================
   Remplace l'ancien .nb-hero/.nb-top/.nb-split/.nb-pf (grille
   texte+cartes ajoutée le 2026-09-22 pour combler le vide desktop —
   devenue obsolète : la nouvelle hero a son propre fond et sa propre
   largeur bornée, plus besoin de ce mécanisme). Palette dédiée,
   indépendante du thème clair/sombre de l'app — même principe déjà
   établi pour .dock (bottom nav) : cette section garde son fond nuit
   même en thème clair, elle ne suit pas --bg/--accent (qui restent
   rose/clair-sombre partout ailleurs dans l'app, inchangés).
   RECOLORÉ (2026-09-23, retour utilisateur : "remets le fond original
   derrière le temple" + "remplace le orange et le bleu... vraiment
   tout") : les couleurs fournies à l'origine (#070B1D, #075BFF...)
   étaient un bleu nuit — exactement la teinte à faire disparaître.
   Plutôt que de rendre la section transparente (le texte blanc/les
   dégradés de cette hero ne fonctionnent qu'sur un fond sombre, quel
   que soit le thème de l'app — un fond clair casserait totalement la
   lisibilité), le principe "toujours sombre, one-off" d'origine est
   conservé tel quel — c'est ÇA le fond "original" à restaurer, pas une
   teinte bleue particulière — simplement reteint en noir/violet nuit
   pour rejoindre la nouvelle palette plutôt que de garder du bleu. */
/* CORRECTIF ordinateur (2026-09-23, retour utilisateur : "adapte ça
   aussi pour les ordinateurs") : les marges négatives fixes (-18px/
   -22px) n'annulaient QUE le padding propre de .wrap — suffisant tant
   que .wrap fait toute la largeur de l'écran (mobile/tablette), mais
   .wrap a aussi un max-width:1120px (voir sa règle plus haut) qui se
   centre avec des marges "auto" dès qu'un écran dépasse ~1160px : cette
   hero restait alors coincée à 1120px de large, comme un rectangle
   sombre flottant au milieu d'un océan de fond rose — jamais
   "adaptée" aux grands écrans, juste redimensionnée. width:100vw +
   margin left/right:calc(50% - 50vw) est la technique standard pour
   qu'un enfant d'un conteneur centré déborde jusqu'aux bords RÉELS du
   viewport quelle que soit la largeur de ce conteneur (ici 100% sur
   mobile, 1120px centré sur grand écran) — se réduit mathématiquement
   à l'ancienne valeur fixe sur mobile (vérifié : 50%-50vw avec le
   padding actuel de .wrap redonne bien -18px/-22px), donc rien ne
   change en dessous de ~1160px, seul le grand écran est corrigé. */
.nb-hero-v2{
  width:100vw;
  margin:calc(var(--sp-3) * -1) calc(50% - 50vw) 0;padding:14px 18px 28px;
  /* CORRECTIF (2026-10-02, retour utilisateur : "continue" après avoir
     confirmé vouloir que cette section suive aussi le noir/blanc pur du
     reste du site) : fond fixe toujours sombre -> var(--bg), suit le
     thème comme le reste de la page. Les textes/traits de cette section
     étaient tous en teintes claires fixes (pensés pour un fond toujours
     sombre) — voir .temple-line/.nova-temple-brand/.nova-action-circle
     svg/.nova-action-title/.nova-action-description/.nb-hero-title/
     .nb-hero-sub2 plus bas, tous repassés en var(--ink) pour suivre le
     thème eux aussi (sinon texte blanc invisible sur fond blanc). */
  background:var(--bg);
  position:relative
  /* PAS de overflow:hidden ici (bug réel trouvé le 2026-09-23, passe 2) :
     ça rognait silencieusement le temple, qui vise min(96vw,780px) —
     plus large que la boîte intérieure de .nb-hero-v2 (100% moins son
     propre padding). Le fond dégradé n'a pas besoin de cette découpe
     (un background n'a jamais débordé de sa propre boîte, overflow ne
     régit que les ENFANTS) ; measuré avant/après : le temple passait de
     ~88-90% de la largeur réellement disponible à ~96% une fois retiré. */
}
@media (min-width:900px){
  .nb-hero-v2{margin:calc(var(--sp-4) * -1) calc(50% - 50vw) 0;padding:20px 22px 36px}
}
/* Temple — PASSE 2 (2026-09-23, nouveau SVG fourni) : remplace le
   premier jet par la structure/coordonnées exactes données cette fois
   (fronton/entablement recalculés, colonnes déplacées, classes
   renommées temple-main/temple-secondary/temple-flute) et insère le
   VRAI logo NovaTitre (ICON.brand, déjà utilisé dans le header — voir
   .nova-temple-brand plus bas) au centre du fronton, à la place du
   triangle simplifié de la passe précédente. Dessin non modifié,
   classes/CSS repris tels quels. */
.nova-temple-section{width:100%;display:flex;justify-content:center;align-items:center;
  box-sizing:border-box;padding:0 4px;margin:0;overflow:visible;position:relative;z-index:1;
  /* CORRECTIF (2026-09-25, retour utilisateur : "parfait mais plus fluide") :
     updateScrollFades() (JS) recalcule translateY/scale à chaque frame de
     scroll rAF-throttled, mais SANS transition CSS le navigateur appliquait
     la nouvelle valeur instantanément à chaque appel — visible comme de
     petits à-coups sur un scroll saccadé (molette par crans, trackpad),
     plutôt qu'un mouvement continu. Cette transition fait "glisser" la
     valeur affichée vers la valeur cible entre deux frames au lieu d'un
     saut instantané. Durée courte (140ms) : assez pour lisser, assez
     court pour ne pas sembler "traîner" derrière un scroll rapide continu. */
  transition:transform .14s ease-out;will-change:transform}
@media (prefers-reduced-motion:reduce){
  .nova-temple-section{transition:none}
}
/* flex-shrink:0 (bug réel trouvé le 2026-09-23, passe 2) : .nova-temple-
   section est display:flex — sans ça, flexbox réduisait silencieusement
   la largeur du temple sous sa valeur voulue (min(96vw,780px)) pour le
   faire tenir dans son conteneur flex, exactement le comportement par
   défaut de flex-shrink:1 qu'aucune propriété ne neutralisait ici. */
/* opacity (2026-09-23, ajustée trois fois le même jour) : .82 → .55
   (retour "transparent comme les 4 boutons") → maintenant multipliée
   par --temple-fade (voir updateScrollFades() en JS, même mécanisme que
   --top-fade du header) : la base .55 reste la transparence "au repos"
   en haut de page, --temple-fade la réduit encore progressivement
   quand le temple défile hors champ ("qu'il s'efface progressivement
   quand on le dépasse en descendant"). */
/* CORRECTIF (2026-10-07, retour utilisateur : "mets le temple beaucoup
   plus gros") — annule la réduction du même jour (min(42vw,300px)) : le
   temple redevient une pièce maîtresse de l'accueil plutôt qu'un petit
   accent. Épaisseurs des traits (plus bas) redimensionnées en
   conséquence. */
.nova-temple{display:block;width:min(88vw,680px);height:auto;overflow:visible;flex-shrink:0;
  opacity:calc(.55 * var(--temple-fade,1));
  /* Même correctif de fluidité que .nova-temple-section ci-dessus, sur
     l'opacity cette fois (--temple-fade change à chaque frame de scroll) —
     transitionne UNIQUEMENT opacity : jamais transform/filter, déjà
     couverts par les animations templeScaleIn/templeGlowPulse plus bas,
     qu'une transition sur ces mêmes propriétés perturberait. */
  transition:opacity .14s ease-out;
  filter:drop-shadow(0 0 3px rgba(255,255,255,.20)) drop-shadow(0 0 11px rgba(0,212,255,.08))}
@media (prefers-reduced-motion:reduce){
  .nova-temple{transition:none}
}
/* Trait légèrement teinté (recoloré 2026-09-28, pivot NovaTitre : violet
   -> cyan, même rôle d'accent secondaire) — #d9f6ff plutôt que #ffffff
   blanc pur, un cyan très clair qui reste lisible comme "blanc" à
   distance mais prend clairement une teinte cyan de près, cohérent avec
   le glow cyan du filter ci-dessus. */
.temple-line{fill:none;stroke:var(--ink);stroke-linecap:round;stroke-linejoin:round;
  vector-effect:non-scaling-stroke}
/* Épaisseurs proportionnées à min(88vw,680px) (2026-10-07, le temple est
   redevenu grand — voir .nova-temple plus haut) : vector-effect:
   non-scaling-stroke garde le trait à une épaisseur EN PIXELS ÉCRAN
   constante quelle que soit la taille d'affichage du SVG, d'où ce
   réajustement à chaque changement de taille notable. */
.temple-main{stroke-width:6.2}
.temple-secondary{stroke-width:4}
.temple-flute{stroke-width:2.2;opacity:.90}
.nova-temple-brand{fill:var(--ink)}
@media (max-width:600px){
  .nova-temple{width:min(94vw,380px);max-width:none}
  /* Retour utilisateur (2026-09-23) : "le temple est un peu trop épais"
     sur téléphone. vector-effect:non-scaling-stroke (ci-dessus) garde
     l'épaisseur du trait CONSTANTE en pixels écran quelle que soit la
     taille d'affichage du temple — sur un petit écran, le temple
     lui-même rétrécit mais le trait garde EXACTEMENT la même épaisseur
     qu'en desktop, donc parait proportionnellement plus épais. D'où un
     correctif spécifique mobile plutôt qu'un changement global (qui
     aurait aussi amincin le temple sur ordinateur, non demandé) —
     réduction légère (~20%, "un tout petit peu"), mêmes proportions
     entre les 3 épaisseurs conservées. Reproportionné (2026-10-07) pour
     la taille mobile actuelle (min(94vw,380px), ~56% de la base desktop
     680px). */
  .temple-main{stroke-width:4.6}
  .temple-secondary{stroke-width:3}
  .temple-flute{stroke-width:1.6}
}
/* Animations (2026-09-23, "un maximum d'animation autour de cette
   architecture romaine") — quatre effets combinés, chacun sur une
   propriété/un élément différent pour ne jamais se marcher dessus :
   1) .nova-temple : léger scale-in une fois au montage (transform),
      jamais l'opacity (déjà pilotée par --temple-fade au scroll — un
      keyframe qui toucherait aussi opacity écraserait cette valeur
      dynamique tant qu'il "tient" sa position finale via fill-mode).
   2) .temple-part (fronton/logo/4 colonnes/marches) : apparition en
      cascade au montage, delay posé en inline juste au-dessus dans le
      HTML — "le temple se construit" plutôt qu'un bloc unique.
   3) .nova-temple lui-même, une 2e animation (filter cette fois, pas
      transform) : respiration lente et continue du halo.
   Le parallaxe au scroll (--temple-parallax) est appliqué en JS sur
   .nova-temple-section, un ÉLÉMENT PARENT différent de .nova-temple :
   même raison, éviter que deux sources (CSS animation + JS scroll)
   ne s'affrontent sur la propriété transform d'un seul et même nœud.
   BUG RÉEL trouvé et retiré en testant (2026-09-23) : une pulsation
   continue (transform:scale) avait été ajoutée sur .nova-temple-brand
   avec transform-box:fill-box pour la faire tourner autour de son
   propre centre plutôt que l'origine du SVG — combinée à l'attribut
   transform SVG déjà présent sur ce même <g> (translate/scale pour son
   positionnement), ça faisait littéralement bondir UN SEUL des 3
   <path> du logo à une position aberrante (loin à droite du temple,
   visible en direct). Un vrai bug d'interaction CSS/SVG, pas un simple
   choix esthétique à ajuster — retirée entièrement plutôt que
   rafistolée : le logo garde son apparition en fondu (#2 ci-dessus),
   sans pulsation continue. */
@keyframes templeScaleIn{ from{transform:scale(.96)} to{transform:scale(1)} }
@keyframes templePartIn{ from{opacity:0} to{opacity:1} }
@keyframes templeGlowPulse{
  0%,100%{ filter:drop-shadow(0 0 3px rgba(255,255,255,.20)) drop-shadow(0 0 11px rgba(0,212,255,.08)) }
  50%{ filter:drop-shadow(0 0 7px rgba(255,255,255,.32)) drop-shadow(0 0 20px rgba(0,212,255,.20)) }
}
/* CORRECTIF (2026-09-29, retour utilisateur : "le temple prend du temps
   à se lancer... je voudrais une animation stylée d'ouverture") : la
   cascade complète (dernier élément .78s + sa propre durée .6s) prenait
   ~1,38s avant d'être entièrement visible — resserrée à ~0,77s (délais
   des colonnes réduits de moitié, marches à .42s au lieu de .78s, durée
   de fondu de chaque partie .35s au lieu de .6s) : même effet "le
   temple se construit" en cascade, nettement plus vif à l'ouverture. */
.nova-temple{
  animation:templeScaleIn .5s cubic-bezier(.22,1,.36,1) both,
    templeGlowPulse 4.5s ease-in-out 1s infinite}
.temple-part{opacity:0;animation:templePartIn .35s cubic-bezier(.22,1,.36,1) both}
/* Survol desktop uniquement (§ voir .nova-action-circle:hover plus haut
   pour le même principe) : le halo s'intensifie légèrement, purement
   décoratif, jamais nécessaire pour comprendre/utiliser la page. */
@media (hover:hover){
  .nova-temple-section:hover .nova-temple{
    filter:drop-shadow(0 0 6px rgba(255,255,255,.30)) drop-shadow(0 0 18px rgba(0,212,255,.18));
    transition:filter .3s ease-out}
}
@media (prefers-reduced-motion:reduce){
  .nova-temple{animation:none}
  .temple-part{opacity:1;animation:none}
}
/* Survol lumineux premium, une seule fois à la toute première visite
   (motion design premium, phase 1, 2026-10-15 — demande explicite :
   "un résultat visuellement spectaculaire sur l'accueil, particulièrement
   autour du temple"). Joué APRÈS la cascade des temple-part (délai .85s,
   cascade complète ~.77s + marge) : un ::after séparé, jamais le même
   nœud que templeScaleIn/templeGlowPulse ci-dessus (évite deux animations
   sur la même propriété du même élément). transform+opacity UNIQUEMENT
   (translation d'un dégradé, jamais background-position qui repeindrait
   toute la zone à chaque frame) -- coût de composition minimal, un seul
   passage, jamais répété. mix-blend-mode:overlay : s'adapte aux deux
   thèmes sans teinte séparée, jamais assez opaque pour gêner la
   lisibilité (aucune donnée financière dans cette zone). Portée par
   :has(.nova-temple:not(.no-intro)) -- réutilise EXACTEMENT le même
   indicateur "déjà vu cette session" que la cascade elle-même
   (templeIntroPlayed, js/page-home.js), jamais un second drapeau. */
.nova-temple-section:has(.nova-temple:not(.no-intro))::after{
  content:'';position:absolute;inset:0;pointer-events:none;overflow:hidden;
  background:linear-gradient(100deg,transparent 35%,rgba(255,255,255,.5) 50%,rgba(0,212,255,.38) 57%,transparent 68%);
  mix-blend-mode:overlay;transform:translateX(-130%);opacity:0;
  animation:templeShimmer 1s ease-out .85s forwards}
@keyframes templeShimmer{
  0%{transform:translateX(-130%);opacity:0}
  14%{opacity:1}
  100%{transform:translateX(130%);opacity:0}
}
@media (prefers-reduced-motion:reduce){
  .nova-temple-section::after{display:none}
}
/* CORRECTIF (2026-09-23, retour utilisateur : "quand je reviens sur
   Accueil le temple n'est plus là et réapparaît au bout de quelques
   secondes") : PAGES.home() reconstruit le SVG en entier à CHAQUE
   navigation vers l'accueil (view.innerHTML remplacé, voir render()) —
   la cascade d'entrée ci-dessus rejouait donc à chaque fois, opacity:0
   pendant jusqu'à ~1,4s (dernier délai .78s + durée .6s), pas
   seulement au tout premier chargement de la page. Perçu comme un bug
   ("le temple a disparu") plutôt que comme un effet d'entrée voulu.
   .no-intro (posée par PAGES.home() en JS, voir templeIntroPlayed) ne
   s'applique qu'à partir de la 2e visite de l'accueil dans la même
   session : le temple s'affiche alors directement à son état final,
   sans repasser par opacity:0. La respiration continue (glow pulse)
   n'est volontairement PAS coupée par .no-intro — seul le scale-in
   ponctuel doit sauter, pas l'animation infinie. */
.nova-temple.no-intro{animation:templeGlowPulse 4.5s ease-in-out 1s infinite}
.nova-temple.no-intro .temple-part{opacity:1;animation:none}
/* 4 raccourcis — verre dépoli violet transparent (brief §3/§4, teinte
   bleu électrique d'origine ; recoloré 2026-09-23 en violet, voir note
   de palette tout en haut), remplace le fond quasi-opaque
   rgba(20,29,58,.72) de la passe précédente : le fond de .nb-hero-v2
   doit rester visible à travers la bulle. Grille à 4 colonnes strictement égales, jamais de
   passage en 2×2 (inchangé). .nova-action-title n'a volontairement
   PAS de white-space:nowrap : la passe précédente avait ce bug exact
   ("Analyser avec l'IA"/"Ajouter au portefeuille" débordant sur leurs
   voisins), corrigé une première fois sur le dock, puis ici — le
   retour à la ligne naturel aux espaces (comportement par défaut,
   jamais explicitement désactivé cette fois) l'évite d'emblée. */
/* margin-top -4px -> 10px (2026-09-23, retour utilisateur : "un tout
   petit peu plus bas") — léger espace supplémentaire entre les marches
   du temple et la rangée de bulles, sans les détacher visuellement
   l'un de l'autre (voir §9 des briefs précédents : ils doivent rester
   un seul ensemble). */
.nova-quick-actions{width:min(96%,900px);margin:10px auto 30px;display:grid;
  grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;align-items:start;
  position:relative;z-index:1}
.nova-quick-action{appearance:none;-webkit-appearance:none;border:0;padding:0;margin:0;
  background:transparent;color:inherit;font:inherit;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
  text-align:center;min-width:0;-webkit-tap-highlight-color:transparent}
/* 92px -> 70px (2026-09-23, retour utilisateur : "les 4 bulles plus
   petites") ; icône réduite dans la même proportion (38 -> 29px) pour
   rester équilibrée dans le cercle plutôt que de simplement rétrécir
   le cercle autour d'une icône inchangée. */
/* Recoloré (2026-09-28, pivot NovaTitre) : violet -> cyan (même rôle
   d'accent secondaire), voir la note de palette tout en haut du fichier.
   rgb(0,212,255)=--cyan-500, rgb(0,168,204)=--cyan-700, rgb(102,232,255)
   =--cyan-300 — valeurs directement reprises de l'échelle plutôt que
   réintroduire un jeu de couleurs séparé non maintenu par les variables. */
.nova-action-circle{width:70px;height:70px;flex-shrink:0;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 50% 35%,rgba(0,212,255,.18),rgba(0,168,204,.08) 55%,rgba(0,168,204,.04) 100%);
  border:1px solid rgba(0,212,255,.42);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  box-shadow:inset 0 0 20px rgba(0,212,255,.07),0 0 18px rgba(0,168,204,.06);
  transition:transform .18s ease,background .18s ease,border-color .18s ease,box-shadow .18s ease}
.nova-action-circle svg{width:29px;height:29px;fill:none;stroke:var(--ink);
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.nova-action-title{display:block;margin-top:10px;color:var(--ink);font-size:14px;font-weight:700;
  line-height:1.15;letter-spacing:-.01em}
.nova-action-description{display:block;max-width:180px;margin-top:7px;
  color:var(--ink);font-size:12px;font-weight:400;line-height:1.32}
@media (hover:hover){
  .nova-quick-action:hover .nova-action-circle{transform:translateY(-2px);
    border-color:rgba(0,212,255,.72);background:rgba(0,168,204,.16);
    box-shadow:inset 0 0 24px rgba(0,212,255,.10),0 7px 26px rgba(0,168,204,.13)}
}
.nova-quick-action:active .nova-action-circle{transform:scale(.96)}
.nova-quick-action:focus-visible .nova-action-circle{outline:2px solid #66e8ff;outline-offset:4px}
/* CORRECTIF (2026-10-07, 2ᵉ passe, §11 : "pas de bleu électrique trop
   agressif [...] même design system que les Nova") : le glow cyan radial
   (base ci-dessus, conservé tel quel en THÈME CLAIR, non concerné par
   cette demande) devient en thème sombre une simple surface de verre
   bleu/violet translucide — même famille que les médaillons des pièces
   d'échecs (--glass-icon/--nova-ai), sans glow ni bordure saturée. */
[data-theme="dark"] .nova-action-circle{
  background:var(--glass-icon);border-color:var(--nova-border);
  box-shadow:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
@media (hover:hover){
  [data-theme="dark"] .nova-quick-action:hover .nova-action-circle{
    border-color:rgba(255,255,255,.16);background:rgba(255,255,255,.13);box-shadow:none}
}
[data-theme="dark"] .nova-quick-action:focus-visible .nova-action-circle{outline-color:var(--accent)}
@media (prefers-reduced-motion:reduce){
  .nova-quick-action:active .nova-action-circle,.nova-quick-action:hover .nova-action-circle{transform:none}
}
@media (max-width:600px){
  .nova-quick-actions{width:calc(100% - 12px);margin-top:8px;margin-bottom:26px;
    grid-template-columns:repeat(4,minmax(0,1fr));gap:3px}
  .nova-action-circle{width:clamp(50px,14.8vw,62px);height:clamp(50px,14.8vw,62px)}
  .nova-action-circle svg{width:clamp(21px,6.1vw,27px);height:clamp(21px,6.1vw,27px)}
  .nova-action-title{margin-top:8px;font-size:clamp(10px,2.75vw,13px);line-height:1.12}
  .nova-action-description{margin-top:5px;padding:0 2px;font-size:clamp(8.5px,2.3vw,11.5px);line-height:1.28}
}
.nb-hero-title{margin-top:26px;font-size:clamp(30px,7.5vw,40px);font-weight:800;
  letter-spacing:-.03em;color:var(--ink);line-height:1.08;position:relative;z-index:1}
.nb-hero-sub2{margin-top:8px;font-size:clamp(15px,3.6vw,17px);color:var(--ink);
  position:relative;z-index:1}
.nb-cta-row{display:flex;gap:10px;margin-top:18px;position:relative;z-index:1}
.nb-cta-a,.nb-cta-s{flex:1;height:52px;border-radius:9999px;font-size:15px;font-weight:700;
  display:flex;align-items:center;justify-content:center;gap:8px}
/* Recoloré (2026-09-28, pivot NovaTitre) : c'était encore le dégradé
   violet de l'identité précédente sur le CTA PRIMAIRE de la page
   d'accueil ("Analyser") — un oubli du reste du rebrand, corrigé ici
   pour rejoindre le même dégradé orange que .btn-a partout ailleurs. */
.nb-cta-a{background:linear-gradient(135deg,var(--accent) 0%,var(--accent-hover) 100%);color:#fff;
  box-shadow:0 8px 28px rgba(230,126,34,.24)}
.nb-cta-a:active{transform:scale(.98)}
.nb-cta-s{background:rgba(17,22,49,.55);border:1px solid rgba(255,255,255,.10);color:#fff}
.nb-cta-s:active{background:rgba(17,22,49,.75)}
@media (prefers-reduced-motion:reduce){ .nb-cta-a:active,.nb-quick-item:active .nb-quick-bub{transform:none} }
/* CORRECTIF (2026-10-02) : même principe que .btn-a plus haut — le CTA
   primaire "Analyser" devient blanc plein en thème sombre, plus aucune
   couleur orange/bleu nuit ici.
   CORRECTIF (2026-10-07, "rendre l'accueil plus instinctif") : "Rechercher"
   (secondaire) devenait EXACTEMENT le même blanc plein que "Analyser"
   (primaire) — plus aucune hiérarchie visuelle entre les deux, un
   utilisateur ne peut pas deviner lequel faire en premier. Repasse en
   contour (fond transparent, liseré blanc) — même traitement déjà établi
   ailleurs dans l'app pour un bouton secondaire en thème sombre (voir
   .btn-blue-outline), pas un nouveau style inventé pour l'occasion. */
[data-theme="dark"] .nb-cta-a{background:#fff;color:#000;box-shadow:none}
[data-theme="dark"] .nb-cta-s{background:transparent;color:#fff;border-color:rgba(255,255,255,.35)}
[data-theme="dark"] .nb-cta-s:active{background:rgba(255,255,255,.12)}
/* Carte "Mes positions" v2 — hors de .nb-hero-v2 (fond normal de l'app
   en dessous, comme avant), toujours un verre sombre quel que soit le
   thème du site (carte "hero" volontairement mise en avant, brief §19).
   CORRECTIF (2026-10-07, 2ᵉ passe, §2 : "Ma position" citée en premier
   exemple de carte à aligner) : rgb(17,22,49)/rgb(16,35,78) étaient une
   teinte navy légèrement différente de --nova-bg-deep (rgb(25,20,62)) —
   recalées sur EXACTEMENT la même teinte que le reste du nouveau système,
   CTA repris en verre blanc translucide (comme les autres boutons du
   même système) plutôt qu'un bloc bleu à part. */
.nb-pf2{background:rgba(25,20,62,.82);border:1px solid rgba(255,255,255,.10);
  border-radius:20px;padding:20px;margin-top:20px}
.nb-pf2-eyebrow{color:var(--accent);font-weight:700;font-size:13px}
.nb-pf2-val{font-size:clamp(30px,7.5vw,38px);font-weight:700;letter-spacing:-.03em;
  color:#fff;line-height:1;margin-top:10px;font-variant-numeric:tabular-nums}
.nb-pf2-var{font-size:14px;font-weight:600;margin-top:10px}
.nb-pf2-var.up-t{color:#45D69A}
.nb-pf2-var.down-t{color:#FF647C}
.nb-pf2-empty p{color:rgba(255,255,255,.65);font-size:14.5px;margin-top:8px;line-height:1.5}
.nb-pf2-detail{display:block;width:100%;margin-top:16px;padding:13px 16px;
  border-radius:14px;background:rgba(255,255,255,.10);color:#fff;font-weight:600;font-size:14.5px;
  text-align:center;transition:background .2s ease}
.nb-pf2-detail:active{background:rgba(255,255,255,.16)}
@media (hover:hover){ .nb-pf2-detail:hover{background:rgba(255,255,255,.14)} }
/* ============================================================
   NOVA — architecture visuelle (2026-09-23)
   ============================================================
   Grille compacte de l'accueil (NovaBot/Nova Review/Nova Event/Nova
   News) : 2 colonnes fixes sur mobile, jamais 4 blocs verticaux pleine
   largeur (refusé explicitement dans le brief) — passe à 4 colonnes
   dès que l'écran le permet (§15 : "sur desktop, utilise intelligemment
   l'espace supplémentaire"). Même traitement "verre dépoli" que .card
   (cohérence visuelle), teinte par carte via --nf-accent/--nf-accent-rgb
   posés en style inline par novaHubCard() — un seul jeu de règles pour
   les 4 cartes plutôt que 4 variantes CSS dupliquées. */
.nova-hub{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:16px}
@media (min-width:640px){ .nova-hub{grid-template-columns:repeat(4,1fr)} }
.nova-hub-card{display:flex;flex-direction:column;align-items:flex-start;gap:8px;
  padding:14px;border-radius:var(--r-m);text-align:left;min-width:0;color:inherit;
  /* Flou réduit 24px -> 16px (2026-09-24, même correctif performance que
     .card) : 4 de ces cartes s'affichent ensemble sur l'accueil, un flou
     large multiplié par 4 en même temps est un coût GPU cumulé réel. */
  background-color:rgba(255,255,255,.6);backdrop-filter:blur(16px) saturate(160%);
  -webkit-backdrop-filter:blur(16px) saturate(160%);border:1px solid rgba(255,255,255,.8);
  transition:transform .25s var(--spring),background-color .25s var(--spring),box-shadow .25s var(--spring)}
[data-theme="dark"] .nova-hub-card{background-color:rgba(22,26,33,.6);border-color:rgba(255,255,255,.08)}
.nova-hub-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-2)}
.nova-hub-card:active{transform:scale(.98)}
/* CORRECTIF (2026-10-07, refonte visuelle §3/§4) : pièce blanche sur
   carré teinté par module, même principe que .nova-big-icon — cohérent
   avec ChessNovaIcon (ui-primitives.js), qui génère ce balisage. */
.nova-hub-i{width:36px;height:36px;border-radius:11px;display:flex;align-items:center;justify-content:center;
  background:rgba(var(--nf-accent-rgb,77,124,255),.16);color:#fff;flex:0 0 auto}
.nova-hub-i svg{width:18px;height:18px}
.nova-hub-t{font-size:13.5px;font-weight:700;letter-spacing:-.015em;color:var(--ink)}
.nova-hub-s{font-size:11px;color:var(--ink-3);line-height:1.3;margin-top:-5px}
@media (prefers-reduced-motion:reduce){ .nova-hub-card{transition:background-color .25s ease} }
/* ============================================================
   NOVA CARD — refonte visuelle complète (2026-10-07)
   ============================================================
   Composant partagé pour les 5 modules Nova (AI/Bot/Review/News/Event),
   seul endroit à modifier pour changer le langage graphique de TOUTES
   les cartes Nova du site (généré par xxxHomeCard(), js/nova.js, qui
   pose uniquement --nf-accent/--nf-accent-rgb en style inline depuis
   NOVA_FEATURES — jamais une couleur en dur dans un composant, §9 de la
   demande). "Plaque de verre teinté bleu nuit" : plus claire que le
   fond (--nova-surface, un blanc à 7.5% d'opacité sur --nova-bg),
   jamais de gros glow ni d'effet néon/crypto — ombre quasi imperceptible,
   bordure fine, grands arrondis. */
.nova-big-card{display:block;width:100%;text-align:left;padding:22px;
  border-radius:26px;margin-top:14px;
  background:var(--nova-surface);border:1px solid var(--nova-border);
  box-shadow:0 1px 2px rgba(0,0,0,.12),0 12px 28px rgba(0,0,0,.14);
  transition:background-color .2s ease,transform .2s ease,box-shadow .2s ease}
/* Survol desktop UNIQUEMENT (§2 : "sur mobile, aucun effet inutile") —
   légère hausse de luminosité + léger décollé, jamais un effet qui
   pourrait rester "collé" sur un écran tactile sans vrai pointeur. */
@media (hover:hover){
  .nova-big-card:hover{background:var(--nova-surface-hover);transform:translateY(-2px);
    box-shadow:0 1px 2px rgba(0,0,0,.14),0 16px 32px rgba(0,0,0,.18)}
}
.nova-big-card:active{transform:scale(.99)}
@media (prefers-reduced-motion:reduce){ .nova-big-card{transition:box-shadow .2s ease} }
/* ChessNovaIcon (§3) : petit carré arrondi translucide teinté par
   module (--nf-accent-rgb en fond à faible opacité), pièce d'échec
   blanche PARFAITEMENT centrée à l'intérieur — jamais recolorée dans la
   teinte du module (seul le carré l'est) : un vrai jeu d'échecs blanc,
   pas 5 pièces de couleurs différentes. */
.nova-big-icon{width:46px;height:46px;border-radius:14px;display:flex;
  align-items:center;justify-content:center;flex:0 0 auto;margin-bottom:14px;
  background:rgba(var(--nf-accent-rgb,77,124,255),.16);color:#fff}
.nova-big-icon svg{width:24px;height:24px;stroke-width:1.6}
/* .nova-big-body (2ᵉ passe, §8 : "disposition horizontale si l'espace le
   permet sur desktop") : enveloppe tout SAUF l'icône — mobile reste
   identique (bloc empilé, icône au-dessus), desktop passe l'icône et ce
   bloc en ligne (voir le média plus bas), sans dupliquer un seul style
   de texte existant. */
.nova-big-body{min-width:0}
@media (min-width:640px){
  .nova-big-card{display:flex;align-items:flex-start;gap:18px}
  .nova-big-icon{margin-bottom:0;margin-top:1px}
  .nova-big-body{flex:1}
}
/* Nom du module (§5) : jamais --accent (orange, exclu des composants
   Nova par consigne explicite) — fallback --nova-ai si jamais une carte
   oublie de poser --nf-accent, jamais "transparent" invisible. */
.nova-big-eyebrow{font-size:var(--t-label);font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--nf-accent,var(--nova-ai))}
.nova-big-lead{font-size:16.5px;font-weight:600;line-height:1.45;margin-top:10px;max-width:40ch;color:var(--nova-text)}
.nova-big-stats{list-style:none;margin-top:14px;display:flex;flex-direction:column;gap:8px}
.nova-big-stats li{display:flex;align-items:center;gap:8px;font-size:14.5px;color:var(--nova-text-secondary)}
.nova-big-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.nova-big-sub{font-size:14px;color:var(--nova-text-secondary);margin-top:10px;line-height:1.4}
.nova-big-link{display:block;margin-top:16px;font-size:14px;font-weight:700;color:var(--nf-accent,var(--nova-ai))}
/* Conversation NovaBot (refonte expérience, 2026-10-08) : RÉORGANISATION
   du contenu uniquement, pas une nouvelle identité — chaque règle
   ci-dessous réutilise un token déjà existant (--accent/--accent-ink/
   --bg-2/--ink/--r-m/--line), jamais une couleur ou un rayon inventés
   pour l'occasion. Préfixe .nb- déjà en usage ailleurs (.nb-pf2,
   .nb-hero-v2, accueil) : même convention de nommage, pas un nouveau
   système. */
/* ============================================================
   NOVABOT V6 — IDENTITÉ VISUELLE PROPRE (maquettes validées, 2026-10-12)
   ============================================================
   Retour utilisateur explicite : "l'interface ressemble encore à un
   prototype [...] je veux un niveau de finition comparable à ChatGPT,
   Claude [...] Conserve l'identité NovaTitre : fond bleu nuit profond,
   violet comme accent de marque." Le reste du site est actuellement en
   noir/blanc pur (--bg/--ink globaux, voir plus haut dans ce fichier,
   retour utilisateur du 2026-10-02) — NovaBot reste volontairement une
   SURFACE À PART, comme ChatGPT/Claude ont leur propre environnement,
   jamais un retour en arrière sur le choix noir/blanc du reste du site.
   Portée : UNIQUEMENT .nb-page et ses descendants (CSS en cascade,
   jamais de fuite vers le reste de l'app — vérifié : aucune règle
   ci-dessous ni plus bas ne touche :root). La plupart des composants
   .nb-* existants consomment déjà --bg-2/-3/-4, --ink/-2/-3/-4, --accent,
   --line — les redéfinir ICI suffit à les faire basculer sans toucher
   leurs propres règles une par une. #805DFF = violet de marque NovaBot
   déjà utilisé ailleurs (NOVA_FEATURES.novabot.accent, js/ui-primitives.js)
   — jamais une nouvelle couleur inventée pour l'occasion. */
.nb-page{
  --bg:#0A0D1C; --bg-2:#11152A; --bg-3:#1A1F3D; --bg-4:#232A4D;
  --ink:#F1F0F7; --ink-2:#F1F0F7; --ink-3:#A6A8C4; --ink-4:#6E7196;
  --line:rgba(255,255,255,.08); --line-2:rgba(255,255,255,.05);
  --accent:#805DFF; --accent-hover:#9478FF; --accent-press:#6B46F0;
  --accent-soft:rgba(128,93,255,.16); --accent-ink:#ffffff;
  background:var(--bg); color:var(--ink); border-radius:var(--r-l);
  padding:0; min-height:0;
  /* Barres de défilement NovaBot (audit 2026-10-14, capture d'écran à
     l'appui : barres jaunes/dorées dans NovaBot et son historique).
     Scope volontairement limité à .nb-page et ses descendants -- demande
     explicite : "évite toute règle CSS globale qui pourrait modifier
     d'autres pages". Aucune règle de scrollbar n'existe ailleurs dans le
     site pour ces composants, donc aucun autre module n'est affecté.
     scrollbar-width/-color : Firefox + Chrome/Edge récents.
     ::-webkit-scrollbar-* : Chrome/Edge/Safari desktop. Volontairement
     aucune règle équivalente pour le tactile (mobile Safari/Chrome
     Android ignorent déjà ces propriétés et gardent leur propre barre
     native fine -- exactement le rendu demandé, rien à ajouter). */
  scrollbar-width:thin;
  scrollbar-color:rgba(110,113,150,.45) transparent;
}
.nb-page ::-webkit-scrollbar{ width:9px; height:9px; }
.nb-page ::-webkit-scrollbar-track{ background:transparent; }
.nb-page ::-webkit-scrollbar-thumb{
  background:rgba(110,113,150,.45); border-radius:999px;
  border:2px solid transparent; background-clip:padding-box;
}
.nb-page ::-webkit-scrollbar-thumb:hover{ background:rgba(110,113,150,.7); }
.nb-page ::-webkit-scrollbar-corner{ background:transparent; }
/* Barre de défilement de la PAGE elle-même (html/body, hors .nb-page --
   elle en dépasse forcément) : retour utilisateur après la 1re
   prévisualisation, la barre jaune restait visible sur le bord de la
   page pendant que NovaBot est affiché. body:has(.nb-page) : syntaxe
   nécessairement globale (html/body sont partagés par toutes les
   pages), mais ne PREND EFFET que quand .nb-page est présent dans le
   DOM, donc jamais quand une autre page (Accueil, Marchés, Radar,
   portefeuille, autres modules IA) est affichée -- aucune autre page
   n'est visuellement affectée. */
body:has(.nb-page){
  scrollbar-width:thin;
  scrollbar-color:rgba(60,66,94,.38) transparent;
}
body:has(.nb-page)::-webkit-scrollbar{ width:9px; height:9px; }
body:has(.nb-page)::-webkit-scrollbar-track{ background:transparent; }
body:has(.nb-page)::-webkit-scrollbar-thumb{
  background:rgba(60,66,94,.38); border-radius:999px;
  border:2px solid transparent; background-clip:padding-box;
}
body:has(.nb-page)::-webkit-scrollbar-thumb:hover{ background:rgba(60,66,94,.6); }
body:has(.nb-page)::-webkit-scrollbar-corner{ background:transparent; }
[data-theme="dark"] body:has(.nb-page){ scrollbar-color:rgba(200,202,224,.3) transparent; }
[data-theme="dark"] body:has(.nb-page)::-webkit-scrollbar-thumb{ background:rgba(200,202,224,.3); }
[data-theme="dark"] body:has(.nb-page)::-webkit-scrollbar-thumb:hover{ background:rgba(200,202,224,.5); }
/* La page NovaBot est posée SUR le fond blanc global (body garde son
   propre fond) : un padding/radius assure qu'elle se présente comme une
   vraie surface distincte plutôt qu'un rectangle collé au bord. */
@media (min-width:681px){ .nb-page{ padding:20px 22px 26px } }
@media (max-width:680px){ .nb-page{ margin:0 -16px; border-radius:0 } }
/* CORRECTIF (audit 2026-10-12, bug confirmé par capture d'écran iPhone :
   "l'historique s'ouvre en deux temps" — un premier panneau apparaît,
   puis un repositionnement/changement de hauteur survient peu après) :
   view.innerHTML est entièrement recréé à CHAQUE render() (voir
   index.html, commentaire au-dessus de render()), y compris pour un
   simple clic sur "Historique" — ce qui redéclenche .page-in (.38s,
   transform) et .page-in>* (stagger, .5s, transform) sur un TOUT
   NOUVEAU noeud à chaque fois, pas seulement à la navigation.
   Piège CSS classique : tant que .nb-page (ou .nb-chat-shell, son
   enfant direct et PARENT de .nb-sidebar) a un `transform` actif (même
   en cours d'animation), il devient le bloc de référence pour tout
   descendant en position:fixed — .nb-sidebar s'affiche alors par
   rapport à CET ancêtre, pas au viewport, jusqu'à la fin de
   l'animation (~380-500ms), où il "saute" à sa vraie position plein
   écran. Solution ciblée : .nb-page et ses enfants directs gardent un
   simple fondu en opacité (aucune perte de transform à n'importe quel
   instant), le reste du site garde pageIn/stagger avec transform
   intacts — portée strictement limitée à .nb-page (cf. commentaire plus
   haut sur le scope de cette classe). */
.page-in.nb-page{animation:nbPageInFade .3s ease}
.page-in.nb-page>*{animation:nbPageInFade .35s ease backwards}
.page-in.nb-page>*:nth-child(1){animation-delay:.02s}
.page-in.nb-page>*:nth-child(2){animation-delay:.06s}
@keyframes nbPageInFade{from{opacity:0}to{opacity:1}}

/* --- en-tête compact (remplace Retour pleine largeur + eyebrow + h1 +
   gros onglets, dénoncé explicitement : "le bouton Retour ne doit plus
   être une énorme barre qui traverse toute la largeur de l'écran"). Une
   seule ligne discrète + une pastille de segment centrée, léger. */
.nb-topbar{display:flex;flex-direction:column;gap:10px;padding:12px 4px 14px;
  border-bottom:1px solid var(--line-2);margin-bottom:14px}
.nb-topbar-row{display:flex;align-items:center;justify-content:space-between;gap:10px}
.nb-icon-btn{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;background:none;border:none;color:var(--ink-3);flex:0 0 auto;
  transition:background-color .15s ease,color .15s ease}
.nb-icon-btn:hover{background:var(--bg-3);color:var(--ink)}
.nb-icon-btn svg{width:16px;height:16px}
.nb-brand-mark{display:flex;align-items:center;gap:8px;min-width:0}
.nb-rook{width:22px;height:22px;border-radius:7px;flex:0 0 auto;
  background:linear-gradient(145deg,var(--accent),#5B3FD9);display:flex;align-items:center;
  justify-content:center;box-shadow:0 0 0 1px rgba(255,255,255,.12),0 4px 10px -2px rgba(128,93,255,.5)}
.nb-rook svg{width:12px;height:12px;color:#fff}
.nb-brand-mark b{font-size:14.5px;font-weight:750;letter-spacing:-.01em;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.nb-seg-pill{align-self:center;display:flex;background:var(--bg-2);border:1px solid var(--line);
  border-radius:999px;padding:3px;width:fit-content}
.nb-seg-pill button{appearance:none;border:none;background:none;color:var(--ink-3);
  font-size:12.3px;font-weight:650;padding:7px 16px;border-radius:999px;transition:.15s ease}
.nb-seg-pill button[aria-pressed="true"]{background:var(--accent);color:#fff}

/* --- onglets Conversations/Portefeuille : .seg déjà existant (période du
   portefeuille personnel, etc.), réutilisé tel quel, juste espacé ici. */
.nb-tabs{margin-top:18px}

/* --- disposition générale : sidebar + colonne de conversation (desktop :
   2 colonnes ; mobile : sidebar en panneau latéral, voir le média plus
   bas). Toujours var(--bg-2)/var(--line)/var(--r-l) déjà existants. */
.nb-chat-shell{display:flex;gap:16px;margin-top:16px;align-items:flex-start}
.nb-sidebar{width:272px;flex:0 0 272px;display:flex;flex-direction:column;gap:10px;
  background:var(--bg-2);border:1px solid var(--line);border-radius:var(--r-l);padding:14px;
  max-height:600px}
.nb-sidebar-search .field{width:100%;padding:10px 14px;font-size:13.5px}
.nb-conv-list{display:flex;flex-direction:column;gap:3px;overflow-y:auto;flex:1;min-height:0}
.nb-conv-item{display:flex;align-items:center;gap:2px;border-radius:var(--r-s);transition:background-color .15s ease}
.nb-conv-item:hover,.nb-conv-item.active{background:var(--bg-3)}
.nb-conv-item-main{flex:1;min-width:0;text-align:left;background:none;border:none;color:var(--ink);
  cursor:pointer;padding:9px 10px;display:flex;flex-direction:column;gap:2px}
.nb-conv-item-title{font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nb-conv-item-date{font-size:11px;color:var(--ink-4)}
.nb-conv-item-actions{display:flex;gap:1px;flex:0 0 auto;padding-right:4px}
.nb-conv-item-actions button{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;color:var(--ink-4);background:none;border:none;cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.nb-conv-item-actions button:hover{background:var(--bg-4);color:var(--ink)}
.nb-conv-item-actions svg{width:14px;height:14px}
/* V5 Phase A (§2 : "archiver les conversations") — section repliable sous
   la liste active, jamais dans le même flex:1 (sinon un historique long
   ferait disparaître le bouton "+ Nouvelle conversation" hors de
   .nb-sidebar, qui a un max-height fixe). max-height propre + overflow-y
   pour que .nb-sidebar garde une hauteur totale prévisible même ouverte. */
.nb-archive-toggle{display:flex;align-items:center;gap:7px;padding:8px 10px;font-size:12.5px;
  font-weight:600;color:var(--ink-3);background:none;border:none;border-radius:var(--r-s);
  cursor:pointer;-webkit-tap-highlight-color:transparent;flex:0 0 auto}
.nb-archive-toggle:hover{background:var(--bg-3);color:var(--ink)}
.nb-archive-toggle svg{width:14px;height:14px;flex:0 0 auto}
.nb-conv-list-archived{display:flex;flex-direction:column;gap:3px;overflow-y:auto;
  max-height:160px;flex:0 0 auto;padding-top:2px;border-top:1px solid var(--line)}
.nb-sidebar-backdrop{display:none}
.nb-thread-col{flex:1;min-width:0;display:flex;flex-direction:column}
.nb-thread-head{display:none}
.nb-history-btn{display:flex;align-items:center;gap:7px;background:var(--bg-2);border:1px solid var(--line);
  border-radius:100px;padding:9px 16px;font-size:13.5px;font-weight:650;color:var(--ink);
  -webkit-tap-highlight-color:transparent}
.nb-history-btn svg{width:16px;height:16px}

/* --- fil de conversation (refonte V4, §2 : "la grande carte violette
   qui enveloppe toute la conversation" supprimée — les messages reposent
   directement sur le fond NovaTitre, plus d'espace vertical, bulles
   utilisateur discrètes, réponses NovaBot aérées avec avatar). */
.nb-chat{display:flex;flex-direction:column;flex:1;min-height:0}
/* RETOUR EN ARRIÈRE ASSUMÉ (retour utilisateur 2026-10-12, capture
   d'écran iPhone à l'appui : "le message de bienvenue apparaît presque
   en bas de l'écran, avec un immense vide au-dessus [...] les premiers
   messages [doivent être] placés naturellement en haut") : le
   margin-top:auto posé le 2026-10-09 pour coller une conversation courte
   au bas de l'écran produisait EXACTEMENT ce défaut — un seul message
   d'accueil se retrouvait plaqué tout en bas, avec un vide énorme
   au-dessus, au lieu de commencer naturellement en haut comme ChatGPT/
   Claude au démarrage d'une conversation. Retiré : flex-start (comporte-
   ment par défaut) laisse les messages s'empiler depuis le haut ; .nb-
   chat-log garde flex:1 (remplit toujours la hauteur disponible jusqu'à
   la saisie, qui reste donc TOUJOURS en bas de l'écran, peu importe le
   nombre de messages) — seul le texte lui-même n'est plus forcé vers le
   bas. Un vide sous le dernier message avant la saisie est attendu et
   normal pour une conversation courte (ChatGPT se comporte identique-
   ment), ce n'est plus confondu avec un bug de mise en page.
   JAMAIS justify-content:flex-end ici (testé en direct, puis retiré le
   2026-10-09) : combiné à overflow-y:auto sur une conversation LONGUE,
   ça fait plafonner scrollHeight à clientHeight dans Chrome/WebKit — les
   messages les plus anciens deviennent invisibles ET non atteignables
   au scroll, un bug connu de flexbox+overflow — toujours valable, ne
   jamais réintroduire. Le défilement vers le bas à l'arrivée d'un
   nouveau message reste géré en JS (voir render(), index.html —
   recollerEnBas), jamais par un artifice CSS. */
.nb-chat-log{display:flex;flex-direction:column;flex:1;min-height:0;gap:18px;max-height:min(70vh,640px);overflow-y:auto;
  overscroll-behavior:contain;padding:2px 2px 10px}
.nb-msg-row{display:flex;gap:10px;align-items:flex-start}
.nb-avatar{flex:0 0 auto;width:28px;height:28px;border-radius:50%;margin-top:2px;
  background:var(--nova-bot);display:flex;align-items:center;justify-content:center}
.nb-avatar svg{width:15px;height:15px}
.nb-msg-col{flex:1;min-width:0;display:flex;flex-direction:column;gap:10px}
.nb-msg{padding:11px 15px;border-radius:var(--r-m);font-size:14.5px;line-height:1.5;
  white-space:pre-wrap;word-break:break-word}
.nb-msg-user{align-self:flex-end;max-width:78%;background:var(--accent-soft);color:var(--ink)}
.nb-msg-bot{background:none;border:none;color:var(--ink);padding:0}
.nb-msg-bot.nb-msg-error{border:1px solid var(--down);border-radius:var(--r-m);padding:11px 15px}
/* Bouton Réessayer (audit 2026-10-12, §4) : dans le même bloc que le
   message d'erreur, jamais une action de la barre .nb-msg-actions (ces
   deux UI sont indépendantes — un message en erreur n'a pas de
   copier/régénérer/feedback puisqu'il n'y a pas de vraie réponse). */
.nb-retry-btn{display:flex;align-items:center;gap:6px;margin-top:10px;padding:7px 13px;
  border:1px solid var(--down);border-radius:999px;background:none;color:var(--down);
  font-size:13px;font-weight:600;cursor:pointer}
.nb-retry-btn svg{width:14px;height:14px}
.nb-retry-btn:active{opacity:.7}

/* CORRECTIF (audit 2026-10-13, "conversations disparues") : badge discret
   quand un message a survécu à la relance automatique (novabotSauvegarder
   Message(), js/novabot-chat.js) sans jamais réussir à persister côté
   serveur — jamais un takeover visuel comme .nb-msg-error (le message
   reste parfaitement lisible/utilisable dans cette session), juste un
   signal clair + une reprise manuelle en un tap. --warn (ambre) : un
   AVERTISSEMENT réel ici, pas une décoration (voir l'audit couleurs
   jaune/doré du 2026-10-13 — ce cas précis en est l'exception légitime). */
.nb-save-failed{display:flex;align-items:center;gap:5px;margin-top:4px;font-size:11.5px;color:var(--warn)}
.nb-save-failed svg{width:11px;height:11px;flex:0 0 auto}
.nb-save-failed button{background:none;border:none;color:var(--warn);font-size:11.5px;font-weight:700;
  text-decoration:underline;cursor:pointer;padding:0}

/* --- actions sur un message : révélées au survol sur ordinateur (évite
   d'alourdir visuellement un fil de conversation calme). */
.nb-msg-user-wrap{display:flex;flex-direction:column}
.nb-msg-actions{display:flex;gap:4px;margin-top:6px;opacity:0;transition:opacity .15s ease}
.nb-msg-actions.nb-msg-actions-user{justify-content:flex-end}
.nb-msg-user-wrap:hover .nb-msg-actions,.nb-msg-row:hover .nb-msg-actions,
.nb-msg-actions:focus-within{opacity:1}
/* CORRECTIF (audit 2026-10-12, §2 : "pas une grosse barre technique
   permanente sous chaque message [...] présentation discrète et
   contextuelle, adaptée aux interactions tactiles sur iPhone") :
   auparavant opacity:1 inconditionnel sur mobile (pas de survol au
   doigt), donc visible en permanence sous CHAQUE message. Collapsed par
   défaut (max-height:0, en plus de opacity:0, pour ne réserver AUCUN
   espace — pas juste invisible, vraiment absent de la mise en page tant
   que fermé) ; .nb-revealed (posée par novabotMessageActionsBasculer(),
   js/novabot-chat.js, au tap sur la bulle) l'ouvre. Un seul message
   ouvert à la fois (état transitoire, jamais persisté). */
@media (max-width:680px){
  .nb-msg-actions{max-height:0;overflow:hidden;margin-top:0;
    transition:opacity .15s ease,max-height .2s ease,margin-top .2s ease}
  .nb-msg-actions.nb-revealed{opacity:1;max-height:44px;margin-top:6px}
  .nb-msg:not(.nb-msg-error){cursor:pointer}
}
.nb-msg-actions button{display:flex;align-items:center;gap:5px;background:var(--bg-2);
  border:1px solid var(--line);color:var(--ink-4);font-size:11px;font-weight:600;
  padding:5px 9px;border-radius:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}
.nb-msg-actions button:hover{color:var(--ink);border-color:var(--accent)}
.nb-msg-actions button.on{color:var(--accent-hover);border-color:var(--accent)}
.nb-msg-actions svg{width:11px;height:11px}

/* --- fiche décision (V6 §7) : toutes les valeurs viennent de
   state.novabot.decisions (voir novabotDecisionCard(), js/nova.js), le
   cours actuel de QUOTES comme partout ailleurs — jamais du modèle. */
.nb-decision-card{margin-top:10px;background:var(--bg-2);border:1px solid var(--line);
  border-radius:14px;padding:13px 15px;max-width:360px}
.nb-decision-card-head{display:flex;align-items:center;gap:10px}
.nb-decision-ticker{width:32px;height:32px;border-radius:9px;background:var(--bg-3);
  display:flex;align-items:center;justify-content:center;font-size:10.5px;font-weight:750;
  color:var(--accent-hover);flex:0 0 auto}
.nb-decision-card-head .t{flex:1;min-width:0}
.nb-decision-card-head .name{font-size:13px;font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nb-decision-card-head .sub{font-size:11px;color:var(--ink-4)}
.nb-decision-evol{font-family:var(--f-mono,inherit);font-size:12px;font-weight:700;
  padding:3px 8px;border-radius:999px;flex:0 0 auto}
.nb-decision-evol.up{background:var(--green-50);color:var(--green-700)}
.nb-decision-evol.down{background:var(--down-soft);color:var(--down)}
.nb-decision-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px 14px;margin-top:11px}
.nb-decision-grid .l{display:block;font-size:10.5px;color:var(--ink-4);margin-bottom:2px}
.nb-decision-grid .v{display:block;font-size:12.6px;font-weight:650;color:var(--ink)}
.nb-decision-reason{font-size:11.8px;color:var(--ink-3);margin:10px 0 0;line-height:1.5;
  padding-top:10px;border-top:1px solid var(--line-2)}

/* --- panneau "NovaBot travaille" (audit 2026-10-13, V7 §4) : chaque
   .nb-activite-etape reflète une étape RÉELLEMENT en cours/terminée/
   échouée de evaluerNovaBot() (js/core.js) — jamais une animation
   décorative sans travail réel derrière. Dans .nb-page (thème sombre
   NovaBot), hérite --bg-2/--accent/--up/--down comme le reste. */
.nb-activite-panel{display:flex;flex-direction:column;gap:2px}
.nb-activite-spin{width:14px;height:14px;border-radius:50%;flex:0 0 auto;
  border:2px solid var(--line);border-top-color:var(--accent);animation:nbActiviteSpin .7s linear infinite}
@keyframes nbActiviteSpin{to{transform:rotate(360deg)}}
.nb-activite-etapes{display:flex;flex-direction:column;margin-top:10px}
.nb-activite-etape{display:flex;align-items:flex-start;gap:10px;padding:7px 0}
.nb-activite-icone{width:18px;height:18px;border-radius:50%;flex:0 0 auto;margin-top:1px;
  display:flex;align-items:center;justify-content:center}
.nb-activite-icone svg{width:12px;height:12px}
.nb-activite-etape.nb-activite-terminee .nb-activite-icone{background:var(--up-soft,rgba(16,185,129,.12));color:var(--up)}
.nb-activite-etape.nb-activite-echouee .nb-activite-icone{background:var(--down-soft);color:var(--down)}
.nb-activite-etape.nb-activite-en_cours .nb-activite-icone i{display:block;width:8px;height:8px;border-radius:50%;
  background:var(--accent);animation:nbActivitePulse 1.2s ease-in-out infinite}
@keyframes nbActivitePulse{0%,100%{opacity:.35}50%{opacity:1}}
.nb-activite-label{font-size:13px;font-weight:600;color:var(--ink)}
.nb-activite-detail{font-size:11.5px;color:var(--ink-4);margin-top:1px}
.nb-activite-resume{font-size:12.5px;color:var(--ink-3);margin:10px 0 0;line-height:1.5;
  padding-top:10px;border-top:1px solid var(--line-2)}
@media (prefers-reduced-motion:reduce){
  .nb-activite-spin{animation:none}
  .nb-activite-etape.nb-activite-en_cours .nb-activite-icone i{animation:none}
}

/* --- mémoire (V6 §6) : exclusions/préférences supprimables, affichées
   dans la feuille "Mon mandat" (hors .nb-page, garde les tokens --accent/
   --bg-2/--ink/--line/--down du thème général du site, pas ceux de
   NovaBot). */
.nb-memory-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
.nb-memory-chip{display:inline-flex;align-items:center;gap:6px;background:var(--bg-2);
  border:1px solid var(--line);border-radius:999px;padding:5px 6px 5px 11px;font-size:12px;
  font-weight:550;color:var(--ink-2)}
.nb-memory-chip.hard{border-color:var(--down-soft);background:var(--down-soft);color:var(--down)}
.nb-memory-chip button{width:17px;height:17px;border-radius:50%;border:none;background:none;
  color:inherit;opacity:.65;font-size:13px;line-height:1;cursor:pointer;display:flex;
  align-items:center;justify-content:center}
.nb-memory-chip button:hover{opacity:1;background:rgba(0,0,0,.08)}

.nb-msg-edit-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
.nb-edit-box{background:var(--bg-2);border:1.5px solid var(--accent);border-radius:16px;
  padding:11px 14px;font-size:14.5px;color:var(--ink);max-width:82%;width:100%;line-height:1.5}
.nb-edit-box textarea{width:100%;background:none;border:none;color:var(--ink);font:inherit;
  font-size:inherit;line-height:inherit;resize:vertical;min-height:44px;outline:none;padding:0}
.nb-edit-btns{display:flex;gap:7px;margin-top:9px;justify-content:flex-end}
.nb-edit-btns button{font-size:11.5px;font-weight:650;padding:7px 13px;border-radius:9px;
  border:none;cursor:pointer}
.nb-edit-btns .cancel{background:var(--bg-3);color:var(--ink-3)}
.nb-edit-btns .save{background:var(--accent);color:#fff}
.nb-edit-note{font-size:11px;color:var(--ink-4);align-self:flex-end;max-width:82%;margin:0}
/* "Nova réfléchit…" (§2/§10) : pastille discrète, jamais le texte brut
   "Chargement…" déjà jugé trop générique ailleurs dans l'app. */
.nb-typing-row{display:flex;align-items:center;gap:4px;padding:9px 14px;background:var(--bg-2);
  border-radius:14px;align-self:flex-start;position:relative}
.nb-typing-row i{width:6px;height:6px;border-radius:50%;background:var(--accent-hover);display:inline-block;
  animation:nbTypingDot 1.1s ease-in-out infinite;transform:translateY(0)}
.nb-typing-row i:nth-child(2){animation-delay:.15s}
.nb-typing-row i:nth-child(3){animation-delay:.3s}
.nb-typing-row b{font-size:12.3px;color:var(--ink-4);font-style:normal;font-weight:500;margin-left:5px}
@keyframes nbTypingDot{0%,60%,100%{opacity:.35;transform:translateY(0)}30%{opacity:1;transform:translateY(-3px)}}
@media (prefers-reduced-motion:reduce){ .nb-typing-row i{animation:none} }
/* Lueur violette très subtile sur l'avatar pendant que Nova réfléchit —
   jamais un faux raisonnement affiché, uniquement un signal d'activité. */
.nb-msg-row:has(+ .nb-msg-row .nb-typing-row) .nb-avatar,
.nb-msg-row:has(.nb-typing-row) .nb-avatar{position:relative}
.nb-msg-row:has(.nb-typing-row) .nb-avatar::after{content:'';position:absolute;inset:-4px;
  border-radius:50%;border:1.5px solid var(--accent);opacity:0;animation:nbPulse 1.8s ease-out infinite}
@keyframes nbPulse{0%{opacity:.5;transform:scale(1)}100%{opacity:0;transform:scale(1.7)}}
@media (prefers-reduced-motion:reduce){ .nb-msg-row:has(.nb-typing-row) .nb-avatar::after{animation:none} }
.nb-suggestions{display:flex;flex-wrap:wrap;gap:8px;margin-top:2px}
.nb-suggestion-chip{padding:9px 14px;border-radius:100px;background:var(--bg-3);border:1px solid var(--line);
  font-size:13px;font-weight:550;color:var(--ink-2);cursor:pointer;-webkit-tap-highlight-color:transparent}
.nb-suggestion-chip:hover{background:var(--bg-4);color:var(--ink)}
/* Saisie : zone de texte auto-agrandissante (remplace l'ancien <input>
   fixe) + bouton d'envoi circulaire ; .nb-stop-btn réutilise la même
   base que .nb-send-btn, jamais une nouvelle couleur, juste permutée
   avec le bouton d'envoi tant qu'une génération est en cours. */
/* Une seule barre arrondie au fond légèrement contrasté (§2 : "barre de
   composition premium"), pas deux éléments arrondis côte à côte — le
   textarea devient transparent/sans bordure, SEUL le conteneur porte le
   fond+la bordure+le rayon. */
.nb-chat-input{display:flex;gap:8px;align-items:flex-end;margin-top:10px;
  background:var(--bg-2);border:1px solid var(--line);border-radius:22px;padding:7px 7px 7px 16px}
/* CORRECTIF (audit 2026-10-12, bug confirmé : "Safari provoque un
   comportement visuel anormal" au toucher du champ) : cause exacte —
   iOS Safari zoome automatiquement la page au focus sur tout champ de
   saisie dont le font-size calculé est SOUS 16px (seuil documenté
   WebKit, aucun rapport avec le viewport ou un bug) ; 14.5px déclenchait
   ce zoom à chaque fois. Jamais user-scalable=no/maximum-scale=1 dans le
   <meta viewport> (demande explicite : ne pas bloquer le zoom
   d'accessibilité pour masquer le problème) — on supprime le
   DÉCLENCHEUR, pas le zoom lui-même. 16px (au lieu de 14.5px) est la
   correction standard documentée, pas un contournement. */
.nb-chat-input textarea{flex:1;min-width:0;resize:none;max-height:120px;min-height:22px;
  background:none;border:none;padding:7px 0;
  font:inherit;font-size:16px;line-height:1.4;color:var(--ink);font-family:inherit}
.nb-chat-input textarea:focus{outline:none}
.nb-send-btn{flex:0 0 auto;width:36px;height:36px;border-radius:50%;background:var(--accent);
  border:none;color:var(--accent-ink);display:flex;align-items:center;justify-content:center;
  cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background-color .15s ease}
.nb-send-btn:hover{background:var(--accent-hover)}
.nb-send-btn svg{width:16px;height:16px}
.nb-send-btn[disabled]{opacity:.4;cursor:default}
.nb-send-btn.nb-stop-btn{background:var(--bg-3);color:var(--ink);border:1px solid var(--line)}
[data-theme="dark"] .nb-send-btn{background:#fff;color:#000}
[data-theme="dark"] .nb-send-btn.nb-stop-btn{background:var(--bg-3);color:#fff}

/* --- portefeuille (inspiration Revolut, §5) : .card/.price/.tag/.kv/
   .chart-stats/.row déjà existants (voir page-portfolio.js), seul .nb-hero
   ajoute un centrage propre à cette carte "valeur totale" en tête de
   page. */
.nb-hero{text-align:center}
.nb-hero .btns{justify-content:center}
/* Graphique tactile (§5 : "valeur à une date précise au toucher") :
   survole/glisse sur la courbe déjà rendue par areaChart() (inchangée),
   une infobulle positionnée en %, jamais de nouveau composant de
   graphique. */
.nb-chart-touch{position:relative}
.nb-chart-tip{position:absolute;top:-8px;transform:translate(-50%,-100%);background:#fff;color:#000;
  font-size:11.5px;font-weight:700;padding:4px 9px;border-radius:8px;white-space:nowrap;pointer-events:none;z-index:2}
/* Répartition liquidités/actions (§5) : barre empilée simple, réutilise
   --accent/--bg-3 déjà existants plutôt qu'un nouveau donut. */
.nb-alloc-bar{display:flex;height:8px;border-radius:999px;overflow:hidden;background:var(--bg-3)}
.nb-alloc-legend{display:flex;gap:16px;margin-top:10px;font-size:12px;color:var(--ink-3)}
.nb-alloc-legend span{display:inline-flex;align-items:center;gap:6px}
.nb-alloc-dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex:0 0 auto}
/* Positions (§5) : pastille "logo" (initiales du ticker) + poids du
   portefeuille, ajoutés à .row déjà existant plutôt qu'un nouveau
   composant de ligne. */
.nb-pos-logo{width:34px;height:34px;border-radius:10px;background:var(--accent-soft);color:var(--accent);
  display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex:0 0 auto}
.nb-pos-weight{font-size:11px;color:var(--ink-4)}

/* <details>/<summary> natifs (aucun JS/CSS de toggle à écrire) : les
   anciens réglages bruts restent accessibles (retour utilisateur :
   "les règles restent accessibles pour modification"), repliés par
   défaut pour ne plus être le contenu principal de la page. */
.nb-advanced summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;
  color:var(--ink-2);-webkit-tap-highlight-color:transparent}
.nb-advanced summary::-webkit-details-marker{display:none}
.nb-advanced summary::before{content:'▸';display:inline-block;transition:transform .2s ease;color:var(--ink-4)}
.nb-advanced[open] summary::before{transform:rotate(90deg)}

/* --- mobile (§10/§15) : "le bouton d'historique ouvre un panneau
   latéral, comme ChatGPT [...] une saisie accessible au-dessus du
   clavier [...] aucune double barre gênante". Le panneau latéral devient
   position:fixed hors-écran, coulissant via transform — jamais display:
   none (garde la transition fluide). */
@media (max-width:900px){
  .nb-chat-shell{display:block;margin-top:14px}
  .nb-thread-head{display:flex;margin-bottom:10px}
  .nb-sidebar{position:fixed;top:0;left:0;bottom:0;width:86vw;max-width:340px;z-index:70;
    border-radius:0;max-height:none;transform:translateX(-100%);transition:transform .25s var(--spring);
    padding:calc(14px + env(safe-area-inset-top)) 14px 14px;
    /* CORRECTIF (2026-10-09, vérifié en direct : panneau latéral mobile
       illisible, le contenu du fil passait au travers) : var(--bg-2) (7.5%
       de blanc) convient à une carte posée PARMI d'autres sur la page,
       pas à un panneau qui doit RECOUVRIR le contenu en plein écran —
       repris sur var(--bg), le même fond opaque que .sheet (la modale déjà
       existante, voir plus haut dans ce fichier), pour la même raison. */
    background:var(--bg)}
  .nb-sidebar-open .nb-sidebar{transform:translateX(0)}
  .nb-sidebar-backdrop{display:block;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:65;
    opacity:0;pointer-events:none;transition:opacity .25s ease}
  .nb-sidebar-open .nb-sidebar-backdrop{opacity:1;pointer-events:auto}
  .nb-chat-log{max-height:min(58vh,440px)}
  .nb-msg{font-size:14px}
  .nb-msg-user{max-width:88%}
  /* Immersif : chaîne flex du haut (.wrap) jusqu'au fil (.nb-chat-log),
     chacun avec min-height:0 (le piège flexbox classique : sans ça, un
     enfant flex ne rétrécit/scroll jamais sous sa taille de contenu) —
     SANS cette chaîne, rien n'oblige .nb-chat-log à remplir l'espace
     sous les onglets/Historique, et une conversation courte laisse un
     grand vide entre la saisie et le bas de l'écran (retour utilisateur
     2026-10-09 : "espaces vides excessifs"), la saisie restant "collée"
     juste après les messages plutôt qu'au bas réel de la zone. Scopé à
     .nb-immersive (mobile, vue Conversations uniquement) : aucune autre
     page ne change. */
  /* height (pas min-height) : un VRAI plafond, pas juste un plancher —
     sinon flex:1 plus bas n'a aucun budget fixe à partir duquel se
     calculer et .nb-chat-log pousse la saisie hors de l'écran (testé en
     direct : overflowDiff > 0, saisie sous le bord inférieur). Moins
     --top-header-h (index.html, syncTopHeaderHeight()) : .wrap est le
     FRÈRE de <header class="top"> (toujours visible, ~60px, mesuré en
     direct), pas son enfant — 100vh plein déborderait donc du bandeau.
     vh d'abord (repli), dvh ensuite (tient compte du clavier/barres iOS
     quand supporté — un navigateur qui ne connaît pas dvh ignore cette
     ligne et garde vh). */
  /* CORRECTIF (audit 2026-10-12, bug confirmé par capture d'écran iPhone :
     "grand rectangle presque noir, avec des marges et des bordures qui
     cassent la continuité visuelle [...] donne l'impression d'une page
     web insérée dans une autre page") : .wrap garde son padding GÉNÉRIQUE
     de page (32px / 22px / 120px — var(--sp-4)/22px/var(--sp-6)+40px,
     prévu pour une page normale avec dock en bas) même en mode immersif,
     où .nb-page a SA PROPRE barre de tête et sa propre saisie ancrée —
     ce padding créait un cadre visible (clair autour, sombre dedans)
     plutôt qu'une surface qui occupe l'écran. padding:0 ici + margin:0
     sur .nb-page (au lieu du margin:0 -16px générique, qui ne
     compensait qu'un padding mobile standard de 16px, pas les 22px/120px
     réels de .wrap) donnent un fond plein écran, sans cadre ni marge
     résiduelle. */
  body.nb-immersive .wrap{display:flex;flex-direction:column;padding:0;
    height:calc(100vh - var(--top-header-h, 60px));
    height:calc(100dvh - var(--top-header-h, 60px))}
  /* CORRECTIF (audit 2026-10-12 suite : "le bouton d'envoi blanc est
     coupé sur la droite" au clavier) : en vidant le padding de .wrap
     ci-dessus pour supprimer le cadre clair/sombre, plus RIEN entre
     .nb-page et ses enfants (.nb-topbar, .nb-chat-input...) ne porte de
     marge horizontale propre — tout, y compris le bouton d'envoi
     circulaire, se retrouvait littéralement au ras du bord de l'écran,
     sans la moindre marge de sécurité. Le padding se pose ICI, sur
     .nb-page, PAS sur .wrap : le fond sombre (background, voir plus
     haut) couvre padding ET contenu par défaut, donc la surface reste
     plein écran bord à bord (aucun retour du cadre clair/sombre) tandis
     que le CONTENU respire, avec les safe-area-inset-left/right pour les
     iPhone avec encoche/Dynamic Island en paysage. */
  body.nb-immersive .nb-page{display:flex;flex-direction:column;flex:1;min-height:0;margin:0;
    padding-left:calc(env(safe-area-inset-left, 0px) + 16px);
    padding-right:calc(env(safe-area-inset-right, 0px) + 16px)}
  body.nb-immersive .nb-chat-shell{display:flex;flex-direction:column;flex:1;min-height:0}
  body.nb-immersive .nb-thread-col{min-height:0}
  /* Le fil récupère toute la hauteur libérée par le masquage du dock —
     moins --kb-inset (clavier iOS ouvert, voir index.html) pour que le
     contenu déjà visible ne finisse pas sous la saisie remontée. */
  body.nb-immersive .nb-chat-log{max-height:calc(min(68vh,620px) - var(--kb-inset, 0px))}
  /* Zone de saisie au-dessus de la barre de navigation basse (jamais
     cachée dessous), même variable --dock-h/--dock-gap déjà utilisée
     ailleurs pour ce calcul (voir .wrap) — sauf en immersif, où il n'y a
     plus de dock sous lequel réserver de la place. +var(--kb-inset) dans
     les deux cas : écart réel entre le viewport visuel et le viewport de
     mise en page quand le clavier iOS est ouvert (0px sinon/ailleurs,
     voir index.html) — sans lui la saisie reste ancrée au bas du layout
     viewport et finit cachée sous le clavier. */
  .nb-chat-input{position:sticky;bottom:calc(var(--dock-h, 82px) + var(--dock-gap, 12px) + 8px + var(--kb-inset, 0px));
    background:var(--bg);padding-top:4px}
  body.nb-immersive .nb-chat-input{bottom:calc(env(safe-area-inset-bottom, 0px) + 8px + var(--kb-inset, 0px))}
  /* CORRECTIF (audit esthétique mobile, 2026-10-13) : cible tactile sous
     le minimum recommandé (44×44px) sur les boutons les plus utilisés au
     doigt — bouton d'envoi (36px) et icônes de la barre de tête (retour,
     Historique, 32px). Pas une question de goût : en dessous de 44px, un
     bouton devient difficile à toucher précisément à une main, ce qui
     contribue directement à une impression de finition bâclée sur
     téléphone. Desktop non concerné (scopé à ce media query), où la
     souris n'a pas ce problème de précision. */
  .nb-send-btn{width:44px;height:44px}
  .nb-send-btn svg{width:18px;height:18px}
  .nb-icon-btn{width:44px;height:44px}
  /* border-radius relevé avec le bouton d'envoi agrandi ci-dessus : la
     barre de saisie, désormais plus haute (44px + 7px de marge interne de
     chaque côté ≈ 58px), gardait un rayon de 22px calibré pour son
     ancienne hauteur — resterait visuellement "moins en pilule" sans cet
     ajustement, un détail qui se voit sur un composeur mis en avant. */
  .nb-chat-input{border-radius:29px}
  /* Boutons d'action sous un message (Copier/Régénérer/Reprendre ici...)
     : secondaires et révélés au tap (jamais permanents, voir le
     correctif contextuel du 2026-10-12), mais 5px/9px de marge interne
     les rendait serrés au doigt une fois ouverts. Toujours pas 44px
     plein (seraient disproportionnés à côté du texte du bouton), juste
     assez d'air pour un tap confortable. */
  .nb-msg-actions button{padding:9px 13px}
}
@media (prefers-reduced-motion:reduce){
  .nb-sidebar{transition:none}
  .nb-sidebar-backdrop{transition:none}
}
/* Rangée d'accès Nova (Portefeuille, fiche action — voir novaSuiteRow()
   en JS) : défilement horizontal plutôt que retour à la ligne, jamais
   de compression/chevauchement quelle que soit la largeur (§15,
   320-430px). Même logique de teinte par bouton que .nova-hub-card. */
.nova-suite{display:flex;gap:9px;margin-top:16px;overflow-x:auto;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;padding-bottom:1px}
.nova-suite::-webkit-scrollbar{display:none}
.nova-suite-btn{display:flex;align-items:center;gap:7px;flex:0 0 auto;padding:10px 16px;border-radius:100px;
  background:rgba(var(--nf-accent-rgb),.12);color:var(--nf-accent);font-size:13.5px;font-weight:650;
  border:1px solid rgba(var(--nf-accent-rgb),.22);white-space:nowrap;
  transition:transform .2s var(--spring),background-color .2s ease}
.nova-suite-btn svg{width:16px;height:16px;flex:0 0 auto}
@media (hover:hover){ .nova-suite-btn:hover{background:rgba(var(--nf-accent-rgb),.2);transform:translateY(-1px)} }
.nova-suite-btn:active{transform:scale(.96)}
@media (prefers-reduced-motion:reduce){ .nova-suite-btn{transition:background-color .2s ease} }
/* Bouton "+" Ajouter des fonds (Portefeuille) — visuel uniquement,
   voir data-soon plus haut : jamais de dépôt réel à ce stade. Rond,
   accent primaire, dans l'en-tête de la carte "Valeur totale" plutôt
   qu'une pleine largeur — un ajout de fonds est une action ponctuelle,
   pas la priorité visuelle de la carte (qui reste le montant). */
.nova-add-funds{width:34px;height:34px;border-radius:50%;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  background:var(--accent);color:var(--accent-ink);
  box-shadow:0 4px 14px rgba(236,72,153,.28);
  transition:transform .2s cubic-bezier(.22,1,.36,1),box-shadow .2s ease}
.nova-add-funds svg{width:18px;height:18px}
@media (hover:hover){ .nova-add-funds:hover{transform:scale(1.08)} }
.nova-add-funds:active{transform:scale(.9)}
/* CORRECTIF (bug visuel réel, trouvé en aperçu mobile de la feuille de
   recherche, 2026-09-17) : .row-t était redéfini ICI, en double par
   rapport à sa première déclaration plus haut dans ce fichier (voir
   ".row-t{...overflow:hidden;text-overflow:ellipsis;white-space:nowrap}")
   — cette seconde définition, presque identique mais SANS la troncature,
   écrasait silencieusement la première partout où .row-t est utilisé (une
   ligne d'action dans une liste, un résultat de recherche récente...).
   Conséquence concrète observée en direct : un nom long ("Vanguard Total
   Stock Market Index Fund ETF Shares") débordait de la feuille de
   recherche au lieu d'être tronqué avec des points de suspension,
   provoquant un défilement horizontal inattendu sur mobile. Supprimée
   plutôt que fusionnée : elle n'apportait rien que la première ne fasse
   déjà (même taille, même graisse, +.01em de letter-spacing négligeable). */
.row-s{display:block;font-size:14.3px;color:var(--ink-3);margin-top:2px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.row-end{display:flex;flex-direction:column;align-items:flex-end;gap:3px;flex:0 0 auto;min-width:92px}
.row-px{font-size:17px;font-weight:600;letter-spacing:-.02em}
.row-px.flash-up{animation:priceFlashUp .9s ease-out}
.row-px.flash-down{animation:priceFlashDown .9s ease-out}
@media (prefers-reduced-motion:reduce){ .row-px.flash-up,.row-px.flash-down{animation:none} }
/* Flash de prix, Portefeuille (motion design premium, phase 2, 2026-10-15) :
   mêmes keyframes exactes que .row-px ci-dessus (Marchés/Radar) — même
   mécanisme partagé PRICE_FLASH (js/data-services.js), juste un sélecteur
   différent car la valeur de position n'est pas construite par stockRow().
   Liste bornée en pratique (positions réellement détenues par
   l'utilisateur, jamais 40+), donc aucun risque de rejouer un flash sur
   une longue liste. */
.pf-value.flash-up{animation:priceFlashUp .9s ease-out}
.pf-value.flash-down{animation:priceFlashDown .9s ease-out}
@media (prefers-reduced-motion:reduce){ .pf-value.flash-up,.pf-value.flash-down{animation:none} }
.row-var{font-size:14.3px;font-weight:550}
.row-var.na{color:var(--ink-4)}
/* CORRECTIF (bug réel mesuré en production, 2026-09-30) : 80x40 + carte
   min-width:320px ne laissait plus que 41px pour TOUT .row-main (ticker
   + nom empilés) -- mesuré sur "LVMH Moët Hennessy - Louis Vuitton"
   (besoin réel 257px), tronqué à 1-2 caractères au lieu d'un ellipsis
   lisible. Réduit à 64x32 (voir aussi minmax de .rows-grid ci-dessous,
   320->380px) : les deux ensemble redonnent la place nécessaire au nom,
   jamais une coupe brute (voir .row-s, déjà display:block + ellipsis,
   inchangé). */
.row-spark{flex:0 0 auto;width:64px;height:32px;overflow:hidden;margin-right:14px}
@media (max-width:430px){ .row-spark{display:none} .row-end{min-width:80px} }
/* Ajout (audit desktop, 2026-09-17) : sur grand écran, une liste de
   valeurs (.rows-grid, réservé aux listes de stockRow() — Marché,
   Radar, Explorer, Marchés, Suivi) devient une grille de fiches au lieu
   d'une colonne unique bordée de vide. Volontairement PAS appliqué à
   .rows générique (menus Réglages, Mes listes) : une liste d'actions
   reste plus lisible en une seule colonne. */
@media (min-width:640px){
  /* auto-fit,minmax(...) : même convention que .g-2/.g-3 déjà présents
     dans ce fichier pour les grilles de cartes — colonnes fluides selon
     la largeur réelle, jamais un nombre de colonnes figé par palier. */
  .rows-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:10px}
  .rows-grid .row{border-bottom:none;border-radius:var(--r-m);padding:14px 16px;margin:0;
    min-height:120px;align-items:center;
    background:var(--bg-2);transition:background var(--fast) ease,transform var(--fast) var(--spring),
      box-shadow var(--fast) var(--spring)}
  .rows-grid .row:hover{margin:0;padding-left:16px;padding-right:16px;background:var(--bg-3)}
}
/* CORRECTIF DESIGN (2026-09-22, "mieux que Revolut") : un léger
   soulèvement au survol donne aux fiches cliquables une vraie présence
   physique — Revolut lui-même n'a pas cette micro-interaction sur ses
   listes. Volontairement réservé à (hover:hover) ET (pointer:fine) :
   sans ce garde-fou, un survol au clavier/tactile resterait "collé" en
   l'état soulevé après le tap (le point §49 du brief le signale
   explicitement — "pas de hover-only behavior" sur mobile). */
@media (min-width:640px) and (hover:hover) and (pointer:fine){
  .rows-grid .row:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(13,17,23,.08)}
  [data-theme="dark"] .rows-grid .row:hover{box-shadow:0 8px 20px rgba(0,0,0,.35)}
}
.nsc{padding:18px;border-radius:14px;background:var(--bg-2);margin-bottom:20px}
.nsc-head{display:flex;align-items:center;gap:16px}
.nsc-val{font-size:44px;font-weight:700;letter-spacing:-.05em;line-height:1}
.nsc-val small{font-size:.4em;font-weight:600;color:var(--ink-4);margin-left:2px}
.nsc-meta b{display:block;font-size:13px;font-weight:650;letter-spacing:.04em;text-transform:uppercase}
.nsc-meta span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:4px}
.nsc-fam{margin-top:18px;display:grid;gap:9px}
.nsc-line{display:grid;grid-template-columns:1fr 90px 32px;align-items:center;gap:12px;font-size:13.5px}
.nsc-line i{display:block;height:4px;border-radius:100px;background:var(--bg-3);overflow:hidden}
/* Entrée animée (motion design premium, phase 2, 2026-10-15) : même forme
   exacte que .gauge i (barre qui se remplit à X%, largeur fixée en ligne
   par JS, voir novascoreBloc(), js/nova-ai.js) qui a déjà gaugeIn — .nsc-
   line n'avait jusqu'ici aucune animation d'entrée, lacune confirmée par
   audit. Réutilise la MÊME keyframe (styles.css, plus bas), zéro
   duplication. Aucune dérogation reduced-motion dédiée ici, même
   traitement que .gauge i : gaugeIn n'utilise ni délai ni fill-mode:
   backwards (contrairement à .page-in), la règle générale suffit déjà. */
.nsc-line em{display:block;height:100%;width:0;background:var(--ink);border-radius:100px;
  transition:width 1.1s var(--spring);animation:gaugeIn 1.1s var(--spring) forwards}
.nsc-line b{text-align:right;font-weight:650}
@media (max-width:430px){ .nsc-line{grid-template-columns:1fr 56px 30px} }
.hm{padding:var(--space-lg) 0 var(--space-md);text-align:center}
.hm-lab{font-size:13.5px;color:var(--ink-3);font-weight:550}
.hm-big{font-size:clamp(46px,13vw,72px);font-weight:700;letter-spacing:-.055em;line-height:1;
  margin-top:10px;font-variant-numeric:tabular-nums}
.hm-var{font-size:15px;font-weight:600;margin-top:12px;font-variant-numeric:tabular-nums}
.hm-cta{margin-top:var(--space-lg);min-width:min(320px,100%)}
.hm-pulse{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;
  padding:18px 20px;border-radius:var(--r-l);background:var(--bg-2);text-align:left}
.hm-pulse:hover{background:var(--bg-3)}
.hm-pulse b{display:block;font-size:15px;font-weight:650;letter-spacing:-.02em}
.hm-pulse span span{display:block;font-size:13px;color:var(--ink-3);margin-top:3px}
.hm-score{font-size:28px;font-weight:700;letter-spacing:-.04em;flex:0 0 auto}
.buy{text-align:center;padding:6px 0}
.buy-co{font-size:14px;color:var(--ink-3);font-weight:550}
.buy-title{font-size:24px;font-weight:700;letter-spacing:-.035em;margin-top:4px}
.buy-big{font-size:clamp(56px,17vw,84px);font-weight:700;letter-spacing:-.055em;line-height:1;
  margin-top:22px;font-variant-numeric:tabular-nums;color:var(--ink)}
.buy-big small{font-size:.42em;font-weight:600;margin-left:4px;opacity:.75}
.buy-sub{font-size:14.5px;color:var(--ink-3);margin-top:10px;font-variant-numeric:tabular-nums}
.buy-range{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:100px;
  background:var(--bg-3);margin:30px 0 22px;outline:none}
.buy-range::-webkit-slider-thumb{-webkit-appearance:none;width:30px;height:30px;border-radius:50%;
  background:var(--ink);border:4px solid var(--bg);box-shadow:var(--shadow-2);cursor:pointer}
.buy-range::-moz-range-thumb{width:30px;height:30px;border-radius:50%;background:var(--accent);
  border:4px solid var(--bg);box-shadow:var(--shadow-2);cursor:pointer;border-radius:50%}
.buy-chips{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
.buy-cash{font-size:13px;color:var(--ink-4);margin-top:18px}
.buy-cta{width:100%;margin-top:24px}
.buy-rows{margin-top:26px;text-align:left}
.buy-rows>div{display:flex;justify-content:space-between;align-items:baseline;gap:16px;
  padding:15px 0;border-bottom:1px solid var(--line-2)}
.buy-rows dt{font-size:14px;color:var(--ink-3)}
.buy-rows dd{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums}
.buy-total{border-bottom:none!important;padding-top:18px!important}
.buy-total dt{font-size:15px;color:var(--ink)!important;font-weight:600}
.buy-total dd{font-size:24px;font-weight:700;letter-spacing:-.03em}
.buy-note{font-size:12.5px;color:var(--ink-4);margin-top:16px;line-height:1.5}
.stk-cta{display:flex;gap:10px;margin-top:24px}
/* Actions rapides circulaires (2026-09-22) — voir le commentaire au
   point d'appel (PAGES.stock) pour le contexte de la refonte. */
.stk-qa{display:flex;gap:22px;margin-top:20px}
.qa{display:flex;flex-direction:column;align-items:center;gap:7px;
  background:none;color:var(--ink-2);font-size:12.5px;font-weight:600}
.qa i{display:flex;align-items:center;justify-content:center;position:relative;
  width:52px;height:52px;border-radius:50%;background:var(--bg-2);color:var(--ink);
  transition:background .2s ease-out,color .2s ease-out}
.qa:hover i{background:var(--bg-3)}
.qa[aria-pressed="true"] i{background:var(--accent);color:var(--accent-ink)}
.qa svg{width:20px;height:20px}
.qa-badge{position:absolute;top:-3px;right:-3px;min-width:17px;height:17px;padding:0 4px;
  border-radius:999px;background:var(--accent);color:var(--accent-ink);
  font-size:10.5px;font-weight:700;line-height:17px;text-align:center;
  box-shadow:0 0 0 2.5px var(--bg)}
.hero{position:relative;isolation:isolate}
.hero > *:not(.hero-chart){position:relative;z-index:1}
.hero-chart{
  position:absolute;top:-24px;bottom:-24px;
  left:50%;width:100vw;margin-left:-50vw;
  z-index:0;pointer-events:none;overflow:hidden;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 6%,#000 94%,transparent);
}
.hero-chart svg{width:100%;height:100%;display:block}
.hc-grid line{stroke:var(--line);stroke-width:1;opacity:.6}
[data-theme="dark"] .hc-grid line{opacity:.42}
.hc-line{stroke-dasharray:3200;stroke-dashoffset:3200;
  animation:hcDraw 14s linear infinite}
@keyframes hcDraw{
  0%  {stroke-dashoffset:3200}
  92% {stroke-dashoffset:0;opacity:1}
  97% {stroke-dashoffset:0;opacity:0}
  98% {stroke-dashoffset:3200;opacity:0}
  100%{stroke-dashoffset:3200;opacity:1}
}
.hc-area{opacity:0;animation:hcArea 14s linear infinite}
@keyframes hcArea{
  0%,6%   {opacity:0}
  55%,92% {opacity:1}
  97%,100%{opacity:0}
}
.hc-tip{opacity:0;transform-box:fill-box;transform-origin:center;
  animation:hcTip 14s linear infinite}
@keyframes hcTip{
  0%,78% {opacity:0}
  88%    {opacity:1}
  94%    {opacity:1}
  97%,100%{opacity:0}
}
.hc-arrow{stroke-dasharray:100;stroke-dashoffset:100;
  animation:hcArrow 14s linear infinite}
@keyframes hcArrow{
  0%,80%  {stroke-dashoffset:100}
  90%,94% {stroke-dashoffset:0}
  97%,100%{stroke-dashoffset:100}
}
.hc-halo{animation:hcHalo 2.4s ease-in-out infinite}
@keyframes hcHalo{
  0%,100%{r:17;opacity:.16}
  50%    {r:27;opacity:.05}
}
@media (prefers-reduced-motion:reduce){
  .hc-line,.hc-area,.hc-tip,.hc-arrow,.hc-halo{animation:none;opacity:1;stroke-dashoffset:0}
}
.no-anim .hc-line,.no-anim .hc-area,.no-anim .hc-tip,.no-anim .hc-arrow,.no-anim .hc-halo{
  animation:none;opacity:1;stroke-dashoffset:0}
.cap-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(268px,1fr));gap:var(--sp-2)}
.cap{display:flex;align-items:center;gap:var(--icon-gap);padding:var(--card-pad);
  border-radius:var(--r-l);background:var(--bg-2);
  transition:transform .35s var(--spring),background .35s var(--spring)}
.cap:hover{background:var(--bg-3)}
.cap-i{width:var(--icon-box);height:var(--icon-box);flex:0 0 var(--icon-box);
  border-radius:14px;background:var(--bg);color:var(--ink-2);box-shadow:var(--shadow-1)}
.cap-i svg{width:22px;height:22px}
.cap-txt{flex:1 1 auto;min-width:0;text-align:left}
.cap b{display:block;font-size:15.5px;font-weight:650;letter-spacing:-.02em}
.cap span{display:block;font-size:13.5px;color:var(--ink-3);margin-top:5px;line-height:1.5}
.nova-card{background:linear-gradient(160deg,var(--accent-soft),transparent 46%),var(--bg)}
.nova-head{display:flex;align-items:center;gap:16px;margin-bottom:var(--sp-3)}
.nova-orb{position:relative;width:52px;height:52px;flex:0 0 auto}
.nova-orb i{position:absolute;inset:0;border-radius:50%;opacity:.55;
  animation:orb 5.5s var(--spring) infinite}
/* CORRECTIF (2026-10-07, 2ᵉ passe, §3 : "je ne veux plus d'orange dans
   le design général") : ce halo accompagne littéralement "Nova AI
   commente votre semaine…" (voir js/nova.js) — un halo orange sur
   l'identité Nova AI (désormais bleu électrique) était l'exemple même
   de couleur décorative à remplacer. Repris sur la famille des couleurs
   Nova (bleu électrique/violet/cyan) plutôt que vert/orange/cyan. */
.nova-orb i:nth-child(1){background:radial-gradient(circle at 30% 30%,var(--nova-news,#6D94FF),var(--nova-ai,#4D7CFF))}
.nova-orb i:nth-child(2){background:radial-gradient(circle at 70% 40%,#a996ff,var(--nova-bot,#805DFF));animation-delay:-1.8s}
.nova-orb i:nth-child(3){background:radial-gradient(circle at 50% 70%,var(--cyan-300),var(--cyan-500));animation-delay:-3.6s}
@keyframes orb{
  0%,100%{transform:translate(0,0) scale(.82)}
  33%{transform:translate(7px,-5px) scale(1)}
  66%{transform:translate(-6px,5px) scale(.9)}
}
.nova-t{font-size:12.5px;font-weight:650;color:var(--ink-3);margin:var(--sp-3) 0 2px}
.conf{display:flex;gap:6px;margin-bottom:var(--sp-1)}
.conf-b{flex:1;height:5px;border-radius:100px;background:var(--bg-3);transition:background .6s var(--spring)}
.conf-b.on{background:var(--accent)}
.avgs{display:grid;grid-template-columns:repeat(auto-fit,minmax(126px,1fr));gap:9px;margin-top:var(--sp-3)}
.avg{padding:13px;border-radius:var(--r-m);background:var(--bg-2);min-width:0}
.avg-k{display:block;font-size:11.5px;color:var(--ink-3);letter-spacing:-.01em}
.avg-v{display:block;font-size:16.5px;font-weight:700;margin-top:5px;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.buy-card{background:linear-gradient(150deg,var(--bg-2),var(--bg))}
.btn-lg{min-height:52px;padding:15px 28px;font-size:16px}
.btn-buy{position:relative;overflow:hidden}
.btn-buy::after{content:'';position:absolute;inset:0;background:linear-gradient(110deg,
  transparent 30%,rgba(255,255,255,.35) 50%,transparent 70%);transform:translateX(-100%)}
.btn-buy:hover::after{animation:sheen 1s var(--spring)}
@keyframes sheen{to{transform:translateX(100%)}}
.amounts{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
.save{background:var(--accent);color:#fff;padding:2px 8px;border-radius:100px;font-size:11px;
  font-weight:700;margin-left:6px}
/* (ancienne règle .dock button{transition:color .3s var(--spring)} ici
   supprimée le 2026-09-23 — doublon avec la transition déjà définie dans
   le bloc .dock principal, qui la redéfinissait silencieusement avec une
   durée différente à cause de l'ordre des règles ; voir la refonte
   complète du dock plus haut dans ce fichier. Un seul endroit maintenant. */
body.gated .top,body.gated .dock{display:none}
body.gated .wrap{padding:0;max-width:100%}
/* Vue Conversations NovaBot en plein écran sur mobile (§2/§10 du prompt
   maître V4, décision validée sur la maquette) : la barre de navigation
   basse disparaît pendant la conversation pour rendre toute la hauteur
   au fil de discussion, comme ChatGPT mobile — jamais masquée sur
   Portefeuille ni sur desktop (classe posée par render() uniquement
   quand route.page==='novabot' && NOVABOT_UI.view==='chat'). */
body.nb-immersive .dock{display:none}
/* CORRECTIF (2026-10-02, retour utilisateur : "la version claire, fond
   completement et seulement blanc") : partait de var(--accent-soft) —
   --orange-50 (#FEF3E7, teinte crème) en thème clair. Halo retiré,
   fond uni var(--bg) (blanc pur en clair). Le thème sombre garde son
   propre halo blanc translucide, inchangé, voir juste en dessous. */
.gate{min-height:calc(100vh - 40px);display:flex;align-items:center;justify-content:center;padding:var(--space-md) 20px;
  background:var(--bg)}
[data-theme="dark"] .gate{background:radial-gradient(120% 90% at 50% 0%,rgba(255,255,255,.06),transparent 58%)}
.gate-card{width:100%;max-width:520px;text-align:center;animation:gateIn .7s var(--spring)}
@keyframes gateIn{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:none}}
/* CORRECTIF (audit de cohérence visuelle, 2026-09-21) : .gate-orb —
   jamais utilisé par aucun balisage (vérifié), un doublon quasi
   identique de .nova-orb (mêmes 3 couleurs, même animation "orb", juste
   74px au lieu de 52px). Supprimé plutôt que recoloré comme .nova-orb :
   contrairement à lui, personne ne l'appelait nulle part, pas de raison
   de garder deux versions mortes d'un même motif. */
/* Trois moyens de connexion (2026-09-24, "modernise la page de
   connexion") — un seul style de bouton partagé (.btn-oauth), chaque
   provider ne redéfinit que sa couleur. min-height:52px (au lieu de
   56px) et un empilement resserré : trois boutons pleine largeur
   prennent déjà de la place, pas besoin d'en ajouter par bouton. */
.btn-oauth{width:100%;min-height:52px;font-size:15px;font-weight:600;
  display:flex;align-items:center;justify-content:center;gap:10px}
/* CORRECTIF (bug réel, trouvé en testant à 320px) : l'icône e-mail
   (svg(ICON.mail), viewBox seul, sans width/height propres — contrai-
   rement à GOOGLE_G/APPLE_LOGO qui fixent les leurs directement sur
   leur <svg>) se retrouvait à width:0 dans ce bouton, invisible.
   Même correctif que .icon-btn svg/.qa svg ailleurs dans ce fichier :
   une taille explicite pour toute icône générée par svg() à cet
   endroit précis. */
.btn-oauth svg{width:18px;height:18px;flex:0 0 auto}
.btn-google{background:var(--bg);color:var(--ink);border:1.5px solid var(--line);box-shadow:var(--shadow-1)}
.btn-google:hover{border-color:var(--ink-4);box-shadow:var(--shadow-2);transform:translateY(-2px)}
.btn-apple{background:#000;color:#fff;margin-top:10px}
.btn-apple:hover{background:#1a1a1a;transform:translateY(-2px)}
/* CORRECTIF (2026-10-02) : .btn-google héritait de var(--bg)/var(--ink)
   — en thème sombre ça donnait un bouton NOIR à texte blanc, l'inverse
   de "tous les boutons blancs". .btn-apple est fixe #000 dans les deux
   thèmes : invisible sur un fond désormais noir lui aussi. Les deux
   passent blanc/texte noir, même principe que les autres boutons. */
[data-theme="dark"] .btn-google{background:#fff;color:#000}
[data-theme="dark"] .btn-apple{background:#fff;color:#000}
[data-theme="dark"] .btn-apple:hover{background:#fff;opacity:.85}
.btn-email{background:var(--bg-2);color:var(--ink);margin-top:10px}
.btn-email:hover{background:var(--bg-3);transform:translateY(-2px)}
[data-theme="dark"] .btn-email{background:#fff;color:#000}
[data-theme="dark"] .btn-email:hover{background:#fff;opacity:.85}
/* Lien texte (formulaire e-mail) — n'existait nulle part dans ce
   fichier avant cette passe (vérifié), jamais qu'un <button class="btn
   ..."> plein pour une action secondaire de type "retour"/"mot de passe
   oublié" ici. */
.linklike{color:var(--ink-3);font-size:13px;font-weight:600;text-decoration:underline;
  text-underline-offset:2px}
.linklike:hover{color:var(--ink)}
.notice-ok{background:var(--up-soft)}
.notice-ok b{color:var(--up)}
/* Remplace .gate-pts (liste verticale de 3 lignes pleine largeur) par
   une grille compacte à 3 colonnes — même esprit que .nova-hub-card
   (verre dépoli, icône teintée, titre + accroche courte), cohérence
   avec le reste de la passe "Nova" de cette session plutôt qu'un style
   isolé propre à cette seule page. Sur 320px, 3 colonnes ~90px chacune :
   les accroches les plus longues passent sur 2 lignes (accepté,
   jamais coupées ni débordantes — mêmes garanties que nova-hub-card). */
.gate-cards{margin-top:22px;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;text-align:left}
.gate-card-i{padding:12px 9px;border-radius:var(--r-m);background:var(--bg-2);min-width:0}
.gate-card-ic{width:28px;height:28px;border-radius:9px;display:flex;align-items:center;justify-content:center;
  background:var(--accent-soft);color:var(--accent)}
.gate-card-ic svg{width:15px;height:15px}
.gate-card-eyebrow{display:block;margin-top:9px;font-size:9.5px;font-weight:700;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-4)}
.gate-card-i b{display:block;margin-top:3px;font-size:12px;font-weight:700;letter-spacing:-.01em;line-height:1.28}
.gate-card-i span{display:block;font-size:10.5px;color:var(--ink-3);margin-top:4px;line-height:1.35}
.onb{text-align:left}
.onb-dots{display:flex;gap:6px;justify-content:center;margin-bottom:var(--space-md)}
.onb-dots span{width:26px;height:4px;border-radius:100px;background:var(--bg-3);transition:background .4s var(--spring)}
.onb-dots span.on{background:var(--accent)}
.onb-list{display:grid;gap:var(--space-xs);margin-top:var(--space-md)}
.onb-c{display:flex;align-items:center;gap:12px;text-align:left;padding:18px;border-radius:var(--r-m);
  background:var(--bg-2);border:1.5px solid transparent;
  transition:border-color .28s var(--spring),background .28s var(--spring),transform .28s var(--spring)}
.onb-c:hover{border-color:var(--accent);background:var(--bg);transform:translateX(3px)}
.onb-c b{display:block;font-size:15.5px;font-weight:650;letter-spacing:-.02em}
.onb-c span:not(.onb-a){display:block;font-size:13px;color:var(--ink-3);margin-top:3px}
.onb-c>b,.onb-c>span:first-of-type{flex:1;min-width:0}
.onb-a{color:var(--ink-4);flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:20px;height:20px}
.onb-a svg{width:16px;height:16px}
.onb-steps{display:grid;gap:var(--space-xs);margin-top:var(--space-md)}
.onb-step{display:flex;align-items:center;gap:12px;padding:16px;border-radius:var(--r-m);background:var(--bg-2)}
.onb-n{width:26px;height:26px;border-radius:50%;background:var(--ink);color:var(--bg);
  font-size:13px;font-weight:700}
.onb-i{width:34px;height:34px;border-radius:11px;background:var(--bg);display:flex;align-items:center;justify-content:center;
  color:var(--accent);flex:0 0 auto;box-shadow:var(--shadow-1)}
.onb-i svg{width:17px;height:17px}
.onb-step b{display:block;font-size:14.5px;font-weight:650;letter-spacing:-.02em}
.onb-step span span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:2px;line-height:1.45}
.cons-head{display:flex;justify-content:space-between;align-items:flex-end;gap:var(--space-md);
  flex-wrap:wrap;padding-bottom:var(--space-sm);border-bottom:1px solid var(--line-2);
  margin-bottom:var(--space-sm)}
.cons-agree{display:flex;flex-direction:column;min-width:0}
.cons-gauge{display:flex;flex-direction:column;gap:5px;min-width:150px}
.gauge{height:8px;border-radius:100px;background:var(--bg-3);overflow:hidden}
.gauge i{display:block;height:100%;width:0;border-radius:100px;background:var(--accent);
  transition:width 1.1s var(--spring);animation:gaugeIn 1.1s var(--spring) forwards}
@keyframes gaugeIn{from{width:0}}
.gauge-v{font-size:12.5px;font-weight:650;color:var(--accent);text-transform:capitalize}
.cons-body h3{margin-top:2px}
.ai-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(238px,1fr));
  gap:var(--space-xs);margin-top:var(--space-md)}
.ai-card{background:var(--bg-2);border-radius:var(--r-m);padding:var(--space-sm);min-width:0;
  transition:transform .3s var(--spring),background .3s var(--spring)}
.ai-card:hover{transform:translateY(-2px);background:var(--bg-3)}
.ai-card-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.ai-alias{font-size:14px;font-weight:650;letter-spacing:-.02em}
.ai-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
/* CORRECTIF (audit de cohérence visuelle, 2026-09-21) : --violet et
   --info n'ont jamais été définis nulle part dans ce fichier (comme
   --accent-2, trouvé et corrigé plus haut cette session) — .ai-dot-2/3
   ne s'affichaient donc jamais réellement. Ce trio de puces (inutilisé
   dans le balisage actuel, probablement prévu pour représenter les
   trois IA du positionnement "Trois IA analysent les marchés") tombe
   naturellement sur les trois couleurs de marque plutôt que sur des
   teintes arbitraires. */
.ai-dot-1{background:var(--accent)} .ai-dot-2{background:var(--blue-accent)} .ai-dot-3{background:var(--up)}
.ai-points{list-style:none;margin-top:12px}
.ai-points li{display:flex;gap:8px;padding:5px 0;font-size:12.8px;color:var(--ink-3);line-height:1.45}
.ai-points li i{flex:0 0 auto;width:17px;height:17px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-style:normal;font-size:11px;font-weight:700;margin-top:1px}
.ai-points li i.p{background:var(--up-soft);color:var(--up)}
.ai-points li i.n{background:var(--down-soft);color:var(--down)}
@media (max-width:430px){ .cons-gauge{min-width:0;width:100%} }
.set-row{display:flex;align-items:center;justify-content:space-between;gap:var(--space-md);
  padding:16px 0;border-bottom:1px solid var(--line-2);flex-wrap:wrap}
.set-row:last-child{border-bottom:none}
.set-txt{flex:1;min-width:160px}
.set-txt b{display:block;font-size:15px;font-weight:600;letter-spacing:-.02em}
.set-txt span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:3px;line-height:1.45}
.set-ctl{flex:0 0 auto}
.sw{width:48px;height:29px;border-radius:100px;background:var(--bg-3);position:relative;flex:0 0 auto;
  transition:background .28s var(--spring)}
.sw i{position:absolute;top:3px;left:3px;width:23px;height:23px;border-radius:50%;background:var(--bg);
  box-shadow:var(--shadow-1);transition:transform .28s var(--spring)}
.sw[aria-checked="true"]{background:var(--accent)}
.sw[aria-checked="true"] i{transform:translateX(19px)}
.export{margin-top:16px;padding:14px;border-radius:var(--r-m);background:var(--bg-2);font-size:11.5px;
  font-family:var(--mono);white-space:pre-wrap;word-break:break-word;max-height:44vh;overflow:auto;
  color:var(--ink-2)}
.no-anim *{animation:none!important;transition:none!important}
body.no-spark .row-spark{display:none}
@media (max-width:520px){ .set-row{align-items:flex-start} .set-ctl{width:100%} .seg{width:100%} }
.ai-focus{background:var(--bg);border-radius:var(--r-s);padding:9px 11px;margin-top:11px}
.focus-l{display:block;font-size:10.5px;font-weight:650;letter-spacing:.03em;color:var(--ink-4);
  text-transform:uppercase}
.focus-v{display:block;font-size:13px;font-weight:650;letter-spacing:-.015em;margin-top:2px}
.bias{padding:4px 9px;border-radius:7px;font-size:11.5px;font-weight:650;white-space:nowrap;
  border:1px solid transparent}
.bias-positif{background:var(--up-soft);color:var(--up);border-color:rgba(0,166,81,.22)}
.bias-neutre{background:var(--bg-3);color:var(--ink-2);border-color:var(--line)}
.bias-negatif{background:var(--down-soft);color:var(--down);border-color:rgba(224,39,59,.22)}
.bias-insuffisant{background:var(--warn-soft);color:var(--warn);border-color:rgba(184,119,10,.22)}
.radar-wrap{display:flex;gap:var(--space-md);align-items:center;flex-wrap:wrap}
.radar-box{flex:0 0 auto;width:100%;max-width:260px;margin:0 auto}
.radar-svg{width:100%;max-width:260px;height:auto;display:block;overflow:visible}
.radar-shape{opacity:0;transform-origin:130px 130px;transform:scale(.2);
  animation:radarIn .9s var(--spring) .15s forwards}
@keyframes radarIn{to{opacity:1;transform:scale(1)}}
.cmp{width:100%;border-collapse:collapse;min-width:420px}
.cmp th,.cmp td{padding:13px 12px;border-bottom:1px solid var(--line-2);text-align:center;
  font-size:14.5px;vertical-align:middle}
.cmp thead th{border-bottom:1px solid var(--line);font-size:13px;color:var(--ink);padding-bottom:14px}
.cmp th:first-child,.cmp td:first-child{text-align:left}
.cmp-t{display:block;font-size:11.5px;color:var(--ink-4);margin-top:2px;font-weight:500}
.cmp-k b{font-size:14px;font-weight:600}
.cmp-n{display:block;font-size:11.5px;color:var(--ink-4);margin-top:3px;font-weight:400;max-width:22ch}
.cmp-best{background:var(--up-soft);color:var(--up);font-weight:700;border-radius:8px}
.cmp-worst{background:var(--down-soft);color:var(--down);font-weight:600;border-radius:8px}
.cmp-na{color:var(--ink-4);font-size:13px}
@media (max-width:430px){
  .cmp th,.cmp td{padding:11px 8px;font-size:13.5px}
  .cmp-n{display:none}
}
.price{font-size:clamp(38px,7.2vw,48px);font-weight:900;line-height:1;letter-spacing:-.04em;
  font-variant-numeric:tabular-nums}
.price small{font-size:.58em;font-weight:600;opacity:.6;margin-left:2px}
/* AnimatedFinancialNumber (motion system, 2026-09-21) : une ANIMATION
   (pas une transition) joue correctement même sur un élément recréé à
   chaque render() — contrairement à une transition/position mesurée en
   JS (voir le correctif du dock plus haut, MOTION_SYSTEM.md), qui exige
   un nœud DOM persistant. Posée par liveTick() uniquement quand le prix
   AFFICHÉ change réellement (jamais à l'ouverture de page ni sur un
   cycle réseau sans changement réel). Micro-surlignage bref, retour
   immédiat à la couleur normale — jamais un flash global de l'écran. */
.price.flash-up{animation:priceFlashUp .9s ease-out}
.price.flash-down{animation:priceFlashDown .9s ease-out}
@keyframes priceFlashUp{0%{color:var(--up)}100%{color:inherit}}
@keyframes priceFlashDown{0%{color:var(--down)}100%{color:inherit}}
@media (prefers-reduced-motion:reduce){ .price.flash-up,.price.flash-down{animation:none} }
.price-chg{font-size:14px;font-weight:600;margin-top:var(--space-xs);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;display:inline-flex;align-items:center;gap:6px}
.kv{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-2);padding:13px 0;
  border-bottom:1px solid var(--line-2);min-width:0}
.kv:last-child{border-bottom:none}
.kv dt{font-size:14.5px;color:var(--ink-3);display:flex;align-items:center;gap:6px;min-width:0}
/* CORRECTIF (2026-09-28, retour utilisateur : "Diversification" se
   superposait à sa propre valeur sur mobile) — cause racine : flex:0 0
   auto interdisait TOUTE réduction de dd, alors que .kv dt n'a lui
   qu'un min-width:0 (rétrécit normalement). Pour un dd COURT (un prix,
   "15 300 €"), aucune différence visible : il tient déjà dans sa largeur
   naturelle. Mais un dd LONG sans saut de ligne prévu (ex. "0 secteur ·
   position la + importante : 0 %", voir bilanHebdomadaireNovaReview()
   dans index.html) forçait alors dt à se réduire jusqu'à ~1px sur un
   écran étroit (confirmé en mesurant getBoundingClientRect() à 390px) —
   son texte débordait alors PAR-DESSUS dd, exactement le chevauchement
   rapporté. flex:0 1 auto (réduction autorisée) laisse dd s'enrouler
   naturellement sur 2 lignes comme n'importe quel texte, sans jamais
   écraser dt — vérifié sur les 2 rangées à contenu déjà multi-ligne de
   ce fichier (elles gèrent leur propre <br>, non affectées) et sur les
   rangées à valeur courte (aucun changement, déjà dans leur largeur
   naturelle). text-align:right conserve l'alignement déjà voulu
   (justify-content:space-between le donnait implicitement en 1 ligne)
   quand le contenu s'enroule sur plusieurs lignes. */
.kv dd{font-family:var(--mono);font-size:15px;font-weight:600;flex:0 1 auto;text-align:right}
.info{width:19px;height:19px;border-radius:50%;background:var(--bg-3);color:var(--ink-4);
  display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex:0 0 auto}
.info:hover{background:var(--accent);color:#fff}
.star{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:var(--ink-4);flex:0 0 auto}
.star:hover{background:var(--bg-2)}
/* CORRECTIF (audit V7 §6, 2026-10-12 : "supprimer le jaune comme
   décoration dominante") : l'étoile de suivi (ajouter/retirer une
   entreprise de la watchlist, visible sur de nombreuses pages — fiches,
   recherche, listes) utilisait --warn (ambre/doré), une couleur de
   AVERTISSEMENT réelle ailleurs dans l'app (notice, erreur de source,
   couverture insuffisante — restées inchangées, ce sont de vrais
   avertissements). "Favori" n'est pas un avertissement : bascule sur
   --accent, déjà la couleur de marque établie, jamais une couleur
   inventée pour l'occasion. */
.star[aria-pressed="true"]{color:var(--accent)}
.star svg{width:20px;height:20px}
.stat{padding:var(--sp-2);border-radius:var(--r-m);background:var(--bg-2);min-width:0}
.stat-k{font-size:12.5px;color:var(--ink-3);letter-spacing:-.01em}
.stat-v{font-size:26px;font-weight:700;letter-spacing:-.04em;margin-top:5px;line-height:1.1}
.stat-n{font-size:12px;color:var(--ink-4);margin-top:4px}
.chart{width:100%;height:auto;display:block}
.divider{height:1px;background:var(--line-2);margin:var(--sp-3) 0}
.flow{display:grid;gap:6px;margin-top:var(--sp-2)}
.flow-s{display:flex;align-items:center;gap:var(--icon-gap);padding:var(--card-pad);
  border-radius:var(--r-m);background:var(--bg-2)}
.flow-n{width:var(--icon-box);height:var(--icon-box);flex:0 0 var(--icon-box);
  border-radius:50%;background:var(--bg);font-size:15px;font-weight:700;
  box-shadow:var(--shadow-1);font-variant-numeric:tabular-nums;color:var(--ink-2)}
.flow-txt{flex:1 1 auto;min-width:0;text-align:left}
.flow-s b{display:block;font-size:14.5px;font-weight:600;letter-spacing:-.02em}
.flow-s span{display:block;font-size:12.5px;color:var(--ink-4);margin-top:2px}
table.t{width:100%;border-collapse:collapse}
table.t th{text-align:left;font-size:12px;font-weight:600;color:var(--ink-4);padding:10px 10px 10px 0;
  border-bottom:1px solid var(--line)}
table.t td{padding:13px 10px 13px 0;border-bottom:1px solid var(--line-2);font-size:14.5px}
table.t td:last-child,table.t th:last-child{text-align:right;padding-right:0}
@media (max-width:900px){
  /* CORRECTIF dock mobile : ancienne valeur fixe (calc(80px+46px)=126px)
     ne tenait pas compte de env(safe-area-inset-bottom), qui varie selon
     l'appareil (ex. ~34px sur iPhone avec Face ID en portrait, différent
     en paysage ou sur d'autres modèles) — confirmé en constatant du
     contenu masqué derrière le dock sur un vrai iPhone Safari, pas
     seulement en supposition. Formule explicite : hauteur du dock +
     son décalage du bas + la VRAIE zone sûre de l'appareil + une marge
     de confort, plutôt qu'un nombre deviné. */
  .wrap{padding:var(--sp-3) 18px calc(var(--dock-h) + var(--dock-gap) + env(safe-area-inset-bottom, 0px) + 28px)}
  .section{margin-top:64px}
  .hero{padding:var(--sp-4) 0 var(--sp-3)}
  /* Dock masqué (règle plus haut) -> plus besoin de lui réserver de la
     place en bas de .wrap ; sinon la conversation garderait un vide égal
     à l'ancienne hauteur du dock malgré sa disparition. */
  body.nb-immersive .wrap{padding-bottom:calc(env(safe-area-inset-bottom, 0px) + 10px)}
}
@media (max-width:430px){
  :root{--icon-box:42px; --icon-gap:14px; --card-pad:16px}
  .cap,.flow-s{align-items:center;flex-direction:row}
  body{font-size:15.5px}
  .card{padding:18px;border-radius:var(--r-m)}
  .ring{width:126px;height:126px}
  .ring-v{font-size:36px}
  .stat-v{font-size:23px}
}
/* --- graphique professionnel (Lightweight Charts) --- */
.chart-toolbar{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.chart-info{display:flex;flex-direction:column;gap:2px;min-width:0}
.cp-price{font-size:24px;font-weight:700;letter-spacing:-.02em}
.cp-var{font-size:14px;font-weight:600}
.cp-date{margin-top:1px}
.chart-pro{width:100%;height:320px;touch-action:pan-y}
.chart-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));gap:10px;margin-top:14px}
.chart-stats>div{padding:10px 12px;border-radius:var(--r-s);background:var(--bg-2)}
.chart-stats .tiny{display:block;color:var(--ink-4)}
.chart-stats b{display:block;margin-top:2px;font-size:14.5px}
.chart-skel{width:100%;height:320px;border-radius:var(--r-m)}
@media (max-width:430px){ .chart-pro{height:260px} .chart-skel{height:260px} .cp-price{font-size:21px} }
