/* ==========================================================================
   ProfitTrack — Design System
   Dark, modern SaaS finance dashboard. Single source of truth for tokens,
   layout shells (marketing / app) and shared components.

   2026-07 refonte "astronomique" (hero + primitives partagées) — handoff
   design fusionné ici. Voir la section HERO plus bas pour l'anatomie
   complète. Les tokens historiques encore utilisés par le dashboard
   applicatif (--good-text, --series-*, --shadow-card, --brand-ring) sont
   conservés en compatibilité, non couverts par le nouveau système.
   ========================================================================== */

:root{
  /* ---- surfaces ---- */
  --page: #080d18;
  --page-deep: #05080f;         /* fond du hero, plus profond que la page */
  --surface: #111a2b;
  --surface-2: #18233a;
  --surface-3: #202c46;
  --surface-hover: #1d2942;

  /* ---- bordures : trois niveaux ---- */
  --border: rgba(255, 255, 255, 0.075);
  --border-soft: rgba(255, 255, 255, 0.045);
  --border-strong: rgba(255, 255, 255, 0.14);

  /* ---- texte ---- */
  --text: #f2f6fb;
  --text-dim: #97a4bb;
  --text-mute: #667792;

  /* ---- marque ---- */
  --brand: #4a8cff;
  --brand-dim: #2563eb;
  --brand-deep: #1d4ed8;
  --brand-soft: rgba(74, 140, 255, 0.14);
  --brand-line: rgba(74, 140, 255, 0.32);

  /* ---- accent secondaire, uniquement fonds atmosphériques du hero ---- */
  --nebula: #7c6cff;
  --nebula-soft: rgba(124, 108, 255, 0.13);

  /* ---- états ---- */
  --good: #16a34a;
  --good-soft: rgba(22, 163, 74, 0.14);
  --warning: #f2b01e;
  --warning-soft: rgba(242, 176, 30, 0.14);
  --critical: #dc4a4a;
  --critical-soft: rgba(220, 74, 74, 0.14);

  /* ---- rayons ---- */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 20px;
  --r-full: 999px;

  /* ---- élévation : ombres longues et diffuses ---- */
  --sh-1: 0 1px 2px rgba(0, 0, 0, .4);
  --sh-2: 0 2px 4px rgba(0, 0, 0, .3), 0 8px 20px -8px rgba(0, 0, 0, .5);
  --sh-3: 0 4px 8px rgba(0, 0, 0, .3), 0 24px 48px -16px rgba(0, 0, 0, .6);
  --sh-glow: 0 0 0 1px var(--brand-line), 0 8px 32px -8px rgba(74, 140, 255, .45);
  --ring: 0 0 0 3px rgba(74, 140, 255, .34);

  /* ---- typo : General Sans (texte/UI) + Cabinet Grotesk (titres), via
     Fontshare (voir <link> dans base.html/base_app.html) - avant ça, pile
     systeme pure, ce qui rendait le site indifferenciable de n'importe quel
     autre outil "code par une IA" (Inter/system-ui est litteralement LE
     signal generique le plus cite). Repli sur la meme pile systeme
     qu'avant si Fontshare est indisponible/bloque, jamais de FOUT visible
     plus d'un instant (font-display:swap deja dans l'URL Fontshare). */
  --font: "General Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-display: "Cabinet Grotesk", var(--font);
  --font-num: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* ---- rythme ---- */
  --shell: 1180px;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);

  /* ---- compatibilité : tokens historiques encore utilisés par le
         dashboard applicatif (KPI, graphiques, tableaux), hors périmètre
         du nouveau système ---- */
  --good-text:      #4ade80;
  --warning-text:   #fbbf24;
  --critical-text:  #f87171;
  /* --text-soft n'existait que cote theme clair jusqu'ici (utilise sans
     definition sombre par .sim-/.chat-/.mkt- deja en prod - lacune
     preexistante, corrigee ici a l'occasion de l'ajout des Bilans qui en
     ont aussi besoin) ; --brand-text/--sh-card/--sh-lift sont nouveaux,
     necessaires aux Bilans (.bl-*), aucun equivalent existant a reutiliser. */
  --text-soft: #7d8ca6;
  --brand-text: #93b8ff;
  /* Filet de separation entre zones du dashboard reorganise (.dz-*) : plus
     visible que --border-soft (disparait sur --page), plus discret que
     --border. */
  --dz-rail: rgba(255, 255, 255, .09);
  --sh-card: 0 1px 0 rgba(255, 255, 255, .03) inset, 0 8px 24px rgba(0, 0, 0, .28);
  --sh-lift: 0 2px 4px rgba(0, 0, 0, .3), 0 14px 34px -10px rgba(0, 0, 0, .55);
  --series-1: #3987e5; /* blue    */
  --series-2: #d95926; /* orange  */
  --series-3: #199e70; /* aqua    */
  --series-4: #c98500; /* yellow  */
  --series-5: #d55181; /* magenta */
  --series-6: #9085e9; /* violet  */
  --shadow-card: 0 1px 0 rgba(255,255,255,.03) inset, 0 8px 24px rgba(0,0,0,.28);
  --brand-ring: rgba(74, 140, 255, .35);

  /* ---- panneau de detail projet (dashboard) : deux tokens propres a ce
         composant, tout le reste de sa palette reprend les tokens
         ci-dessus ---- */
  --pd-sunken: #0c1424;    /* fond de la zone "Activite" (journaux), plus sombre que --surface pour un effet de renfoncement */
  --text-head: #7d8ca6;    /* libelles en capitales - --text-mute seul tombe sous le seuil AA a 11px */

  /* ---- bloc d'outils du dashboard (.dash-tools) : creux de la piste des
         segments Reel/Objectif et Unitaires/Abonnements ---- */
  --seg-track: #0b1220;
  --seg-hi: rgba(255, 255, 255, .05);

  /* ---- puits/renfoncements (formulaires, paliers, journaux du panneau de
         detail projet) : valeurs identiques a celles utilisees avant leur
         extraction en tokens, seul le theme clair (plus bas) les redefinit ---- */
  --surface-sunk-1: rgba(5, 8, 15, .35);
  --surface-sunk-2: rgba(5, 8, 15, .4);
  --surface-sunk-3: rgba(5, 8, 15, .55);
  --surface-sunk-4: rgba(5, 8, 15, .6);
  --surface-sunk-5: rgba(0, 0, 0, .28);
  --surface-log: rgba(24, 35, 58, .55);
  --surface-log-row: rgba(12, 20, 36, .7);
  --tooltip-bg: #060a14;
  --autofill-bg: #0c1220;
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */
*, *::before, *::after{ box-sizing: border-box; }

html{
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* Cabinet Grotesk pour les titres, General Sans pour tout le reste - jamais
   pour les chiffres (tuiles KPI, .dz-hero-v...) : ces classes utilisent deja
   font-variant-numeric:tabular-nums avec --font, changer leur police casserait
   cet alignement en colonnes sans benefice visuel (les chiffres n'ont pas
   besoin de "personnalite" typographique, juste d'etre lisibles/alignes). */
h1, h2, h3, h4, h5, h6,
.hero-title, .pg-h1, .bl-h1, .np-h1, .sim-title, .chat-name,
.card-title, .modal-box h3, .auth-title{
  font-family: var(--font-display);
  letter-spacing: -0.01em;
}

body{
  margin: 0;
  font-family: var(--font);
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--text);
  background: var(--page);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Voile atmosphérique global, très discret : évite le "noir plat". */
body::before{
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(1100px 620px at 50% -10%, rgba(74, 140, 255, .10), transparent 70%),
    radial-gradient(900px 500px at 90% 0%, var(--nebula-soft), transparent 72%);
}
body > *{ position: relative; z-index: 1; }

a{ color: inherit; text-decoration: none; transition: color .18s var(--ease); }
a:hover{ color: var(--brand); }
a:focus-visible{ outline: none; box-shadow: var(--ring); border-radius: var(--r-sm); }

h1,h2,h3,h4{
  margin: 0 0 .5em;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -.022em;
  color: var(--text);
  text-wrap: balance;
}
h1{ font-size: clamp(2.1rem, 5vw, 3.5rem); letter-spacing: -.035em; font-weight: 620; }
h2{ font-size: clamp(1.55rem, 3vw, 2.2rem); letter-spacing: -.028em; }
h3{ font-size: 1.18rem; }
h4{ font-size: 1rem; }

p{ margin: 0 0 1em; color: var(--text-dim); text-wrap: pretty; }
p:last-child{ margin-bottom: 0; }

strong, b{ color: var(--text); font-weight: 600; }
small{ font-size: .8125rem; color: var(--text-mute); }

hr{
  height: 1px; border: 0; margin: 2rem 0;
  background: linear-gradient(90deg, transparent, var(--border), transparent);
}

code, kbd, pre{ font-family: var(--font-num); font-size: .875em; }

::selection{ background: rgba(74, 140, 255, .3); color: #fff; }

/* Barre de défilement discrète */
*{ scrollbar-width: thin; scrollbar-color: var(--surface-3) transparent; }
*::-webkit-scrollbar{ width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb{
  background: var(--surface-3);
  border: 3px solid transparent;
  border-radius: var(--r-full);
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover{ background: #2c3a58; background-clip: content-box; }

/* Chiffres : tabulaires partout, indispensable pour un dashboard de rentabilité */
table, .metric, .kpi, .amount, .value, [class*="num"], [class*="stat"]{
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   Boutons — .btn et variantes (noms inchangés)
   -------------------------------------------------------------------------- */
.btn{
  --btn-bg: var(--surface-2);
  --btn-fg: var(--text);
  --btn-bd: var(--border-strong);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .625rem 1.05rem;
  min-height: 40px;
  font: inherit;
  font-size: .9375rem;
  font-weight: 550;
  letter-spacing: -.012em;
  line-height: 1;
  color: var(--btn-fg);
  background: var(--btn-bg);
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  text-decoration: none;
  isolation: isolate;
  font-family: inherit;
  transition:
    background-color .18s var(--ease),
    border-color .18s var(--ease),
    box-shadow .18s var(--ease),
    transform .12s var(--ease),
    color .18s var(--ease);
}
/* Liseré lumineux en haut : la touche "premium" sans dégradé criard */
.btn::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, 0) 42%);
  opacity: .9;
  transition: opacity .18s var(--ease);
}
.btn:hover{ background: var(--surface-hover); border-color: rgba(255, 255, 255, .2); }
.btn:active{ transform: translateY(.5px) scale(.995); box-shadow: none; }
.btn:focus-visible{ outline: none; box-shadow: var(--sh-1), var(--ring); }
.btn:disabled,
.btn[aria-disabled="true"]{
  opacity: .45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}
.btn:disabled::after{ opacity: 0; }

.btn-primary{
  --btn-bg: var(--brand-dim);
  --btn-fg: #fff;
  --btn-bd: rgba(255, 255, 255, .16);
  background: linear-gradient(180deg, var(--brand) 0%, var(--brand-dim) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .18),
    0 1px 2px rgba(0, 0, 0, .5),
    0 6px 22px -10px rgba(74, 140, 255, .8);
}
.btn-primary:hover{
  background: linear-gradient(180deg, #5c98ff 0%, #2a6ae8 100%);
  border-color: rgba(255, 255, 255, .24);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 1px 2px rgba(0, 0, 0, .5),
    0 10px 30px -10px rgba(74, 140, 255, .95);
}
.btn-primary:active{ background: linear-gradient(180deg, var(--brand-dim), var(--brand-deep)); }
.btn-primary::after{ opacity: 0; }

.btn-ghost{
  --btn-bg: transparent;
  --btn-fg: var(--text-dim);
  --btn-bd: transparent;
  box-shadow: none;
}
.btn-ghost::after{ opacity: 0; }
.btn-ghost:hover{
  color: var(--text);
  background: rgba(255, 255, 255, .05);
  border-color: var(--border);
}
.btn-ghost:active{ background: rgba(255, 255, 255, .03); }

.toggle-pair{ display:flex; gap:8px; margin:14px 0; }
.toggle-btn{
  flex:1;
  padding:8px 12px;
  border-radius:8px;
  border:1px solid var(--border);
  background:var(--surface-2);
  color:var(--text-dim);
  font-size:13px;
  font-weight:600;
  cursor:pointer;
  transition:background .15s,color .15s,border-color .15s;
}
.toggle-btn.is-active{
  background:var(--brand-soft);
  color:var(--brand);
  border-color:var(--brand);
}

.btn-ghost.is-active{
  background:var(--brand-soft);
  color:var(--brand);
  border-color:var(--brand-line);
}

/* ==========================================================================
   BASCULES DU DASHBOARD (.dash-tools) - handoff design,
   doc/design_handoff_dash_toolbar/. Style des segments (piste en creux +
   pouce qui glisse) et du bouton Simuler uniquement - PAS de cadre/carte
   commun : chaque rangee reste a son emplacement d'origine (bascule de
   section, puis bascule de vue + simulateur, sous .app-topbar dont la
   rangee d'actions - Ajouter un element/Exporter/Nouveau projet - est
   restee inchangee en haut a droite). `.dash-tools` n'est ici qu'un
   conteneur de mise en page (espacement vertical entre les deux rangees),
   pas une surface visuelle.
   Les deux toggle-pair (section ET vue) partagent une seule mecanique -
   piste en creux + pouce qui glisse, position du pouce geree en CSS pur
   via :has() sur .is-active, aucun JS a ecrire - et ne se distinguent que
   par la taille/le poids (section plus grosse + reglette de marque =
   plus important, vue plus discrete). Scope volontairement sous
   `.dash-tools` : .toggle-pair/.toggle-btn de base (settings.html) ne
   sont pas touches.
   ========================================================================== */
.dash-tools{ display:flex; flex-direction:column; gap:14px; margin-bottom:22px; }
.dash-tools-row{display:flex;align-items:center;column-gap:20px;row-gap:12px;flex-wrap:wrap}

.dash-tools-seg{
  position:relative;display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  gap:2px;padding:4px;
  background:var(--seg-track);
  border:1px solid var(--border);
  border-radius:calc(var(--r-md) + 4px);
}
.dash-tools-thumb{
  position:absolute;top:4px;bottom:4px;left:4px;
  width:calc((100% - 8px - 2px) / 2);
  background:var(--surface-2);
  border:1px solid var(--border-strong);
  border-radius:var(--r-md);
  box-shadow:var(--sh-2);
  transition:transform .28s var(--ease);
  pointer-events:none;
}
.dash-tools-seg:has(> .toggle-btn:nth-of-type(2).is-active) .dash-tools-thumb{
  transform:translateX(calc(100% + 2px));
}
/* Repli sans :has() : le pouce disparait, le segment actif porte lui-meme
   son fond. Meme rendu, sans le glissement. */
@supports not selector(:has(*)){
  .dash-tools-thumb{display:none}
  .dash-tools .toggle-btn.is-active{background:var(--surface-2);
    border-color:var(--border-strong);box-shadow:var(--sh-2)}
}
.dash-tools .toggle-btn{
  position:relative;z-index:1;
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  padding:.5rem .95rem;min-height:38px;
  font:inherit;font-size:.9375rem;font-weight:550;letter-spacing:-.012em;line-height:1;
  color:var(--text-dim);background:transparent;border:1px solid transparent;
  border-radius:var(--r-md);cursor:pointer;white-space:nowrap;
  transition:color .18s var(--ease),background-color .18s var(--ease)}
.dash-tools .toggle-btn:hover{color:var(--text);background:var(--seg-hi)}
.dash-tools .toggle-btn.is-active{color:var(--text);background:transparent}
.dash-tools .toggle-btn.is-active:hover{background:transparent}
.dash-tools .toggle-btn:focus-visible{outline:none;box-shadow:var(--ring)}
/* Emoji desature tant que le segment est inactif - relief gratuit, aucun
   effet sur le texte (deja gris), evite d'entourer l'emoji d'un <span>. */
.dash-tools .toggle-btn{filter:grayscale(.7)}
.dash-tools .toggle-btn.is-active{filter:none}

.dash-tools-seg-section{width:100%;max-width:480px}
.dash-tools-seg-section .toggle-btn{min-height:42px;font-size:.96875rem}
/* Seul repere de couleur de tout le bloc : dit "vous etes ici" sans
   repeindre le fond - employe une seule fois, il designe. */
.dash-tools-seg-section .dash-tools-thumb::after{
  content:"";position:absolute;left:14px;right:14px;bottom:-1px;height:2px;
  border-radius:2px;background:var(--brand);opacity:.9}

.dash-tools-row-view{justify-content:space-between}
.dash-tools-seg-view{max-width:320px}
.dash-tools-seg-view .toggle-btn{min-height:40px;font-size:.9375rem;padding:.5rem .95rem}
.dash-tools-seg-view .dash-tools-thumb{box-shadow:var(--sh-1)}

/* Simuler n'est PAS un etat, il ouvre une modale : forme de bouton pleine
   (jamais un segment), separe des bascules par un filet vertical. */
.dash-tools-action{display:flex;align-items:center;gap:20px}
.dash-tools-sep{width:1px;align-self:stretch;margin:2px 0;background:var(--border);
  border-radius:1px}
.dash-tools-sim{
  --btn-bg:var(--brand-soft);--btn-fg:var(--brand);--btn-bd:var(--brand-line);
  min-height:46px;padding:.65rem 1.25rem;font-size:1.0625rem;gap:.55rem;
  border-radius:var(--r-md);box-shadow:none}
.dash-tools-sim::after{opacity:0}
.dash-tools-sim:hover{background:rgba(74,140,255,.2);border-color:var(--brand)}
/* "..." = "ceci ouvre autre chose" - le signe le plus rapide a lire, avant
   meme la forme du bouton. */
.dash-tools-sim::before{content:"…";margin-left:.15rem;order:2;font-weight:700;
  letter-spacing:.06em;opacity:.65}

@media (prefers-reduced-motion:reduce){
  .dash-tools-thumb{transition:none}
}
@media (max-width:640px){
  .dash-tools-seg-section{max-width:none}
  .dash-tools-row-view{flex-direction:column;align-items:stretch}
  .dash-tools-action{width:100%}
  .dash-tools-sep{display:none}
  .dash-tools-sim{width:100%}
  /* Cible tactile : la bascule de vue passe de 34px a 44px recommandes -
     contrairement a ce que laissait supposer le handoff, .btn-sm ne fait
     ce bump nulle part ailleurs dans ce fichier, donc l'ecrire ici meme. */
  .dash-tools-seg-view .toggle-btn{min-height:44px}
}

/* ==========================================================================
   SECTION "PROJETS ARCHIVES" - reste dans la MEME carte que "Mes projets"
   (pas un bloc a part) mais la transition entre les deux doit sauter aux
   yeux : un bandeau plein-largeur, teinte ambre, en travers de la carte,
   marque clairement "on change de zone" sans pour autant briser le cadre
   visuel unique du widget.
   ========================================================================== */
.archived-divider{
  display: flex; align-items: center; gap: .625rem; flex-wrap: wrap;
  margin-top: 1.25rem;
  padding: .75rem 1.375rem;
  background: linear-gradient(180deg, rgba(242,176,30,.1), rgba(242,176,30,.03));
  border-top: 1px solid var(--border);
  border-bottom: 1px dashed rgba(242,176,30,.4);
}
.archived-divider-icon{ font-size: 1rem; line-height: 1; }
.archived-divider-label{ font-size: .8125rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--warning-text); }
.archived-divider-count{
  font-size: .6875rem; font-weight: 700; padding: .0625rem .5rem; border-radius: var(--r-full);
  background: var(--warning-soft); color: var(--warning-text);
}
.archived-divider-sub{ font-size: .75rem; color: var(--text-mute); margin-left: auto; }

.archived-list{ display: flex; flex-direction: column; padding: 0 1.375rem 1.125rem; }
.archived-row{
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .75rem 0; border-top: 1px dashed var(--border-soft);
}
.archived-row:first-child{ border-top: none; }
.archived-row-name{ flex: 1 1 220px; min-width: 0; font-size: .875rem; color: var(--text-dim); }
.archived-row-client{ font-size: .75rem; color: var(--text-mute); margin-top: .125rem; }
.archived-row-amount{ flex: none; font-size: .8125rem; color: var(--text-mute); font-variant-numeric: tabular-nums; min-width: 5.5rem; text-align: right; }
.archived-row-profit{ flex: none; font-size: .875rem; font-weight: 700; font-variant-numeric: tabular-nums; min-width: 5.5rem; text-align: right; }
.archived-row-btn{
  flex: none; display: inline-flex; align-items: center; gap: .375rem;
  padding: .375rem .6875rem; min-height: 32px;
  font: inherit; font-size: .75rem; font-weight: 600; color: var(--text-dim);
  background: transparent; border: 1px dashed var(--border-strong); border-radius: var(--r-sm);
  cursor: pointer; transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.archived-row-btn:hover{ color: var(--text); background: rgba(255,255,255,.05); border-color: var(--border-strong); }

@media (max-width: 560px){
  .archived-row{ gap: .5rem; }
  .archived-row-amount, .archived-row-profit{ text-align: left; min-width: 0; }
  .archived-row-btn{ flex: 1 1 100%; justify-content: center; }
}

.btn-danger{
  --btn-bg: var(--critical-soft);
  --btn-fg: #ff9d9d;
  --btn-bd: rgba(220, 74, 74, .34);
  box-shadow: none;
}
.btn-danger::after{ opacity: 0; }
.btn-danger:hover{
  color: #fff;
  background: var(--critical);
  border-color: var(--critical);
  box-shadow: 0 6px 22px -12px rgba(220, 74, 74, .9);
}
.btn-danger:focus-visible{ box-shadow: 0 0 0 3px rgba(220, 74, 74, .34); }

.btn-sm{
  padding: .375rem .7rem;
  min-height: 30px;
  font-size: .8125rem;
  border-radius: var(--r-sm);
  gap: .375rem;
}

.btn-block{ display: flex; width: 100%; }

.btn-icon{
  padding: 0;
  width: 36px;
  min-height: 36px;
  height: 36px;
  border-radius: var(--r-sm);
  color: var(--text-dim);
}
.btn-icon:hover{ color: var(--text); transform: scale(1.08); }
.btn-icon:active{ transform: scale(.95); }
.btn-icon.btn-sm{ width: 30px; height: 30px; min-height: 30px; }
.btn-icon svg{ width: 16px; height: 16px; display: block; }

/* Label au survol/focus des icones d'action (colonne Actions) - element
   partage #iconTooltip (voir base_app.html + static/js/icon-tooltip.js),
   position:fixed calculee en JS plutot qu'un ::after positionne en absolu
   par bouton : les boutons vivent dans un conteneur overflow-x:auto (voir
   dashboard.html) qui force aussi overflow-y a auto, ce qui rognait/
   masquait un tooltip absolu selon la ligne du tableau. Pas de delai avant
   apparition - doit s'afficher a chaque survol, meme bref. */
.icon-tooltip{
  position: fixed;
  max-width: 220px;
  width: max-content;
  padding: .375rem .625rem;
  border-radius: var(--r-sm);
  background: var(--text);
  color: var(--page);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.35;
  text-align: left;
  box-shadow: 0 4px 14px rgba(0,0,0,.18);
  opacity: 0;
  pointer-events: none;
  z-index: 500;
}
.icon-tooltip.is-visible{ opacity: 1; }

.btn + .btn{ margin-left: 0; }
.btn-block + .btn-block{ margin-top: .5rem; }

/* ==========================================================================
   Cards / surfaces
   ========================================================================== */

.card{
  position: relative;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .028), rgba(255, 255, 255, 0) 120px),
    var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  transition:
    border-color .22s var(--ease),
    box-shadow .22s var(--ease),
    transform .22s var(--ease),
    background-color .22s var(--ease);
}
/* Filet de lumière en haut de carte — signature discrète */
.card::before{
  content: "";
  position: absolute;
  top: 0; left: 12%; right: 12%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .16), transparent);
  pointer-events: none;
}
.card:hover{ border-color: var(--border-strong); }

/* Cartes cliquables : un lien ou un bouton qui enveloppe la carte */
a.card:hover,
button.card:hover,
.card[role="button"]:hover,
.card[tabindex]:hover{
  transform: translateY(-2px);
  box-shadow: var(--sh-3);
  border-color: var(--brand-line);
}
a.card:focus-visible,
button.card:focus-visible,
.card[tabindex]:focus-visible{ outline: none; box-shadow: var(--sh-2), var(--ring); }

.card-pad{ padding: 1.25rem 1.375rem; }

/* Resume "carte de resultat" - partage entre l'ecran de creation de projet
   (templates/index.html) et le popup de resume par projet (dashboard.html,
   clic sur le nom d'un projet). */
.result-summary{
  text-align: center;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 20px;
}

.result-profit{
  font-size: 44px;
  font-weight: 800;
  line-height: 1.1;
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}

.result-row{
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 13px 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13.5px;
  color: var(--text-dim);
}

.result-row:last-of-type{ border-bottom: none; }

.result-row .value{
  font-size: 17px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.result-actions{
  display: flex;
  gap: 12px;
  margin-top: 20px;
}

.result-actions .btn{ flex: 1; padding: 14px; }

.card-header{
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.125rem 1.375rem;
  border-bottom: 1px solid var(--border-soft);
}
.card-header + .card-pad{ padding-top: 1.125rem; }

/* ---- Widget "Echeances de paiement" (calendrier mensuel) ---- */
/* En largeur "petite" (w-half), le titre + la nav de mois doivent pouvoir
   passer sur deux lignes plutot que deborder - .card-header n'a pas de
   flex-wrap par defaut (les autres widgets n'ont jamais de bloc aussi
   large que la nav de mois a cote du titre). */
.dash-widget[data-item-id="payment_calendar"] .card-header{ flex-wrap: wrap; }
.pcal-nav{ display: flex; align-items: center; gap: 6px; flex: none; }
.pcal-nav-btn{
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  font-size: 15px; line-height: 1; color: var(--text-dim);
  background: rgba(255,255,255,.04); border: 1px solid var(--border); border-radius: var(--r-sm);
  cursor: pointer; transition: color .15s, background-color .15s;
}
.pcal-nav-btn:hover{ color: var(--text); background: rgba(255,255,255,.08); }
.pcal-month-label{
  min-width: 108px; text-align: center; font-size: 13px; font-weight: 600; color: var(--text);
  text-transform: capitalize;
}
.pcal-today-btn{
  margin-left: 4px; padding: 4px 10px; font-size: 11.5px; font-weight: 600; color: var(--text-dim);
  background: transparent; border: 1px solid var(--border); border-radius: var(--r-full);
  cursor: pointer; transition: color .15s, background-color .15s;
}
.pcal-today-btn:hover{ color: var(--text); background: rgba(255,255,255,.06); }

.pcal-weekdays{
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  padding: .875rem 1.375rem 0;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-mute); text-align: center;
}
.pcal-grid{
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  padding: 4px 1.375rem 1.125rem;
}
.pcal-day{
  position: relative; min-height: 56px; padding: 4px 4px 5px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: rgba(255,255,255,.02); border: 1px solid var(--border-soft); border-radius: var(--r-sm);
  font-size: 11.5px; color: var(--text-dim);
}
.pcal-day.is-other-month{ opacity: .35; }
.pcal-day.is-today{ border-color: var(--brand-line); box-shadow: inset 0 0 0 1px var(--brand-line); }
.pcal-day-num{ font-variant-numeric: tabular-nums; }
.pcal-day.has-due{ cursor: pointer; background: rgba(255,255,255,.045); }
.pcal-day.has-due:hover{ border-color: var(--border-strong); }
.pcal-day.has-due:focus-visible{ outline: none; box-shadow: var(--ring); }
.pcal-day-due{
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 18px; padding: 0 5px;
  font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums;
  border-radius: var(--r-full);
  background: var(--warning-soft); color: var(--warning-text);
}
.pcal-day-due.is-late{ background: var(--critical-soft); color: var(--critical-text); }
.pcal-day.is-selected{ border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }

.pcal-empty{ padding: 0 1.375rem 1.125rem; font-size: 13px; color: var(--text-mute); }

.pcal-detail{
  margin: 0 1.375rem 1.125rem; padding: .75rem .875rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm);
}
.pcal-detail-head{
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  margin-bottom: .5rem; font-size: 12.5px; font-weight: 700; color: var(--text);
}
.pcal-detail-close{
  background: none; border: none; color: var(--text-mute); font-size: 16px; line-height: 1;
  cursor: pointer; padding: 2px 4px;
}
.pcal-detail-close:hover{ color: var(--text); }
.pcal-detail-row{
  display: flex; align-items: baseline; justify-content: space-between; gap: .75rem;
  padding: 5px 0; font-size: 12.5px; border-top: 1px solid var(--border-soft);
}
.pcal-detail-row:first-of-type{ border-top: none; }
.pcal-detail-name{ color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcal-detail-amount{ flex: none; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.pcal-detail-amount.is-late{ color: var(--critical-text); }

@media (max-width: 560px){
  .pcal-day{ min-height: 42px; font-size: 10.5px; }
  .pcal-month-label{ min-width: 84px; font-size: 12px; }
}

.card-title{
  margin: 0;
  font-size: .9375rem;
  font-weight: 600;
  letter-spacing: -.014em;
  color: var(--text);
  line-height: 1.35;
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-subtitle{
  margin: .25rem 0 0;
  font-size: .8125rem;
  font-weight: 450;
  color: var(--text-mute);
  letter-spacing: 0;
  line-height: 1.45;
}
.card-title + .card-subtitle{ margin-top: .2rem; }

/* ==========================================================================
   Split workspace — one unified surface, two panes with an internal divider
   (form + live result, main settings + secondary list, etc.) instead of two
   separate floating cards.
   ========================================================================== */

.split-card{
  display: grid;
  grid-template-columns: 1fr 1px minmax(280px, 360px);
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.split-divider{ background: var(--border); }

.split-main{ padding: 30px; min-width: 0; }

.split-side{
  padding: 30px;
  background: var(--surface-2);
  min-width: 0;
}

.split-icon{
  width: 34px; height: 34px;
  border-radius: var(--r-sm);
  background: var(--brand-soft);
  display: flex; align-items: center; justify-content: center;
  font-size: 16px;
  flex: none;
}

@media (max-width: 950px){
  .split-card{ grid-template-columns: 1fr; }
  .split-divider{ display: none; }
  .split-side{ border-top: 1px solid var(--border); }
}

/* ==========================================================================
   Badges / pills / status
   ========================================================================== */

.badge{
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .1875rem .5rem;
  font-size: .75rem;
  font-weight: 550;
  line-height: 1.4;
  letter-spacing: -.005em;
  white-space: nowrap;
  color: var(--text-dim);
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  font-variant-numeric: tabular-nums;
}
/* Pastille d'état générée en CSS : le markup existant pose encore un
   <span class="badge-dot"></span> dans certains templates (non retiré,
   contrainte "aucun HTML modifié") — il reste vide et invisible sans
   règle dédiée, cette pastille ::before le remplace visuellement. */
.badge-good::before,
.badge-warning::before,
.badge-critical::before{
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 3px color-mix(in oklab, currentColor 18%, transparent);
  flex: none;
}
.badge-good     { color: #4ade80; background: var(--good-soft);     border-color: rgba(22, 163, 74, .3); }
.badge-warning  { color: #fcd34d; background: var(--warning-soft);  border-color: rgba(242, 176, 30, .3); }
.badge-critical { color: #ff9d9d; background: var(--critical-soft); border-color: rgba(220, 74, 74, .3); }
.badge-neutral  { background: var(--surface-3); color: var(--text-dim); }

/* ==========================================================================
   Tooltip (info icon)
   ========================================================================== */

.tip{
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-mute);
  font-size: 10px;
  cursor: help;
  flex: none;
}

.tip .tip-text{
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s var(--ease);
  position: absolute;
  z-index: 40;
  bottom: 135%;
  right: -12px;
  width: 200px;
  max-width: calc(100vw - 32px);
  /* Toujours sombre, dans les deux themes : un tooltip ponctuel qui reste
     lisible sans dependre du theme de la page, comme les tooltips OS. */
  background: var(--tooltip-bg);
  color: #f2f6fb;
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.5;
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
}

.tip:hover .tip-text{ visibility: visible; opacity: 1; }

/* ==========================================================================
   Forms
   ========================================================================== */

label{
  display: block;
  margin-bottom: .375rem;
  font-size: .8125rem;
  font-weight: 550;
  color: var(--text-dim);
  letter-spacing: -.005em;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="date"],
select,
textarea{
  display: block;
  width: 100%;
  padding: .625rem .8125rem;
  min-height: 42px;
  font: inherit;
  font-size: .9375rem;
  color: var(--text);
  background: var(--surface-sunk-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35);
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease), background-color .18s var(--ease);
  appearance: none;
}
textarea{ min-height: 96px; resize: vertical; line-height: 1.55; }

input::placeholder, textarea::placeholder{ color: var(--text-mute); opacity: 1; }

input:hover, select:hover, textarea:hover{ border-color: var(--border-strong); }

input:focus, select:focus, textarea:focus{
  outline: none;
  background: var(--surface-sunk-4);
  border-color: var(--brand);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35), var(--ring);
}

input:disabled, select:disabled, textarea:disabled{ opacity: .5; cursor: not-allowed; }

/* Champ invalide (voir static/js/field-validation.js) : remplace un
   alert() navigateur non stylise par un retour visuel directement sur le
   champ en cause - montre AU LIEU de dire quel champ pose probleme. */
input.input-error, select.input-error, textarea.input-error{
  border-color: var(--critical) !important;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35), 0 0 0 3px var(--critical-soft) !important;
}

input:-webkit-autofill,
input:-webkit-autofill:focus{
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px var(--autofill-bg) inset, inset 0 1px 2px rgba(0, 0, 0, .35);
  caret-color: var(--text);
}

input[type="checkbox"], input[type="radio"]{
  width: 16px; height: 16px; min-height: 0;
  padding: 0; margin: 0;
  accent-color: var(--brand);
  cursor: pointer;
}

select{
  padding-right: 2.25rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-mute) 50%),
    linear-gradient(135deg, var(--text-mute) 50%, transparent 50%);
  background-position: calc(100% - 17px) 52%, calc(100% - 12px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
  /* Chromium 135+ : sort le popup d'options de sa fenetre OS separee (que le
     CSS de la page ne peut quasiment pas styliser - fond/fleche natifs, pas
     de theme) pour le ramener dans la page, entierement stylisable. Simple
     amelioration progressive : sur un navigateur qui ne reconnait pas
     "base-select", cette valeur est invalide et ignoree - la ligne
     "appearance: none" ci-dessus au-dessus reste seule en vigueur, aucune
     casse possible. */
  appearance: base-select;
}
select::picker-icon{
  /* La fleche nativement dessinee par le picker fait doublon avec notre
     propre chevron (background-image ci-dessus, qui reste visible sur l'etat
     ferme du select) - supprimee plutot que les superposer. */
  display: none;
}
select::picker(select){
  appearance: base-select;
  margin-top: 6px;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: var(--sh-2);
  padding: 4px;
}
select:focus{
  background-image:
    linear-gradient(45deg, transparent 50%, var(--brand) 50%),
    linear-gradient(135deg, var(--brand) 50%, transparent 50%);
}
select option{
  padding: .5rem .625rem;
  border-radius: var(--r-sm);
}
select option:hover,
select option:focus{
  background: var(--surface-hover);
}
select option:checked{
  background: var(--brand-soft);
  color: var(--brand-dim);
}

/* ==========================================================================
   SELECT PERSONNALISE (.csel) — voir static/js/custom-select.js
   Sur Chromium, le popup natif d'un <select> s'ouvre dans une fenetre OS
   a part que le CSS ne peut quasiment pas styliser (ni fond, ni fleche de
   connexion, theme ignore). Pour les select ou ca s'est vu (regime fiscal,
   immatriculation...), le JS construit ce menu maison a la place - le
   <select> d'origine reste dans le DOM (valeur/soumission/onchange
   inchanges) mais n'est plus jamais ouvert directement par l'utilisateur.
   ========================================================================== */
.csel{ position: relative; }
.csel-native{ position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }

.csel-trigger{
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  width: 100%; padding: .625rem .8125rem; min-height: 42px;
  font: inherit; font-size: .9375rem; color: var(--text); text-align: left;
  background: var(--surface-sunk-3); border: 1px solid var(--border-strong); border-radius: var(--r-md);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35);
  cursor: pointer;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.csel-trigger:hover{ border-color: rgba(255, 255, 255, .24); }
.csel-trigger:focus-visible{
  outline: none; border-color: var(--brand);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35), var(--ring);
}
.csel-label{ flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.csel-arrow{
  flex: none; width: 7px; height: 7px; margin-top: -3px;
  border-right: 1.5px solid var(--text-mute); border-bottom: 1.5px solid var(--text-mute);
  transform: rotate(45deg); transition: transform .15s var(--ease), margin .15s var(--ease);
}
.csel.is-open .csel-arrow{ transform: rotate(225deg); margin-top: 2px; }
.csel.is-open .csel-trigger{ border-color: var(--brand); box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35), var(--ring); }

.csel-panel{
  position: absolute; z-index: 60; top: calc(100% + 6px); left: 0; right: 0;
  max-height: 280px; overflow-y: auto;
  background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: var(--r-md);
  box-shadow: var(--sh-2); padding: 4px;
  display: none;
}
.csel.is-open .csel-panel{ display: block; }
.csel-opt{
  padding: .5rem .625rem; border-radius: var(--r-sm); font-size: .9375rem;
  color: var(--text-dim); cursor: pointer; line-height: 1.4;
}
.csel-opt.is-active{ background: var(--surface-hover); color: var(--text); }
.csel-opt.is-selected{ background: var(--brand-soft); color: var(--brand-dim); font-weight: 550; }

/* Miroir de select.input-error (voir custom-select.js) - le <select> reel
   est cache, c'est ce bouton que l'utilisateur voit. */
.csel-trigger.input-error{
  border-color: var(--critical) !important;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .35), 0 0 0 3px var(--critical-soft) !important;
}
html[data-theme="light"] .csel-trigger.input-error{
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07), 0 0 0 3px var(--critical-soft) !important;
}

html[data-theme="light"] .csel-trigger{
  background: #ffffff; border-width: 1.5px; border-color: var(--control-line);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07);
}
html[data-theme="light"] .csel-trigger:hover{ border-color: var(--control-line-hover); }
html[data-theme="light"] .csel-trigger:focus-visible{
  border-color: var(--brand-dim);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07), var(--ring);
}
html[data-theme="light"] .csel.is-open .csel-trigger{
  border-color: var(--brand-dim);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07), var(--ring);
}

#waitlist{
  display: grid;
  gap: .625rem;
  width: 100%;
  max-width: 380px;
}
#waitlist .btn,
#waitlist button[type="submit"]{ margin-top: .125rem; }
#waitlist small,
#waitlist .form-note{ display: block; margin-top: .125rem; }

.field-grid{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.field-hint{ font-size: 12px; color: var(--text-mute); margin-top: 6px; }

/* Champ mot de passe avec bouton "afficher/masquer" (l'oeil). Generique,
   utilise partout ou un mot de passe est saisi (login, signup). Logique
   dans static/js/password-toggle.js. */
.field-password{ position: relative; }
.field-password input{ padding-right: 2.75rem; }
.field-password-toggle{
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--text-mute);
  cursor: pointer;
}
.field-password-toggle:hover{ color: var(--text-dim); background: var(--surface-hover); }
.field-password-toggle:focus-visible{ outline: none; box-shadow: var(--ring); }
.field-password-toggle svg{ width: 18px; height: 18px; }
.field-password-toggle .eye-slash{ opacity: 0; transition: opacity .15s var(--ease); }
.field-password-toggle.is-visible .eye-slash{ opacity: 1; }

/* ==========================================================================
   Tables
   ========================================================================== */

table{ width: 100%; border-collapse: collapse; }

.data-table thead th{
  text-align: left;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-mute);
  font-weight: 600;
  padding: 0 14px 12px;
  border-bottom: 1px solid var(--border);
}

.data-table tbody td{
  padding: 14px;
  border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
}

.data-table tbody tr:last-child td{ border-bottom: none; }

.data-table tbody tr.row-clickable{ cursor: pointer; transition: background .15s var(--ease); }
.data-table tbody tr.row-clickable:hover{ background: var(--surface-2); }
.data-table tbody tr.row-clickable:hover .project-avatar{ transform: scale(1.08); }
.data-table tbody tr.row-clickable:hover .chevron{ color: var(--text-dim); }

/* ==========================================================================
   Empty / loading states
   ========================================================================== */

.empty-state{
  padding: 48px 20px;
  text-align: center;
  color: var(--text-dim);
}

.empty-state .empty-icon{ font-size: 28px; margin-bottom: 10px; }

/* ==========================================================================
   Modal
   ========================================================================== */

.modal-overlay{
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(4,7,15,.65);
  backdrop-filter: blur(2px);
  z-index: 999;
  justify-content: center;
  align-items: center;
  padding: 20px;
}

.modal-box{
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 26px;
  width: 100%;
  max-width: 340px;
  text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
  animation: modal-pop .16s var(--ease);
}

@keyframes modal-pop{
  from{ transform: scale(.94); opacity: 0; }
  to{ transform: scale(1); opacity: 1; }
}

.modal-actions{ display: flex; gap: 10px; margin-top: 18px; }
.modal-actions .btn{ flex: 1; }

.export-modal-box{ max-width: 460px; text-align: left; }

.export-item-list{
  max-height: 320px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.export-item{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-md, 8px);
  border: 1px solid var(--border);
  background: var(--bg-soft, transparent);
  cursor: pointer;
  font-size: 14px;
  transition: background .12s, border-color .12s;
}

.export-item:hover{ border-color: var(--accent, #4a8cff); }

.export-item.is-checked{
  background: rgba(74,140,255,.14);
  border-color: var(--accent, #4a8cff);
}

/* ---- modale de personnalisation de fin de tuto : liste + apercu concret ---- */
.tour-indicators-modal-box{ max-width: 840px; }

.tour-indicators-layout{
  display: flex;
  gap: 22px;
  align-items: flex-start;
}

.tour-indicators-list-col{ flex: 1 1 auto; min-width: 0; }

.tour-indicators-preview-col{
  width: 340px;
  flex: none;
  position: sticky;
  top: 0;
}

.tour-preview-label{
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-bottom: 8px;
}

.tour-preview-box{
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  padding: 16px;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tour-preview-box .tour-preview-placeholder{
  color: var(--text-mute);
  font-size: 13px;
  text-align: center;
  padding: 20px 6px;
}

.tour-preview-box > .stat-tile,
.tour-preview-box > .card{ width: 100%; margin: 0; cursor: default; }
.tour-preview-box > .stat-tile:hover{ transform: none; }

@media (max-width: 800px){
  .tour-indicators-layout{ flex-direction: column; }
  .tour-indicators-preview-col{ width: 100%; position: static; }
}

.export-item input.export-checkbox{
  width: 16px;
  height: 16px;
  accent-color: var(--accent, #4a8cff);
  flex: none;
}

/* ==========================================================================
   Marketing shell (landing + informational pages)
   ========================================================================== */

.m-header{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 40px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: rgba(11,17,32,.85);
  backdrop-filter: blur(10px);
  z-index: 50;
}

.m-logo{
  font-weight: 800;
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  letter-spacing: -.01em;
  flex: none;
}

.m-logo .mark{
  width: 30px; height: 30px;
  border-radius: 9px;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
.m-logo .mark img{ width: 100%; height: 100%; display: block; }

.m-nav{
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 22px;
  row-gap: 10px;
}

.m-nav a{
  color: var(--text-dim);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  transition: color .15s var(--ease);
}

.m-nav a:hover{ color: var(--text); }

.m-nav a:not(.btn){
  position: relative;
  padding-bottom: 3px;
}

.m-nav a:not(.btn)::after{
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--brand);
  transition: right .2s var(--ease);
}

.m-nav a:not(.btn):hover::after{ right: 0; }

/* ---- mobile hamburger toggle ---- */

.m-nav-toggle{
  display: none;
  background: none;
  border: none;
  padding: 8px;
  margin: -8px;
  cursor: pointer;
  border-radius: var(--r-sm);
  transition: background .15s var(--ease);
}

.m-nav-toggle:hover{ background: var(--surface-2); }

.m-nav-toggle .bars{
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 20px;
  height: 16px;
}

.m-nav-toggle .bars span{
  display: block;
  height: 2px;
  background: var(--text);
  border-radius: 2px;
  transition: transform .25s var(--ease), opacity .2s var(--ease);
}

.m-nav-toggle.open .bars span:nth-child(1){ transform: translateY(7px) rotate(45deg); }
.m-nav-toggle.open .bars span:nth-child(2){ opacity: 0; }
.m-nav-toggle.open .bars span:nth-child(3){ transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 768px){
  .m-header{ padding: 14px 18px; }

  .m-nav-toggle{ display: inline-flex; align-items: center; justify-content: center; }

  .m-nav{
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 10px 18px 18px;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity .18s var(--ease), transform .18s var(--ease), visibility 0s linear .18s;
    box-shadow: 0 16px 40px rgba(0,0,0,.4);
  }

  .m-nav.open{
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity .18s var(--ease), transform .18s var(--ease);
  }

  .m-nav a:not(.btn){ padding: 12px 10px; border-radius: var(--r-sm); }
  .m-nav a:not(.btn)::after{ display: none; }
  .m-nav a:not(.btn):hover{ background: var(--surface-2); }
  .m-nav .btn{ margin-top: 8px; justify-content: center; }
}

/* ==========================================================================
   Selecteur de langue — drapeaux. Remplace le <select> natif : les emoji
   drapeau (regional indicators) ne s'affichent pas comme des drapeaux sur
   Windows, juste comme le code pays en lettres ("FR") — d'ou ce composant
   en SVG inline, aucun asset externe. Utilise dans base.html (variante
   compacte) et base_app.html (variante pleine largeur, .lang-switch--block).
   Logique dans static/js/lang-switch.js.
   ========================================================================== */

.lang-switch{ position: relative; flex: none; }

.lang-switch-btn{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 9px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  transition: background .18s var(--ease), border-color .18s var(--ease);
}
.lang-switch-btn:hover{ background: var(--surface-hover); }
.lang-switch-btn:focus-visible{ outline: none; box-shadow: var(--ring); }
.lang-switch-btn[aria-expanded="true"]{ border-color: var(--brand-line); }

.flag-icon{ width: 18px; height: 12px; border-radius: 2px; flex: none; display: block; }

.lang-switch-chevron{ width: 8px; height: 8px; color: var(--text-mute); flex: none; transition: transform .18s var(--ease); }
.lang-switch-btn[aria-expanded="true"] .lang-switch-chevron{ transform: rotate(180deg); }

.lang-switch-btn-label{ font-size: 12.5px; }

.lang-switch-menu{
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 172px;
  padding: 6px;
  margin: 0;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-3);
}
.lang-switch-menu[hidden]{ display: none; }

.lang-switch-option{
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 9px;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--text-dim);
  font-family: inherit;
  font-size: 13.5px;
  cursor: pointer;
  text-align: left;
}
.lang-switch-option:hover, .lang-switch-option:focus-visible{ background: var(--surface-2); color: var(--text); outline: none; }
.lang-switch-option.is-active{ color: var(--text); font-weight: 600; }
.lang-switch-option.is-active::after{ content: "✓"; margin-left: auto; color: var(--brand); font-weight: 700; }

/* Variante pleine largeur (sidebar de l'app, base_app.html). */
.lang-switch--block{ width: 100%; }
.lang-switch--block .lang-switch-btn{ width: 100%; justify-content: space-between; padding: 8px 10px; }
.lang-switch--block .lang-switch-btn-inner{ display: flex; align-items: center; gap: 8px; }
.lang-switch--block .lang-switch-menu{ left: 0; right: 0; min-width: 0; }

.m-wrap{
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 30px;
  width: 100%;
  flex: 1 0 auto;
}

/* Sous ~400px, .m-wrap (30px) + .card-pad (22px) laissaient moins de 300px
   de large disponibles pour le widget Cloudflare Turnstile en mode
   "flexible", qui a un plancher documente de 300px - il depassait alors du
   .card. On reduit les marges ici pour se rapprocher du seuil. */
@media (max-width: 420px){
  .m-wrap{ padding: 0 14px; }
}

.auth-card{ max-width: 420px; margin: 40px auto; }
@media (max-width: 420px){
  .auth-card{ margin: 24px auto; }
  .auth-card.card-pad{ padding: 1.25rem .75rem; }
}

.m-footer{
  flex: none;
  margin-top: 40px;
  padding: 28px 30px;
  border-top: 1px solid var(--border);
}

.m-footer-inner{
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  justify-content: center;
  gap: 30px;
  flex-wrap: wrap;
  font-size: 13.5px;
}

.m-footer-inner a{ color: var(--text-mute); text-decoration: none; }
.m-footer-inner a:hover{ color: var(--text-dim); }

/* ==========================================================================
   HERO — landing page (templates/landing.html)
   Direction "astronomique" : ciel étoilé sobre, nébuleuses très basses en
   opacité, mockup d'écran incliné en 3D, courbes de données qui se tracent.
   Registre visé : Linear / Vercel / Stripe. Sélecteurs alignés sur le
   markup réel de landing.html (voir commentaires) — pas de HTML modifié.
   ========================================================================== */

.hero{
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Le hero du handoff est concu comme une section pleine largeur, hors de
     tout conteneur (voir preview.html : <section class="hero"> est un
     enfant direct de <body>). Ici il est imbrique dans .m-wrap
     (max-width:1100px) : on le fait deborder de ce conteneur pour qu'il
     couvre toute la largeur de la page, comme prevu. */
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding: clamp(5rem, 12vh, 9rem) 1.5rem clamp(5rem, 11vh, 8rem);
  background: var(--page-deep);
}

/* Nébuleuses très basses en opacité, jamais de dégradé criard. */
.hero::before{
  content: "";
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 150%;
  z-index: -3;
  pointer-events: none;
  background:
    radial-gradient(760px 460px at 22% 6%,  rgba(74, 140, 255, .20), transparent 68%),
    radial-gradient(680px 420px at 84% 22%, var(--nebula-soft), transparent 70%),
    radial-gradient(1200px 700px at 50% 108%, rgba(74, 140, 255, .10), transparent 72%);
  filter: saturate(115%);
  animation: pt-drift 34s var(--ease) infinite alternate;
}
/* Raccord bas : le hero fond dans --page, pas de couture visible. */
.hero::after{
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 220px;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, var(--page));
}
@keyframes pt-drift{
  from{ transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to  { transform: translate3d(2%, 1.5%, 0) scale(1.06); }
}

/* Champ d'étoiles : .hero-bg (element reel, vide, dans landing.html) —
   grille de mesure en ::before, etoiles en ::after. Aucun asset, aucun JS. */
.hero-bg{
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: radial-gradient(130% 105% at 50% 30%, #000 42%, transparent 88%);
          mask-image: radial-gradient(130% 105% at 50% 30%, #000 42%, transparent 88%);
}
.hero-bg::before{
  content: "";
  position: absolute;
  inset: -50%;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, .028) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, .028) 1px, transparent 1px);
  background-size: 64px 64px;
  -webkit-mask-image: radial-gradient(60% 55% at 50% 42%, #000, transparent 75%);
          mask-image: radial-gradient(60% 55% at 50% 42%, #000, transparent 75%);
  transform: perspective(900px) rotateX(58deg) translateY(22%);
  transform-origin: 50% 100%;
  opacity: .8;
}
.hero-bg::after{
  content: "";
  position: absolute;
  inset: -25%;
  background-image:
    radial-gradient(1px 1px at 12% 18%, rgba(255,255,255,.75) 50%, transparent 51%),
    radial-gradient(1px 1px at 27% 62%, rgba(255,255,255,.55) 50%, transparent 51%),
    radial-gradient(1px 1px at 41% 9%,  rgba(255,255,255,.62) 50%, transparent 51%),
    radial-gradient(1px 1px at 58% 34%, rgba(255,255,255,.48) 50%, transparent 51%),
    radial-gradient(1px 1px at 69% 77%, rgba(255,255,255,.6)  50%, transparent 51%),
    radial-gradient(1px 1px at 83% 25%, rgba(255,255,255,.5)  50%, transparent 51%),
    radial-gradient(1px 1px at 92% 55%, rgba(255,255,255,.66) 50%, transparent 51%),
    radial-gradient(1px 1px at 7%  84%, rgba(255,255,255,.42) 50%, transparent 51%),
    radial-gradient(1px 1px at 35% 91%, rgba(255,255,255,.5)  50%, transparent 51%),
    radial-gradient(1px 1px at 51% 71%, rgba(255,255,255,.4)  50%, transparent 51%),
    radial-gradient(1px 1px at 76% 6%,  rgba(255,255,255,.52) 50%, transparent 51%),
    radial-gradient(1px 1px at 20% 40%, rgba(255,255,255,.45) 50%, transparent 51%),
    radial-gradient(2px 2px at 31% 28%, rgba(190,215,255,.9) 45%, transparent 46%),
    radial-gradient(2px 2px at 64% 52%, rgba(190,215,255,.7) 45%, transparent 46%),
    radial-gradient(2px 2px at 88% 80%, rgba(190,215,255,.6) 45%, transparent 46%),
    radial-gradient(2px 2px at 15% 68%, rgba(200,190,255,.6) 45%, transparent 46%);
  background-repeat: no-repeat;
  animation: pt-twinkle 7.5s ease-in-out infinite alternate,
             pt-parallax 60s linear infinite alternate;
  will-change: opacity, transform;
}
@keyframes pt-twinkle{ 0%{ opacity: .55; } 50%{ opacity: .95; } 100%{ opacity: .7; } }
@keyframes pt-parallax{ from{ transform: translate3d(0,0,0); } to{ transform: translate3d(-2.5%, 1.5%, 0); } }

/* .hero-header-glow existait pour faire deborder un halo dans le header
   quand le hero etait pense pour s'etendre derriere lui. Le nouveau hero
   est autonome (overflow:hidden, ne deborde plus) : cet element reste dans
   le HTML (non retire, contrainte "aucun HTML modifie") mais n'a plus de
   role visuel. */
.hero-header-glow{ display: none; }

/* Calque des courbes SVG (.hero-deco enveloppe les deux <svg class="hero-curve
   hero-curve-*"> reels de landing.html). .hero lui-meme est overflow:hidden,
   donc plus besoin d'un clip dedie ici. */
.hero-deco{
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.hero-curve{
  position: absolute;
  pointer-events: none;
  overflow: visible;
}
.hero-curve-a{ width: 190px; bottom: 86px; left: 10px; }
.hero-curve-b{ width: 210px; top: 6px; right: 26px; }

/* Arc de planete (couche 4/7 du handoff d'origine, restauree ici) : limbe
   eclaire d'une planete vue du dessus, en bas du hero. Element reel vide
   dans le markup (.hero-curve-arc), aucun asset. */
.hero-curve-arc{
  position: absolute;
  left: -10%; right: -10%; bottom: -14%;
  height: 60%;
  z-index: -1;
  border-radius: 50% 50% 0 0 / 100% 100% 0 0;
  border-top: 1px solid rgba(74, 140, 255, .3);
  background: radial-gradient(100% 130% at 50% 100%, rgba(74, 140, 255, .1), transparent 62%);
  box-shadow: 0 -24px 70px -30px rgba(74, 140, 255, .55);
  pointer-events: none;
}

[class*="hero-curve"] path{
  fill: none;
  stroke: var(--brand-line);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 6px rgba(74, 140, 255, .45));
  stroke-width: 2.5px;
  /* 240 ~ longueur reelle des path de landing.html (mesuree ~226 unites) ;
     la valeur de reference du handoff (1800) est prevue pour des path bien
     plus longs et tronquerait le tempo du tracé ici. */
  stroke-dasharray: 240;
  animation: pt-draw 4.5s ease-in-out infinite;
}
.hero-curve-b path{ animation-duration: 5.5s; animation-delay: -1.8s; }

[class*="hero-curve"] [class*="dot"]{
  fill: #bcd4ff;
  r: 3.2px;
  offset-rotate: 0deg;
  filter: drop-shadow(0 0 8px rgba(74, 140, 255, .9));
  animation: pt-draw-dot 4.5s ease-in-out infinite, pt-orb 4.5s ease-in-out infinite;
}
.hero-curve-a [class*="dot"]{ offset-path: path('M10,100 L48,82 L80,92 L118,55 L155,65 L190,18'); }
.hero-curve-b [class*="dot"]{
  offset-path: path('M10,55 L48,72 L86,35 L124,52 L162,22 L200,38');
  animation-duration: 5.5s;
  animation-delay: -1.8s;
}

@keyframes pt-draw{
  0%{ stroke-dashoffset: 240; opacity: 0; }
  8%{ opacity: .85; }
  55%{ stroke-dashoffset: 0; opacity: .85; }
  80%{ stroke-dashoffset: 0; opacity: .85; }
  95%{ opacity: 0; }
  100%{ stroke-dashoffset: 240; opacity: 0; }
}
@keyframes pt-draw-dot{
  0%{ offset-distance: 0%; opacity: 0; }
  8%{ opacity: 1; }
  55%{ offset-distance: 100%; opacity: 1; }
  80%{ offset-distance: 100%; opacity: 1; }
  95%{ opacity: 0; }
  100%{ offset-distance: 0%; opacity: 0; }
}
@keyframes pt-orb{ 0%, 100%{ transform: scale(.85); } 50%{ transform: scale(1.15); } }

@media (max-width: 900px){
  .hero-curve{ display: none; }
}

/* --- Contenu --- */
.hero-content{
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  align-items: center;
  gap: clamp(2.5rem, 6vw, 5.5rem);
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
}

.hero-text{ max-width: 34rem; text-align: left; }

.hero h1{
  margin-bottom: 1.1rem;
  font-size: clamp(2.3rem, 5.4vw, 3.75rem);
  font-weight: 630;
  letter-spacing: -.04em;
  line-height: 1.04;
  background: linear-gradient(176deg, #ffffff 8%, #c8d6ec 96%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
.hero h1 span{
  font-style: normal;
  background: linear-gradient(100deg, var(--brand) 0%, #8ea8ff 55%, var(--nebula) 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.hero-sub{
  font-size: clamp(1rem, 1.35vw, 1.135rem);
  line-height: 1.62;
  color: var(--text-dim);
  max-width: 33rem;
}

.hero-badge{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 24px;
  backdrop-filter: blur(8px);
}

.waitlist-form{
  margin-top: 1.75rem;
  display: flex;
  justify-content: flex-start;
  gap: 10px;
  flex-wrap: wrap;
}
.waitlist-form input{ width: 280px; padding: 14px 16px; }
.waitlist-form .btn{ padding: 14px 22px; min-height: 44px; }

.hero-note{ font-size: 13px; color: var(--text-mute); margin-top: 14px; }

/* Preuve sociale : uniquement des chiffres reels issus de la base
   (voir app.py _get_landing_stats) ; le bloc est masque cote serveur
   tant qu'ils restent trop bas pour etre credibles. */
.hero-stats{
  display: flex;
  gap: clamp(2rem, 5vw, 3.5rem);
  margin-top: 28px;
  padding-top: 22px;
  border-top: 1px solid var(--border-soft);
}
.hero-stat{ display: flex; flex-direction: column; gap: 4px; }
.hero-stat-value{
  font-size: clamp(2.75rem, 6vw, 4rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(100deg, var(--text) 0%, var(--brand-line) 130%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-stat-label{ font-size: 13.5px; color: var(--text-mute); }

/* Entrée en fondu-montée en cascade, une seule fois. */
.hero-text > *{ animation: pt-rise .85s var(--ease-out) both; }
.hero-text > *:nth-child(1){ animation-delay: .04s; }
.hero-text > *:nth-child(2){ animation-delay: .12s; }
.hero-text > *:nth-child(3){ animation-delay: .2s; }
.hero-text > *:nth-child(4){ animation-delay: .28s; }
.hero-text > *:nth-child(5){ animation-delay: .36s; }
.hero-text > *:nth-child(6){ animation-delay: .44s; }
@keyframes pt-rise{
  from{ opacity: 0; transform: translate3d(0, 14px, 0); filter: blur(6px); }
  to  { opacity: 1; transform: none; filter: none; }
}

/* --- Mockup d'écran : .hero-device* (structure reelle : .hero-device-wrap
   > .hero-device > .hero-device-bar + .hero-device-screen) --- */
.hero-device-wrap{
  position: relative;
  z-index: 3;
  perspective: 1700px;
  perspective-origin: 60% 40%;
  display: flex;
  justify-content: center;
}

/* Second ecran en retrait derriere le mockup principal : suggere une
   application multi-ecrans sans ajouter d'asset (juste un chassis vide,
   deforme/attenue, positionne derriere et decale). */
.hero-device-ghost{
  position: absolute;
  z-index: 0;
  width: 72%;
  aspect-ratio: 4 / 3;
  right: 2%;
  bottom: -4%;
  border-radius: var(--r-lg);
  background: linear-gradient(155deg, #1c2740 0%, #0e1729 55%, #0a0f1a 100%);
  border: 1px solid rgba(255, 255, 255, .06);
  box-shadow: 0 30px 60px -26px rgba(0, 0, 0, .85);
  opacity: .5;
  transform: rotateY(-13deg) rotateX(5deg) rotateZ(-1.2deg) translate3d(6%, 5%, -40px) scale(.9);
  pointer-events: none;
  animation: pt-rise 1s var(--ease-out) .1s both;
}

.hero-device{
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 460px;
  border-radius: var(--r-lg);
  padding: 10px 10px 22px;
  background: linear-gradient(155deg, #24314c 0%, #131c2e 45%, #0b1120 100%);
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .14),
    inset 0 0 0 1px rgba(0, 0, 0, .5),
    0 40px 80px -30px rgba(0, 0, 0, .9),
    0 8px 24px -12px rgba(0, 0, 0, .8);
  --tx: 0px;
  --ty: 0px;
  transform: rotateY(-13deg) rotateX(5deg) rotateZ(-1.2deg);
  transform-style: preserve-3d;
  transition: transform .55s var(--ease);
  animation: pt-rise 1s var(--ease-out) .22s both, pt-device-float 7s ease-in-out .9s infinite;
}
@keyframes pt-device-float{
  0%, 100%{ transform: rotateY(-13deg) rotateX(5deg) rotateZ(-1.2deg) translateY(0); }
  50%{ transform: rotateY(-13deg) rotateX(5deg) rotateZ(-1.2deg) translateY(-14px); }
}
/* Halo porté sous l'appareil : c'est lui qui donne la profondeur. */
.hero-device::before{
  content: "";
  position: absolute;
  left: 8%; right: 8%; bottom: -6%;
  height: 42%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 140, 255, .34), transparent 72%);
  filter: blur(38px);
  pointer-events: none;
  animation: pt-pulse 9s ease-in-out infinite alternate;
}
@keyframes pt-pulse{ from{ opacity: .55; transform: scale(.94); } to{ opacity: .95; transform: scale(1.06); } }

/* Liseré de bord anime (registre Raycast) : un conic-gradient plus grand
   que le chassis tourne derriere lui ; comme le chassis est opaque, seul
   un fin cerne colore reste visible sur le contour. Rotation via
   transform (pas d'animation de l'angle du gradient) : compatible partout,
   aucune dependance a @property. */
.hero-device::after{
  content: "";
  position: absolute;
  inset: -2.5px;
  z-index: -2;
  border-radius: inherit;
  background: conic-gradient(from 0deg,
    transparent 0 66%,
    var(--brand-line) 78%,
    var(--nebula) 86%,
    var(--brand-line) 92%,
    transparent 100%);
  opacity: .9;
  pointer-events: none;
  animation: pt-border-spin 7s linear infinite;
}
@keyframes pt-border-spin{ to{ transform: rotate(360deg); } }

/* Approche au survol : le chassis vient au centre de l'ecran, bien droit,
   grossi, comme s'il s'approchait du visiteur — voir landing.html pour
   la logique. Transform/transition/animation sont pilotes entierement en
   style inline pendant le survol (jamais de classe CSS pour ces
   proprietes-la) : melanger animations CSS continues, transitions et
   classes s'est revele fragile (une animation, meme en pause, garde
   toujours la priorite sur une transition pour la meme propriete ; un
   deuxieme survol juste apres la fin du retour pouvait demarrer sur un
   etat intermediaire et se figer). Le style inline est sans ambiguite :
   chaque survol repart explicitement de la valeur de repos connue.
   ca reste position:relative tout du long, jamais fixed : un changement
   de position ne peut pas s'animer, alors qu'un transform toujours-
   relatif s'anime parfaitement dans les deux sens, retour inclus. */

/* pt-rise (l'entree au chargement) ne doit plus jamais faire partie de
   la liste une fois terminee : meme finie et tenue par fill-mode:both,
   elle continue de "detenir" transform:none en arriere-plan, seulement
   masquee par pt-device-float. pt-rise-done (ajoutee en JS des la fin
   reelle de pt-rise) la retire pour de bon. */
.hero-device.pt-rise-done{ animation: pt-device-float 7s ease-in-out infinite; }

.hero-device-bar{ display: flex; gap: 6px; padding: 8px 6px 10px; }
.hero-device-bar span{ width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.14); }

.hero-device-screen{
  position: relative;
  overflow: hidden;
  display: flex;
  gap: 12px;
  padding: 16px;
  border-radius: calc(var(--r-lg) - 6px);
  background: radial-gradient(120% 90% at 50% 0%, #162034, #0a1020 70%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07), inset 0 2px 18px rgba(0, 0, 0, .6);
}
/* Reflet de verre qui balaie l'écran, une fois toutes les 9 s. */
.hero-device-screen::after{
  content: "";
  position: absolute;
  top: -60%; bottom: -60%; left: -40%;
  width: 45%;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .07) 45%, transparent);
  transform: rotate(14deg);
  animation: pt-sweep 9s var(--ease) 1.6s infinite;
}
@keyframes pt-sweep{ 0%, 62%{ left: -45%; opacity: 0; } 66%{ opacity: 1; } 100%{ left: 118%; opacity: 0; } }

/* Bandeau lateral, comme la sidebar reelle de l'app. */
.hero-device-sidebar{
  flex: none;
  width: 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding-top: 2px;
}
.hero-device-sidebar .hds-mark{
  width: 20px; height: 20px;
  border-radius: 6px;
  background: linear-gradient(135deg, var(--brand), var(--nebula));
  margin-bottom: 4px;
  flex: none;
}
.hero-device-sidebar .hds-link{
  width: 26px; height: 22px;
  border-radius: 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  flex: none;
}
.hero-device-sidebar .hds-link.active{ background: var(--brand-soft); border-color: var(--brand); }

.hero-device-main{ flex: 1; min-width: 0; }

.hero-device-topbar{
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  margin-bottom: 14px;
}

/* Calculateur interactif du hero : remplace l'ancien mockup statique par
   un vrai calcul en direct (taux naif vs taux reel apres charges). Le
   visiteur experimente le coeur du produit — l'ecart entre ce qu'il croit
   gagner et ce qu'il gagne vraiment — avant meme de creer un compte,
   plutot que de regarder une capture d'ecran figee. */
.hc-topbar{ justify-content: space-between; }
.hc-live{
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
  color: var(--brand-text);
}
.hc-live-dot{
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--brand);
  animation: hc-pulse 1.8s ease-out infinite;
}
@keyframes hc-pulse{
  0%{ box-shadow: 0 0 0 0 var(--brand-line); }
  70%{ box-shadow: 0 0 0 5px rgba(74, 140, 255, 0); }
  100%{ box-shadow: 0 0 0 0 rgba(74, 140, 255, 0); }
}

.hc-fields{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-bottom: 8px; }
.hc-field{ display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.hc-field-label{ font-size: 8px; color: var(--text-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hc-input-wrap{
  display: flex; align-items: center; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px;
  padding: 5px 6px;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.hc-input-wrap:focus-within{ border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-soft); }
.hc-input{
  width: 100%; min-width: 0;
  font: inherit; font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text); background: transparent; border: none; outline: none;
  -moz-appearance: textfield;
}
.hc-input::-webkit-outer-spin-button, .hc-input::-webkit-inner-spin-button{ -webkit-appearance: none; margin: 0; }
.hc-unit{ font-size: 9px; color: var(--text-mute); flex: none; }

.hc-compare{
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 10px;
}
.hc-compare-row{ display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 3px 0; }
.hc-compare-label{ font-size: 9.5px; color: var(--text-mute); }
.hc-compare-value{ font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-dim); }
.hc-compare-row-real .hc-compare-label{ color: var(--text-dim); }
.hc-compare-row-real .hc-compare-value{ font-size: 17px; color: var(--good-text); transition: color .2s var(--ease); }
.hc-compare-row-real .hc-compare-value.is-down{ color: var(--critical-text); }
.hc-compare-bar{ margin-top: 6px; height: 4px; border-radius: 999px; background: rgba(255, 255, 255, .08); overflow: hidden; }
.hc-compare-bar-fill{ display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--critical), var(--warning)); transition: width .25s var(--ease); }

.hc-hint{ margin-top: 8px; font-size: 9.5px; line-height: 1.5; color: var(--text-mute); }
.hc-hint strong{ color: var(--text-dim); }

@media (max-width: 420px){
  .hc-fields{ grid-template-columns: 1fr 1fr; }
  .hc-fields .hc-field:last-child{ grid-column: 1 / -1; }
}

@media (max-width: 960px){
  .hero{ padding-top: clamp(3.5rem, 8vh, 5.5rem); }
  .hero-content{ grid-template-columns: 1fr; gap: 3rem; }
  .hero-text{ max-width: 40rem; text-align: center; }
  .hero-sub{ margin: 0 auto; }
  .waitlist-form{ justify-content: center; }
  .hero-device-wrap{ perspective: 1400px; }
  .hero-device-ghost{ display: none; }
  .hero-device{ max-width: 360px; transform: rotateY(-8deg) rotateX(4deg); }
  .hero-device:hover{ transform: rotateY(-8deg) rotateX(4deg); }
}

@media (max-width: 640px){
  .hero{ padding-inline: 1.125rem; }
  .hero h1{ font-size: clamp(2rem, 9vw, 2.6rem); }
  .hero-device{ transform: none; }
  .hero-device:hover{ transform: none; }
}

@media (prefers-reduced-motion: reduce){
  .hero::before,
  .hero-bg::after,
  .hero-curve path,
  [class*="hero-curve"] [class*="dot"],
  .hero-device,
  .hero-device::before,
  .hero-device::after,
  .hero-device-ghost,
  .hero-device-screen::after,
  .hc-live-dot{ animation: none !important; }
  [class*="hero-curve"] path{ stroke-dashoffset: 0; }
  .hero-text > *{ opacity: 1; transform: none; filter: none; }
}

/* ==========================================================================
   LANDING — sections complementaires (comment ca marche, tarifs, FAQ, CTA)
   Meme systeme que le hero : bandes pleine largeur (debordent de .m-wrap),
   contenu recentre a l'interieur via --shell. Prolonge le handoff
   "astronomique" au reste de la page (pas seulement le hero).
   ========================================================================== */

/* Bande pleine largeur, generique : dechire .m-wrap (max-width:1100px) pour
   couvrir tout l'ecran, exactement comme .hero. Sans ca, ces sections
   auraient un fond qui s'arrete a 1100px avec du vide sur les cotes. */
.lp-band{
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding: clamp(4rem, 9vh, 6rem) 1.5rem;
}
.lp-band-inner{
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
}
.lp-band + .lp-band{ border-top: 1px solid var(--border-soft); }

.lp-eyebrow{
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--brand-line);
  text-transform: uppercase;
  margin-bottom: .75rem;
}
.lp-band h2{ max-width: 32rem; }
.lp-band-lede{ max-width: 34rem; color: var(--text-dim); font-size: 1rem; line-height: 1.65; margin-top: .75rem; }

/* Echo tres discret de l'atmosphere du hero sur une bande sur deux —
   jamais plus qu'une nebuleuse en tres basse opacite, comme prescrit
   par le handoff ("jamais de degrade criard"). */
.lp-band-atmo{ background: var(--page-deep); }
.lp-band-atmo::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(900px 420px at 18% 0%, rgba(124, 108, 255, .08), transparent 70%),
              radial-gradient(700px 380px at 86% 100%, rgba(74, 140, 255, .07), transparent 72%);
}

/* --- Reveal au scroll : progressive enhancement --- */
/* Par defaut (pas de JS, ou navigateur sans IntersectionObserver) : contenu
   visible immediatement, aucune degradation. */
.reveal{ opacity: 1; transform: none; }
.js .reveal{
  opacity: 0;
  transform: translateY(28px);
  transition: opacity .8s var(--ease-out), transform .8s var(--ease-out);
}
.js .reveal.is-visible{ opacity: 1; transform: none; }
/* --i (inline style, optionnel) permet de faire cascader plusieurs .reveal
   voisins (etapes, cartes tarifs, items FAQ) sans JS de cascade dedie. */
.js .reveal{ transition-delay: calc(var(--i, 0) * 90ms); }

/* --- Comment ca marche (apercu, 3 etapes) --- */
.hiw-steps{
  position: relative;
  margin-top: 2.5rem;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
  counter-reset: hiw;
}
.hiw-step{ position: relative; padding-top: 2.75rem; }
.hiw-step::before{
  counter-increment: hiw;
  content: counter(hiw);
  position: absolute;
  top: 0; left: 0;
  width: 2.25rem; height: 2.25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r-full);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-weight: 700;
  font-size: .9375rem;
  font-variant-numeric: tabular-nums;
}
/* Ligne de connexion entre les etapes : un mini-escalier montant (pas une
   ligne plate) qui rejoue litteralement le modele "staircase" deja utilise
   pour les revenus d'abonnement (compute_subscription_timeline) - la
   metaphore de l'app plutot qu'une ligne generique. Trace au scroll comme
   les courbes du hero (meme esprit, stroke-dashoffset via pathLength). */
.hiw-stairs{
  /* left + width (pas left+right) : un SVG absolument positionne est un
     "replaced element" - avec width:auto, le navigateur retombe sur sa
     taille intrinseque (celle du viewBox) au lieu de s'etirer entre
     left/right. width explicite = pas d'ambiguite. */
  position: absolute;
  left: calc(100% / 6);
  width: calc(100% / 3 * 2);
  top: calc(1.125rem - 6px);
  height: 12px;
  z-index: -1;
  pointer-events: none;
}
.hiw-stairs path{
  fill: none;
  stroke: var(--brand-line);
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}
.js .hiw-stairs path{ stroke-dashoffset: 1; }
.js .hiw-steps:has(.hiw-step.is-visible) .hiw-stairs path{
  animation: ptx-trace-stairs 1.4s var(--ease-out) .25s both;
}
@supports not selector(:has(a)){ .js .hiw-stairs path{ stroke-dashoffset: 0; } }
@keyframes ptx-trace-stairs{ from{ stroke-dashoffset: 1; } to{ stroke-dashoffset: 0; } }
.hiw-step h3{ font-size: 1.0625rem; margin-bottom: .375rem; }
.hiw-step p{ font-size: .9375rem; color: var(--text-dim); line-height: 1.6; }
.lp-steps-more{ margin-top: 2rem; }

/* --- Apercu tarifs --- */
.lp-pricing-grid{
  margin-top: 2.5rem;
  margin-left: auto;
  margin-right: auto;
  /* width:100% obligatoire ici : en tant qu'enfant flex de .lp-band-centered
     .lp-band-inner (align-items:center), un bloc sans largeur explicite
     retombe sur sa taille de contenu (fit-content) au lieu d'atteindre son
     max-width - il redevenait plus etroit qu'avant le recentrage. */
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
  max-width: 46rem;
}
/* Une seule carte (mensuel/annuel bascule via .pc-toggle) : grille a 1
   colonne, centree, largeur contenue pour ne pas etaler la carte pleine
   largeur comme le faisaient les 2 cartes cote a cote. */
.lp-pricing-grid-single{ grid-template-columns: 1fr; max-width: 22rem; }

.pc-toggle{
  position: relative;
  display: inline-flex;
  margin-top: 1.75rem;
  padding: 4px;
  gap: 2px;
  border-radius: var(--r-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.pc-toggle-btn{
  position: relative;
  z-index: 1;
  border: none;
  background: transparent;
  padding: .5rem 1.25rem;
  font-size: .8125rem;
  font-weight: 600;
  color: var(--text-mute);
  border-radius: var(--r-full);
  cursor: pointer;
  transition: color .2s var(--ease);
}
.pc-toggle-btn.is-active{ color: #fff; }
.pc-toggle-thumb{
  position: absolute;
  top: 4px; left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  border-radius: var(--r-full);
  background: var(--brand);
  transition: transform .28s var(--ease);
  z-index: 0;
}
.pc-toggle-thumb.is-annual{ transform: translateX(100%); }

.pricing-card{ display: flex; flex-direction: column; gap: .375rem; }
.pricing-card .pc-amount{ font-variant-numeric: tabular-nums; }
.pricing-card .pc-period{ font-size: .8125rem; color: var(--text-mute); }
.pricing-card .pc-amount{ font-size: 2rem; font-weight: 800; letter-spacing: -.02em; margin: .5rem 0; }
.pricing-card .pc-amount small{ font-size: .875rem; font-weight: 500; color: var(--text-mute); }
.pricing-card .pc-note{ font-size: .8125rem; color: var(--text-mute); margin-bottom: 1rem; }
.pricing-card.is-highlight{ border-color: var(--brand-line); box-shadow: var(--sh-glow); }
.pricing-card .pc-badge{ align-self: flex-start; margin-bottom: .5rem; }

.lp-pricing-no-card{
  margin-top: 1.25rem;
  text-align: center;
  font-size: .875rem;
  color: var(--text-mute);
}

/* --- FAQ (details/summary natif, aucun JS necessaire) --- */
.lp-faq-list{ margin-top: 2rem; margin-left: auto; margin-right: auto; width: 100%; max-width: 42rem; display: flex; flex-direction: column; gap: .625rem; }
.lp-faq-item{
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.lp-faq-item summary{
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.25rem;
  font-weight: 600;
  font-size: .9375rem;
  transition: background .18s var(--ease);
}
.lp-faq-item summary::-webkit-details-marker{ display: none; }
.lp-faq-item summary:hover{ background: var(--surface-hover); }
.lp-faq-item summary:focus-visible{ outline: none; box-shadow: inset var(--ring); }
.lp-faq-item summary::after{
  content: "+";
  flex: none;
  font-size: 1.25rem;
  font-weight: 400;
  color: var(--text-mute);
  transition: transform .22s var(--ease);
}
.lp-faq-item[open] summary::after{ transform: rotate(45deg); }
.lp-faq-item .faq-a{ padding: 0 1.25rem 1.125rem; color: var(--text-dim); font-size: .9375rem; line-height: 1.65; }

/* --- CTA final --- */
.lp-cta{ text-align: center; }
.lp-cta .lp-band-inner{ display: flex; flex-direction: column; align-items: center; }
.lp-cta h2{ max-width: 28rem; }
.lp-cta .lp-band-lede{ text-align: center; }
.lp-cta .btn{ margin-top: 1.75rem; padding: 1rem 2rem; }

/* --- Sections entierement recentrees (eyebrow/titre/lede + contenu) :
   meme principe que .lp-cta ci-dessus, reutilise pour tarifs et FAQ plutot
   que de ne centrer que la grille/liste en laissant le titre a gauche. --- */
.lp-band-centered{ text-align: center; }
.lp-band-centered .lp-band-inner{ display: flex; flex-direction: column; align-items: center; }

@media (max-width: 900px){
  .hiw-steps{ grid-template-columns: 1fr; gap: 2.25rem; }
  .hiw-stairs{ display: none; }
  .lp-pricing-grid{ grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce){
  .js .reveal{ opacity: 1; transform: none; }
}

/* ==========================================================================
   App shell (sidebar + topbar) — dashboard / new project / profile
   ========================================================================== */

.app-shell{
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
  /* z-index:auto overrides the `body > *{z-index:1}` rule (keeps app content
     above the atmospheric bg glow) - without this, .app-shell becomes its
     own stacking context and traps .app-sidebar's z-index:80 inside it, so
     .drawer-backdrop (z-index:70, a sibling of .app-shell, not a descendant)
     paints above the whole sidebar and every link becomes unclickable. */
  z-index: auto;
}

.app-sidebar{
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.app-sidebar .m-logo{ padding: 0 10px; margin-bottom: 22px; }

.side-link{
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  color: var(--text-dim);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
  transition: background .15s var(--ease), color .15s var(--ease);
}

.side-link .ico{ font-size: 15px; width: 18px; text-align: center; flex: none; transition: transform .15s var(--ease); }
/* "Comment ca marche" utilise une icone SVG (currentColor) plutot que
   l'emoji Unicode ❔ : cet emoji se rend en glyphe pale/creux sur beaucoup
   de systemes (contraste tres faible en theme clair) et son rendu depend du
   systeme d'exploitation, pas du CSS - impossible a rendre "sombre sur fond
   clair / clair sur fond sombre" de façon fiable. Le SVG en currentColor
   suit automatiquement color: var(--text-dim) du lien (deja theme-aware). */
.side-link .ico-help{ display: inline-flex; align-items: center; justify-content: center; }

.side-link:hover{ background: var(--surface-2); color: var(--text); }
.side-link:hover .ico{ transform: scale(1.15); }

.side-link.active{ background: var(--brand-soft); color: #93c5fd; }

.side-spacer{ flex: 1; }

.side-user{
  padding: 12px;
  border-radius: var(--r-md);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  gap: 10px;
}

.side-user-link{
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  color: inherit;
  text-decoration: none;
  border-radius: var(--r-sm);
  transition: color .15s var(--ease);
}

.side-user-link:hover{ color: var(--text); }

.side-user .avatar{
  width: 28px; height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand), #60a5fa);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: #fff;
  flex: none;
}

.side-user .email{
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-main{
  padding: 26px 34px 40px;
  min-width: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Cible de la navigation partielle htmx (voir base_app.html) - ne doit
   rien changer a la mise en page flex de .app-main ci-dessus : ses enfants
   (le contenu de chaque page) doivent rester des items flex directs de
   .app-main, exactement comme avant l'ajout de ce wrapper. */
#app-content{
  display: contents;
}

.app-footer{
  flex: none;
  margin-top: auto;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.app-topbar{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 26px;
  flex-wrap: wrap;
}

.app-topbar h1{ font-size: 22px; }

.app-topbar .subtitle{ color: var(--text-dim); font-size: 13.5px; margin-top: 5px; }

.app-topbar-actions{ display: flex; gap: 10px; position: relative; }

@media (max-width: 640px){
  .app-topbar-actions{ flex-wrap: wrap; width: 100%; }
  .app-topbar-actions > .btn{ flex: 1 1 auto; }
  .add-item-menu{ max-width: calc(100vw - 56px); }
}

/* ---- mobile topbar + off-canvas drawer ---- */

.app-mobile-bar{
  display: none;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 60;
}

.drawer-backdrop{ display: none; }

@media (max-width: 980px){
  .app-mobile-bar{ display: flex; }

  .app-shell{ grid-template-columns: 1fr; }

  .app-sidebar{
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: 260px;
    max-width: 82vw;
    z-index: 80;
    transform: translateX(-100%);
    transition: transform .25s var(--ease);
    box-shadow: 20px 0 50px rgba(0,0,0,.4);
  }

  .app-sidebar.open{ transform: translateX(0); }

  .app-sidebar .m-logo{ display: none; }

  .side-spacer{ display: block; }
  .side-user{ display: flex; }

  .drawer-backdrop{
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(4,7,15,.6);
    z-index: 70;
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s var(--ease);
  }

  .drawer-backdrop.open{
    opacity: 1;
    pointer-events: auto;
  }

  .app-main{ padding: 22px 18px 40px; }
}

/* ==========================================================================
   Utility
   ========================================================================== */

/* ==========================================================================
   Stat tiles / KPI row
   ========================================================================== */

.kpi-grid{
  display: grid;
  /* 4 colonnes fixes (2 lignes de 4 pour les 8 KPI) plutot qu'un auto-fit
     qui recalait 6 tuiles sur la 1ere ligne et forcait les montants longs
     a passer a la ligne (ex. "19000.00 €" coupe en deux). */
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}

/* Bascule Reel/Objectif (voir switchDashboardView) : cible les tuiles via
   [data-view], jamais element.style.display directement - une tuile deja
   masquee par l'utilisateur (kpi_hidden, inline style="display:none") doit
   le rester quelle que soit la vue active, ce que ce ciblage par classe sur
   le conteneur + attribut garantit sans jamais toucher son style inline. */
.kpi-grid.view-mode-reel [data-view="objectif"],
.kpi-grid.view-mode-objectif [data-view="reel"]{
  display: none !important;
}

.stat-tile{
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 18px;
  box-shadow: var(--shadow-card);
  min-width: 0;
  transition: transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease), opacity .15s var(--ease);
}

.stat-tile:hover{
  transform: translateY(-3px);
  border-color: rgba(255,255,255,.14);
  box-shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 14px 32px rgba(0,0,0,.32);
}

.stat-tile .drag-handle,
.dash-widget .drag-handle{
  position: absolute;
  top: 10px;
  right: 32px;
  font-size: 15px;
  line-height: 1;
  color: var(--text-mute);
  cursor: grab;
  opacity: 0;
  padding: 3px 5px;
  border-radius: var(--r-sm);
  transition: opacity .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
}

.dash-widget .widen-btn{
  position: absolute;
  top: 10px;
  right: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-mute);
  background: none;
  border: none;
  cursor: pointer;
  opacity: 0;
  padding: 3px 5px;
  border-radius: var(--r-sm);
  transition: opacity .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
}
.dash-widget .widen-btn svg{ width: 15px; height: 15px; }
/* Deux icones distinctes plutot qu'une seule tournee a 180° : les fleches
   d'agrandissement (vers l'exterieur) et de reduction (vers l'interieur)
   ne sont pas symetriques par rotation, un simple flip ne les inversait pas
   visuellement. Seule celle qui correspond a l'action encore possible
   (agrandir si normal, reduire si deja large) reste affichee. */
.dash-widget .widen-icon-compress{ display: none; }
.dash-widget.is-wide .widen-icon-expand{ display: none; }
.dash-widget.is-wide .widen-icon-compress{ display: block; }

.stat-tile .trash-btn,
.dash-widget .trash-btn{
  position: absolute;
  top: 10px;
  right: 8px;
  font-size: 13px;
  line-height: 1;
  color: var(--text-mute);
  background: none;
  border: none;
  cursor: pointer;
  opacity: 0;
  padding: 3px 5px;
  border-radius: var(--r-sm);
  transition: opacity .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
}

.stat-tile:hover .drag-handle, .stat-tile:hover .trash-btn,
.dash-widget:hover .drag-handle, .dash-widget:hover .trash-btn, .dash-widget:hover .widen-btn{ opacity: 1; }

.stat-tile .drag-handle:hover, .dash-widget .drag-handle:hover{ background: var(--surface-2); color: var(--text); }
.stat-tile .trash-btn:hover, .dash-widget .trash-btn:hover{ background: rgba(208,59,59,.15); color: var(--critical); }
.dash-widget .widen-btn:hover{ background: var(--surface-2); color: var(--text); }

.stat-tile[draggable="true"], .dash-widget[draggable="true"]{ cursor: grab; }
.stat-tile[draggable="true"]:active, .dash-widget[draggable="true"]:active{ cursor: grabbing; }
.stat-tile .trash-btn, .dash-widget .trash-btn,
.stat-tile button, .dash-widget button,
.stat-tile a, .dash-widget a,
.stat-tile input, .dash-widget input,
.stat-tile label, .dash-widget label{ cursor: pointer; }
.stat-tile input, .dash-widget input{ cursor: text; }

.stat-tile.dragging, .dash-widget.dragging{
  opacity: .35;
  cursor: grabbing;
}

.stat-tile.dragging .drag-handle, .dash-widget.dragging .drag-handle{ cursor: grabbing; }

/* ---- dashboard: grille unique de widgets (glisser-deposer + suppression) ---- */
.dashboard-grid{
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-flow: dense;
  gap: 16px;
  align-items: stretch;
}

.dash-widget{ position: relative; min-width: 0; }

/* Seulement deux tailles, qui se recombinent toujours parfaitement
   (3+3=6) quel que soit l'ordre : un widget deplace par glisser-deposer
   s'aligne donc toujours proprement, sans espace vide au milieu de la
   grille. */
.w-half{ grid-column: span 3; }
.w-full{ grid-column: span 6; }

/* Widget etendu sur toute la ligne (bouton .widen-btn) - passe outre
   .w-half (plus specifique : deux classes contre une), sans toucher a
   .w-full qui reste statique/non basculable. "1 / -1" reste correct meme
   quand la grille repasse a 1 colonne sous 1100px. */
.dash-widget.is-wide{ grid-column: 1 / -1; }

@media (max-width: 1100px){
  .dashboard-grid{ grid-template-columns: 1fr; }
  .dashboard-grid > .dash-widget{ grid-column: 1 / -1; }
  .kpi-grid{ grid-template-columns: repeat(2, 1fr); }
}

/* ---- panneau "ajouter un element masque" ---- */
.add-item-menu{
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 8px;
  min-width: 250px;
  max-width: 320px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 16px 40px rgba(0,0,0,.4);
  padding: 12px;
  z-index: 50;
}

.add-item-menu.open{ display: block; }

.add-item-menu-title{
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-dim);
  margin-bottom: 8px;
  text-transform: uppercase;
  letter-spacing: .04em;
}

/* ---- panneau notifications (topbar dashboard) : n'avait jamais recu de
   CSS dediee - le contenu (interrupteurs + bouton Sauvegarder) s'affichait
   donc en flux normal dans la topbar au lieu d'etre une vraie popup ancree
   au bouton cloche, poussant les boutons voisins et cassant la mise en page
   a l'ouverture. Meme mecanique .open que .add-item-menu ci-dessus, mais
   ancree a droite (right:0) plutot qu'a gauche : le bouton cloche vit pres
   du bord droit de la topbar, une ouverture vers la gauche reste toujours
   dans l'ecran au lieu de deborder hors du viewport. */
.notif-bell-wrap{ position: relative; }

.notif-panel{
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 8px;
  width: max-content;
  min-width: 280px;
  max-width: min(340px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 16px 40px rgba(0,0,0,.4);
  padding: 14px;
  z-index: 50;
}

.notif-panel.open{ display: block; }

.notif-row{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--border-soft);
}

.notif-row:first-of-type{ border-top: none; }

.notif-label{
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
}

.notif-desc{
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-mute);
  max-width: 220px;
}

@media (max-width: 640px){
  .notif-panel{ max-width: calc(100vw - 24px); right: -8px; }
}

.add-item-row{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 6px;
  border-radius: var(--r-sm);
  font-size: 13.5px;
  cursor: pointer;
  transition: background .15s var(--ease);
}

.add-item-row:hover{ background: var(--surface-2); }
.add-item-row .add-item-plus{ color: var(--brand); font-weight: 700; }

.stat-tile .stat-label{
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-dim);
  font-weight: 600;
  margin-bottom: 10px;
}

/* Puce coloree derriere chaque icone SVG (voir --chip, pose par tuile dans
   dashboard.html) - une icone monochrome toute seule sur fond sombre
   manquait de la couleur/vie que les emoji d'origine avaient, retour
   utilisateur direct le 2026-08-18. Le trait de l'icone reste blanc/clair
   (contraste), seul le fond change de teinte par tuile - jamais l'inverse
   (fond uni + icone coloree serait moins lisible sur les fonds clairs
   comme sombres). */
.stat-tile .stat-icon{
  --chip: var(--brand);
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: var(--r-sm);
  background: var(--chip); color: #fff;
  vertical-align: -6px; margin-right: 1px;
}

.stat-tile .stat-value{
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1.2;
}

.stat-tile .stat-sub{
  font-size: 12px;
  color: var(--text-mute);
  margin-top: 6px;
}


/* ==========================================================================
   Chart layout / legend
   ========================================================================== */

.row-2{ display: grid; grid-template-columns: 1.7fr 1fr; gap: 16px; align-items: stretch; }
.row-3{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }

@media (max-width: 1100px){
  .row-2{ grid-template-columns: 1fr; }
  .row-3{ grid-template-columns: 1fr; }
}

.chart-box{ position: relative; height: 240px; }
.chart-box.chart-box-sm{ height: 200px; }

.legend-list{ display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }

.legend-row{
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  padding: 4px 6px;
  margin: 0 -6px;
  border-radius: var(--r-sm);
  transition: background .15s var(--ease);
}

.legend-row:hover{ background: var(--surface-2); }

.legend-row .swatch{ width: 9px; height: 9px; border-radius: 3px; flex: none; transition: transform .15s var(--ease); }
.legend-row:hover .swatch{ transform: scale(1.3); }
.legend-row .legend-name{ color: var(--text-dim); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.legend-row .legend-value{ font-weight: 600; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   Meter (threshold comparison)
   ========================================================================== */

.meter-track{
  height: 10px;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
  position: relative;
}

.meter-fill{
  height: 100%;
  border-radius: 999px;
  transition: width .4s var(--ease);
}

.meter-labels{
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--text-mute);
  margin-top: 8px;
}

/* ==========================================================================
   Ranked lists
   ========================================================================== */

.rank-row{
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 6px;
  margin: 0 -6px;
  border-bottom: 1px solid var(--border-soft);
  border-radius: var(--r-sm);
  font-size: 13.5px;
  transition: background .15s var(--ease);
}

.rank-row:hover{ background: var(--surface-2); }
.rank-row-clickable{ cursor: pointer; }

.rank-row:last-child{ border-bottom: none; }

.rank-num{
  width: 22px; height: 22px;
  border-radius: 7px;
  background: var(--surface-2);
  color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 700;
  flex: none;
  transition: transform .15s var(--ease);
}

.rank-row:hover .rank-num{ transform: scale(1.1); }

.rank-row:nth-child(1) .rank-num{ background: var(--brand-soft); color: #93c5fd; }

.rank-name{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); font-weight: 500; }
.rank-value{ font-weight: 700; font-variant-numeric: tabular-nums; }

.text-dim{ color: var(--text-dim); }
.text-mute{ color: var(--text-mute); }
.text-good{ color: var(--good-text); }
.text-critical{ color: var(--critical-text); }
.text-warning{ color: var(--warning-text); }
.flex{ display: flex; }
.items-center{ align-items: center; }
.gap-8{ gap: 8px; }
.gap-12{ gap: 12px; }
.mt-8{ margin-top: 8px; }
.mt-16{ margin-top: 16px; }

@media (max-width: 700px){
  .field-grid{ grid-template-columns: 1fr; }
}

/* ==========================================================================
   Projects table (dashboard)
   ========================================================================== */

.project-cell{ display: flex; align-items: center; gap: 12px; }

.project-avatar{
  width: 34px; height: 34px;
  border-radius: 10px;
  background: var(--surface-3);
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;
  flex: none;
  transition: transform .15s var(--ease);
}

.project-cell .project-name{ font-weight: 600; color: var(--text); }
.project-cell .project-client{ font-size: 12px; color: var(--text-mute); margin-top: 2px; }

.chevron{ transition: transform .15s var(--ease); color: var(--text-mute); font-size: 11px; }
tr.detail-open .chevron{ transform: rotate(90deg); }

.detail-row td{ padding: 0 !important; border-bottom: 1px solid var(--border-soft); }
.detail-row{ display: none; }
.detail-row.open{ display: table-row; }

/* .detail-panel/.detail-line restent utilisees hors du panneau de detail
   projet ci-dessous (redessine en .pd- fin 2026) : widget resume du profil
   et fiche client du dashboard. */
.detail-panel{
  padding: 6px 14px 22px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 22px;
}

.detail-panel-2col{ grid-template-columns: repeat(2, 1fr); padding: 20px 0 0; }

@media (max-width: 900px){ .detail-panel{ grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px){
  .detail-panel{ grid-template-columns: 1fr; gap: 18px; }
  .detail-panel-2col{ grid-template-columns: 1fr; }
}

.detail-panel .section-label{
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-mute);
  font-weight: 700;
  margin-bottom: 10px;
}

.detail-line{
  display: flex;
  align-items: center;
  gap: 6px;
  justify-content: space-between;
  font-size: 13px;
  color: var(--text-dim);
  padding: 5px 0;
}

.detail-line b{ color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   PANNEAU DE DETAIL PROJET (deroule sous une ligne de "Mes projets")
   Toutes les classes sont prefixees .pd- (panneau detail) -> aucune
   collision avec .card/.btn/.row/.detail-line d'origine. Quatre niveaux de
   poids visuel : bandeau cle (1) > blocs Argent/Rentabilite/Temps/Dates (2)
   > paliers d'abonnement en <details> repliés (3) > zone "Activite" en
   retrait pour les 3 journaux (4). Voir le brief/README de portage pour le
   detail des choix. Tokens repris de :root ci-dessus, seuls --pd-sunken et
   --text-head sont propres a ce composant.
   ========================================================================== */
.pd{
  padding: clamp(.875rem, 2vw, 1.25rem);
  background: var(--surface);
  border: 1px solid var(--border); border-top: 1px solid var(--border-soft);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  box-shadow: 0 12px 32px -12px rgba(0,0,0,.5);
}
.pd-grid{ display:grid; gap:.75rem; }

/* ---- 1. Bandeau cle : les 2-4 chiffres qui decident, seul element accentue ---- */
.pd-key{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:1px;
  background:var(--border); border:1px solid var(--brand-line); border-radius:var(--r-md);
  overflow:hidden; box-shadow:0 0 32px -14px rgba(74,140,255,.45);
}
.pd-key-cell{
  position:relative; padding:1rem 1.125rem 1.0625rem;
  background:linear-gradient(180deg,rgba(74,140,255,.06),rgba(255,255,255,0) 70px),var(--surface-2);
  display:grid; gap:.25rem; align-content:start;
}
.pd-key-label{ font-size:.6875rem; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--text-head); }
.pd-key-val{ font-size:clamp(1.5rem,3.2vw,1.9rem); font-weight:700; letter-spacing:-.035em; line-height:1.05; font-variant-numeric:tabular-nums; }
.pd-key-val.pos{ color:var(--good-text); }
.pd-key-val.neg{ color:var(--critical-text); }
.pd-key-sub{ font-size:.75rem; color:var(--text-mute); line-height:1.4; }
.pd-key-sub b{ color:var(--text-dim); font-weight:600; }

/* ---- Blocs standards (Argent/Rentabilite/Temps/Dates) ---- */
.pd-block{ position:relative; padding:1rem 1.125rem 1.125rem; background:var(--surface-2); border:1px solid var(--border); border-radius:var(--r-md); }
.pd-cols{ display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:.75rem; align-items:start; }

/* Glisser-deposer des 4 blocs Argent/Rentabilite/Temps/Dates dans .pd-cols
   (voir setupDashboardDragGroup, meme mecanique que les tuiles KPI/widgets
   du dashboard) - regles autonomes plutot qu'ajoutees aux selecteurs
   .stat-tile/.dash-widget partages plus haut, pour ne pas risquer d'en
   oublier une occurrence eparpillee dans ce fichier. */
.pd-cols[data-drag-ready] .pd-block{ cursor:grab; }
.pd-block .drag-handle{
  position:absolute;
  top:.625rem;
  right:.625rem;
  font-size:14px;
  line-height:1;
  cursor:grab;
  opacity:0;
  padding:3px 5px;
  transition:opacity .15s var(--ease);
}
.pd-block:hover .drag-handle,
.pd-block:focus-within .drag-handle{ opacity:1; }
.pd-block.dragging{
  opacity:.5;
  border-color:var(--brand-line);
  box-shadow:var(--sh-glow);
  cursor:grabbing;
}

.pd-bhead{ display:flex; align-items:center; gap:.625rem; margin-bottom:.875rem; }
.pd-bicon{
  flex:none; display:grid; place-items:center; width:26px; height:26px; border-radius:var(--r-sm);
  background:rgba(255,255,255,.05); box-shadow:inset 0 0 0 1px rgba(255,255,255,.09); color:var(--text-dim);
}
.pd-btitle{ font-size:.875rem; font-weight:620; letter-spacing:-.012em; color:var(--text); }
.pd-bflag{ margin-left:auto; font-size:.75rem; color:var(--text-mute); font-variant-numeric:tabular-nums; }

.pd-lines{ display:grid; gap:0; }
.pd-line{ display:flex; align-items:baseline; justify-content:space-between; gap:1rem; padding:.4375rem 0; border-top:1px solid var(--border-soft); }
.pd-line:first-child{ border-top:0; padding-top:0; }
.pd-line dt, .pd-line .k{ font-size:.8125rem; color:var(--text-dim); min-width:0; }
.pd-line dd, .pd-line .v{ margin:0; font-size:.875rem; font-weight:600; font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap; }
.pd-line.total{ margin-top:.25rem; padding-top:.625rem; border-top:1px solid var(--border-strong); }
.pd-line.total .k{ color:var(--text); font-weight:600; font-size:.875rem; }
.pd-line.total .v{ font-size:1.0625rem; font-weight:700; letter-spacing:-.02em; }
.pd-line .v.pos{ color:var(--good-text); }
.pd-line .v.neg{ color:var(--critical-text); }
.pd-line .v.warn{ color:var(--warning-text); }
.pd-line .v.dim{ color:var(--text-dim); font-weight:500; }

.pd-badge{
  display:inline-flex; align-items:center; gap:.375rem; padding:.125rem .4375rem;
  font-size:.6875rem; font-weight:600; line-height:1.4; white-space:nowrap;
  border-radius:var(--r-full); border:1px solid var(--border); background:rgba(255,255,255,.05); color:var(--text-dim);
  font-variant-numeric:tabular-nums;
}
.pd-badge::before{ content:""; width:5px; height:5px; border-radius:50%; background:currentColor; flex:none; }
.pd-badge.plain::before{ display:none; }
.pd-badge.good{ color:var(--good-text); background:var(--good-soft); border-color:rgba(22,163,74,.3); }
.pd-badge.warn{ color:var(--warning-text); background:var(--warning-soft); border-color:rgba(242,176,30,.3); }
.pd-badge.crit{ color:var(--critical-text); background:var(--critical-soft); border-color:rgba(220,74,74,.3); }
.pd-badge.brand{ color:#a9c4ff; background:var(--brand-soft); border-color:var(--brand-line); }

/* ---- 3. Paliers d'abonnement : ligne compacte, LTV repliee dans <details> ---- */
.pd-tiers{ display:grid; gap:.4375rem; }
.pd-tier{ background:var(--surface-sunk-2); border:1px solid var(--border); border-radius:var(--r-sm); transition:border-color .2s var(--ease),background-color .2s var(--ease); }
.pd-tier:hover{ border-color:var(--border-strong); }
.pd-tier[open]{ background:var(--surface-sunk-4); border-color:var(--brand-line); }
.pd-tier > summary{ display:flex; align-items:center; gap:.75rem; padding:.5625rem .75rem; cursor:pointer; list-style:none; min-height:44px; }
.pd-tier > summary::-webkit-details-marker{ display:none; }
.pd-tier > summary::marker{ content:""; }
.pd-tier > summary:focus-visible{ outline:none; box-shadow:var(--ring); border-radius:var(--r-sm); }
.pd-tier-name{ font-size:.875rem; font-weight:600; flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pd-tier-price{ font-size:.875rem; font-weight:600; font-variant-numeric:tabular-nums; flex:none; }
.pd-tier-ltv{ font-size:.75rem; color:var(--text-mute); font-variant-numeric:tabular-nums; flex:none; }
.pd-tier-ltv b{ color:var(--text-dim); font-weight:600; }
.pd-tier-chev{
  flex:none; width:7px; height:7px; margin-left:.125rem;
  border-right:1.5px solid var(--text-mute); border-bottom:1.5px solid var(--text-mute);
  transform:rotate(45deg) translateY(-2px); transition:transform .28s var(--ease),border-color .28s var(--ease);
}
.pd-tier[open] .pd-tier-chev{ transform:rotate(225deg) translateY(-2px); border-color:var(--brand); }
.pd-tier-body{ padding:.75rem; margin:0 .5rem .5rem; background:var(--surface-sunk-5); border:1px solid var(--border-soft); border-radius:var(--r-sm); display:grid; gap:.75rem; }
.pd-tier-stats{ display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:.625rem; }
.pd-stat{ display:grid; gap:.1875rem; }
.pd-stat-k{ font-size:.6875rem; text-transform:uppercase; letter-spacing:.05em; color:var(--text-head); }
.pd-stat-v{ font-size:.9375rem; font-weight:650; font-variant-numeric:tabular-nums; letter-spacing:-.018em; }
.pd-stat-v.pd-stat-none{ color:var(--text-mute); font-weight:500; }

/* ---- 4. Zone Activite : les 3 journaux, en retrait ---- */
.pd-activity{ padding:.9375rem 1rem 1rem; background:var(--pd-sunken); border:1px solid var(--border-soft); border-radius:var(--r-md); box-shadow:inset 0 1px 0 rgba(0,0,0,.4); }
.pd-activity-head{ display:flex; align-items:center; gap:.625rem; margin-bottom:.875rem; }
.pd-activity-title{ font-size:.75rem; font-weight:650; text-transform:uppercase; letter-spacing:.08em; color:var(--text-head); }
.pd-activity-hint{ margin-left:auto; font-size:.75rem; color:var(--text-mute); }
.pd-logs{ display:grid; grid-template-columns:repeat(auto-fit,minmax(258px,1fr)); gap:.625rem; }
.pd-log{ padding:.75rem .8125rem .8125rem; background:var(--surface-log); border:1px solid var(--border-soft); border-radius:var(--r-sm); display:grid; gap:.5rem; align-content:start; }
.pd-log-head{ display:flex; align-items:baseline; gap:.5rem; }
.pd-log-title{ font-size:.8125rem; font-weight:600; color:var(--text-dim); }
.pd-log-sum{ margin-left:auto; font-size:.75rem; color:var(--text-mute); font-variant-numeric:tabular-nums; }
.pd-log-list{ display:grid; gap:1px; background:var(--border-soft); border-radius:6px; overflow:hidden; }
.pd-log-row{ display:flex; align-items:center; gap:.5rem; padding:.375rem .5rem; background:var(--surface-log-row); font-size:.8125rem; min-height:34px; }
.pd-log-row .d{ color:var(--text-mute); font-size:.75rem; font-variant-numeric:tabular-nums; flex:none; }
.pd-log-row .l{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--text-dim); }
.pd-log-row .n{ font-weight:600; font-variant-numeric:tabular-nums; flex:none; }
.pd-log-empty{ padding:.625rem .5rem; font-size:.8125rem; color:var(--text-mute); font-style:italic; }

/* ---- Formulaires d'ajout : inline, jamais de modale ---- */
.pd-add{ display:flex; flex-wrap:wrap; gap:.375rem; align-items:center; }
.pd-f{
  min-width:0; padding:.4375rem .5625rem; min-height:36px;
  font:inherit; font-size:.8125rem; color:var(--text);
  background:var(--surface-sunk-3); border:1px solid var(--border-strong); border-radius:6px;
  outline:none; transition:border-color .18s var(--ease),box-shadow .18s var(--ease);
}
.pd-f::placeholder{ color:var(--text-mute); opacity:1; }
.pd-f:hover{ border-color:var(--border-strong); }
.pd-f:focus{ border-color:var(--brand); box-shadow:var(--ring); }
.pd-f:disabled{ opacity:.5; cursor:not-allowed; }
.pd-f-grow{ flex:3 1 96px; }
.pd-f-num{ flex:1 1 64px; font-variant-numeric:tabular-nums; }
.pd-f-date{ flex:1 1 118px; }
.pd-f-sel{ flex:1 1 100px; cursor:pointer; color-scheme:inherit; }

/* ---- Boutons ---- */
.pd-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.375rem;
  padding:.4375rem .6875rem; min-height:36px;
  font:inherit; font-size:.8125rem; font-weight:550; line-height:1; color:var(--text-dim);
  background:rgba(255,255,255,.04); border:1px solid var(--border-strong); border-radius:6px;
  cursor:pointer; white-space:nowrap;
  transition:color .18s var(--ease),background-color .18s var(--ease),border-color .18s var(--ease);
}
.pd-btn:hover{ color:var(--text); background:rgba(255,255,255,.08); border-color:rgba(255,255,255,.24); }
.pd-btn:focus-visible{ outline:none; box-shadow:var(--ring); }
.pd-btn:disabled{ opacity:.45; cursor:not-allowed; }
.pd-btn-brand{ color:#fff; background:linear-gradient(180deg,var(--brand),var(--brand-dim)); border-color:rgba(255,255,255,.16); box-shadow:inset 0 1px 0 rgba(255,255,255,.18); }
.pd-btn-brand:hover{ background:linear-gradient(180deg,#5c98ff,#2a6ae8); color:#fff; }
.pd-btn-quiet{ background:transparent; border-color:var(--border); }
.pd-btn-danger{ color:var(--critical-text); background:var(--critical-soft); border-color:rgba(220,74,74,.34); }
.pd-btn-danger:hover{ color:#fff; background:var(--critical); border-color:var(--critical); }
.pd-btn-icon{ flex:none; width:28px; height:28px; min-height:28px; padding:0; color:var(--text-mute); background:transparent; border-color:transparent; }
.pd-btn-icon:hover{ color:var(--critical-text); background:var(--critical-soft); border-color:rgba(220,74,74,.34); }
.pd-btn-icon:focus-visible{ box-shadow:0 0 0 3px rgba(220,74,74,.34); }

/* ---- Checklist ---- */
.pd-prog{ height:6px; border-radius:3px; background:rgba(255,255,255,.07); overflow:hidden; margin-bottom:.75rem; }
.pd-prog > i{ display:block; height:100%; border-radius:3px; background:linear-gradient(90deg,var(--brand),#7c6cff); }
.pd-tasks{ display:grid; gap:1px; background:var(--border-soft); border-radius:var(--r-sm); overflow:hidden; }
.pd-task{ display:flex; align-items:center; gap:.625rem; padding:.5rem .6875rem; min-height:40px; background:var(--surface-sunk-1); font-size:.8125rem; }
.pd-task input[type=checkbox]{ flex:none; width:16px; height:16px; accent-color:var(--brand); cursor:pointer; margin:0; }
.pd-task label{ flex:1; min-width:0; cursor:pointer; color:var(--text-dim); }
.pd-task .h{ flex:none; font-size:.75rem; color:var(--text-mute); font-variant-numeric:tabular-nums; }
.pd-task.done label{ color:var(--text-mute); text-decoration:line-through; }

/* ---- 7. Actions : visibles en permanence, pas seulement sur mobile ---- */
.pd-actions{ display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; padding-top:.875rem; margin-top:.125rem; border-top:1px solid var(--border-soft); }
.pd-actions .sp{ flex:1 1 auto; }

/* ---- Etat verrouille (projet unitaire marque paye) ---- */
.pd-locked{
  display:flex; align-items:center; gap:.5rem; padding:.5rem .75rem; margin-bottom:.75rem;
  font-size:.8125rem; color:var(--good-text);
  background:var(--good-soft); border:1px solid rgba(22,163,74,.3); border-radius:var(--r-sm);
}
.pd-locked svg{ flex:none; }
.pd.is-locked .pd-add{ opacity:.5; pointer-events:none; }
.pd.is-locked .pd-btn-icon{ display:none; }
.pd.is-locked .pd-task input{ pointer-events:none; }

@media (max-width:900px){
  .pd-cols{ grid-template-columns:1fr; }
  .pd-logs{ grid-template-columns:1fr; }
  .pd-key{ grid-template-columns:1fr 1fr; }
}
@media (max-width: 640px){
  .col-priority-low{ display: none; }
}
@media (max-width:640px){
  .pd{ padding:.75rem; border-radius:0 0 var(--r-md) var(--r-md); }
  .pd-key{ grid-template-columns:1fr; }
  .pd-key-cell{ padding:.8125rem .875rem; }
  .pd-block, .pd-activity{ padding:.8125rem .875rem .9375rem; }
  .pd-tier > summary{ flex-wrap:wrap; row-gap:.1875rem; padding:.625rem .75rem; }
  .pd-tier-name{ flex:1 1 100%; }
  .pd-tier-chev{ margin-left:auto; }
  .pd-tier-body{ margin:0 .375rem .375rem; }
  .pd-log-row{ flex-wrap:wrap; row-gap:.125rem; }
  .pd-log-row .l{ flex:1 1 100%; order:-1; }
  .pd-add .pd-btn{ flex:1 1 100%; }
  .pd-actions .sp{ display:none; }
  .pd-actions .pd-btn{ flex:1 1 calc(50% - .25rem); }
}

/* ==========================================================================
   Legal / informational pages
   ========================================================================== */

.legal-wrap{
  max-width: 820px;
  margin: 0 auto;
  padding: 40px;
}

.legal-wrap h1{ font-size: 30px; margin-bottom: 8px; }

.legal-wrap h2{ font-size: 17px; margin-top: 32px; margin-bottom: 10px; }

.legal-wrap p, .legal-wrap li{ color: var(--text-dim); line-height: 1.7; font-size: 14.5px; }

.legal-wrap ul{ margin: 0; padding-left: 20px; }

.step{
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 22px;
  border-radius: var(--r-md);
  margin-bottom: 16px;
  transition: transform .2s var(--ease), border-color .2s var(--ease);
}

.step:hover{ transform: translateY(-2px); border-color: rgba(255,255,255,.16); }

.step h2{ margin-top: 0; font-size: 16px; }
.step p{ color: var(--text-dim); line-height: 1.6; font-size: 14px; }

/* ==========================================================================
   Accessibilité & impression
   ========================================================================== */

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

@media (prefers-contrast: more){
  :root{
    --border: rgba(255, 255, 255, .2);
    --border-strong: rgba(255, 255, 255, .32);
    --text-dim: #b7c3d6;
    --text-mute: #8b9ab3;
  }
}

@media print{
  body::before, .hero::before, .hero::after, .hero-bg{ display: none; }
  body{ background: #fff; color: #000; }
}

/* ==========================================================================
   ProfitTrack — DASHBOARD, passe visuelle
   Ajoute depuis un handoff de design externe (design_handoff_dashboard/),
   adapte au markup reel de templates/dashboard.html et templates/base_app.html.

   CSS UNIQUEMENT. Aucune structure, aucun id, aucune classe, aucun data-*
   n'est renomme ni ajoute. Le JS de reordonnancement, de masquage et
   Chart.js ne voient aucune difference.

   Convention : on ne redefinit QUE des proprietes visuelles (fond, bordure,
   rayon, ombre, couleur, transition). Jamais `display`, `position`,
   `overflow` ni `transform` sur les elements que le drag & drop manipule -
   voir la note "Zones interdites" en fin de bloc.

   Ajustements faits par rapport au bundle livre (voir conversation) :
   - --shadow-card retiree : deja definie plus haut dans ce fichier avec la
     meme valeur, celle du repo fait foi.
   - Conteneur principal reel : .app-main (pas .dashboard/.dash-main devines).
   - Sidebar reelle : .app-sidebar + liens .side-link (pas .sidebar/.dash-sidebar).
   - Widgets deja des .card/.card-pad avec .card-header/.card-title/.card-subtitle
     (styles deja existants plus haut dans ce fichier) : les selecteurs de
     "widget header" devines par le bundle (.widget-header, etc.) ne
     correspondent a rien et sont laisses tels quels, sans effet - inutile
     de les retirer, mais inutile de s'y fier non plus.
   - Etat "cible de depot" (.drag-over/.is-drop-target) jamais pose par le JS
     de ce site (seul .dragging existe) : ces regles restent inactives, ce
     n'est pas un bug.
   - Collision corrigee : le dega text du chiffre (.stat-value) ecrasait les
     classes .text-good/.text-critical (couleur du profit, deja utilisees
     dans dashboard.html) - override ajoute en fin de section pour les
     preserver.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tokens complementaires — ajouts uniquement
   -------------------------------------------------------------------------- */
:root {
  --dash-tile-pad: clamp(1rem, 1.6vw, 1.375rem);
  --dash-gap: clamp(.75rem, 1.4vw, 1.125rem);
  /* Repris de la landing pour que les deux pages partagent la meme signature */
  --dash-sheen: linear-gradient(180deg, rgba(255, 255, 255, .03), rgba(255, 255, 255, 0) 96px);
}

/* --------------------------------------------------------------------------
   Fond de page — reprend l'atmosphere de la landing, en beaucoup plus sobre
   -------------------------------------------------------------------------- */
.app-main,
.dashboard,
.dash-main,
.dashboard-main,
main.dashboard {
  position: relative;
  background:
    radial-gradient(1100px 520px at 78% -8%, rgba(74, 140, 255, .07), transparent 68%),
    radial-gradient(760px 420px at 8% 2%, var(--nebula-soft), transparent 72%),
    var(--page);
}

/* --------------------------------------------------------------------------
   Grille KPI — respiration, sans toucher au flux
   -------------------------------------------------------------------------- */
.kpi-grid,
#kpiGrid {
  gap: var(--dash-gap);
}

/* --------------------------------------------------------------------------
   Tuiles KPI — .stat-tile
   Le chiffre passe en tres gros, facon .landing-stats-bar, mais calibre pour
   une grille dense : on ne peut pas y mettre du 4rem sans casser 4 colonnes.
   -------------------------------------------------------------------------- */
.stat-tile {
  padding: var(--dash-tile-pad);
  background: var(--dash-sheen), var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card, var(--sh-2));
  transition:
    border-color .22s var(--ease),
    box-shadow .22s var(--ease),
    transform .22s var(--ease),
    background-color .22s var(--ease);
}
/* Filet de lumiere en haut — signature de .card sur la landing. */
.stat-tile { position: relative; }
.stat-tile::before {
  content: "";
  position: absolute;
  top: 0; left: 14%; right: 14%;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .16), transparent);
}

.stat-tile:hover {
  border-color: var(--border-strong);
  box-shadow: var(--sh-3);
  transform: translateY(-4px);
}

/* Le chiffre. .stat-value est la vraie classe utilisee dans dashboard.html -
   les autres restent en repli au cas ou. */
.stat-tile .stat-value,
.stat-tile .kpi-value,
.stat-tile .tile-value,
.stat-tile [data-value] {
  font-size: clamp(1.85rem, 3vw, 2.6rem);
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1.02;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(174deg, #ffffff, #b9cbe8);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  /* La grille KPI est desormais a 4 colonnes fixes (voir .kpi-grid) : on
     interdit le retour a la ligne au milieu d'un montant plutot que de
     le laisser se couper (ex. "19000.00" / "0 €" sur 2 lignes). */
  white-space: nowrap;
}
/* Preserve le code couleur profit positif/negatif (deja dans
   dashboard.html : <div class="stat-value text-good|text-critical">) -
   sans ca le degrade de texte les ecrase completement. */
.stat-tile .stat-value.text-good,
.stat-tile .stat-value.text-critical {
  background: none;
  -webkit-background-clip: unset;
          background-clip: unset;
  -webkit-text-fill-color: unset;
}
.stat-tile .stat-value.text-good{ color: var(--good-text); }
.stat-tile .stat-value.text-critical{ color: var(--critical-text); }

/* Legende sous le chiffre */
.stat-tile .stat-label,
.stat-tile .kpi-label,
.stat-tile .tile-label {
  font-size: .8125rem;
  font-weight: 500;
  color: var(--text-mute);
  letter-spacing: 0;
}

/* Variation / delta, si presente */
.stat-tile .stat-delta,
.stat-tile .kpi-delta {
  font-size: .8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.stat-tile .is-up,   .stat-tile .stat-up   { color: #4ade80; }
.stat-tile .is-down, .stat-tile .stat-down { color: #ff9d9d; }

/* --------------------------------------------------------------------------
   Widgets — .dash-widget (deja combine avec .card/.card-pad existants :
   ce bloc renforce surtout l'ombre et l'elevation au survol)
   -------------------------------------------------------------------------- */
.dash-widget {
  position: relative;
  background: var(--dash-sheen), var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card, var(--sh-2));
  transition:
    border-color .22s var(--ease),
    box-shadow .22s var(--ease),
    transform .22s var(--ease);
}
.dash-widget::before {
  content: "";
  position: absolute;
  top: 0; left: 12%; right: 12%;
  height: 1px;
  pointer-events: none;
  z-index: 1;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .16), transparent);
}
.dash-widget:hover {
  border-color: var(--border-strong);
  box-shadow: var(--sh-3);
  transform: translateY(-4px);
}

/* En-tete de widget — .card-header/.card-title/.card-subtitle deja stylees
   plus haut dans ce fichier, rien a ajouter ici. Repli garde au cas ou. */
.dash-widget > header,
.dash-widget .widget-header,
.dash-widget .dash-widget-header {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--border-soft);
}
.dash-widget .widget-subtitle {
  margin: .1875rem 0 0;
  font-size: .8125rem;
  font-weight: 450;
  color: var(--text-mute);
}
.dash-widget > .widget-body,
.dash-widget > .dash-widget-body {
  padding: 1.125rem 1.25rem 1.25rem;
}

/* --------------------------------------------------------------------------
   Poignee de glissement — .drag-handle
   -------------------------------------------------------------------------- */
.drag-handle {
  color: var(--text-mute);
  border-radius: var(--r-sm);
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.drag-handle:hover {
  color: var(--text-dim);
  background: rgba(255, 255, 255, .05);
}
.drag-handle:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}
.stat-tile .drag-handle,
.dash-widget .drag-handle {
  opacity: 0;
  transition: opacity .2s var(--ease), color .18s var(--ease), background-color .18s var(--ease);
}
.stat-tile:hover .drag-handle,
.dash-widget:hover .drag-handle,
.stat-tile:focus-within .drag-handle,
.dash-widget:focus-within .drag-handle,
.drag-handle:focus-visible { opacity: 1; }

/* Etats de glissement. .dragging est la vraie classe posee par le JS
   (voir templates/dashboard.html) ; .is-dragging/.drag-over/.is-drop-target
   restent en repli mais ne sont jamais poses par ce site. */
.stat-tile.dragging,
.dash-widget.dragging,
.stat-tile.is-dragging,
.dash-widget.is-dragging {
  opacity: .5;
  border-color: var(--brand-line);
  box-shadow: var(--sh-glow);
}
.stat-tile.drag-over,
.dash-widget.drag-over,
.stat-tile.is-drop-target,
.dash-widget.is-drop-target {
  border-color: var(--brand);
  box-shadow: 0 0 0 1px var(--brand), 0 0 28px -8px rgba(74, 140, 255, .6);
}

/* --------------------------------------------------------------------------
   Graphiques — .chart-box (hauteur deja fixee plus haut dans ce fichier,
   on n'y touche pas ici, uniquement l'habillage visuel)
   -------------------------------------------------------------------------- */
.chart-box {
  position: relative;
  padding: .25rem;
  border-radius: var(--r-md);
}
.chart-box::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  border-radius: inherit;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, .022) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, .022) 1px, transparent 1px);
  background-size: 40px 40px;
  -webkit-mask-image: radial-gradient(80% 80% at 50% 40%, #000, transparent 82%);
          mask-image: radial-gradient(80% 80% at 50% 40%, #000, transparent 82%);
}
.chart-box > canvas {
  position: relative;
  z-index: 1;
}

/* --------------------------------------------------------------------------
   Panneau de visibilite des tuiles (masquer/afficher)
   -------------------------------------------------------------------------- */
.tile-toggle,
.widget-toggle {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .625rem;
  font-size: .875rem;
  color: var(--text-dim);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: background-color .18s var(--ease), color .18s var(--ease);
}
.tile-toggle:hover,
.widget-toggle:hover { background: rgba(255, 255, 255, .05); color: var(--text); }

/* --------------------------------------------------------------------------
   Barre laterale et tiroir mobile — .app-sidebar/.side-link sont les vraies
   classes (templates/base_app.html) ; .sidebar/.dash-sidebar en repli.
   Pas de `transform`/`transition` sur l'ouverture : le JS existant pilote
   deja l'animation du tiroir.
   -------------------------------------------------------------------------- */
.app-sidebar,
.sidebar,
.dash-sidebar {
  background: var(--page-deep);
  border-right: 1px solid var(--border-soft);
}
.app-sidebar .side-link,
.sidebar a,
.dash-sidebar a {
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.app-sidebar .side-link.active,
.app-sidebar .side-link[aria-current="page"],
.sidebar a.active,
.dash-sidebar a.active {
  box-shadow: inset 0 0 0 1px var(--brand-line);
}

/* --------------------------------------------------------------------------
   Entree en fondu-montee, facon landing. Purement CSS, jouee une fois au
   chargement, SANS classe ajoutee par JS - rien a cabler, aucun risque pour
   le drag & drop (animation terminee bien avant toute interaction).
   -------------------------------------------------------------------------- */
@keyframes dash-rise {
  from { opacity: 0; transform: translate3d(0, 10px, 0); }
  to   { opacity: 1; transform: none; }
}
.stat-tile,
.dash-widget { animation: dash-rise .55s var(--ease-out) both; }

.kpi-grid > *:nth-child(1),  #kpiGrid > *:nth-child(1)  { animation-delay: .02s; }
.kpi-grid > *:nth-child(2),  #kpiGrid > *:nth-child(2)  { animation-delay: .06s; }
.kpi-grid > *:nth-child(3),  #kpiGrid > *:nth-child(3)  { animation-delay: .1s; }
.kpi-grid > *:nth-child(4),  #kpiGrid > *:nth-child(4)  { animation-delay: .14s; }
.kpi-grid > *:nth-child(5),  #kpiGrid > *:nth-child(5)  { animation-delay: .18s; }
.kpi-grid > *:nth-child(6),  #kpiGrid > *:nth-child(6)  { animation-delay: .22s; }

.stat-tile.dragging,
.dash-widget.dragging,
.stat-tile.is-dragging,
.dash-widget.is-dragging { animation: none; }

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 720px) {
  :root { --dash-tile-pad: 1rem; }
  .kpi-grid{ grid-template-columns: 1fr; }
  .stat-tile .stat-value,
  .stat-tile .kpi-value,
  .stat-tile .tile-value,
  .stat-tile [data-value] { font-size: 1.85rem; }
  .stat-tile .drag-handle,
  .dash-widget .drag-handle { opacity: 1; }
  .stat-tile:hover,
  .dash-widget:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   Badge du mois en cours, dans l'en-tete du dashboard
   -------------------------------------------------------------------------- */
.month-badge{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: .75rem;
  font-weight: 600;
  color: var(--good-text);
  background: var(--good-soft);
  border: 1px solid rgba(22, 163, 74, .28);
  border-radius: 999px;
  padding: 3px 10px 3px 8px;
  vertical-align: middle;
  margin-left: 10px;
}

/* --------------------------------------------------------------------------
   Accessibilite
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .stat-tile,
  .dash-widget {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .stat-tile:hover,
  .dash-widget:hover { transform: none !important; }
}

@media (hover: none) {
  .stat-tile:hover,
  .dash-widget:hover { transform: none; }
}

@media print {
  .app-sidebar, .sidebar, .dash-sidebar, .drag-handle { display: none; }
  .stat-tile, .dash-widget {
    animation: none;
    box-shadow: none;
    break-inside: avoid;
  }
}

/* --------------------------------------------------------------------------
   Pagination du tableau "Mes projets"
   -------------------------------------------------------------------------- */
.pagination{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 16px 0;
}
.pagination-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 8px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: background-color .18s var(--ease), color .18s var(--ease), border-color .18s var(--ease);
}
.pagination-btn:hover{ background: var(--surface-hover); color: var(--text); }
.pagination-btn.is-active{
  background: var(--brand-dim);
  border-color: var(--brand-dim);
  color: #fff;
}
.pagination-btn.is-disabled{
  opacity: .35;
  pointer-events: none;
}

/* Lignes du tableau "Mes projets" hors de la page courante - !important
   pour l'emporter sur .detail-row.open (meme specificite, la ligne de
   detail d'un projet masque par pagination ne doit jamais rester visible
   meme si elle avait ete ouverte avant de changer de page). */
.page-hidden{ display: none !important; }

/* ==========================================================================
   ZONES INTERDITES — ne pas ajouter ici, sous peine de casser le JS existant

   • `display`, `position: absolute/fixed`, `float` ou `order` sur .stat-tile /
     .dash-widget : le drag natif lit la position dans le flux.
   • `transform` permanent (autre que le :hover ci-dessus, remis a `none`
     pendant le drag) : decale la boite de collision du drop.
   • `pointer-events` sur .drag-handle ou ses enfants.
   • Toute hauteur/largeur fixe sur un <canvas> : Chart.js s'en charge.
   • `overflow: hidden` sur un ancetre de .dash-widget si le drag utilise un
     element fantome positionne.
   • Renommage de .stat-tile, .dash-widget, .kpi-grid, #kpiGrid, .chart-box,
     .drag-handle, ou de tout [data-group] / [data-item-id] / [data-label].
   ========================================================================== */

/* ==========================================================================
   Visite guidee du dashboard (voir templates/dashboard.html, bloc scripts)
   ========================================================================== */

.tour-backdrop{
  position: fixed;
  inset: 0;
  background: rgba(4, 8, 16, .55);
  z-index: 1000;
}

.tour-highlight{
  position: fixed;
  z-index: 1001;
  border-radius: 14px;
  /* le "trou" dans le voile : une ombre geante autour d'une boite par
     ailleurs transparente, plutot qu'un clip-path/mask sur le backdrop.
     Bordure plus epaisse/opaque + halo lumineux : le cadre doit sauter
     aux yeux, pas juste suggerer un contour. */
  box-shadow:
    0 0 0 9999px rgba(4, 8, 16, .62),
    0 0 0 3px rgba(93, 158, 255, .95),
    0 0 0 6px rgba(93, 158, 255, .28),
    0 0 26px 6px rgba(93, 158, 255, .45);
  /* Pas de transition sur top/left : le cadre doit suivre le scroll au
     pixel pres (voir le listener "scroll" plus bas) - une transition
     amortie le ferait visiblement decrocher de la zone pendant le defilement. */
  pointer-events: none;
}

/* Contenu a l'interieur de la zone en cours de visite guidee : le texte
   secondaire (labels, sous-titres, deltas, legendes...) est volontairement
   attenue partout ailleurs sur le site (--text-dim/--text-mute), mais
   pendant le tutoriel on veut au contraire que tout soit lu sans effort a
   l'interieur du cadre - la classe est posee sur l'element cible par
   focusTarget() (voir le script en bas de page). */
.tour-focused .text-dim,
.tour-focused .text-mute,
.tour-focused .stat-sub,
.tour-focused .stat-label,
.tour-focused .card-subtitle,
.tour-focused .card-title,
.tour-focused .legend-name,
.tour-focused .rank-name,
.tour-focused .field-hint,
.tour-focused .stat-delta{
  color: var(--text) !important;
}

.tour-popover{
  position: fixed;
  z-index: 1002;
  width: min(340px, calc(100vw - 28px));
  background: var(--surface);
  border: 1px solid rgba(93, 158, 255, .55);
  border-radius: var(--r-lg);
  box-shadow: 0 20px 48px rgba(0,0,0,.45), 0 0 0 1px rgba(93, 158, 255, .18), 0 0 22px rgba(93, 158, 255, .18);
  padding: 18px;
}

.tour-popover.is-centered{
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.tour-popover-count{
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 8px;
}

.tour-popover-title{
  font-size: 16px;
  font-weight: 700;
  margin-bottom: 8px;
}

.tour-popover-text{
  font-size: 13.5px;
  color: var(--text-dim);
  line-height: 1.5;
}

.tour-popover-actions{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 18px;
}

@media (max-width: 640px){
  .tour-popover{ padding: 14px; }
  .tour-popover-actions{ flex-wrap: wrap; }
  /* justify-content:flex-end (pas flex:1 sur les boutons) : #tourPrevBtn
     reste dans le flux meme masque via visibility:hidden (etape 1, pas de
     "precedent") - avec flex:1 il prenait quand meme la moitie de la ligne
     et decalait #tourNextBtn hors de l'alignement avec #tourSkipBtn. */
  .tour-popover-actions > div{ width: 100%; display: flex; gap: 8px; justify-content: flex-end; }
  .tour-popover-actions > div button{ flex: none; }
}

/* ==========================================================================
   Explication du calcul - clic sur un chiffre du dashboard
   ========================================================================== */

/* Chaque .stat-value cliquable garde son style d'origine (degrade, couleur
   good/critical...) - on ajoute juste une affordance de clic discrete. */
.stat-value-explain{
  cursor: pointer;
  border-radius: 6px;
  transition: opacity .15s var(--ease);
}
.stat-value-explain:hover,
.stat-value-explain:focus-visible{
  opacity: .82;
}
.stat-value-explain:focus-visible{
  outline: 2px solid rgba(93, 158, 255, .65);
  outline-offset: 3px;
}

#explainModal .modal-box{
  max-width: 420px;
  text-align: left;
}

.explain-modal-eyebrow{
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 6px;
}

.explain-formula-box{
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.explain-formula-label{
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-mute);
  margin-bottom: 5px;
}

.explain-formula-text{
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Bloc "exemple chiffre" - calcul avec les vraies donnees du compte, sous
   la formule generique. Accent de couleur pour le distinguer visuellement
   de la formule abstraite juste au-dessus. */
.explain-example-box{
  margin-top: 10px;
  background: rgba(93, 158, 255, .08);
  border-color: rgba(93, 158, 255, .3);
}
.explain-example-box .explain-formula-label{ color: #8fb8ff; }

/* ==========================================================================
   ProfitTrack — BAS DE PAGE : nouvelles fonctionnalités + mise en mouvement
   À APPENDRE à static/css/style.css.

   Préfixe `ptx-` pour tout ce qui est bas de page, afin de le distinguer des
   keyframes `pt-` du hero (qui ne sont PAS touchées).
   Le mécanisme .reveal + IntersectionObserver existant est ENRICHI, pas
   remplacé : on ajoute des variantes qui réagissent à la même classe.
   ========================================================================== */

:root {
  --ptx-gap: clamp(.875rem, 1.6vw, 1.25rem);
  --ptx-section-y: clamp(4.5rem, 11vh, 8rem);
}

/* --------------------------------------------------------------------------
   1. VARIANTES DE RÉVÉLATION
   S'ajoutent à .reveal existant. La classe de déclenchement posée par votre
   IntersectionObserver reste la même — vérifiez son nom (.is-visible ou
   .revealed) et alignez la ligne ci-dessous si besoin.
   -------------------------------------------------------------------------- */

/* Point d'entrée unique : si votre observer pose une autre classe, changez ici. */
.reveal.is-visible,
.reveal.revealed,
.reveal.visible { --ptx-in: 1; }

/* -- Variante « montée » : le défaut existant, on ne le touche pas. -- */

/* -- Variante « balayage latéral » : pour les sections en deux colonnes -- */
.reveal-left,
.reveal-right {
  opacity: 0;
  transition:
    opacity .8s var(--ease-out) var(--ptx-delay, 0s),
    transform .8s var(--ease-out) var(--ptx-delay, 0s);
}
.reveal-left  { transform: translate3d(-26px, 0, 0); }
.reveal-right { transform: translate3d(26px, 0, 0); }
.reveal-left.is-visible,  .reveal-left.revealed,  .reveal-left.visible,
.reveal-right.is-visible, .reveal-right.revealed, .reveal-right.visible {
  opacity: 1;
  transform: none;
}

/* -- Variante « échelle » : pour les mockups, qui gagnent à arriver de face -- */
.reveal-scale {
  opacity: 0;
  transform: scale(.965) translate3d(0, 12px, 0);
  transition:
    opacity .9s var(--ease-out) var(--ptx-delay, 0s),
    transform .9s var(--ease-out) var(--ptx-delay, 0s);
}
.reveal-scale.is-visible, .reveal-scale.revealed, .reveal-scale.visible {
  opacity: 1;
  transform: none;
}

/* -- Variante « masque » : le texte se dévoile par le bas, sans flou -- */
.reveal-mask { overflow: hidden; }
.reveal-mask > * {
  display: block;
  transform: translate3d(0, 100%, 0);
  transition: transform .9s var(--ease-out) var(--ptx-delay, 0s);
}
.reveal-mask.is-visible > *, .reveal-mask.revealed > *, .reveal-mask.visible > * {
  transform: none;
}

/* -- Cascade : à poser sur un conteneur -- */
.ptx-stagger > *:nth-child(1) { --ptx-delay: 0s; }
.ptx-stagger > *:nth-child(2) { --ptx-delay: .09s; }
.ptx-stagger > *:nth-child(3) { --ptx-delay: .18s; }
.ptx-stagger > *:nth-child(4) { --ptx-delay: .27s; }
.ptx-stagger > *:nth-child(5) { --ptx-delay: .36s; }
.ptx-stagger > *:nth-child(6) { --ptx-delay: .45s; }

/* --------------------------------------------------------------------------
   2. SECTION « NOUVEAUTÉS » — 3 fonctionnalités, une par bande alternée
   -------------------------------------------------------------------------- */
.ptx-features { position: relative; }

/* Filet vertical qui relie les trois bandes — rappel discret des courbes du hero */
.ptx-features::before {
  content: "";
  position: absolute;
  top: 8%; bottom: 8%;
  left: 50%;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--brand-line) 12%, var(--brand-line) 88%, transparent);
  opacity: .45;
  pointer-events: none;
}
@media (max-width: 960px) { .ptx-features::before { display: none; } }

.ptx-feature {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
  padding: clamp(2.5rem, 6vw, 4.5rem) 0;
}
/* Alternance : le mockup passe à gauche une bande sur deux */
.ptx-feature:nth-child(even) > .ptx-feature-visual { order: -1; }

.ptx-feature-text { display: grid; gap: .875rem; align-content: start; max-width: 30rem; }

.ptx-feature-tag {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  justify-self: start;
  width: fit-content;
  padding: .25rem .625rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #a9c4ff;
  background: var(--brand-soft);
  border: 1px solid var(--brand-line);
  border-radius: var(--r-full, 999px);
}
.ptx-feature-text h3 {
  margin: 0;
  font-size: clamp(1.35rem, 2.4vw, 1.85rem);
  letter-spacing: -.028em;
  line-height: 1.15;
}
.ptx-feature-text p { margin: 0; font-size: 1rem; line-height: 1.62; color: var(--text-dim); }

/* Précision honnête sous le texte (best-effort, pas de conseil juridique…) */
.ptx-feature-caveat {
  display: flex;
  gap: .5rem;
  margin: .25rem 0 0;
  padding-top: .75rem;
  border-top: 1px solid var(--border-soft);
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-mute);
}
.ptx-feature-caveat::before {
  content: "";
  flex: none;
  width: 3px;
  border-radius: 2px;
  background: var(--border-strong);
}

/* --------------------------------------------------------------------------
   3. CHÂSSIS DE MOCKUP — commun aux trois, décliné ensuite
   -------------------------------------------------------------------------- */
.ptx-mock {
  position: relative;
  padding: 8px;
  border-radius: var(--r-lg);
  background: linear-gradient(155deg, #22304a 0%, #131c2e 46%, #0b1120 100%);
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .13),
    0 30px 64px -28px rgba(0, 0, 0, .9);
  transition: transform .5s var(--ease), box-shadow .5s var(--ease), border-color .5s var(--ease);
}
/* Halo porté — reprend celui du hero device, en plus contenu */
.ptx-mock::before {
  content: "";
  position: absolute;
  left: 10%; right: 10%; bottom: -4%;
  height: 36%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 140, 255, .26), transparent 72%);
  filter: blur(34px);
  pointer-events: none;
}
.ptx-feature:hover .ptx-mock {
  transform: translateY(-4px);
  border-color: var(--brand-line);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .15),
    0 36px 76px -28px rgba(0, 0, 0, .95),
    0 0 44px -16px rgba(74, 140, 255, .5);
}

.ptx-mock-screen {
  position: relative;
  overflow: hidden;
  border-radius: calc(var(--r-lg) - 5px);
  background: radial-gradient(120% 90% at 50% 0%, #17233a, #0a1020 72%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
}
.ptx-mock-bar {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem .75rem;
  border-bottom: 1px solid var(--border-soft);
  background: rgba(255, 255, 255, .022);
  font-size: .6875rem;
  /* Valeur fixe, pas var(--text-mute) : .ptx-mock reste sombre dans les deux
     themes (voir plus haut), donc un token qui bascule au clair (#47536b)
     devient illisible sur ce fond qui ne bascule jamais. Meme cause que
     .ptx-xls-count et .ptx-field-label ci-dessous. */
  color: #667792;
}
.ptx-mock-body { padding: .875rem; display: grid; gap: .625rem; }

/* Barres abstraites tenant lieu de texte dans les mockups */
.ptx-sk { height: 6px; border-radius: 3px; background: rgba(255, 255, 255, .12); }
.ptx-sk-dim { background: rgba(255, 255, 255, .06); }

/* --------------------------------------------------------------------------
   3a. MOCKUP EXPORT EXCEL — lignes qui se sélectionnent en boucle
   -------------------------------------------------------------------------- */
.ptx-xls-row {
  display: grid;
  grid-template-columns: 14px 1fr 62px 52px;
  align-items: center;
  gap: .625rem;
  padding: .4375rem .5rem;
  border-radius: 6px;
  border: 1px solid transparent;
  transition: background-color .4s var(--ease), border-color .4s var(--ease);
}
/* Case à cocher dessinée en CSS */
.ptx-xls-check {
  width: 14px; height: 14px;
  border-radius: 4px;
  border: 1px solid var(--border-strong);
  background: rgba(0, 0, 0, .35);
  position: relative;
  transition: background-color .4s var(--ease), border-color .4s var(--ease);
}
.ptx-xls-check::after {
  content: "";
  position: absolute;
  left: 4px; top: 1px;
  width: 4px; height: 8px;
  border: solid #fff;
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(42deg) scale(0);
  transition: transform .3s var(--ease);
}
/* Sélection en boucle : trois lignes s'allument l'une après l'autre */
/* Seules les 3 premières lignes se cochent — cohérent avec « 3 projets
   sélectionnés » et avec le repli reduced-motion. La 4e reste décochée. */
.ptx-anim .ptx-xls-row:nth-child(-n+3) { animation: ptx-select 7s var(--ease) infinite; }
.ptx-anim .ptx-xls-row:nth-child(1) { animation-delay: .3s; }
.ptx-anim .ptx-xls-row:nth-child(2) { animation-delay: .9s; }
.ptx-anim .ptx-xls-row:nth-child(3) { animation-delay: 1.5s; }
.ptx-anim .ptx-xls-row:nth-child(-n+3) .ptx-xls-check::after { animation: ptx-check 7s var(--ease) infinite; }
.ptx-anim .ptx-xls-row:nth-child(1) .ptx-xls-check::after { animation-delay: .3s; }
.ptx-anim .ptx-xls-row:nth-child(2) .ptx-xls-check::after { animation-delay: .9s; }
.ptx-anim .ptx-xls-row:nth-child(3) .ptx-xls-check::after { animation-delay: 1.5s; }

@keyframes ptx-select {
  0%, 4%     { background: transparent; border-color: transparent; }
  10%, 72%   { background: var(--brand-soft); border-color: var(--brand-line); }
  84%, 100%  { background: transparent; border-color: transparent; }
}
@keyframes ptx-check {
  0%, 4%    { transform: rotate(42deg) scale(0); }
  12%, 72%  { transform: rotate(42deg) scale(1); }
  82%, 100% { transform: rotate(42deg) scale(0); }
}
.ptx-anim .ptx-xls-row:nth-child(-n+3) .ptx-xls-check { animation: ptx-check-box 7s var(--ease) infinite; }
.ptx-anim .ptx-xls-row:nth-child(1) .ptx-xls-check { animation-delay: .3s; }
.ptx-anim .ptx-xls-row:nth-child(2) .ptx-xls-check { animation-delay: .9s; }
.ptx-anim .ptx-xls-row:nth-child(3) .ptx-xls-check { animation-delay: 1.5s; }
@keyframes ptx-check-box {
  0%, 4%    { background: rgba(0,0,0,.35); border-color: var(--border-strong); }
  12%, 72%  { background: var(--brand); border-color: var(--brand); }
  82%, 100% { background: rgba(0,0,0,.35); border-color: var(--border-strong); }
}

/* Pied du mockup : le bouton d'export qui s'active */
.ptx-xls-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  margin-top: .25rem;
  padding-top: .625rem;
  border-top: 1px solid var(--border-soft);
}
.ptx-xls-count { font-size: .6875rem; color: #667792; font-variant-numeric: tabular-nums; }
.ptx-xls-btn {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .3125rem .625rem;
  font-size: .6875rem;
  font-weight: 600;
  color: #fff;
  background: linear-gradient(180deg, var(--brand), var(--brand-dim));
  border-radius: 6px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2);
}
.ptx-anim .ptx-xls-btn { animation: ptx-btn-pulse 7s var(--ease) 2.4s infinite; }
@keyframes ptx-btn-pulse {
  0%, 24%   { box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 0 0 0 rgba(74,140,255,0); }
  32%       { box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 0 0 5px rgba(74,140,255,.28); }
  44%, 100% { box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 0 0 0 rgba(74,140,255,0); }
}

/* Étiquette « aucune formule » — l'argument sécurité, matérialisé */
.ptx-xls-safe {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .1875rem .5rem;
  font-size: .625rem;
  font-weight: 600;
  color: #6ee7a0;
  background: var(--good-soft);
  border: 1px solid rgba(22, 163, 74, .3);
  border-radius: var(--r-full, 999px);
}

/* --------------------------------------------------------------------------
   3b. MOCKUP PHOTO → CHAMPS — la flèche et le remplissage progressif
   -------------------------------------------------------------------------- */
.ptx-photo {
  display: grid;
  grid-template-columns: 1fr 34px 1fr;
  align-items: center;
  gap: .5rem;
}
/* La « photo » : un carré incliné avec des lignes manuscrites suggérées */
.ptx-photo-src {
  position: relative;
  aspect-ratio: 3 / 4;
  max-height: 190px;
  justify-self: center;
  width: 100%;
  border-radius: 8px;
  background:
    repeating-linear-gradient(180deg, transparent 0 13px, rgba(255,255,255,.07) 13px 14px),
    linear-gradient(150deg, #1e2a42 0%, #141d30 100%);
  background-color: #141d30;
  border: 1px solid var(--border-strong);
  overflow: hidden;
  transform: rotate(-2.5deg);
  box-shadow: 0 10px 24px -12px rgba(0, 0, 0, .8);
}
.ptx-photo-src::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(255, 255, 255, .06), transparent 50%);
}
/* Le scan qui balaie la photo en boucle */
.ptx-anim .ptx-photo-src::before {
  content: "";
  position: absolute;
  left: 0; right: 0; height: 30%;
  z-index: 1;
  background: linear-gradient(180deg, transparent, rgba(74, 140, 255, .28), transparent);
  box-shadow: 0 0 16px rgba(74, 140, 255, .5);
  animation: ptx-scan 6s var(--ease) infinite;
}
@keyframes ptx-scan {
  0%, 8%    { top: -30%; opacity: 0; }
  14%       { opacity: 1; }
  46%       { top: 100%; opacity: 1; }
  52%, 100% { top: 100%; opacity: 0; }
}

/* La flèche entre les deux */
.ptx-photo-arrow {
  display: grid;
  place-items: center;
  height: 100%;
  color: var(--brand);
}
.ptx-photo-arrow svg { width: 22px; height: 22px; }
.ptx-anim .ptx-photo-arrow svg { animation: ptx-nudge 6s var(--ease) infinite; }
@keyframes ptx-nudge {
  0%, 42%   { transform: translateX(-3px); opacity: .4; }
  52%       { transform: translateX(3px);  opacity: 1; }
  66%, 100% { transform: translateX(-3px); opacity: .4; }
}

/* Le formulaire qui se remplit */
.ptx-photo-form { display: grid; gap: .4375rem; }
.ptx-field {
  padding: .375rem .5rem;
  border-radius: 6px;
  background: rgba(5, 8, 15, .5);
  border: 1px solid var(--border);
  display: grid;
  gap: .25rem;
  transition: border-color .4s var(--ease);
}
.ptx-field-label { font-size: .5625rem; text-transform: uppercase; letter-spacing: .06em; color: #667792; }
.ptx-field-value { height: 6px; border-radius: 3px; background: rgba(255, 255, 255, .1); width: 0; }
.ptx-anim .ptx-field { animation: ptx-field-lit 6s var(--ease) infinite; }
.ptx-anim .ptx-field-value { animation: ptx-fill 6s var(--ease) infinite; }
.ptx-anim .ptx-field:nth-child(1), .ptx-anim .ptx-field:nth-child(1) .ptx-field-value { animation-delay: 2.6s; }
.ptx-anim .ptx-field:nth-child(2), .ptx-anim .ptx-field:nth-child(2) .ptx-field-value { animation-delay: 2.9s; }
.ptx-anim .ptx-field:nth-child(3), .ptx-anim .ptx-field:nth-child(3) .ptx-field-value { animation-delay: 3.2s; }
.ptx-anim .ptx-field:nth-child(4), .ptx-anim .ptx-field:nth-child(4) .ptx-field-value { animation-delay: 3.5s; }
@keyframes ptx-fill {
  0%, 2%    { width: 0; }
  12%, 78%  { width: 72%; }
  88%, 100% { width: 0; }
}
@keyframes ptx-field-lit {
  0%, 2%    { border-color: var(--border); }
  12%, 78%  { border-color: var(--brand-line); }
  88%, 100% { border-color: var(--border); }
}
/* Le dernier champ reste vide : best-effort, pas magique. Message honnête. */
.ptx-field-manual { border-style: dashed; }
.ptx-anim .ptx-field-manual { animation: none; border-color: var(--border-strong); }
.ptx-anim .ptx-field-manual .ptx-field-value { animation: none; width: 28%; background: rgba(255,255,255,.06); }
.ptx-field-hint {
  font-size: .5625rem;
  color: var(--text-mute);
  text-align: right;
  font-style: italic;
}

/* --------------------------------------------------------------------------
   3c. MOCKUP FACTURE — le tampon « conforme » qui se pose
   -------------------------------------------------------------------------- */
.ptx-invoice {
  display: grid;
  gap: .5rem;
  position: relative;
  /* Bande réservée en bas pour le tampon : il ne partage plus la ligne des
     mentions légales, donc plus aucun chevauchement possible, à toute largeur.
     Valeur calée sur la hauteur du tampon PIVOTÉ (~44px à -9°), pas sur sa
     hauteur au repos — sinon son coin haut mord sur la dernière ligne. */
  padding-bottom: 3.5rem;
}
.ptx-invoice-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--border-soft);
}
.ptx-invoice-lines { display: grid; gap: .375rem; }
.ptx-invoice-line {
  display: grid;
  grid-template-columns: 1fr 48px;
  gap: .625rem;
  align-items: center;
}
.ptx-invoice-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: .25rem;
  padding-top: .5rem;
  border-top: 1px solid var(--border-soft);
  font-size: .8125rem;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}
/* Les mentions légales, en tout petit */
.ptx-invoice-legal {
  display: grid;
  gap: .1875rem;
  margin-top: .25rem;
  padding-top: .5rem;
  border-top: 1px solid var(--border-soft);
}
.ptx-invoice-legal span {
  font-size: .5625rem;
  color: var(--text-mute);
  line-height: 1.4;
}
/* Le tampon */
.ptx-stamp {
  position: absolute;
  left: 0; bottom: .25rem;
  padding: .3125rem .625rem;
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #6ee7a0;
  border: 2px solid #6ee7a0;
  border-radius: 6px;
  transform: rotate(-9deg) scale(1.6);
  opacity: 0;
}
.ptx-anim .ptx-stamp { animation: ptx-stamp 8s var(--ease) infinite; }
@keyframes ptx-stamp {
  0%, 22%   { opacity: 0; transform: rotate(-9deg) scale(1.6); }
  30%       { opacity: 1; transform: rotate(-9deg) scale(.96); }
  34%       { transform: rotate(-9deg) scale(1.03); }
  38%, 84%  { opacity: 1; transform: rotate(-9deg) scale(1); }
  94%, 100% { opacity: 0; transform: rotate(-9deg) scale(1.6); }
}

/* Verrou : la facture n'est débloquée qu'avec le profil complété */
.ptx-invoice-lock {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .1875rem .5rem;
  font-size: .625rem;
  font-weight: 600;
  color: #fcd34d;
  background: var(--warning-soft);
  border: 1px solid rgba(242, 176, 30, .3);
  border-radius: var(--r-full, 999px);
}

/* --------------------------------------------------------------------------
   4. MISE EN MOUVEMENT DES SECTIONS EXISTANTES
   Aucune structure modifiée : on greffe sur les classes déjà présentes.
   -------------------------------------------------------------------------- */

/* -- Cartes de fonctionnalité existantes : survol plus riche -- */
.feature-card {
  position: relative;
  isolation: isolate;
  transition:
    transform .3s var(--ease),
    border-color .3s var(--ease),
    box-shadow .3s var(--ease);
}
.feature-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(90% 60% at 50% 0%, rgba(74, 140, 255, .14), transparent 70%);
  transition: opacity .3s var(--ease);
}
.feature-card:hover { transform: translateY(-4px); border-color: var(--border-strong); }
.feature-card:hover::after { opacity: 1; }

/* -- Bandeau de stats : le chiffre respire une fois révélé --
   Alignement : les valeurs portent .hero-stat-value + [data-count-to]
   (voir landing.html), pas .stat-value - .landing-stats-bar doit aussi
   porter .reveal pour recevoir .is-visible de l'observer. */
.landing-stats-bar .stat-value,
.landing-stats-bar [data-count-to] {
  font-variant-numeric: tabular-nums;
}
.landing-stats-bar.is-visible .stat-value,
.landing-stats-bar.is-visible [data-count-to] {
  animation: ptx-value-in .7s var(--ease-out) both;
}
@keyframes ptx-value-in {
  from { opacity: 0; transform: translate3d(0, 8px, 0) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* -- Pricing : lueur ambiante lente sur le plan mis en avant --
   Alignement : la carte annuelle porte .is-highlight (voir landing.html),
   pas .featured/.is-featured/.plan-featured - conservees en filet de
   securite si une future variante de carte les utilise. */
.pricing-card.featured,
.pricing-card.is-featured,
.pricing-card.is-highlight,
.plan-featured {
  position: relative;
  isolation: isolate;
}
.pricing-card.featured::before,
.pricing-card.is-featured::before,
.pricing-card.is-highlight::before,
.plan-featured::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(120% 70% at 50% 0%, rgba(74, 140, 255, .16), transparent 62%);
  animation: ptx-breathe 7s ease-in-out infinite alternate;
}
@keyframes ptx-breathe {
  from { opacity: .55; }
  to   { opacity: 1; }
}

/* -- FAQ : chaque item se révèle en cascade, chevron animé --
   Alignement : les items reels portent .lp-faq-item (voir landing.html). */
.faq details,
details.faq-item,
.lp-faq-item {
  transition: border-color .22s var(--ease), background-color .22s var(--ease);
}
.faq details[open],
details.faq-item[open],
.lp-faq-item[open] { border-color: var(--brand-line); }

/* -- CTA final : filet lumineux qui traverse au moment de la révélation --
   Alignement : la section reelle porte .lp-cta (voir landing.html), qui
   recoit desormais .reveal directement (en plus de son .lp-band-inner
   interne, deja revele independamment). */
.final-cta,
.cta-final,
.lp-cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.final-cta::after,
.cta-final::after,
.lp-cta::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  height: 1px;
  width: 100%;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(74, 140, 255, .8), transparent);
  transform: translateX(-100%);
}
.final-cta.is-visible::after,
.cta-final.is-visible::after {
  animation: ptx-sweep-line 1.6s var(--ease-out) .3s both;
}
/* .lp-cta ne porte pas .reveal lui-meme (seul son .lp-band-inner interne
   l'a deja, avec le fade+translate par defaut - dupliquer .reveal sur le
   parent aurait cumule les deux transforms). On se raccroche donc a l'etat
   de l'enfant via :has(), sans toucher au template. */
.lp-cta:has(.lp-band-inner.is-visible)::after {
  animation: ptx-sweep-line 1.6s var(--ease-out) .3s both;
}
@keyframes ptx-sweep-line {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* -- « Comment ça marche » : escalier montant --
   La ligne de connexion est desormais .hiw-stairs (SVG, section
   "Comment ca marche" plus haut dans ce fichier) et non plus le
   pseudo-element .hiw-steps::after - voir ce bloc pour l'animation de
   trace au scroll (meme mecanisme :has(.hiw-step.is-visible)). */
.how-it-works,
.steps { position: relative; }

/* -- FAQ : ouverture/fermeture animee (JS pilote max-height, voir
   landing.html) -- */
.lp-faq-item .faq-a{
  overflow: hidden;
  transition: max-height .32s var(--ease);
}


/* --------------------------------------------------------------------------
   7. ENVELOPPE DE SECTION — ajoutée pour le partial livré
   -------------------------------------------------------------------------- */
.ptx-features-section {
  position: relative;
  padding: var(--ptx-section-y) 0;
}
.ptx-feature-head {
  display: grid;
  gap: .875rem;
  max-width: 44rem;
  margin: 0 auto clamp(2rem, 5vw, 3.25rem);
  text-align: center;
}
.ptx-feature-head .ptx-feature-tag { justify-self: center; }
.ptx-feature-head h2 {
  margin: 0;
  font-size: clamp(1.55rem, 3vw, 2.2rem);
  letter-spacing: -.028em;
}
.ptx-feature-head p {
  margin: 0 auto;
  max-width: 38rem;
  color: var(--text-dim);
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   5. RESPONSIVE — mobile prioritaire, conformément au brief
   -------------------------------------------------------------------------- */
@media (max-width: 960px) {
  .ptx-feature {
    grid-template-columns: 1fr;
    gap: 2rem;
    padding: clamp(2rem, 6vw, 3rem) 0;
  }
  /* Le mockup repasse toujours SOUS le texte en colonne : on lit d'abord, on voit ensuite */
  .ptx-feature:nth-child(even) > .ptx-feature-visual { order: 0; }
  .ptx-feature-text { max-width: none; }
  .ptx-feature-visual { max-width: 520px; width: 100%; }
  /* Les variantes latérales redeviennent verticales : pas de scroll horizontal */
  .reveal-left, .reveal-right { transform: translate3d(0, 20px, 0); }
}

@media (max-width: 560px) {
  .ptx-photo { grid-template-columns: 1fr; gap: .625rem; }
  .ptx-photo-src { aspect-ratio: 16 / 9; transform: none; }
  .ptx-photo-arrow { height: 24px; transform: rotate(90deg); }
  .ptx-xls-row { grid-template-columns: 14px 1fr 54px; }
  .ptx-xls-row > :last-child { display: none; }
  .ptx-invoice { padding-bottom: 3rem; }
  .ptx-stamp { left: 0; bottom: .125rem; font-size: .5625rem; }
}

/* --------------------------------------------------------------------------
   6. ACCESSIBILITÉ — même règle que le hero
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .reveal-left, .reveal-right, .reveal-scale {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .reveal-mask > * { transform: none !important; transition: none !important; }
  .ptx-anim *,
  .ptx-anim *::before,
  .ptx-anim *::after { animation: none !important; }
  /* États de repos lisibles, pas des états à zéro */
  .ptx-anim .ptx-xls-row:nth-child(-n+3) { background: var(--brand-soft); border-color: var(--brand-line); }
  .ptx-anim .ptx-xls-row:nth-child(-n+3) .ptx-xls-check { background: var(--brand); border-color: var(--brand); }
  .ptx-anim .ptx-xls-row:nth-child(-n+3) .ptx-xls-check::after { transform: rotate(42deg) scale(1); }
  .ptx-anim .ptx-field-value { width: 72%; }
  .ptx-anim .ptx-field { border-color: var(--brand-line); }
  .ptx-anim .ptx-field-manual .ptx-field-value { width: 28%; }
  .ptx-anim .ptx-stamp { opacity: 1; transform: rotate(-9deg) scale(1); }
  .ptx-anim .ptx-photo-src::before { display: none; }
  .pricing-card.featured::before,
  .pricing-card.is-featured::before,
  .pricing-card.is-highlight::before,
  .plan-featured::before { animation: none; opacity: .8; }
  .feature-card:hover { transform: none; }
  .final-cta::after, .cta-final::after, .lp-cta::after { display: none; }
  .hiw-stairs path { stroke-dashoffset: 0 !important; animation: none !important; }
}

/* Sans JS : les variantes doivent rester visibles. Le <noscript> du <head> ne
   couvre que .reveal — cette règle couvre les nouvelles variantes.
   À reporter dans le même <noscript> de base.html :
     .reveal-left,.reveal-right,.reveal-scale{opacity:1;transform:none}
     .reveal-mask>*{transform:none}                                        */

@media print {
  .ptx-anim *, .ptx-anim *::before, .ptx-anim *::after { animation: none !important; }
  .reveal-left, .reveal-right, .reveal-scale { opacity: 1; transform: none; }
  .reveal-mask > * { transform: none; }
  .ptx-mock { box-shadow: none; break-inside: avoid; }
}

/* ==========================================================================
   ProfitTrack — THÈME CLAIR
   Handoff Claude Design (doc/design_handoff_theme_clair/), intégré et
   corrigé le 2026-08-05 : plusieurs sélecteurs du bundle livré visaient des
   classes qui n'existent nulle part dans ce depot (.pt-nav, .pt-eyebrow,
   .pt-faq-item, .pt-section-alt, .pt-cta, .pt-plan-flag, .pt-cycle-save,
   .pt-features li::before, .hero-device-frame/-shell, .hero-curve-bottom) -
   la conception n'avait pas acces au HTML/CSS reel de ce projet pour ces
   sections. Corrections faites ici : noms alignes sur les vraies classes
   (.m-header, .lp-eyebrow, .lp-faq-item, .lp-band-atmo::before, .hero-device,
   .hero-curve-arc, .app-sidebar, .side-link), le reste (sans equivalent reel
   trouve : plan-flag, cycle-save, features li::before, cta::before/after)
   a ete retire plutot que laisse a viser dans le vide. Voir le README du
   bundle pour la justification complete de chaque valeur (contrastes AA
   mesures, raison de chaque token).

   MAJ 2026-08-07 (v2, plus contraste) : suite a l'audit AA/AAA, les tokens
   neutres (texte, bordures, ombres) sont assombris/renforces ; la marque
   (--brand-dim/--brand-deep) et les couleurs d'etat (bon/attention/critique)
   restent volontairement quasi inchangees pour ne pas perdre en vivacite.
   Voir doc/design_theme_clair_contraste/ pour le detail des ratios mesures.

   Activation : <html data-theme="light"> sur les pages connectees.
   Sans l'attribut -> theme sombre inchange. Aucune regle sombre n'est
   modifiee : tout ce qui suit est enferme dans html[data-theme="light"].
   ========================================================================== */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */
html[data-theme="light"] {
  color-scheme: light;

  /* --- Surfaces -----------------------------------------------------------
     Papier bleute, jamais #fff sur le fond de page. Page a peine plus
     soutenue que la v1 : l'essentiel de la separation carte/page vient
     des bordures et ombres ci-dessous, pas d'assombrir le fond. */
  --page: #e3e8f1;
  --page-deep: #d5dce9;
  --surface: #f7f9fd;
  --surface-2: #ffffff;
  --surface-3: #ffffff;
  --surface-hover: #eaf0f9;

  /* --- Bordures : alphas renforces (v2, +contraste) ------------------------
     Structurelles (cartes, separateurs, filets) - pas soumises au 3:1,
     jamais seul indicateur d'un controle. Pour les controles, voir
     --control-line juste en dessous. */
  --border: rgba(16, 26, 46, .20);
  --border-soft: rgba(16, 26, 46, .12);
  --border-strong: rgba(16, 26, 46, .32);

  /* --- Ligne des controles --------------------------------------------------
     v2 : 7,2:1 sur --surface-2 (contre 3,4:1 en v1) - la bordure d'un
     champ/bouton secondaire EST son affordance, WCAG demande 3:1. */
  --control-line: #4c586f;
  --control-line-hover: #37415a;

  /* --- Texte ---------------------------------------------------------------
     Encre bleu-nuit assombrie (v2) : --text-mute passe de 5,1:1 a 7,7:1
     sur --surface, le plus faible des quatre paliers desormais AAA. */
  --text: #0a1120;
  --text-dim: #2e3a52;
  --text-mute: #47536b;
  --text-soft: #3d4a63;
  --text-head: #3d4a63;

  /* --- Marque ---------------------------------------------------------------
     INCHANGEE en v2 (a dessein, pour garder la vivacite du bleu de marque) :
     --brand reste #4a8cff. Pour le texte/aplats portant du blanc, on
     descend sur --brand-dim/--brand-deep, qui seuls passent AA sur clair. */
  --brand: #4a8cff;
  --brand-dim: #2563eb;
  --brand-deep: #1a44c0;
  --brand-soft: rgba(74, 140, 255, .12);
  --brand-line: rgba(74, 140, 255, .48);

  --nebula: #7c6cff;
  --nebula-soft: rgba(124, 108, 255, .12);

  /* --- Etats : a peine assombris (v2), restent reconnaissables comme le
     meme vert/orange/rouge, pas une version terne -------------------------- */
  --good: #15803d;
  --good-soft: rgba(21, 128, 61, .14);
  --good-text: #0f6b32;
  --warning: #a16207;
  --warning-soft: rgba(217, 160, 20, .18);
  --warning-text: #7a4c03;
  --critical: #c02626;
  --critical-soft: rgba(192, 38, 38, .12);
  --critical-text: #a01c1c;

  /* --- Elevation : ombre bleu-nuit plus marquee (v2), jamais noire pure ---- */
  --sh-1: 0 1px 2px rgba(16, 26, 46, .12);
  --sh-2: 0 1px 3px rgba(16, 26, 46, .10), 0 9px 22px -8px rgba(16, 26, 46, .20);
  --sh-3: 0 3px 7px rgba(16, 26, 46, .12), 0 24px 48px -14px rgba(16, 26, 46, .26);
  --sh-glow: 0 0 0 1px rgba(74, 140, 255, .48), 0 9px 28px -10px rgba(74, 140, 255, .38);
  --ring: 0 0 0 3px rgba(74, 140, 255, .34);

  --shadow-card: 0 1px 0 rgba(255, 255, 255, .95) inset, 0 5px 16px rgba(16, 26, 46, .13);
  --dash-sheen: linear-gradient(180deg, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0) 96px);

  --seg-track: #e4eaf3;
  --seg-hi: rgba(16, 26, 46, .045);

  --pd-sunken: #dfe6f0;

  /* Nouveaux en clair, memes raisons qu'en sombre ci-dessus (voir :root). */
  --brand-text: #1d4ed8;
  --dz-rail: rgba(16, 26, 46, .12);
  --sh-card: 0 1px 0 rgba(255, 255, 255, .6) inset, 0 6px 18px rgba(16, 26, 46, .10);
  --sh-lift: 0 2px 4px rgba(16, 26, 46, .08), 0 12px 28px -10px rgba(16, 26, 46, .22);
}

/* ==========================================================================
   2. SELECTEURS DEDIES
   Uniquement la ou la base code une couleur en dur, hors token.
   ========================================================================== */

/* --- Fond de page et voile atmospherique --------------------------------- */
html[data-theme="light"] body {
  background: var(--page);
}
html[data-theme="light"] body::before {
  background:
    radial-gradient(1100px 620px at 50% -10%, rgba(74, 140, 255, .1), transparent 70%),
    radial-gradient(900px 500px at 90% 0%, rgba(124, 108, 255, .08), transparent 72%);
}
html[data-theme="light"] ::selection { background: rgba(74, 140, 255, .26); color: #0a1120; }

/* --- Barre de defilement --------------------------------------------------- */
html[data-theme="light"] * { scrollbar-color: #b7c2d6 transparent; }
html[data-theme="light"] *::-webkit-scrollbar-thumb { background: #b7c2d6; background-clip: content-box; }
html[data-theme="light"] *::-webkit-scrollbar-thumb:hover { background: #9aa8c0; background-clip: content-box; }

/* --- Boutons --------------------------------------------------------------- */
html[data-theme="light"] .btn::after {
  background: linear-gradient(180deg, rgba(255, 255, 255, .82), rgba(255, 255, 255, 0) 46%);
}
html[data-theme="light"] .btn-primary {
  background: linear-gradient(180deg, var(--brand-dim) 0%, var(--brand-deep) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .24),
    0 1px 2px rgba(16, 26, 46, .18),
    0 6px 18px -8px rgba(37, 99, 235, .52);
}
html[data-theme="light"] .btn-primary:hover {
  background: linear-gradient(180deg, #2d6df0 0%, #1c47cc 100%);
  border-color: rgba(255, 255, 255, .3);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .28),
    0 1px 2px rgba(16, 26, 46, .18),
    0 10px 24px -8px rgba(37, 99, 235, .62);
}
html[data-theme="light"] .btn-primary:active {
  background: linear-gradient(180deg, var(--brand-deep), #163a9f);
}
html[data-theme="light"] .btn-ghost:hover { background: rgba(16, 26, 46, .07); }
html[data-theme="light"] .btn-ghost:active { background: rgba(16, 26, 46, .04); }
html[data-theme="light"] .btn-danger { --btn-fg: var(--critical-text); --btn-bd: rgba(192, 38, 38, .36); }
html[data-theme="light"] .btn-danger:hover { color: #fff; background: var(--critical); border-color: var(--critical); }
html[data-theme="light"] .btn { --btn-bd: var(--control-line); }
html[data-theme="light"] .btn:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .btn-ghost { --btn-bd: transparent; }
html[data-theme="light"] .btn-ghost:hover { border-color: var(--border-strong); }
/* --brand sur --brand-soft clair ne passe pas AA - --brand-deep oui. */
html[data-theme="light"] .dash-tools-sim { --btn-fg: var(--brand-deep); }

/* --- Cartes ------------------------------------------------------------------ */
html[data-theme="light"] .card {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .92), rgba(255, 255, 255, 0) 120px),
    var(--surface);
}
html[data-theme="light"] .card::before {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .98), transparent);
}

/* --- Badges de statut ---------------------------------------------------------
   Les trois textes clairs du theme sombre (#4ade80/#fcd34d/#ff9d9d) sont
   illisibles sur clair (1,7 a 2,1:1). Remplaces par les encres. */
html[data-theme="light"] .badge { background: rgba(16, 26, 46, .06); }
html[data-theme="light"] .badge-good     { color: var(--good-text);     border-color: rgba(21, 128, 61, .32); }
html[data-theme="light"] .badge-warning  { color: var(--warning-text);  border-color: rgba(161, 98, 7, .32); }
html[data-theme="light"] .badge-critical { color: var(--critical-text); border-color: rgba(192, 38, 38, .32); }

/* --- Formulaires ---------------------------------------------------------------
   Un champ est un creux en sombre (fond plus sombre que la surface).
   L'inverse ne marche pas en clair (un champ plus clair que sa carte
   disparait) : le champ reste blanc, la bordure franche + l'ombre interne
   portent l'affordance. */
html[data-theme="light"] input[type="text"],
html[data-theme="light"] input[type="email"],
html[data-theme="light"] input[type="password"],
html[data-theme="light"] input[type="number"],
html[data-theme="light"] input[type="search"],
html[data-theme="light"] input[type="tel"],
html[data-theme="light"] input[type="url"],
html[data-theme="light"] input[type="date"],
html[data-theme="light"] select,
html[data-theme="light"] textarea {
  color: var(--text);
  background: #ffffff;
  border-color: var(--control-line);
  border-width: 1.5px;
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07);
}
html[data-theme="light"] input:hover,
html[data-theme="light"] select:hover,
html[data-theme="light"] textarea:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] input:focus,
html[data-theme="light"] select:focus,
html[data-theme="light"] textarea:focus {
  background: #ffffff;
  border-color: var(--brand-dim);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07), var(--ring);
}
html[data-theme="light"] input:-webkit-autofill,
html[data-theme="light"] input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 1000px #eef3fc inset, inset 0 1px 2px rgba(16, 26, 46, .07);
}
html[data-theme="light"] select {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--text-mute) 50%),
    linear-gradient(135deg, var(--text-mute) 50%, transparent 50%);
}
html[data-theme="light"] select:focus {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--brand-dim) 50%),
    linear-gradient(135deg, var(--brand-dim) 50%, transparent 50%);
}

/* --- Degrades de texte (background-clip: text) --------------------------------
   .pt-stat-value du bundle original retire : .hero-stat-value (le vrai nom)
   utilise deja var(--text)/var(--brand-line) comme stops de degrade, donc
   s'adapte automatiquement au theme clair sans regle dediee. */
html[data-theme="light"] .hero-text h1,
html[data-theme="light"] .stat-tile .stat-value,
html[data-theme="light"] .stat-tile .kpi-value,
html[data-theme="light"] .stat-tile .tile-value,
html[data-theme="light"] .stat-tile [data-value] {
  background: linear-gradient(176deg, #060c1a 8%, #2a3958 96%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
html[data-theme="light"] .hero-text h1 em,
html[data-theme="light"] .hero-text h1 span {
  background: linear-gradient(100deg, var(--brand-dim) 0%, #4c6ae8 55%, #5b4ae0 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* --- Barre de navigation de la landing (.pt-nav -> .m-header, vrai nom) ------ */
html[data-theme="light"] .m-header { background: rgba(227, 232, 241, .78); }

/* --- Section alternee (.pt-section-alt -> .lp-band-atmo::before, vrai nom) --- */
html[data-theme="light"] .lp-band-atmo::before {
  background:
    radial-gradient(900px 420px at 18% 0%, rgba(124, 108, 255, .10), transparent 70%),
    radial-gradient(700px 380px at 86% 100%, rgba(74, 140, 255, .09), transparent 72%);
}

/* --- Eyebrows (.pt-eyebrow -> .lp-eyebrow, vrai nom) --------------------------
   .lp-eyebrow utilisait var(--brand-line) - une rgba translucide pensee pour
   des traits, pas du texte : en clair elle devient un bleu quasi invisible.
   Descend sur --brand-dim comme les autres textes de marque. */
html[data-theme="light"] .lp-eyebrow,
html[data-theme="light"] .ptx-feature-tag { color: var(--brand-dim); }

/* --- FAQ (.pt-faq-item -> .lp-faq-item, vrai nom ; le "+" est un glyphe
   texte via content:"+", pas une bordure dessinee - color, pas border-color) */
html[data-theme="light"] .lp-faq-item summary::after { color: var(--text-mute); }
html[data-theme="light"] .lp-faq-item[open] summary::after { color: var(--brand-dim); }

/* --- Panneau de detail projet (.pd-) ------------------------------------------ */
html[data-theme="light"] .pd-key-cell {
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .08), rgba(255, 255, 255, 0) 70px),
    var(--surface-2);
}
html[data-theme="light"] .pd-key-val.pos { color: var(--good-text); }
html[data-theme="light"] .pd-key-val.neg { color: var(--critical-text); }
html[data-theme="light"] .pd-line .v.pos { color: var(--good-text); }
html[data-theme="light"] .pd-line .v.neg { color: var(--critical-text); }
html[data-theme="light"] .pd-line .v.warn { color: var(--warning-text); }
html[data-theme="light"] .pd-bicon { background: rgba(16, 26, 46, .06); box-shadow: inset 0 0 0 1px rgba(16, 26, 46, .14); }
html[data-theme="light"] .pd-tier { background: rgba(255, 255, 255, .8); }
html[data-theme="light"] .pd-tier[open] { background: #ffffff; }
html[data-theme="light"] .pd-tier-body { background: rgba(16, 26, 46, .045); }
html[data-theme="light"] .pd-tier-chev { border-color: var(--text-mute); }
html[data-theme="light"] .pd-tier[open] .pd-tier-chev { border-color: var(--brand-dim); }
html[data-theme="light"] .pd-activity { box-shadow: inset 0 1px 3px rgba(16, 26, 46, .09); }
html[data-theme="light"] .pd-log { background: rgba(255, 255, 255, .84); }
html[data-theme="light"] .pd-log-row { background: #ffffff; }
html[data-theme="light"] .pd-f { background: #ffffff; border-color: var(--control-line); }
html[data-theme="light"] .pd-f:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .pd-f:focus { border-color: var(--brand-dim); }
html[data-theme="light"] .pd-btn { color: var(--text-dim); background: rgba(16, 26, 46, .045); border-color: var(--control-line); }
html[data-theme="light"] .pd-btn:hover { color: var(--text); background: rgba(16, 26, 46, .09); border-color: var(--control-line-hover); }
html[data-theme="light"] .pd-btn-brand {
  color: #fff;
  background: linear-gradient(180deg, var(--brand-dim), var(--brand-deep));
  border-color: rgba(255, 255, 255, .22);
}
html[data-theme="light"] .pd-btn-brand:hover { background: linear-gradient(180deg, #2d6df0, #1c47cc); }
html[data-theme="light"] .pd-btn-danger { color: var(--critical-text); border-color: rgba(192, 38, 38, .38); }
html[data-theme="light"] .pd-btn-danger:hover { color: #fff; background: var(--critical); border-color: var(--critical); }
html[data-theme="light"] .pd-btn-icon:hover { color: var(--critical-text); }
html[data-theme="light"] .pd-prog { background: rgba(16, 26, 46, .1); }
html[data-theme="light"] .pd-prog > i { background: linear-gradient(90deg, var(--brand-dim), #6b5ce8); }
html[data-theme="light"] .pd-task { background: rgba(255, 255, 255, .8); }
html[data-theme="light"] .pd-locked { color: var(--good-text); }
html[data-theme="light"] .pd-badge { background: rgba(16, 26, 46, .06); }
html[data-theme="light"] .pd-badge.good { color: var(--good-text); }
html[data-theme="light"] .pd-badge.warn { color: var(--warning-text); }
html[data-theme="light"] .pd-badge.crit { color: var(--critical-text); }
html[data-theme="light"] .pd-badge.brand { color: var(--brand-dim); }
html[data-theme="light"] .pd { box-shadow: 0 11px 28px -14px rgba(16, 26, 46, .34); }

/* --- Page de suggestions (.fv-) : base dans templates/suggestions.html,
   ces surcharges gagnent quand meme la cascade (specificite html[..] plus
   elevee que la regle de base a specificite egale par ailleurs) ------------ */
html[data-theme="light"] .fv-tab-slide {
  background: linear-gradient(180deg, #ffffff, #eef3fb);
  border-color: rgba(16, 26, 46, .22);
  box-shadow: 0 1px 3px rgba(16, 26, 46, .16), inset 0 1px 0 rgba(255, 255, 255, .92);
}
html[data-theme="light"] .fv-tab-n { color: var(--text-mute); background: rgba(16, 26, 46, .08); }
html[data-theme="light"] .fv-tab[aria-selected="true"] .fv-tab-n { color: var(--brand-dim); background: var(--brand-soft); }
html[data-theme="light"] .fv-compose {
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .07), rgba(255, 255, 255, 0) 120px),
    var(--surface-2);
}
html[data-theme="light"] .fv-compose-rule { background: rgba(16, 26, 46, .06); }
html[data-theme="light"] .fv-ta { background: #ffffff; border-color: var(--control-line); }
html[data-theme="light"] .fv-ta:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .fv-ta:focus { border-color: var(--brand-dim); }
html[data-theme="light"] .fv-reply-f { background: #ffffff; border-color: var(--control-line); }
html[data-theme="light"] .fv-reply-f:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .fv-reply-f:focus { border-color: var(--brand-dim); }
/* .fv-prop:hover est blanc pur en sombre (base du fichier) - invisible sur
   une carte claire une fois le theme clair actif. */
html[data-theme="light"] .fv-prop:hover { color: var(--brand-deep); }
html[data-theme="light"] .fv-card-star {
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .1), rgba(255, 255, 255, 0) 140px),
    var(--surface-2);
}
html[data-theme="light"] .fv-rank {
  color: #fff;
  background: var(--brand-dim);
  border-color: rgba(255, 255, 255, .42);
  box-shadow: 0 2px 6px rgba(16, 26, 46, .24);
}
html[data-theme="light"] .fv-votes { background: rgba(16, 26, 46, .035); }
html[data-theme="light"] .fv-vote { background: rgba(16, 26, 46, .04); border-color: var(--control-line); }
html[data-theme="light"] .fv-vote:hover { background: rgba(16, 26, 46, .09); border-color: var(--control-line-hover); }
html[data-theme="light"] .fv-vote[aria-pressed="true"].up { color: var(--good-text); border-color: rgba(21, 128, 61, .42); }
html[data-theme="light"] .fv-vote[aria-pressed="true"].down { color: var(--critical-text); border-color: rgba(192, 38, 38, .42); }
html[data-theme="light"] .fv-mine { background: rgba(16, 26, 46, .06); }
html[data-theme="light"] .fv-cbtn { background: rgba(16, 26, 46, .04); border-color: var(--border-strong); }
html[data-theme="light"] .fv-cbtn:hover { background: rgba(16, 26, 46, .09); }
html[data-theme="light"] .fv-cbtn[aria-expanded="true"] { color: var(--brand-dim); background: var(--brand-soft); }
html[data-theme="light"] .fv-thread {
  background: var(--pd-sunken);
  box-shadow: inset 0 1px 3px rgba(16, 26, 46, .09);
}
html[data-theme="light"] .fv-comment { background: #ffffff; }
html[data-theme="light"] .fv-badge { background: rgba(16, 26, 46, .06); }
html[data-theme="light"] .fv-badge.dev { color: var(--brand-dim); }
html[data-theme="light"] .fv-badge.done { color: var(--good-text); }
html[data-theme="light"] .fv-abtn { color: var(--brand-dim); }
html[data-theme="light"] .fv-abtn:hover { color: var(--brand-deep); background: var(--brand-soft); }
html[data-theme="light"] .fv-abtn.back { color: var(--text-dim); border-color: var(--border-strong); }
html[data-theme="light"] .fv-abtn.back:hover { background: rgba(16, 26, 46, .06); border-color: rgba(16, 26, 46, .32); }
html[data-theme="light"] .fv-admin { background: rgba(74, 140, 255, .06); }
html[data-theme="light"] .fv-btn { background: rgba(16, 26, 46, .04); border-color: var(--control-line); }
html[data-theme="light"] .fv-btn:hover { background: rgba(16, 26, 46, .09); border-color: var(--control-line-hover); }
html[data-theme="light"] .fv-btn-primary {
  color: #fff;
  background: linear-gradient(180deg, var(--brand-dim), var(--brand-deep));
  border-color: rgba(255, 255, 255, .22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24), 0 1px 2px rgba(16, 26, 46, .18), 0 6px 18px -8px rgba(37, 99, 235, .52);
}
html[data-theme="light"] .fv-btn-primary:hover { background: linear-gradient(180deg, #2d6df0, #1c47cc); }
html[data-theme="light"] .fv-locked-icon { background: var(--warning-soft); box-shadow: inset 0 0 0 1px rgba(161, 98, 7, .32); }
html[data-theme="light"] .fv-empty-ico { box-shadow: inset 0 0 0 1px rgba(74, 140, 255, .48); }

/* --- Dashboard (.stat-tile/.dash-widget/.chart-box/.app-sidebar) -------------
   .app-main ajoute a la liste (vrai nom, absent de la liste .dashboard/
   .dash-main/.dashboard-main/main.dashboard du bundle original). */
html[data-theme="light"] .app-main,
html[data-theme="light"] .dashboard,
html[data-theme="light"] .dash-main,
html[data-theme="light"] .dashboard-main,
html[data-theme="light"] main.dashboard {
  background:
    radial-gradient(1100px 520px at 78% -8%, rgba(74, 140, 255, .09), transparent 68%),
    radial-gradient(760px 420px at 8% 2%, rgba(124, 108, 255, .07), transparent 72%),
    var(--page);
}
html[data-theme="light"] .stat-tile::before,
html[data-theme="light"] .dash-widget::before {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .98), transparent);
}
html[data-theme="light"] .stat-tile .is-up,   html[data-theme="light"] .stat-tile .stat-up   { color: var(--good-text); }
html[data-theme="light"] .stat-tile .is-down, html[data-theme="light"] .stat-tile .stat-down { color: var(--critical-text); }
html[data-theme="light"] .chart-box::before {
  background-image:
    linear-gradient(to right, rgba(16, 26, 46, .07) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(16, 26, 46, .07) 1px, transparent 1px);
}
html[data-theme="light"] .drag-handle:hover { background: rgba(16, 26, 46, .07); }
html[data-theme="light"] .tile-toggle:hover,
html[data-theme="light"] .widget-toggle:hover { background: rgba(16, 26, 46, .07); }
/* .side-link est le vrai nom (.sidebar/.dash-sidebar du bundle original ne
   correspondent a rien : .app-sidebar herite deja correctement de
   var(--page-deep) via la regle "passe visuelle" existante, sans besoin de
   regle dediee ici - seul l'etat actif avait vraiment besoin d'un fix). */
html[data-theme="light"] .side-link.active,
html[data-theme="light"] .side-link[aria-current="page"] {
  color: var(--brand-deep);
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 1px var(--brand-line);
}
html[data-theme="light"] .stat-tile.dragging,
html[data-theme="light"] .dash-widget.dragging,
html[data-theme="light"] .stat-tile.is-dragging,
html[data-theme="light"] .dash-widget.is-dragging { opacity: .55; }

/* --- "Nouveau projet" (.np-) ---------------------------------------------------
   Bloc CSS auto-contenu dans templates/index.html (pas dans ce fichier),
   construit avant l'integration du theme clair et jamais mis a jour depuis :
   plusieurs couleurs y sont codees en dur pour le sombre (--np-surface-sec,
   fond des champs .np-f, ombres noires) au lieu de reprendre les tokens.
   Corrige ici plutot que dans le template pour garder une seule source de
   verite theme claire/sombre (voir static/css/style.css en general). */
html[data-theme="light"] { --np-surface-sec: #ffffff; --np-text-head: var(--text-mute); }

html[data-theme="light"] .np-ibadge { background: rgba(16, 26, 46, .06); box-shadow: inset 0 0 0 1px rgba(16, 26, 46, .14); }
html[data-theme="light"] .np-ibadge-brand { color: var(--brand-dim); }

html[data-theme="light"] .np-card {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset, 0 8px 24px rgba(16, 26, 46, .13);
}
html[data-theme="light"] .np-card:hover {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .8) inset, 0 14px 34px rgba(16, 26, 46, .18);
}
html[data-theme="light"] .np-card-key {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .85) inset, 0 10px 32px rgba(16, 26, 46, .18), 0 0 44px -22px rgba(74, 140, 255, .4);
}
html[data-theme="light"] .np-card-key:hover {
  box-shadow: 0 1px 0 rgba(255, 255, 255, .85) inset, 0 10px 32px rgba(16, 26, 46, .18), 0 0 44px -22px rgba(74, 140, 255, .4);
}
html[data-theme="light"] .np-shead-title { color: var(--text); }
html[data-theme="light"] .np-opt { color: var(--text-mute); background: rgba(16, 26, 46, .06); border-color: rgba(16, 26, 46, .14); }

/* Champ de saisie : meme bascule que les inputs/select globaux (voir plus
   haut) - fond blanc, la bordure + l'ombre interne portent l'affordance. */
html[data-theme="light"] .np-f {
  background: #ffffff;
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .07);
}
html[data-theme="light"] .np-f:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .np-f-dim { background: rgba(16, 26, 46, .035); border-color: rgba(16, 26, 46, .14); }

html[data-theme="light"] .np-type { background: #ffffff; }
html[data-theme="light"] .np-type:hover { background: rgba(16, 26, 46, .06); border-color: var(--control-line-hover); }
html[data-theme="light"] .np-type-dot { border-color: var(--control-line); }
html[data-theme="light"] .np-type-dot i { background: #fff; }

html[data-theme="light"] .np-btn-ghost:hover { background: rgba(16, 26, 46, .06); border-color: var(--control-line-hover); }
html[data-theme="light"] .np-btn-add { color: var(--brand-dim); }
html[data-theme="light"] .np-btn-add:hover { color: var(--brand-deep); }
html[data-theme="light"] .np-btn-add-quiet { border-color: rgba(16, 26, 46, .22); }
html[data-theme="light"] .np-btn-add-quiet:hover { border-color: rgba(16, 26, 46, .32); background: rgba(16, 26, 46, .045); }
html[data-theme="light"] .np-btn-primary {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24), 0 1px 2px rgba(16, 26, 46, .2), 0 10px 30px -12px rgba(74, 140, 255, .6);
}
html[data-theme="light"] .np-btn-primary:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), 0 1px 2px rgba(16, 26, 46, .2), 0 14px 38px -12px rgba(74, 140, 255, .7);
}
html[data-theme="light"] .np-tail .np-btn:hover { background: rgba(16, 26, 46, .05); }
html[data-theme="light"] .np-del:hover { color: var(--critical-text); }

/* --- Mockups de produit : vraiment clairs, ecran allume comme le reste --------
   .hero-device .card/.badge du bundle precedent ne visaient rien de reel (la
   tablette n'a jamais contenu de .card, seulement .hero-device-kpi/-chart/
   -donut/-spark/-table, deja pilotes par des tokens qui s'adaptent tout seuls
   des que le chassis/l'ecran eux-memes passent au clair - inutile de les
   toucher un par un). Ne restent a fixer que les couleurs litterales : le
   chassis (degrade sombre code en dur), l'ecran, les points de la barre de
   titre, et - cote mockups ptx- (section "Trois ajouts") - le meme chassis
   partage plus le contenu propre a chacun des 3 (export/photo/facture). */
html[data-theme="light"] .hero-device {
  background: linear-gradient(155deg, #ffffff 0%, #eef2f8 45%, #e3e9f2 100%);
  border-color: rgba(16, 26, 46, .16);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .85),
    inset 0 0 0 1px rgba(16, 26, 46, .07),
    0 28px 60px -26px rgba(16, 26, 46, .4),
    0 6px 18px -10px rgba(16, 26, 46, .26);
}
html[data-theme="light"] .hero-device-screen {
  background: radial-gradient(120% 90% at 50% 0%, #f9fbfd, #eef2f7 72%);
  box-shadow: inset 0 0 0 1px rgba(16, 26, 46, .08);
}
html[data-theme="light"] .hero-device-screen::after {
  background: linear-gradient(100deg, transparent, rgba(16, 26, 46, .07) 45%, transparent);
}
html[data-theme="light"] .hero-device-bar span { background: rgba(16, 26, 46, .2); }
html[data-theme="light"] .k-bar { background: rgba(16, 26, 46, .1); }
html[data-theme="light"] .hero-device-ghost {
  background: linear-gradient(155deg, #f4f7fb 0%, #e7edf5 55%, #dde5f0 100%);
  border-color: rgba(16, 26, 46, .14);
  box-shadow: 0 30px 60px -26px rgba(16, 26, 46, .3);
}

html[data-theme="light"] .ptx-mock {
  background: linear-gradient(155deg, #ffffff 0%, #f4f7fb 46%, #e9eef5 100%);
  border-color: rgba(16, 26, 46, .16);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .85),
    0 20px 44px -22px rgba(16, 26, 46, .32);
}
html[data-theme="light"] .ptx-feature:hover .ptx-mock {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .9),
    0 26px 56px -22px rgba(16, 26, 46, .36),
    0 0 40px -16px rgba(74, 140, 255, .42);
}
html[data-theme="light"] .ptx-mock-screen {
  background: radial-gradient(120% 90% at 50% 0%, #ffffff, #f1f5f9 72%);
  box-shadow: inset 0 0 0 1px rgba(16, 26, 46, .08);
}
html[data-theme="light"] .ptx-mock-bar { background: rgba(16, 26, 46, .04); }
/* Barres abstraites tenant lieu de texte : blanc translucide -> encre translucide. */
html[data-theme="light"] .ptx-sk { background: rgba(16, 26, 46, .16); }
html[data-theme="light"] .ptx-sk-dim { background: rgba(16, 26, 46, .08); }

/* --- Mockup export Excel ------------------------------------------------------- */
html[data-theme="light"] .ptx-xls-check { background: #ffffff; border-color: var(--control-line); }
html[data-theme="light"] .ptx-anim .ptx-xls-row:nth-child(-n+3) .ptx-xls-check {
  animation-name: ptx-check-box-light;
}
@keyframes ptx-check-box-light {
  0%, 4%    { background: #ffffff; border-color: var(--control-line); }
  12%, 72%  { background: var(--brand); border-color: var(--brand); }
  82%, 100% { background: #ffffff; border-color: var(--control-line); }
}
/* Texte clair du sombre (#6ee7a0) illisible sur badge clair -> encre --good-text. */
html[data-theme="light"] .ptx-xls-safe { color: var(--good-text); }
html[data-theme="light"] .ptx-xls-btn {
  background: linear-gradient(180deg, var(--brand-dim), var(--brand-deep));
}

/* --- Mockup photo -> champs ------------------------------------------------------
   La "photo" devient un vrai papier clair (coherent avec l'idee "photo d'une
   note manuscrite") plutot qu'une vitre sombre. */
html[data-theme="light"] .ptx-photo-src {
  background:
    repeating-linear-gradient(180deg, transparent 0 13px, rgba(16, 26, 46, .08) 13px 14px),
    linear-gradient(150deg, #fdfdfb 0%, #f3f0e6 100%);
  background-color: #f3f0e6;
  border-color: var(--border-strong);
}
html[data-theme="light"] .ptx-photo-src::after {
  background: linear-gradient(160deg, rgba(255, 255, 255, .55), transparent 50%);
}
html[data-theme="light"] .ptx-field { background: #ffffff; border-color: var(--control-line); }
html[data-theme="light"] .ptx-field-value { background: rgba(16, 26, 46, .16); }
html[data-theme="light"] .ptx-anim .ptx-field-manual .ptx-field-value { background: rgba(16, 26, 46, .08); }

/* --- Mockup facture --------------------------------------------------------------
   Memes encres d'etat que le reste de l'app (badge lock/tampon "conforme"). */
html[data-theme="light"] .ptx-invoice-lock { color: var(--warning-text); }
html[data-theme="light"] .ptx-stamp { color: var(--good-text); border-color: var(--good-text); }

/* --- Contraste renforce ------------------------------------------------------- */
@media (prefers-contrast: more) {
  html[data-theme="light"] {
    --border: rgba(16, 26, 46, .3);
    --border-strong: rgba(16, 26, 46, .46);
    --text-dim: #1c2740;
    --text-mute: #333f58;
    --text-soft: #2f3b54;
    --control-line: #3a4560;
    --control-line-hover: #2b3550;
  }
}

/* ==========================================================================
   3. HERO "ASTRONOMIQUE" EN CLAIR - nuit -> petit matin
   ========================================================================== */
html[data-theme="light"] .hero { background: var(--page-deep); }
html[data-theme="light"] .hero::before {
  background:
    radial-gradient(760px 460px at 22% 6%, rgba(74, 140, 255, .24), transparent 68%),
    radial-gradient(680px 420px at 84% 22%, rgba(124, 108, 255, .18), transparent 70%),
    radial-gradient(1200px 700px at 50% 108%, rgba(74, 140, 255, .13), transparent 72%);
}
html[data-theme="light"] .hero::after {
  background: linear-gradient(180deg, transparent, var(--page));
}
html[data-theme="light"] .hero-bg::before {
  background-image:
    linear-gradient(to right, rgba(16, 26, 46, .055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(16, 26, 46, .055) 1px, transparent 1px);
}
/* Etoiles : masquees en clair (invisibles de jour). */
html[data-theme="light"] .hero-bg::after { display: none; }
html[data-theme="light"] .hero-curve-arc {
  border-top-color: rgba(74, 140, 255, .5);
  background: radial-gradient(100% 130% at 50% 100%, rgba(74, 140, 255, .16), transparent 62%);
  box-shadow: 0 -24px 70px -30px rgba(74, 140, 255, .44);
}
html[data-theme="light"] [class*="hero-curve"] path {
  stroke: rgba(37, 99, 235, .44);
  filter: drop-shadow(0 0 5px rgba(74, 140, 255, .35));
}
html[data-theme="light"] [class*="hero-curve"] [class*="dot"] {
  fill: var(--brand-dim);
  filter: drop-shadow(0 0 6px rgba(74, 140, 255, .6));
}
html[data-theme="light"] .hero-device::before {
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 140, 255, .3), transparent 72%);
}
html[data-theme="light"] .ptx-mock::before {
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 140, 255, .25), transparent 72%);
}

/* ==========================================================================
   4. CHART.JS
   Valeurs par defaut posees en JS - exposees ici comme tokens, lus au
   moment d'instancier les graphiques (voir templates/dashboard.html).
   ========================================================================== */
html[data-theme="light"] {
  --chart-text: #2e3a52;
  --chart-grid: rgba(16, 26, 46, .13);
  --chart-border: rgba(16, 26, 46, .2);
  --chart-point-border: #ffffff;
  --chart-series-1: #2563eb;
  --chart-series-2: #6b5ce8;
  --chart-series-3: #15803d;
  --chart-series-4: #a16207;
  --chart-series-5: #c02626;
  --chart-series-6: #0e7490;
  --chart-fill-from: rgba(37, 99, 235, .24);
  --chart-fill-to: rgba(37, 99, 235, 0);
  --chart-baseline: rgba(46, 58, 82, .5);
}
/* Contreparties sombres, pour que le JS lise les memes noms partout. */
:root {
  --chart-text: #97a4bb;
  --chart-grid: rgba(255, 255, 255, .05);
  --chart-border: rgba(255, 255, 255, .06);
  --chart-point-border: #111a2b;
  --chart-series-1: #4a8cff;
  --chart-series-2: #7c6cff;
  --chart-series-3: #16a34a;
  --chart-series-4: #f2b01e;
  --chart-series-5: #dc4a4a;
  --chart-series-6: #0e9aa7;
  --chart-fill-from: rgba(74, 140, 255, .33);
  --chart-fill-to: rgba(74, 140, 255, 0);
  --chart-baseline: rgba(148, 163, 184, .35);
}


/* ==========================================================================
   ProfitTrack — SIMULATEUR, CHATBOT, COMPARAISON MARCHÉ, RÉSILIATION
   À APPENDRE à la fin de static/css/style.css, APRÈS le bloc du thème clair.

   Préfixes : .sim- (simulateur) · .chat- (chatbot) · .mkt- (comparaison)
              .bill- (facturation / résiliation)
   Aucune classe existante n'est redéfinie. Aucune couleur en dur : tout passe
   par les tokens, donc les deux thèmes suivent sans règle dupliquée. Les rares
   surcharges html[data-theme="light"] en fin de fichier ne concernent que des
   effets qui ne peuvent pas s'exprimer en token (liserés, halos, ombres).
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tokens partagés par ces quatre composants
   -------------------------------------------------------------------------- */
:root {
  --sim-w: 660px;
  --chat-w: 384px;
  --chat-h: 560px;
  /* Fond des zones de saisie posées dans une carte : plus sombre que
     --surface-2 en sombre, plus clair en clair. Un seul nom, deux valeurs. */
  --field-well: rgba(5, 8, 15, .5);
  /* Piste des jauges et curseurs. */
  --track: rgba(255, 255, 255, .08);
}

/* ==========================================================================
   1. SIMULATEUR DE RENTABILITÉ (.sim-)
   ========================================================================== */

.sim-modal {
  width: min(var(--sim-w), calc(100vw - 2rem));
  max-height: min(88vh, 860px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .06), rgba(255, 255, 255, 0) 180px),
    var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3), 0 0 60px -30px rgba(74, 140, 255, .5);
}

/* --- En-tête ----------------------------------------------------------- */
.sim-head {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: 1.25rem 1.375rem 1.125rem;
  border-bottom: 1px solid var(--border-soft);
}
.sim-head-ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 10px;
  color: #a9c4ff;
  background: var(--brand-soft);
  box-shadow: inset 0 0 0 1px var(--brand-line);
}
.sim-head-txt { flex: 1; min-width: 0; }
.sim-title { font-size: .9375rem; font-weight: 620; letter-spacing: -.014em; line-height: 1.35; }
.sim-sub { font-size: .8125rem; color: var(--text-dim); line-height: 1.45; }
.sim-close {
  flex: none;
  display: grid; place-items: center;
  width: 32px; height: 32px;
  color: var(--text-mute);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.sim-close:hover { color: var(--text); background: var(--surface-hover); }
.sim-close:focus-visible { outline: none; box-shadow: var(--ring); }

/* --- Bascule paiement unique / abonnement ------------------------------
   Même mécanique que la bascule de tarifs de la landing : deux radios
   masqués + sélecteurs frères. Fonctionne sans JS, navigable au clavier
   nativement, et rien à neutraliser en reduced-motion. */
.sim-mode-input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.sim-modes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  margin: 1.125rem 1.375rem .25rem;
  padding: 3px;
  background: var(--field-well);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.sim-mode {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 38px;
  padding: .4375rem .75rem;
  margin: 0;
  font-size: .875rem;
  font-weight: 550;
  letter-spacing: -.01em;
  color: var(--text-dim);
  border-radius: calc(var(--r-md) - 4px);
  cursor: pointer;
  transition: color .2s var(--ease), background-color .2s var(--ease), box-shadow .2s var(--ease);
}
.sim-mode:hover { color: var(--text); }
#sim-mode-once:checked ~ .sim-modes label[for="sim-mode-once"],
#sim-mode-sub:checked  ~ .sim-modes label[for="sim-mode-sub"] {
  color: #fff;
  background: linear-gradient(180deg, var(--brand), var(--brand-dim));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 2px 10px -5px rgba(74, 140, 255, .9);
}
.sim-mode-input:focus-visible ~ .sim-modes { box-shadow: var(--ring); }

/* Bascule des deux panneaux */
.sim-pane { display: none; }
#sim-mode-once:checked ~ .sim-body .sim-pane-once,
#sim-mode-sub:checked  ~ .sim-body .sim-pane-sub { display: grid; }

/* --- Corps -------------------------------------------------------------- */
.sim-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 1.125rem 1.375rem 1.375rem;
}
.sim-pane { gap: 1.25rem; }

.sim-fields { display: grid; gap: .875rem; }
.sim-duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: .875rem; }

.sim-field { display: grid; gap: .375rem; }
.sim-label {
  display: flex;
  align-items: center;
  gap: .375rem;
  font-size: .8125rem;
  font-weight: 550;
  color: var(--text-dim);
}
.sim-label-hint {
  margin-left: auto;
  font-size: .75rem;
  font-weight: 450;
  color: var(--text-mute);
  font-variant-numeric: tabular-nums;
}

/* Champ avec unité collée : l'unité est DANS le cadre, pas à côté —
   le montant et son symbole se lisent comme un seul objet. */
.sim-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
  background: var(--field-well);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .3);
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.sim-input-wrap:hover { border-color: var(--border-strong); }
.sim-input-wrap:focus-within { border-color: var(--brand); box-shadow: inset 0 1px 2px rgba(0, 0, 0, .3), var(--ring); }
.sim-input {
  flex: 1;
  min-width: 0;
  width: 100%;
  min-height: 44px;
  padding: .5rem .75rem;
  font: inherit;
  font-size: 1.0625rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  outline: none;
  -moz-appearance: textfield;
}
.sim-input::-webkit-outer-spin-button,
.sim-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.sim-input::placeholder { color: var(--text-mute); font-weight: 500; }
.sim-unit {
  flex: none;
  padding: 0 .75rem 0 .5rem;
  font-size: .875rem;
  color: var(--text-mute);
  pointer-events: none;
}

/* --- Résultats ---------------------------------------------------------
   La hiérarchie du panneau : UN chiffre en vedette, le reste en lignes. */
.sim-out {
  display: grid;
  gap: .875rem;
  padding: 1.125rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.sim-hero-out {
  display: grid;
  gap: .3125rem;
  padding-bottom: .875rem;
  border-bottom: 1px solid var(--border-soft);
}
.sim-hero-k {
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.sim-hero-v {
  font-size: clamp(1.9rem, 5vw, 2.4rem);
  font-weight: 640;
  letter-spacing: -.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.sim-hero-v.pos { color: var(--good-text); }
.sim-hero-v.neg { color: var(--critical-text); }
.sim-hero-note { font-size: .8125rem; color: var(--text-dim); line-height: 1.45; }

.sim-lines { display: grid; gap: .5rem; }
.sim-line {
  display: flex;
  align-items: baseline;
  gap: .75rem;
  font-size: .875rem;
}
.sim-line dt { flex: 1; min-width: 0; color: var(--text-dim); }
.sim-line dd {
  margin: 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.014em;
  color: var(--text);
}
.sim-line dd.pos { color: var(--good-text); }
.sim-line dd.neg { color: var(--critical-text); }
.sim-line dd.warn { color: var(--warning-text); }
.sim-line dd.dim { color: var(--text-mute); font-weight: 500; }
.sim-line.total {
  padding-top: .625rem;
  margin-top: .125rem;
  border-top: 1px solid var(--border-soft);
}
.sim-line.total dt { color: var(--text); font-weight: 550; }
.sim-line.total dd { font-size: 1rem; }

/* Jauge d'écart à l'objectif : la seule donnée du panneau qui a un SEUIL,
   donc la seule qui mérite une représentation graphique. */
.sim-gauge { display: grid; gap: .4375rem; }
.sim-gauge-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  font-size: .8125rem;
}
.sim-gauge-k { color: var(--text-dim); }
.sim-gauge-v { font-weight: 600; font-variant-numeric: tabular-nums; }
.sim-gauge-v.pos { color: var(--good-text); }
.sim-gauge-v.neg { color: var(--critical-text); }
.sim-gauge-track {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: var(--track);
  overflow: hidden;
}
.sim-gauge-track > i {
  display: block;
  height: 100%;
  width: var(--sim-gauge, 0%);
  border-radius: 3px;
  background: linear-gradient(90deg, var(--brand), var(--nebula));
  transition: width .5s var(--ease);
}
.sim-gauge-track.over > i { background: linear-gradient(90deg, var(--good), #34d399); }
.sim-gauge-track.under > i { background: linear-gradient(90deg, var(--warning), var(--critical)); }
/* Repère de l'objectif */
.sim-gauge-mark {
  position: absolute;
  top: -2px; bottom: -2px;
  left: var(--sim-target, 100%);
  width: 2px;
  background: var(--text-soft);
  border-radius: 1px;
}
/* Porte la valeur de l'objectif : donnée lue, pas décor. */
.sim-gauge-legend { font-size: .75rem; color: var(--text-soft); }

/* --- Pied -------------------------------------------------------------- */
.sim-foot {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .875rem 1.375rem;
  border-top: 1px solid var(--border-soft);
  background: var(--surface);
}
.sim-foot-note { flex: 1; min-width: 0; font-size: .75rem; color: var(--text-soft); line-height: 1.45; }

@media (max-width: 560px) {
  .sim-modal { max-height: 94vh; border-radius: var(--r-md); }
  .sim-head, .sim-body, .sim-foot { padding-inline: 1rem; }
  .sim-modes { margin-inline: 1rem; }
  .sim-foot { flex-wrap: wrap; }
  .sim-foot-note { flex: 1 0 100%; order: 2; }
}

/* ==========================================================================
   2. CHATBOT (.chat-)
   ========================================================================== */

/* --- Bulle flottante ---------------------------------------------------- */
.chat-bubble {
  /* isolation crée un contexte d'empilement ET, avec position, le bloc
     conteneur dont dépendent .chat-dot et le halo .is-calling::after.
     Sans lui, une intégration qui repositionne la bulle (barre d'outils,
     conteneur relatif) détache la pastille vers le coin de la page. */
  position: fixed;
  isolation: isolate;
  right: max(1.25rem, env(safe-area-inset-right));
  bottom: max(1.25rem, env(safe-area-inset-bottom));
  z-index: 90;
  display: grid;
  place-items: center;
  width: 54px; height: 54px;
  color: #fff;
  background: linear-gradient(160deg, var(--brand), var(--brand-deep));
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 50%;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .24),
    0 8px 24px -8px rgba(74, 140, 255, .8),
    0 2px 6px rgba(0, 0, 0, .4);
  transition: transform .22s var(--ease), box-shadow .22s var(--ease);
}
.chat-bubble:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .28),
    0 14px 32px -8px rgba(74, 140, 255, .9),
    0 2px 6px rgba(0, 0, 0, .4);
}
.chat-bubble:active { transform: translateY(0); }
.chat-bubble:focus-visible { outline: none; box-shadow: var(--ring), 0 8px 24px -8px rgba(74, 140, 255, .8); }
.chat-bubble svg { width: 23px; height: 23px; }
/* Les deux icônes se croisent : la fermeture apparaît quand le panneau est ouvert */
.chat-bubble .chat-ico-close { display: none; }
.chat-bubble[aria-expanded="true"] .chat-ico-open { display: none; }
.chat-bubble[aria-expanded="true"] .chat-ico-close { display: block; }

/* Pastille de message non lu */
.chat-dot {
  position: absolute;
  top: 2px; right: 2px;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--critical);
  border: 2px solid var(--page);
}
/* Halo d'appel, lent et discret — trois pulsations puis silence */
@keyframes chat-ping {
  0%   { box-shadow: 0 0 0 0 rgba(74, 140, 255, .5); }
  70%  { box-shadow: 0 0 0 14px rgba(74, 140, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(74, 140, 255, 0); }
}
.chat-bubble.is-calling::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  animation: chat-ping 2.4s var(--ease) 3;
}

/* --- Panneau ------------------------------------------------------------ */
.chat-panel {
  position: fixed;
  right: max(1.25rem, env(safe-area-inset-right));
  bottom: calc(max(1.25rem, env(safe-area-inset-bottom)) + 66px);
  z-index: 91;
  display: flex;
  flex-direction: column;
  width: min(var(--chat-w), calc(100vw - 2rem));
  height: min(var(--chat-h), calc(100vh - 8rem));
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-3), 0 0 50px -24px rgba(74, 140, 255, .45);
  transform-origin: 100% 100%;
  transition: opacity .22s var(--ease), transform .22s var(--ease), visibility .22s;
}
.chat-panel[hidden],
.chat-panel[aria-hidden="true"] {
  display: flex;              /* on anime, donc on ne peut pas display:none */
  visibility: hidden;
  opacity: 0;
  transform: translateY(10px) scale(.97);
  pointer-events: none;
}

.chat-head {
  display: flex;
  align-items: center;
  gap: .625rem;
  flex: none;
  padding: .8125rem .875rem;
  border-bottom: 1px solid var(--border-soft);
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .09), rgba(255, 255, 255, 0)),
    var(--surface-2);
}
.chat-avatar {
  flex: none;
  position: relative;
  display: grid; place-items: center;
  width: 32px; height: 32px;
  border-radius: 10px;
  color: #fff;
  background: linear-gradient(155deg, var(--brand), var(--nebula));
  box-shadow: 0 2px 8px -2px rgba(74, 140, 255, .7);
}
.chat-avatar svg { width: 17px; height: 17px; }
.chat-head-txt { flex: 1; min-width: 0; }
.chat-name { font-size: .875rem; font-weight: 600; letter-spacing: -.014em; line-height: 1.3; }
.chat-status {
  font-size: .75rem;
  color: var(--text-soft);
}
.chat-head-btn {
  flex: none;
  display: grid; place-items: center;
  width: 30px; height: 30px;
  color: var(--text-mute);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color .18s var(--ease), background-color .18s var(--ease);
}
.chat-head-btn:hover { color: var(--text); background: var(--surface-hover); }
.chat-head-btn:focus-visible { outline: none; box-shadow: var(--ring); }

/* --- Fil ---------------------------------------------------------------- */
.chat-log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: .625rem;
  padding: .875rem;
  background: var(--pd-sunken);
}
.chat-msg {
  max-width: 84%;
  padding: .5625rem .75rem;
  font-size: .875rem;
  line-height: 1.5;
  border-radius: var(--r-md);
  overflow-wrap: anywhere;
}
.chat-msg p { margin: 0; }
.chat-msg p + p { margin-top: .5rem; }
.chat-msg.from-bot {
  align-self: flex-start;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-bottom-left-radius: var(--r-sm);
}
.chat-msg.from-user {
  align-self: flex-end;
  color: #fff;
  background: linear-gradient(165deg, var(--brand), var(--brand-dim));
  border: 1px solid rgba(255, 255, 255, .14);
  border-bottom-right-radius: var(--r-sm);
}
.chat-msg.from-bot.is-error { color: var(--critical-text); border-color: var(--critical-text); }
.chat-when {
  align-self: center;
  padding: .125rem .5rem;
  font-size: .6875rem;
  color: var(--text-soft);
  background: var(--surface);
  border-radius: var(--r-full);
}
/* Entrée d'un message : courte, une seule fois */
@keyframes chat-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.chat-msg { animation: chat-in .24s var(--ease-out) both; }

/* Indicateur « en train d'écrire » */
.chat-thinking {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: .3125rem;
  padding: .625rem .75rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  border-bottom-left-radius: var(--r-sm);
}
.chat-thinking[hidden] { display: none; }
.chat-thinking i {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-mute);
  animation: chat-blip 1.3s var(--ease) infinite;
}
.chat-thinking i:nth-child(2) { animation-delay: .18s; }
.chat-thinking i:nth-child(3) { animation-delay: .36s; }
@keyframes chat-blip {
  0%, 60%, 100% { opacity: .35; transform: translateY(0); }
  30%           { opacity: 1;   transform: translateY(-3px); }
}

/* Suggestions de démarrage */
.chat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
  padding: 0 .875rem .625rem;
}
.chat-chip {
  padding: .375rem .625rem;
  font: inherit;
  font-size: .75rem;
  font-weight: 500;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-full);
  cursor: pointer;
  transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.chat-chip:hover { color: var(--text); background: var(--surface-hover); border-color: var(--brand-line); }
.chat-chip:focus-visible { outline: none; box-shadow: var(--ring); }

/* --- Saisie ------------------------------------------------------------- */
.chat-compose {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: .5rem;
  padding: .625rem .75rem calc(.625rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border-soft);
  background: var(--surface);
}
.chat-input {
  flex: 1;
  min-width: 0;
  min-height: 40px;
  max-height: 132px;
  padding: .5625rem .75rem;
  font: inherit;
  font-size: .875rem;
  line-height: 1.45;
  color: var(--text);
  background-color: var(--field-well);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  resize: none;
  outline: none;
  overflow-y: auto;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
.chat-input::placeholder { color: var(--text-mute); }
.chat-input:focus { border-color: var(--brand); box-shadow: var(--ring); }
.chat-send {
  flex: none;
  display: grid; place-items: center;
  width: 40px; height: 40px;
  color: #fff;
  background: linear-gradient(165deg, var(--brand), var(--brand-dim));
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--r-md);
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 2px 8px -3px rgba(74, 140, 255, .8);
  transition: filter .18s var(--ease), opacity .18s var(--ease);
}
.chat-send:hover { filter: brightness(1.08); }
.chat-send:focus-visible { outline: none; box-shadow: var(--ring); }
.chat-send:disabled { opacity: .45; cursor: not-allowed; filter: none; }
.chat-send svg { width: 17px; height: 17px; }
/* Avertissement sur la fiabilité de l'assistant : doit être lu. */
.chat-legal { padding: 0 .75rem .625rem; font-size: .6875rem; color: var(--text-soft); text-align: center; }

@media (max-width: 480px) {
  .chat-panel {
    right: .5rem; left: .5rem;
    bottom: calc(max(.75rem, env(safe-area-inset-bottom)) + 62px);
    width: auto;
    height: min(var(--chat-h), calc(100vh - 6rem));
  }
}

/* ==========================================================================
   3. COMPARAISON AU MARCHÉ (.mkt-)
   ========================================================================== */

.mkt-btn {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .4375rem .75rem;
  min-height: 36px;
  font: inherit;
  font-size: .8125rem;
  font-weight: 550;
  color: var(--text-dim);
  background: transparent;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
.mkt-btn:hover { color: #a9c4ff; background: var(--brand-soft); border-color: var(--brand-line); }
.mkt-btn:focus-visible { outline: none; box-shadow: var(--ring); }
.mkt-btn:disabled { opacity: .55; cursor: progress; }
.mkt-btn svg { width: 14px; height: 14px; flex: none; }
/* Rotation pendant la recherche */
@keyframes mkt-spin { to { transform: rotate(360deg); } }
.mkt-btn.is-loading svg { animation: mkt-spin .9s linear infinite; }

.mkt-result {
  display: grid;
  gap: .75rem;
  margin-top: .75rem;
  padding: 1rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  animation: chat-in .3s var(--ease-out) both;
}
.mkt-head {
  display: flex;
  align-items: baseline;
  gap: .625rem;
  flex-wrap: wrap;
}
.mkt-verdict { font-size: .9375rem; font-weight: 620; letter-spacing: -.014em; }
.mkt-verdict.low  { color: var(--warning-text); }
.mkt-verdict.fair { color: var(--good-text); }
.mkt-verdict.high { color: var(--brand); }
.mkt-delta {
  margin-left: auto;
  font-size: .8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}

/* Échelle : la fourchette du marché, et votre prix posé dessus.
   --mkt-lo / --mkt-hi / --mkt-you sont des pourcentages posés en JS. */
.mkt-scale {
  position: relative;
  height: 8px;
  margin: .625rem 0 1.25rem;
  border-radius: 4px;
  background: var(--track);
}
.mkt-band {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--mkt-lo, 30%);
  right: calc(100% - var(--mkt-hi, 70%));
  border-radius: 4px;
  background: linear-gradient(90deg, var(--brand-soft), rgba(74, 140, 255, .3));
  box-shadow: inset 0 0 0 1px var(--brand-line);
}
.mkt-you {
  position: absolute;
  top: 50%;
  left: var(--mkt-you, 50%);
  width: 3px; height: 20px;
  transform: translate(-50%, -50%);
  border-radius: 2px;
  background: var(--text);
  box-shadow: 0 0 0 3px var(--surface-2), 0 2px 6px rgba(0, 0, 0, .5);
  z-index: 2;
}
.mkt-you::after {
  content: attr(data-label);
  position: absolute;
  top: calc(100% + .375rem);
  left: 50%;
  transform: translateX(-50%);
  font-size: .6875rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Repere mediane sur la barre elle-meme (pas seulement en texte dans
   l'en-tete) - un point plus discret que .mkt-you (qui marque VOTRE prix)
   pour rester lisible meme quand les deux tombent pres l'un de l'autre. */
.mkt-median {
  position: absolute;
  top: 50%;
  width: 8px; height: 8px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 2px var(--brand);
  z-index: 1;
}
/* Trois reperes (basse/mediane/haute) positionnes a leur vraie valeur sur
   l'echelle, jamais aux deux bouts seulement : la mediane n'est pas
   forcement au milieu de [lo, hi], donc jamais centree par un simple
   flex/justify-content comme lo et hi seuls auraient pu l'etre. */
.mkt-ticks {
  position: relative;
  height: 1rem;
  font-size: .6875rem;
  color: var(--text-soft);
  font-variant-numeric: tabular-nums;
}
.mkt-tick {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}
.mkt-tick-lo { transform: translateX(0); }
.mkt-tick-hi { transform: translateX(-100%); }
.mkt-tick-median {
  color: var(--brand);
  font-weight: 600;
}

.mkt-note { font-size: .8125rem; line-height: 1.55; color: var(--text-dim); margin: 0; }
.mkt-sources { display: grid; gap: .3125rem; padding-top: .625rem; border-top: 1px solid var(--border-soft); }
.mkt-sources-k { font-size: .6875rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-soft); }
.mkt-source {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  font-size: .75rem;
  color: var(--text-mute);
}
.mkt-source a { color: var(--text-dim); }
.mkt-source a:hover { color: var(--brand); }
.mkt-sources-toggle {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .1875rem;
  background: rgba(255, 255, 255, .04);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  padding: .375rem .625rem;
  margin: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: color .18s var(--ease), background-color .18s var(--ease), border-color .18s var(--ease);
}
/* Avant ce style, seul un "▾" discret dans le texte signalait que la
   ligne est cliquable - repose sur :hover, donc invisible au toucher.
   Sur mobile ca rendait les sources reperees comme absentes alors
   qu'elles etaient juste repliees sans aucun indice qu'il fallait taper
   dessus (signale par l'utilisateur - meme comparaison, sources visibles
   sur desktop). Le cadre en tirets + fond, deja utilise par .mkt-btn plus
   haut, rend la zone tapable evidente sans dependre du survol. */
.mkt-sources-toggle:hover,
.mkt-sources-toggle:focus-visible { background: var(--brand-soft); border-color: var(--brand-line); }
.mkt-sources-toggle:focus-visible { outline: none; box-shadow: var(--ring); }
.mkt-sources-toggle:hover .mkt-source,
.mkt-sources-toggle:focus-visible .mkt-source { color: var(--brand); }
.mkt-sources-list {
  display: grid;
  gap: .25rem;
  margin: .375rem 0 0;
  padding-left: 1rem;
  list-style: disc;
}
.mkt-sources-list li { font-size: .75rem; }
.mkt-sources-list a {
  color: var(--text-dim);
  text-decoration: none;
  word-break: break-word;
}
.mkt-sources-list a:hover { color: var(--brand); text-decoration: underline; }
/* Obligatoire dans tous les rendus, donc lisible : --text-soft. */
.mkt-disclaimer { font-size: .6875rem; color: var(--text-soft); line-height: 1.5; margin: 0; }

/* Paragraphe editable de l'email de relance (voir reminderModal,
   /remind_client/<id>/preview) - tokens uniquement, s'adapte donc au theme
   clair/sombre sans bloc dedie. */
.rem-ta {
  width: 100%;
  resize: vertical;
  font: inherit;
  font-size: .8125rem;
  line-height: 1.4;
  padding: .5rem .625rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
}
.rem-ta:hover { border-color: var(--border-strong); }
.rem-ta:focus { border-color: var(--brand); outline: none; }
.rem-ta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .25rem;
}
.rem-ta-reset {
  font-size: .71875rem;
  padding: .125rem .5rem;
}

/* État vide / erreur : ton neutre, jamais alarmant */
.mkt-empty {
  margin-top: .75rem;
  padding: .875rem 1rem;
  font-size: .8125rem;
  line-height: 1.55;
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
}

/* ==========================================================================
   4. FACTURATION / RÉSILIATION (.bill-)
   Le ton est le sujet : rien de rouge tant que l'utilisateur n'a pas
   confirmé, et même après, on informe — on n'alarme pas.
   ========================================================================== */

.bill-state {
  display: flex;
  align-items: flex-start;
  gap: .875rem;
  padding: 1rem 1.125rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.bill-state-ico {
  flex: none;
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border-radius: 10px;
}
.bill-state-txt { flex: 1; min-width: 0; display: grid; gap: .1875rem; }
.bill-state-k { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-size: .9375rem; font-weight: 600; letter-spacing: -.014em; }
.bill-state-v { font-size: .8125rem; color: var(--text-dim); line-height: 1.5; }
.bill-state-v b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Actif */
.bill-state.active .bill-state-ico { color: var(--good-text); background: var(--good-soft); box-shadow: inset 0 0 0 1px rgba(22, 163, 74, .3); }
/* En cours d'annulation : ambre, pas rouge — l'accès n'est pas encore perdu */
.bill-state.ending { border-color: rgba(242, 176, 30, .3); }
.bill-state.ending .bill-state-ico { color: var(--warning-text); background: var(--warning-soft); box-shadow: inset 0 0 0 1px rgba(242, 176, 30, .3); }
/* Résilié : neutre, pas critique — c'est un état, pas une erreur */
.bill-state.ended .bill-state-ico { color: var(--text-dim); background: rgba(255, 255, 255, .05); box-shadow: inset 0 0 0 1px var(--border-strong); }

.bill-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .875rem;
}
.bill-actions .sp { flex: 1 1 auto; }
/* Le lien d'annulation est volontairement discret : ce n'est pas une action
   qu'on met en avant, mais elle ne doit pas non plus être cachée. */
.bill-cancel {
  padding: .375rem .5rem;
  font: inherit;
  font-size: .8125rem;
  /* Contrôle interactif : 4,5:1 minimum dans les DEUX thèmes. La discrétion
     vient du soulignement fin et de l'absence de cadre, jamais d'un gris
     illisible — une action qu'on n'encourage pas doit rester lisible. */
  color: var(--text-soft);
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--border-strong);
  transition: color .18s var(--ease), text-decoration-color .18s var(--ease);
}
.bill-cancel:hover { color: var(--text); text-decoration-color: var(--text-mute); }
.bill-cancel:focus-visible { outline: none; box-shadow: var(--ring); }

.bill-confirm { display: grid; gap: 1rem; }
.bill-confirm-lede { font-size: .9375rem; line-height: 1.6; color: var(--text-dim); margin: 0; }
.bill-keep {
  display: grid;
  gap: .5rem;
  padding: .875rem 1rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.bill-keep-k { font-size: .8125rem; font-weight: 600; color: var(--text); }
.bill-keep-list { display: grid; gap: .375rem; margin: 0; padding: 0; list-style: none; }
.bill-keep-list li {
  display: grid;
  grid-template-columns: 14px 1fr;
  gap: .5rem;
  align-items: start;
  font-size: .8125rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.bill-keep-list li::before {
  content: "";
  width: 14px; height: 14px;
  margin-top: .125rem;
  border-radius: 50%;
  background-color: var(--good-soft);
  box-shadow: inset 0 0 0 1px rgba(22, 163, 74, .34);
  background-image:
    linear-gradient(45deg, transparent 44%, var(--good-text) 44%, var(--good-text) 56%, transparent 56%),
    linear-gradient(-45deg, transparent 52%, var(--good-text) 52%, var(--good-text) 64%, transparent 64%);
  background-size: 6px 2px, 3.5px 2px;
  background-position: 6.5px 7.5px, 4px 8.5px;
  background-repeat: no-repeat;
}

/* ==========================================================================
   5. SURCHARGES DU THÈME CLAIR
   Uniquement ce qui ne peut pas s'exprimer en token : liserés blancs,
   ombres colorées, fonds translucides. Tout le reste suit les tokens.
   ========================================================================== */
html[data-theme="light"] {
  --field-well: #ffffff;
  --track: rgba(16, 26, 46, .1);
}

/* Simulateur */
html[data-theme="light"] .sim-modal {
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .05), rgba(255, 255, 255, 0) 180px),
    var(--surface);
  box-shadow: var(--sh-3), 0 0 50px -26px rgba(74, 140, 255, .3);
}
html[data-theme="light"] .sim-head-ico { color: var(--brand-dim); }
html[data-theme="light"] .sim-modes { background: rgba(16, 26, 46, .04); }
html[data-theme="light"] #sim-mode-once:checked ~ .sim-modes label[for="sim-mode-once"],
html[data-theme="light"] #sim-mode-sub:checked  ~ .sim-modes label[for="sim-mode-sub"] {
  background: linear-gradient(180deg, var(--brand-dim), var(--brand-deep));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 2px 10px -5px rgba(37, 99, 235, .6);
}
html[data-theme="light"] .sim-input-wrap {
  border-color: var(--control-line);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .06);
}
html[data-theme="light"] .sim-input-wrap:hover { border-color: var(--control-line-hover); }
html[data-theme="light"] .sim-input-wrap:focus-within {
  border-color: var(--brand-dim);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .06), var(--ring);
}
html[data-theme="light"] .sim-close:hover { background: rgba(16, 26, 46, .05); }
html[data-theme="light"] .sim-gauge-track.over > i { background: linear-gradient(90deg, var(--good), #2f9e5b); }

/* Chatbot */
html[data-theme="light"] .chat-bubble {
  background: linear-gradient(160deg, var(--brand-dim), var(--brand-deep));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .24),
    0 8px 22px -8px rgba(37, 99, 235, .55),
    0 2px 6px rgba(16, 26, 46, .18);
}
html[data-theme="light"] .chat-bubble:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .28),
    0 14px 30px -8px rgba(37, 99, 235, .6),
    0 2px 6px rgba(16, 26, 46, .18);
}
html[data-theme="light"] .chat-panel { box-shadow: var(--sh-3), 0 0 40px -22px rgba(37, 99, 235, .3); }
html[data-theme="light"] .chat-head {
  background:
    linear-gradient(180deg, rgba(74, 140, 255, .07), rgba(255, 255, 255, 0)),
    var(--surface-2);
}
html[data-theme="light"] .chat-avatar { box-shadow: 0 2px 8px -2px rgba(37, 99, 235, .5); }
html[data-theme="light"] .chat-msg.from-user {
  background: linear-gradient(165deg, var(--brand-dim), var(--brand-deep));
  border-color: rgba(255, 255, 255, .18);
}
html[data-theme="light"] .chat-head-btn:hover { background: rgba(16, 26, 46, .05); }
html[data-theme="light"] .chat-input {
  border-color: var(--control-line);
  box-shadow: inset 0 1px 2px rgba(16, 26, 46, .06);
}
html[data-theme="light"] .chat-input:focus { border-color: var(--brand-dim); box-shadow: inset 0 1px 2px rgba(16, 26, 46, .06), var(--ring); }
html[data-theme="light"] .chat-send {
  background: linear-gradient(165deg, var(--brand-dim), var(--brand-deep));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22), 0 2px 8px -3px rgba(37, 99, 235, .55);
}
html[data-theme="light"] .chat-chip { border-color: var(--control-line); }

/* Comparaison marché */
html[data-theme="light"] .mkt-btn { border-color: var(--control-line); }
html[data-theme="light"] .mkt-btn:hover { color: var(--brand-dim); border-color: var(--brand-line); }
html[data-theme="light"] .mkt-verdict.high { color: var(--brand-dim); }
html[data-theme="light"] .mkt-band { background: linear-gradient(90deg, var(--brand-soft), rgba(74, 140, 255, .28)); }
html[data-theme="light"] .mkt-you { box-shadow: 0 0 0 3px var(--surface-2), 0 2px 6px rgba(16, 26, 46, .22); }
html[data-theme="light"] .mkt-empty { border-color: var(--control-line); }

/* Facturation */
html[data-theme="light"] .bill-state.ended .bill-state-ico {
  background: rgba(16, 26, 46, .05);
  box-shadow: inset 0 0 0 1px var(--border-strong);
}

/* ==========================================================================
   6. ACCESSIBILITÉ
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .chat-msg,
  .mkt-result { animation: none !important; }
  .chat-thinking i { animation: none !important; opacity: .6; }
  .chat-bubble.is-calling::after { animation: none !important; }
  .mkt-btn.is-loading svg { animation: none !important; }
  .chat-panel,
  .sim-gauge-track > i,
  .chat-bubble { transition: none !important; }
  .chat-panel[hidden],
  .chat-panel[aria-hidden="true"] { transform: none; }
}

@media (prefers-contrast: more) {
  .sim-input-wrap,
  .chat-input,
  .chat-chip { border-color: var(--text-mute); }
  .chat-msg.from-bot,
  .sim-out,
  .mkt-result { border-color: var(--border-strong); }
}

/* ==========================================================================
   BILANS (.bl-) — handoff Claude Design 2026-08-15, doc/design_handoff_bilans/
   Aucune couleur nouvelle : --sunken de la maquette de reference correspond
   au token --pd-sunken deja existant dans cette feuille (substitue partout
   ci-dessous). --brand-text/--sh-card/--sh-lift ajoutes plus haut (:root et
   html[data-theme="light"]), aucun equivalent existant a reutiliser.
   ========================================================================== */
.bl{color:var(--text)}
.bl-head{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;
  flex-wrap:wrap;margin-bottom:26px}
.bl-h1{margin:0;font-size:1.75rem;font-weight:700;letter-spacing:-.03em}
.bl-sub{margin:.375rem 0 0;max-width:46ch;font-size:.875rem;line-height:1.55;color:var(--text-soft)}

/* --- Le fil de tendance : la reponse a « ou j'en suis » avant toute lecture --- */
.bl-spark{display:flex;flex-direction:column;gap:8px;padding:12px 14px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg)}
.bl-spark-body{display:flex;align-items:flex-end;gap:6px;height:52px}
.bl-spark-col{position:relative;display:flex;align-items:flex-end;width:14px;height:100%;
  border-radius:var(--r-sm);background:var(--pd-sunken)}
.bl-spark-col>i{display:block;width:100%;border-radius:var(--r-sm);
  background:var(--surface-3);transition:background-color .18s var(--ease)}
html[data-theme="light"] .bl-spark-col>i{background:#c3cee0}
.bl-spark-col.is-month>i{background:var(--brand-line)}
.bl-spark-col.is-current>i{background:var(--brand)}
.bl-spark-l{font-size:.6875rem;letter-spacing:.04em;text-transform:uppercase;color:var(--text-mute)}

/* --- Groupes de mois --- */
.bl-month{margin:0 0 12px;font-size:.75rem;font-weight:650;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-mute)}
.bl-month + .bl-group{margin-bottom:30px}
.bl-group{display:flex;flex-direction:column}

/* --- Une entree = un rail + une carte. Le rail est ce qui fait une
       chronologie plutot qu'une pile de lignes identiques. --- */
.bl-item{position:relative;display:grid;grid-template-columns:26px 1fr;gap:14px;
  padding-bottom:14px}
.bl-item:last-child{padding-bottom:0}
.bl-rail{position:relative;display:flex;justify-content:center}
.bl-rail::before{content:"";position:absolute;top:0;bottom:-14px;width:2px;
  background:var(--border);border-radius:1px}
.bl-item:last-child .bl-rail::before{bottom:auto;height:26px}
.bl-node{position:relative;z-index:1;margin-top:22px;width:9px;height:9px;border-radius:50%;
  background:var(--surface-3);box-shadow:0 0 0 4px var(--page)}
html[data-theme="light"] .bl-node{background:#b7c3d6}

.bl-card{display:block;padding:16px 18px;text-decoration:none;color:inherit;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  box-shadow:var(--sh-card);
  transition:border-color .18s var(--ease),background-color .18s var(--ease),
    transform .18s var(--ease),box-shadow .18s var(--ease)}
.bl-card:hover{background:var(--surface-hover);border-color:var(--border-strong);
  transform:translateY(-1px);box-shadow:var(--sh-lift)}
.bl-card:focus-visible{outline:none;box-shadow:var(--sh-card),var(--ring)}
.bl-card-h{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.bl-kind{font-size:.6875rem;font-weight:650;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-mute)}
.bl-when{font-size:.875rem;font-weight:600;letter-spacing:-.012em;color:var(--text-dim)}
.bl-tag{margin-left:auto;padding:2px 8px;font-size:.6875rem;font-weight:600;
  color:var(--brand-text);background:var(--brand-soft);border:1px solid var(--brand-line);
  border-radius:var(--r-full)}

.bl-figs{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.bl-profit{font-size:1.5rem;font-weight:700;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--text)}
.bl-profit-lbl{font-size:.8125rem;color:var(--text-mute)}
.bl-delta{margin-left:auto;display:inline-flex;align-items:center;gap:4px;
  padding:3px 9px;font-size:.75rem;font-weight:650;font-variant-numeric:tabular-nums;
  border-radius:var(--r-full);border:1px solid transparent;white-space:nowrap}
.bl-delta.pos{color:var(--good-text);background:var(--good-soft);border-color:rgba(22,163,74,.3)}
.bl-delta.neg{color:var(--critical-text);background:var(--critical-soft);border-color:rgba(220,74,74,.3)}
.bl-delta--lg{margin-left:0;padding:5px 12px;font-size:.9375rem}

/* La barre proportionnelle au profit : lues de haut en bas, les entrees
   dessinent la courbe de l'activite. C'est l'organisation ENTRE les bilans. */
.bl-bar{height:4px;margin:12px 0 10px;background:var(--pd-sunken);border-radius:var(--r-full);
  overflow:hidden}
.bl-bar>i{display:block;height:100%;border-radius:var(--r-full);background:var(--surface-3)}
html[data-theme="light"] .bl-bar>i{background:#b7c3d6}
.bl-item.is-current .bl-bar>i{background:var(--brand)}
.bl-meta{font-size:.8125rem;color:var(--text-soft);font-variant-numeric:tabular-nums}
.bl-none{color:var(--text-mute);font-style:italic}

/* --- Le bilan mensuel : plus lourd, c'est le recap qui compte ce dimanche-la. --- */
.bl-item--month .bl-node{width:15px;height:15px;margin-top:19px;background:var(--brand);
  box-shadow:0 0 0 4px var(--page),0 0 0 6px var(--brand-soft)}
.bl-item--month .bl-card{padding:20px 22px;background:
  linear-gradient(150deg,var(--brand-soft),transparent 58%),var(--surface);
  border-color:var(--brand-line)}
.bl-item--month .bl-kind{color:var(--brand-text)}
.bl-item--month .bl-profit{font-size:1.875rem}
.bl-item--month .bl-bar>i{background:var(--brand-dim)}

/* ==========================================================================
   DETAIL
   ========================================================================== */
.bl-detail{display:flex;flex-direction:column;gap:22px}
.bl-dhead{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap}
.bl-dtitle{margin:.25rem 0 0;font-size:1.5rem;font-weight:700;letter-spacing:-.03em}
.bl-dnav{font-size:.8125rem;color:var(--text-mute)}

/* Etage 1 — un seul chiffre domine la page. */
.bl-key{display:grid;grid-template-columns:1.15fr 1fr;gap:1px;
  background:var(--border);border:1px solid var(--border);border-radius:var(--r-xl);
  overflow:hidden;box-shadow:var(--sh-card)}
.bl-key-main{padding:22px 24px;background:
  linear-gradient(160deg,var(--brand-soft),transparent 62%),var(--surface)}
.bl-key-cmp{padding:22px 24px;background:var(--surface)}
.bl-key-l{font-size:.75rem;font-weight:650;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-mute)}
.bl-key-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:8px}
.bl-key-v{font-size:2.75rem;font-weight:750;line-height:1;letter-spacing:-.04em;
  font-variant-numeric:tabular-nums}
.bl-key-sub{display:block;margin-top:10px;font-size:.875rem;color:var(--text-soft);
  font-variant-numeric:tabular-nums}
.bl-margin{height:6px;margin-top:12px;background:var(--pd-sunken);border-radius:var(--r-full);
  overflow:hidden}
.bl-margin>i{display:block;height:100%;border-radius:var(--r-full);background:var(--brand)}
.bl-cmp-t{font-size:.75rem;font-weight:650;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-mute)}
.bl-cmp-row{display:flex;align-items:center;gap:10px;padding:9px 0;
  border-bottom:1px solid var(--border-soft);font-size:.875rem;color:var(--text-dim)}
.bl-cmp-row:first-of-type{margin-top:6px}
.bl-cmp-row b{margin-left:auto;font-size:1rem;font-weight:650;color:var(--text);
  font-variant-numeric:tabular-nums}
.bl-cmp-row .bl-delta{margin-left:0;min-width:76px;justify-content:center}
.bl-cmp-note{margin:12px 0 0;font-size:.8125rem;line-height:1.55;color:var(--text-mute)}

/* Etage 2 — sections et tuiles. */
.bl-sec{padding:18px 20px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);box-shadow:var(--sh-card)}
.bl-sec--muted{background:transparent;box-shadow:none;border-style:dashed}
.bl-sec-h{display:flex;align-items:baseline;gap:12px;margin-bottom:14px;
  padding-bottom:12px;border-bottom:1px solid var(--border-soft)}
.bl-sec-h h3{margin:0;font-size:1rem;font-weight:650;letter-spacing:-.02em}
.bl-sec-h span{margin-left:auto;font-size:.8125rem;color:var(--text-mute)}
.bl-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.bl-tile{display:flex;flex-direction:column;gap:6px;padding:14px;
  background:var(--surface-2);border:1px solid var(--border-soft);border-radius:var(--r-md)}
.bl-tile-l{font-size:.75rem;color:var(--text-mute);line-height:1.3}
.bl-tile-v{font-size:1.125rem;font-weight:650;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;color:var(--text)}
/* Les deux tuiles de tete de chaque section : memes tuiles, plus de poids. */
.bl-tile.is-lead{background:var(--surface-3);border-color:var(--border)}
.bl-tile.is-lead .bl-tile-v{font-size:1.375rem;font-weight:700}
.bl-tile.is-alert .bl-tile-v{color:var(--warning-text)}
.bl-tile.is-empty .bl-tile-v{font-size:.9375rem;font-weight:500}

.bl-alert{display:flex;align-items:center;gap:12px;margin-top:14px;padding:12px 14px;
  text-decoration:none;font-size:.875rem;color:var(--text-dim);
  background:var(--warning-soft);border:1px solid rgba(242,176,30,.3);
  border-radius:var(--r-md);transition:background-color .18s var(--ease)}
.bl-alert:hover{background:rgba(242,176,30,.2)}
.bl-alert-b{flex:none;padding:3px 9px;font-size:.75rem;font-weight:650;
  color:var(--warning-text);background:var(--warning-soft);
  border:1px solid rgba(242,176,30,.34);border-radius:var(--r-full)}
.bl-alert-go{margin-left:auto;font-size:1.25rem;color:var(--text-mute)}
.bl-note{margin:12px 0 0;font-size:.8125rem;line-height:1.55;color:var(--text-mute)}

/* --------------------------------------------------------------------------
   Etroit : 2 colonnes maximum, comme les tuiles KPI du dashboard.
   Pilote par la largeur du CONTENEUR, pas du viewport : la meme liste vit
   dans une page web large et dans un ecran de 390 px.
   -------------------------------------------------------------------------- */
.bl-page{container-type:inline-size;container-name:bl}
@container bl (max-width:720px){
  .bl-key{grid-template-columns:1fr}
  .bl-grid{grid-template-columns:repeat(2,1fr);gap:10px}
  .bl-key-v{font-size:2.25rem}
}
@container bl (max-width:440px){
  .bl-head{flex-direction:column;gap:16px}
  .bl-spark{width:100%}
  .bl-spark-body{gap:8px}
  .bl-spark-col{flex:1;width:auto}
  .bl-item{grid-template-columns:20px 1fr;gap:10px}
  .bl-card,.bl-item--month .bl-card{padding:14px}
  .bl-key-main,.bl-key-cmp{padding:16px}
  .bl-sec{padding:14px}
  .bl-profit{font-size:1.375rem}
  .bl-item--month .bl-profit{font-size:1.625rem}
  .bl-figs{gap:6px}
  .bl-delta{margin-left:0}
  .bl-key-v{font-size:2rem}
  .bl-tile{padding:11px}
  .bl-tile-v{font-size:1rem}
  .bl-tile.is-lead .bl-tile-v{font-size:1.1875rem}
  .bl-alert{flex-wrap:wrap}
  .bl-alert-go{display:none}
}
/* Repli sans container queries : le meme seuil en viewport. */
@supports not (container-type:inline-size){
  @media (max-width:760px){
    .bl-key{grid-template-columns:1fr}
    .bl-grid{grid-template-columns:repeat(2,1fr)}
  }
}
@media (prefers-reduced-motion:reduce){
  .bl-card{transition:none}
  .bl-card:hover{transform:none}
}

/* ==========================================================================
   REORGANISATION VISUELLE (passe 1) — dashboard en 3 zones + navigation
   Prefixes : .dz-* (zones du tableau de bord), .nv-* (barre laterale web +
   profil mobile), .pg-* (en-tetes de page communs). Aucune regle ne cible
   .card/.btn/.row ni aucune classe courte. Aucun selecteur ne touche
   .stat-tile/.dash-widget/.kpi-grid/#kpiGrid/[data-item-id]/.drag-handle :
   le systeme de personnalisation existant continue de fonctionner a
   l'identique a l'interieur des zones. --dz-rail/--brand-text : voir :root
   plus haut dans ce fichier (poses une seule fois, section 1).
   ========================================================================== */

/* ---- §1 · En-tete de page — commun a toutes les pages de l'app ---- */
.pg-head{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;
  flex-wrap:wrap;margin-bottom:26px}
.pg-titles{min-width:0}
.pg-h1{margin:0;font-size:1.75rem;font-weight:700;letter-spacing:-.03em;color:var(--text)}
.pg-sub{margin:.375rem 0 0;max-width:52ch;font-size:.875rem;line-height:1.55;
  color:var(--text-soft)}
.pg-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}

/* ---- §2 · Zones du tableau de bord ---- */
.dz{display:flex;flex-direction:column;gap:30px;container-type:inline-size;
  container-name:dz}

.dz-zone{display:flex;flex-direction:column;gap:14px}
.dz-zone-h{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.dz-zone-t{margin:0;font-size:.75rem;font-weight:650;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-mute)}
.dz-zone-q{font-size:.8125rem;color:var(--text-mute);opacity:.85}
.dz-zone-tools{margin-left:auto;display:flex;align-items:center;gap:8px}

/* Zone 1 « L'essentiel » : 1 chiffre dominant + 3 satellites, un seul bloc. */
.dz-lead{display:grid;grid-template-columns:1.25fr 1fr;gap:1px;
  background:var(--dz-rail);border:1px solid var(--border);
  border-radius:var(--r-xl);overflow:hidden;box-shadow:var(--sh-card)}

.dz-hero{padding:24px 26px;background:
  linear-gradient(158deg,var(--brand-soft),transparent 62%),var(--surface)}
.dz-hero-l{display:block;font-size:.75rem;font-weight:650;letter-spacing:.07em;
  text-transform:uppercase;color:var(--text-mute)}
.dz-hero-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:10px}
.dz-hero-v{font-size:2.75rem;font-weight:750;line-height:1;letter-spacing:-.04em;
  font-variant-numeric:tabular-nums;color:var(--text)}
.dz-hero-sub{display:block;margin-top:10px;font-size:.875rem;color:var(--text-soft);
  font-variant-numeric:tabular-nums}
.dz-hero-bar{height:6px;margin-top:14px;background:var(--pd-sunken);
  border-radius:var(--r-full);overflow:hidden}
.dz-hero-bar>i{display:block;height:100%;border-radius:var(--r-full);
  background:var(--brand)}

.dz-sats{display:flex;flex-direction:column;background:var(--dz-rail);gap:1px}
.dz-sat{display:flex;align-items:baseline;gap:12px;padding:14px 22px;flex:1;
  background:var(--surface)}
.dz-sat-l{font-size:.8125rem;color:var(--text-mute)}
.dz-sat-v{margin-left:auto;font-size:1.25rem;font-weight:650;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;color:var(--text)}
.dz-sat-d{min-width:74px;justify-content:center}
.dz-sat.is-warn .dz-sat-v{color:var(--warning-text)}
.dz-sat.is-bad  .dz-sat-v{color:var(--critical-text)}

.dz-delta{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;
  font-size:.75rem;font-weight:650;font-variant-numeric:tabular-nums;
  border:1px solid transparent;border-radius:var(--r-full);white-space:nowrap}
.dz-delta.pos{color:var(--good-text);background:var(--good-soft);
  border-color:rgba(22,163,74,.3)}
.dz-delta.neg{color:var(--critical-text);background:var(--critical-soft);
  border-color:rgba(220,74,74,.3)}
.dz-delta.flat{color:var(--text-mute);background:var(--surface-2);
  border-color:var(--border)}
.dz-delta--lg{padding:5px 12px;font-size:.9375rem}

/* Zone 2 « Suivi » : lecture en balayage, la grille reste #kpiGrid/.kpi-grid -
   le systeme de personnalisation existant (drag/masquer/elargir) continue de
   fonctionner a l'identique, seule l'habillage visuel change. */
.dz-scan{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));
  gap:1px;background:var(--dz-rail);border:1px solid var(--border);
  border-radius:var(--r-lg);overflow:hidden}
.dz-cell{display:flex;flex-direction:column;gap:5px;padding:13px 15px;
  background:var(--surface);min-width:0}
.dz-cell-l{font-size:.75rem;line-height:1.3;color:var(--text-mute);
  overflow-wrap:anywhere}
.dz-cell-v{display:flex;align-items:baseline;gap:8px;font-size:1.0625rem;
  font-weight:650;letter-spacing:-.02em;font-variant-numeric:tabular-nums;
  color:var(--text)}
.dz-cell-v small{font-size:.75rem;font-weight:500;color:var(--text-mute)}
.dz-none{font-size:.875rem;font-weight:500;font-style:italic;color:var(--text-mute)}

/* Zone 3 « Analyses » : les widgets, repliables par groupe. */
.dz-group{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);box-shadow:var(--sh-card);overflow:hidden}
.dz-group + .dz-group{margin-top:12px}
.dz-group > summary{display:flex;align-items:center;gap:12px;padding:15px 18px;
  cursor:pointer;list-style:none;color:var(--text);
  transition:background-color .18s var(--ease-out,ease)}
.dz-group > summary::-webkit-details-marker{display:none}
.dz-group > summary:hover{background:var(--surface-hover)}
.dz-group > summary:focus-visible{outline:none;box-shadow:var(--ring)}
.dz-group-t{font-size:.9375rem;font-weight:650;letter-spacing:-.02em}
.dz-group-n{padding:1px 8px;font-size:.75rem;font-weight:600;color:var(--text-mute);
  background:var(--surface-2);border:1px solid var(--border-soft);
  border-radius:var(--r-full)}
.dz-group-q{font-size:.8125rem;color:var(--text-mute)}
.dz-group-chev{margin-left:auto;font-size:.875rem;color:var(--text-mute);
  transition:transform .2s var(--ease-out,ease)}
.dz-group[open] .dz-group-chev{transform:rotate(90deg)}
.dz-group-body{padding:0 18px 18px;border-top:1px solid var(--border-soft)}
.dz-group-body > *:first-child{margin-top:18px}

/* Bandeau « tous projets » : global_total/global_real_total decrivent
   l'ensemble du compte, pas la section courante — sortent de la grille. */
.dz-all{display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  padding:13px 18px;background:var(--pd-sunken);
  border:1px solid var(--border-soft);border-radius:var(--r-lg)}
.dz-all-t{font-size:.75rem;font-weight:650;letter-spacing:.07em;
  text-transform:uppercase;color:var(--text-mute)}
.dz-all-i{display:flex;align-items:baseline;gap:8px;font-size:.875rem;
  color:var(--text-dim)}
.dz-all-i b{font-size:1rem;font-weight:650;color:var(--text);
  font-variant-numeric:tabular-nums}

.dz-edit{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;
  font:inherit;font-size:.8125rem;font-weight:600;color:var(--text-dim);
  background:transparent;border:1px solid var(--border);border-radius:var(--r-full);
  cursor:pointer;transition:color .18s var(--ease-out,ease),
    border-color .18s var(--ease-out,ease),background-color .18s var(--ease-out,ease)}
.dz-edit:hover{color:var(--text);background:var(--surface-2);
  border-color:var(--border-strong)}
.dz-edit:focus-visible{outline:none;box-shadow:var(--ring)}

/* Bouton "promouvoir"/"reculer" sur chaque tuile lead/scan — seul mecanisme
   de passage entre zones cette passe (voir README : le glisser-deposer reste
   scope a l'interieur de la zone 2, comme #kpiGrid l'a toujours ete). Injecte
   en JS (voir dashboard.html) dans le coin superieur droit de .stat-tile,
   au meme niveau que .drag-handle/.trash-btn deja positionnes en absolu la —
   .stat-tile reste position:relative, donc ce positionnement fonctionne
   quelle que soit la zone (heros/satellite/suivi) qui l'accueille. */
.stat-tile .dz-zone-btn{position:absolute;top:10px;right:56px;width:22px;height:22px;
  display:grid;place-items:center;font-size:.75rem;color:var(--text-mute);
  background:var(--surface-2);border:1px solid var(--border-soft);
  border-radius:var(--r-full);cursor:pointer;opacity:0;
  transition:opacity .15s var(--ease,ease),color .18s var(--ease-out,ease),
    background-color .18s var(--ease-out,ease)}
.stat-tile:hover .dz-zone-btn, .stat-tile:focus-within .dz-zone-btn{opacity:1}
.dz-zone-btn:hover{color:var(--text);background:var(--surface-3)}

/* ---- Habillage de .stat-tile (KPI, inchange cote Jinja/logique) par zone :
   la MEME tuile devient heros/satellite/cellule de suivi selon la zone qui
   l'accueille, sans dupliquer sa structure HTML. ---- */
.dz-hero-slot{min-width:0}
.dz-hero-slot .stat-tile{height:100%;border:0;box-shadow:none;border-radius:0;
  padding:24px 26px;background:linear-gradient(158deg,var(--brand-soft),transparent 62%),var(--surface)}
.dz-hero-slot .stat-tile:hover{transform:none;border-color:transparent;box-shadow:none}
.dz-hero-slot .stat-label{font-size:.75rem;font-weight:650;letter-spacing:.07em;text-transform:uppercase}
.dz-hero-slot .stat-value{margin-top:10px;font-size:2.75rem;font-weight:750;line-height:1;letter-spacing:-.04em}
.dz-hero-slot .stat-delta,.dz-hero-slot .stat-sub{margin-top:10px;font-size:.875rem}

/* ---- Passe 2 (Abonnements) : le heros MRR porte une barre de seuil -----
   .dz-hero-bar existe deja (rail + remplissage), reutilisee telle quelle ici
   avec deux ajouts seulement : la teinte d'avertissement quand le seuil
   d'abonnes n'est pas couvert, et le libelle de socle en dessous. Place a
   l'interieur de .dz-hero-slot .stat-tile (voir tile_mrr, dashboard.html),
   jamais dans .dz-sats/.dz-scan - ce bloc n'a de sens que pour le heros. */
.dz-hero-bar.is-under>i{background:var(--warning-text)}
/* --text-dim et non --text-mute comme .dz-hero-l/.dz-sat-l voisins : c'est
   du texte informatif (dit si le mois est rentable), donc au seuil AA. */
.dza-floor{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;
  margin-top:8px;font-size:.8125rem;color:var(--text-dim)}
.dza-floor b{font-weight:650;color:var(--text);font-variant-numeric:tabular-nums}

.dz-sats{display:flex;flex-direction:column;background:var(--dz-rail);gap:1px;min-width:0}
.dz-sats .stat-tile{display:flex;align-items:center;flex-wrap:wrap;gap:4px 12px;
  border:0;box-shadow:none;border-radius:0;padding:14px 56px 14px 22px}
.dz-sats .stat-tile:hover{transform:none;border-color:transparent;box-shadow:none}
.dz-sats .stat-label{flex:none;font-size:.8125rem;color:var(--text-mute)}
.dz-sats .stat-value{margin-left:auto;font-size:1.25rem;font-weight:650;letter-spacing:-.02em;
  white-space:nowrap}
.dz-sats .stat-delta,.dz-sats .stat-sub{flex:0 0 100%;margin:0;text-align:right;font-size:.75rem}

.dz-scan .stat-tile, #kpiAllBanner .stat-tile{border:0;box-shadow:none;border-radius:0;
  padding:13px 15px;gap:5px}
.dz-scan .stat-tile:hover, #kpiAllBanner .stat-tile:hover{transform:none;border-color:transparent;box-shadow:none}
.dz-scan .stat-label{font-size:.75rem;color:var(--text-mute)}
.dz-scan .stat-value{font-size:1.0625rem;font-weight:650;letter-spacing:-.02em;
  white-space:nowrap}
/* Correctif remonte a l'occasion de la passe 2 (Abonnements) : sans nowrap,
   une valeur se coupait entre le nombre et son unite ("27,60 €" -> le €
   seul sur la ligne suivante) des que le libelle voisin etait long.
   Invisible cote Unitaires (libelles courts : "Encaisse", "Taux horaire"),
   mais le defaut appartient a .dz-* dans son ensemble, pas a l'abonnement -
   d'ou une regle qui vise les deux zones ci-dessus plutot qu'un selecteur
   propre a une section. */
#kpiAllBanner{display:flex;flex-wrap:wrap}
#kpiAllBanner .stat-tile{flex:1 1 220px}

@container dz (max-width:840px){
  .dz-lead{grid-template-columns:1fr}
  .dz-hero-v{font-size:2.375rem}
}
@container dz (max-width:440px){
  .dz{gap:24px}
  .dz-hero{padding:18px}
  .dz-hero-v{font-size:2rem}
  .dz-sat{padding:12px 18px}
  .dz-scan{grid-template-columns:repeat(2,1fr)}
  .dz-cell{padding:11px 12px}
  .dz-cell-v{font-size:1rem}
  .dz-group > summary{padding:13px 14px;flex-wrap:wrap}
  .dz-group-q{display:none}
  .dz-group-body{padding:0 14px 14px}
  .dz-all{gap:12px;padding:12px 14px}
}
@supports not (container-type:inline-size){
  @media (max-width:880px){ .dz-lead{grid-template-columns:1fr} }
  @media (max-width:480px){ .dz-scan{grid-template-columns:repeat(2,1fr)} }
}

/* ---- §3 · Navigation — une seule taxonomie, deux surfaces ---- */
.nv{display:flex;flex-direction:column;gap:22px}
.nv-group{display:flex;flex-direction:column;gap:2px}
.nv-group-t{margin:0 0 6px;padding:0 12px;font-size:.6875rem;font-weight:650;
  letter-spacing:.08em;text-transform:uppercase;color:var(--text-mute)}

.nv-item{display:flex;align-items:center;gap:12px;padding:10px 12px;
  text-decoration:none;color:var(--text-dim);background:transparent;
  border:1px solid transparent;border-radius:var(--r-md);font-size:.9375rem;
  font-weight:550;letter-spacing:-.012em;
  transition:color .18s var(--ease-out,ease),background-color .18s var(--ease-out,ease)}
.nv-item:hover{color:var(--text);background:var(--surface-2)}
.nv-item:focus-visible{outline:none;box-shadow:var(--ring)}
.nv-ico{flex:none;width:20px;text-align:center;font-size:1rem;opacity:.8}
.nv-item.is-active{color:var(--text);background:var(--brand-soft);
  border-color:var(--brand-line)}
.nv-item.is-active .nv-ico{opacity:1}
.nv-item.is-active{position:relative}
.nv-item.is-active::before{content:"";position:absolute;left:-1px;top:8px;bottom:8px;
  width:2px;border-radius:var(--r-full);background:var(--brand)}
.nv-sub{margin-left:auto;font-size:.75rem;color:var(--text-mute);
  font-variant-numeric:tabular-nums}
.nv-badge{margin-left:auto;padding:1px 8px;font-size:.6875rem;font-weight:650;
  color:var(--brand-text);background:var(--brand-soft);
  border:1px solid var(--brand-line);border-radius:var(--r-full)}

.nv-danger{margin-top:6px;padding-top:16px;border-top:1px solid var(--dz-rail)}
.nv-item.is-quiet{color:var(--text-mute);font-size:.875rem}
.nv-item.is-danger{color:var(--critical-text)}
.nv-item.is-danger:hover{background:var(--critical-soft)}

.nv-inline{display:flex;flex-wrap:wrap;gap:6px 16px;padding:8px 12px}
.nv-inline a{font-size:.8125rem;color:var(--text-mute);text-decoration:none}
.nv-inline a:hover{color:var(--text-dim);text-decoration:underline}

/* Barre laterale web */
.nv-side{display:flex;flex-direction:column;gap:22px;width:250px;flex:none;
  padding:18px 12px;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);align-self:flex-start;position:sticky;top:18px}
.nv-brand{display:flex;align-items:center;gap:10px;padding:0 12px 4px}
.nv-brand-n{font-size:1rem;font-weight:700;letter-spacing:-.02em;color:var(--text)}
.nv-me{display:flex;align-items:center;gap:11px;margin:0 4px;padding:10px 8px;
  background:var(--surface-2);border:1px solid var(--border-soft);
  border-radius:var(--r-md);text-decoration:none}
.nv-me:hover{background:var(--surface-3)}
.nv-av{flex:none;display:grid;place-items:center;width:34px;height:34px;
  border-radius:var(--r-full);background:var(--brand-dim);color:#fff;
  font-size:.8125rem;font-weight:700}
.nv-me-n{display:block;font-size:.875rem;font-weight:600;color:var(--text);
  line-height:1.25}
.nv-me-p{display:block;font-size:.75rem;color:var(--text-mute)}

/* Profil mobile : memes groupes, en cartes (utilise par la previsualisation
   web des ecrans mobile, pas par l'app RN elle-meme qui a ses propres
   styles natifs — voir mobile/src/screens/profile.tsx). */
.nv-card{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);box-shadow:var(--sh-card);overflow:hidden}
.nv-card .nv-item{border-radius:0;border:0;padding:14px 16px;min-height:52px}
.nv-card .nv-item + .nv-item{border-top:1px solid var(--border-soft)}
.nv-card .nv-item.is-active::before{display:none}
.nv-two{display:block;min-width:0}
.nv-two b{display:block;font-size:.9375rem;font-weight:600;color:var(--text);
  line-height:1.3}
.nv-two span{display:block;margin-top:1px;font-size:.75rem;color:var(--text-mute);
  line-height:1.35}
.nv-go{margin-left:auto;flex:none;font-size:1.125rem;color:var(--text-mute)}

.nv-acct{display:flex;align-items:center;gap:14px;padding:18px;
  background:linear-gradient(158deg,var(--brand-soft),transparent 64%),var(--surface);
  border:1px solid var(--brand-line);border-radius:var(--r-lg);
  box-shadow:var(--sh-card)}
.nv-acct .nv-av{width:52px;height:52px;font-size:1.125rem}
.nv-acct-n{margin:0;font-size:1.125rem;font-weight:700;letter-spacing:-.02em;
  color:var(--text)}
.nv-acct-m{margin:2px 0 0;font-size:.8125rem;color:var(--text-soft)}
.nv-acct-p{display:inline-flex;margin-top:7px;padding:2px 9px;font-size:.6875rem;
  font-weight:650;color:var(--brand-text);background:var(--brand-soft);
  border:1px solid var(--brand-line);border-radius:var(--r-full)}

@media (prefers-reduced-motion:reduce){
  .dz-group-chev,.dz-group > summary,.nv-item,.dz-edit,.dz-zone-btn{transition:none}
}
/* ==========================================================================
   ProfitTrack — landing, bas de page refondu
   À ajouter en fin de static/css/style.css.

   Préfixe unique : .lpx-*  (landing page, eXtension)
   Ne touche AUCUNE classe existante : ni .card / .btn / .row, ni les .ptx-*
   des mockups Nouveautés, ni les .pt-* du hero, ni .pricing-card.
   Réutilise le mécanisme .reveal existant (variable --ptx-in, classes
   .is-visible / .revealed / .visible) sans le modifier.

   Aucune couleur nouvelle : tokens de :root uniquement.
   ========================================================================== */

/* Poses sur :root et non sur un wrapper .lpx : le handoff d'origine
   attendait un <div class="lpx"> autour des 3 sections (voir
   apercu-landing.html), mais landing.html les a chacune en <section>
   directe, sans ce parent commun. Sans lui, --lpx-r-xl et consorts
   n'etaient jamais definies nulle part : var(--lpx-r-xl) sans valeur de
   repli rend la declaration entiere invalide au calcul, donc border-radius
   (et gap/padding/bordures) retombaient tous silencieusement sur 0 - le
   bug exact remonte par l'utilisateur (coins carres, malgre un cache vide).
   :root les rend disponibles partout sans dependre d'un wrapper. */
:root{
  --lpx-r:var(--r-lg);
  --lpx-r-xl:var(--r-xl);
  --lpx-cut:var(--r-sm);
  --lpx-hair:1px solid var(--border);
  --lpx-hair-soft:1px solid var(--border-soft);
  --lpx-gut:clamp(1.25rem,3.4vw,2.75rem);
  --lpx-pad:clamp(1.125rem,2.4vw,1.5rem);
}

/* ==========================================================================
   §0 · PAS DE COUCHE DE FOND
   ==========================================================================
   Trois systèmes ont été essayés ici — nébuleuse uniforme, trame réglée, onde
   de points — et tous retirés. Le bas de page s'appuie désormais sur le fond
   existant du site (--page et .lp-band-atmo::before des bandes alternées),
   sans rien y ajouter.
   Conséquence à connaître : les deux premières sections n'ont plus de calque,
   donc plus besoin de position:relative ni d'isolation. Ce qui reste de
   « vivant » sur cette page est porté par les blocs eux-mêmes (le liseré
   conique du relevé, son lavis qui respire) et par les animations de contenu
   (la rétraction des barres, la dérive des hachures, la promotion de la FAQ),
   pas par le fond.
   -------------------------------------------------------------------------- */

/* Reprend l'atmosphere deja etablie ailleurs sur cette meme page
   (.lp-band-atmo::before, hero + tarifs) plutot que d'inventer un 4e systeme
   de fond : la section §2 (FAQ persona) est la plus longue et la plus vide en
   contenu graphique des trois, c'est elle qui en a le plus besoin. Meme
   opacite tres basse, jamais un aplat mort ni un aplat charge. */
/* inset:0, pas une marge negative : un debordement vers le haut peignait
   par-dessus le bas de la section precedente (Tarifs), visible comme un
   cadre qui deborde a la jointure des deux sections. */
.lpx-faq{ position: relative; isolation: isolate; }
.lpx-faq::before{
  content: "";
  position: absolute; inset: 0;
  z-index: -1; pointer-events: none;
  background: radial-gradient(900px 420px at 84% 0%, rgba(124, 108, 255, .07), transparent 70%),
              radial-gradient(700px 380px at 8% 100%, rgba(74, 140, 255, .06), transparent 72%);
}

/* ==========================================================================
   §1 · « CE QUE VOUS CROYEZ GAGNER » — remplace « Comment ça marche »
   ==========================================================================
   L'escalier SVG est abandonné. Les trois étapes ne sont plus trois cartes
   côte à côte mais TROIS ÉTATS D'UN MÊME CHIFFRE : 600 €/h annoncés au devis,
   puis les heures réelles, puis les retards de paiement. Le mouvement n'est
   pas un ornement, c'est la démonstration : le nombre se corrige à mesure
   qu'on descend, et c'est exactement ce que fait le produit.
   -------------------------------------------------------------------------- */
.lpx-corr{position:relative;padding:clamp(2.5rem,6vw,4.5rem) 0}
.lpx-corr-h{max-width:60ch;margin:0 0 clamp(2rem,4vw,3rem)}
.lpx-kicker{display:block;margin:0 0 .75rem;font-size:.75rem;font-weight:650;
  letter-spacing:.14em;text-transform:uppercase;color:var(--text-mute)}
.lpx-h2{margin:0;font-size:clamp(1.75rem,4.2vw,2.75rem);font-weight:700;
  line-height:1.08;letter-spacing:-.035em;color:var(--text);text-wrap:balance}
.lpx-lede{margin:1rem 0 0;max-width:52ch;font-size:1rem;line-height:1.6;
  color:var(--text-soft)}

/* Le registre : trois lignes, filets pleine largeur. Pas de cartes — une
   carte isole, et ici il faut au contraire que les trois lignes se lisent
   comme une seule opération continue. */
/* Plafond de largeur. Sans lui, au-delà de ~76rem la colonne de texte (44ch)
   ne remplit plus son 1fr et chaque rangée se creuse au milieu — c'est
   exactement ce qui fait « plat » sur un grand écran. */
/* Cadre qui regroupe les trois etapes en un seul bloc percu comme une
   unite - avant, seuls des filets horizontaux les separaient, sans aucune
   bordure englobante : la lecture "un seul registre" restait a deviner. */
.lpx-ledger{max-width:76rem;border:var(--lpx-hair);
  border-radius:var(--lpx-r-xl);background:var(--pd-sunken);
  padding:.25rem clamp(1.25rem,3vw,2rem)}
.lpx-step{display:grid;grid-template-columns:3.5rem 1fr minmax(0,26rem);
  align-items:start;gap:var(--lpx-gut);
  padding:clamp(1.5rem,3vw,2.25rem) 0;border-bottom:var(--lpx-hair)}
.lpx-step:last-child{border-bottom:none}
/* La colonne de l'ardoise s'élargit d'étape en étape : la conclusion occupe
   physiquement plus de place que la prémisse. C'est ce qui empêche les trois
   rangées de se lire comme une liste plate. */
.lpx-step[data-lpx-step="2"]{grid-template-columns:3.5rem 1fr minmax(0,29rem)}
.lpx-step[data-lpx-step="3"]{grid-template-columns:3.5rem 1fr minmax(0,32rem);
  padding-bottom:clamp(2rem,4vw,3rem)}
/* Les numéros passent de 13 px à 2 rem et sortent à gauche du registre :
   une colonne de chiffres timides devenait invisible et laissait la rangée
   commencer au ras du bord. */
.lpx-step-n{font-size:2rem;font-weight:750;letter-spacing:-.04em;line-height:1;
  color:var(--text-mute);opacity:.42;font-variant-numeric:tabular-nums;
  padding-top:.125rem;margin-left:-.5rem}
.lpx-step-b{min-width:0}
.lpx-step-t{margin:0;font-size:1.1875rem;font-weight:650;letter-spacing:-.022em;
  color:var(--text)}
.lpx-step-p{margin:.5rem 0 0;max-width:44ch;font-size:.9375rem;
  line-height:1.6;color:var(--text-soft)}

/* La colonne de droite : l'ardoise où le chiffre se corrige. */
/* L'ardoise : rayons généreux, coin bas-gauche presque carré. La barre
   déborde du padding jusqu'aux bords (voir .lpx-bar), donc le bloc n'est plus
   un rectangle poli mais une fiche dont un angle est coupé et dont le contenu
   touche le bord. */
/* Pas de bordure ici : le fond --surface (fiche) contre --pd-sunken (cadre
   du registre) suffit a separer les deux - une bordure grise en plus, sur
   le fond deja plus sombre du cadre, ressortait comme un rectangle dur au
   lieu de se fondre. */
.lpx-slate{position:relative;padding:var(--lpx-pad);
  padding-bottom:calc(var(--lpx-pad) - .25rem);
  background:var(--surface);
  border-radius:var(--lpx-r-xl);
  overflow:hidden}
.lpx-slate-l{display:block;font-size:.6875rem;font-weight:650;
  letter-spacing:.1em;text-transform:uppercase;color:var(--text-head)}
/* margin en entier, pas margin-top : c'est le plus gros corps de la page,
   donc son 1em de marge basse par défaut laissait 55 px de vide entre le
   chiffre et la barre — et éloignait la barre du nombre qu'elle explique. */
.lpx-fig{display:flex;align-items:baseline;gap:.625rem;flex-wrap:wrap;margin:.5rem 0 0;
  font-variant-numeric:tabular-nums;font-size:clamp(2.25rem,6vw,3.5rem);
  font-weight:750;line-height:1;letter-spacing:-.045em;color:var(--text)}
.lpx-fig-u{font-size:.9375rem;font-weight:600;letter-spacing:-.01em;
  color:var(--text-mute)}

/* Le chiffre corrigé : DEUX valeurs en texte réel, l'ancienne barrée, la
   nouvelle en grand. Pas de counter(), pas de @property — les trois nombres
   qui portent l'argument de la page doivent être du texte sélectionnable,
   lisible par un lecteur d'écran, et présent dans n'importe quel rendu. */
.lpx-was{font-size:1.125rem;font-weight:600;letter-spacing:-.02em;
  color:var(--text-mute);text-decoration:line-through;
  text-decoration-color:var(--critical-text);text-decoration-thickness:2px}

/* ---- La barre : c'est ICI qu'est le mouvement visible ----
   Une seule échelle pour les trois étapes (600 €/h = 100 %). Les longueurs
   sont donc 100 % / 52 % / 36,5 % : empilées, les trois barres dessinent
   elles-mêmes l'escalier descendant que le SVG raté essayait de tracer à
   côté. La donnée EST le graphique. */
/* Plein cadre : la barre sort du padding et touche les trois bords de
   l'ardoise. Elle y gagne 40 px de largeur — donc l'animation gagne autant en
   visibilité — et le bloc perd son air de rectangle centré. */
.lpx-bar{position:relative;display:flex;align-items:stretch;height:38px;
  margin:1.125rem calc(var(--lpx-pad) * -1) calc(.25rem - var(--lpx-pad));
  background:var(--pd-sunken);border-top:var(--lpx-hair-soft);
  border-radius:var(--r-md,12px);overflow:hidden}
.lpx-bar-fill{flex:0 0 auto;width:100%;
  background:linear-gradient(180deg,var(--brand),var(--brand-dim));
  border-radius:var(--r-md,12px) 0 0 var(--r-md,12px);
  transition:width 1.05s var(--ease-out,cubic-bezier(.22,1,.36,1)) var(--lpx-d,0s)}
/* La part retirée : elle occupe la place que la barre vient de libérer. Le
   visiteur ne voit pas une barre raccourcir, il voit une somme se faire
   prendre — hachures + montant écrit dedans. */
.lpx-bar-cut{flex:1 1 auto;display:flex;align-items:center;justify-content:flex-end;
  gap:.375rem;padding-right:.625rem;min-width:0;overflow:hidden;
  background:repeating-linear-gradient(135deg,
    var(--critical-soft) 0 6px,transparent 6px 12px);
  border-left:2px solid var(--critical-text);
  font-size:.75rem;font-weight:650;letter-spacing:-.01em;white-space:nowrap;
  color:var(--critical-text);
  opacity:0;transition:opacity .5s ease var(--lpx-d2,.4s)}
/* IDLE — les hachures dérivent en continu, lentement, vers la gauche. C'est
   le seul mouvement permanent de la section et il dit quelque chose : cette
   part-là fuit tant que rien n'est fait. Une fois la barre posée, la section
   ne redevient jamais une image fixe. 17 px = une période exacte du motif à
   135°, donc la boucle est invisible.
   2,1 s et non 1,9 : 1,9 s est exactement le dixième des 19 s du lavis du
   relevé, donc les deux se recalaient toutes les 19 secondes. 21 = 3 × 7, et
   aucune des autres durées (13/19/23/25/29/31/41) n'est divisible par 3 ni
   par 7 — le rapport est donc sans harmonique. */
.reveal.is-visible .lpx-bar-cut,
.reveal.revealed .lpx-bar-cut,
.reveal.visible .lpx-bar-cut{opacity:1;animation:lpx-drift 2.1s linear infinite}
@keyframes lpx-drift{from{background-position:0 0}to{background-position:-17px 0}}
/* L'ajout de l'étape 3 : la seule part qui pousse au lieu de se retirer. */
.lpx-bar-add{flex:0 0 auto;width:0;display:flex;align-items:center;
  justify-content:center;overflow:hidden;
  background:var(--good-soft);border-left:2px solid var(--good-text);
  font-size:.6875rem;font-weight:650;color:var(--good-text);white-space:nowrap;
  transition:width .8s var(--ease-out,cubic-bezier(.22,1,.36,1)) var(--lpx-d2,.4s)}

.reveal.is-visible .lpx-bar-fill,
.reveal.revealed .lpx-bar-fill,
.reveal.visible .lpx-bar-fill{width:var(--lpx-w,100%)}

/* HOVER — survoler l'ardoise remet la barre à 100 % : le visiteur reprend
   lui-même le chiffre qu'il croyait gagner, et le lâche en retirant la
   souris. C'est la manipulation, pas la démonstration : même mécanique que
   le calculateur du hero, transposée à un geste d'un dixième de seconde. */
.lpx-slate{cursor:default;transition:border-color .25s var(--ease-out,ease)}
.lpx-slate:hover{border-color:var(--brand-line)}
.reveal.is-visible .lpx-slate:hover .lpx-bar-fill,
.reveal.revealed .lpx-slate:hover .lpx-bar-fill,
.reveal.visible .lpx-slate:hover .lpx-bar-fill{
  width:100%;transition-duration:.45s;transition-delay:0s}
.lpx-slate:hover .lpx-bar-cut,
.lpx-slate:hover .lpx-bar-add{opacity:0;transition-duration:.2s;transition-delay:0s}
.reveal.is-visible .lpx-bar-add,
.reveal.revealed .lpx-bar-add,
.reveal.visible .lpx-bar-add{width:var(--lpx-a,0)}

/* La retenue est écrite DANS la barre (.lpx-bar-cut), pas sous elle : une
   légende séparée répétait le même montant à 40 px d'écart. */
.lpx-step-p b{font-weight:650;color:var(--text-dim);font-variant-numeric:tabular-nums}

/* L'étape 3 est la conclusion : son ardoise porte la marque, les deux
   premières restent neutres. Une seule teinte sur trois, sinon aucune ne
   désigne plus rien. */
.lpx-step[data-lpx-step="3"] .lpx-slate{
  background:linear-gradient(158deg,var(--brand-soft),transparent 66%),var(--surface)}
.lpx-step[data-lpx-step="3"] .lpx-slate-l{color:var(--brand-text)}
.lpx-step[data-lpx-step="3"] .lpx-sub b{color:var(--good-text)}

/* Filet de progression sur la colonne des numéros : il se trace pendant que
   les chiffres se corrigent. Remplace l'escalier SVG raté — même intention,
   mais rattaché à un élément qui existe déjà. */
.lpx-ledger{position:relative}
/* .lpx-ledger-fill est un vrai element (voir landing.html), pas un
   ::before : un pseudo-element n'est pas selectionnable en JS, or ce trait
   doit suivre la position de scroll en continu (comme dans le prototype
   d'exploration) et pas juste se reveler une fois. Degrade plutot qu'un
   bleu uni : le trait suit la meme logique que les barres qu'il relie
   (bleu -> vert au niveau de l'etape 3, ou la penalite recuperee bascule la
   couleur en positif), avec les couleurs deja en usage dans cette section
   plutot qu'une teinte ambre inedite ici. */
.lpx-ledger-fill{position:absolute;top:0;
  left:calc(1.75rem - 1px + clamp(1.25rem,3vw,2rem));
  width:2px;height:100%;
  background:linear-gradient(var(--brand),var(--brand) 60%,var(--good-text));
  opacity:.7;pointer-events:none}
/* Sans JS (ou reduced-motion) : trait plein d'emblee, aucune degradation -
   la hauteur n'est reduite a 0 que si un script va effectivement la piloter. */
.js .lpx-ledger-fill{height:0}
@media (prefers-reduced-motion:reduce){
  .js .lpx-ledger-fill{height:100% !important}
}

/* ==========================================================================
   §2 · « VOTRE SITUATION » — remplace la FAQ en accordéon
   ==========================================================================
   L'accordéon est abandonné : il cache les réponses derrière un clic et son
   animation est invisible par construction. Ici TOUTES les réponses sont
   lisibles d'emblée ; ce qui est interactif, c'est la mise en avant. Le
   visiteur déclare sa situation et les trois questions qui le concernent
   remontent en tête. Même logique que le calculateur du hero : on ne promet
   pas la pertinence, on la fait manipuler. 100 % CSS, radios + order.
   -------------------------------------------------------------------------- */
.lpx-faq{padding:clamp(2.5rem,6vw,4.5rem) 0}
.lpx-faq-top{display:flex;align-items:flex-end;justify-content:space-between;
  gap:var(--lpx-gut);flex-wrap:wrap;margin-bottom:clamp(1.5rem,3vw,2.25rem)}

/* Le sélecteur de situation. Pas un onglet : une déclaration.
   AUCUN id, et chaque <input> est enveloppé dans son <label> : le bloc peut
   donc être rendu plusieurs fois sur une même page (aperçu à deux thèmes,
   page de style, futur A/B) sans que les copies se pilotent l'une l'autre.
   Le ciblage passe par :has() + [value], jamais par #id. */
.lpx-who{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;
  font-size:.875rem;color:var(--text-mute)}
.lpx-who input{position:absolute;opacity:0;width:0;height:0}
.lpx-who label{padding:.5rem .9375rem;font-size:.875rem;font-weight:600;
  letter-spacing:-.012em;color:var(--text-dim);background:transparent;
  border:var(--lpx-hair);
  border-radius:var(--r-full) var(--r-full) var(--r-full) var(--lpx-cut);
  cursor:pointer;
  transition:color .18s var(--ease-out,ease),background-color .18s var(--ease-out,ease),
    border-color .18s var(--ease-out,ease)}
.lpx-who label:hover{color:var(--text);background:var(--surface)}
.lpx-who label:has(input:focus-visible){outline:none;box-shadow:var(--ring)}
.lpx-who label:has(input:checked){color:var(--text);
  background:var(--brand-soft);border-color:var(--brand-line)}

/* .lpx-qs vit dans le conteneur flex .lpx-who (le :has() est posé sur .lpx-who,
   donc les questions doivent être ses descendantes) et doit reprendre toute la
   largeur, sinon il se range à côté des étiquettes. */
.lpx-qs{display:flex;flex-direction:column;flex:1 0 100%;width:100%;max-width:76rem;
  margin-top:clamp(1.25rem,2.6vw,1.875rem);border-top:var(--lpx-hair)}
.lpx-q{position:relative;display:grid;
  grid-template-columns:minmax(0,20rem) minmax(0,1fr);
  gap:var(--lpx-gut);padding:clamp(1.125rem,2.4vw,1.625rem) 0;
  border-bottom:var(--lpx-hair);order:2;
  transition:padding-left .35s var(--ease-out,ease),
    background-color .2s var(--ease-out,ease)}
/* TRANSITION D'ÉTAT — l'ordre CSS ne s'animant pas, c'est le décalage vers la
   droite et le filet de marque qui rendent la promotion visible : au clic sur
   une situation, trois rangées se décalent et trois filets poussent. */
.lpx-q::before{content:"";position:absolute;left:0;top:.75rem;bottom:.75rem;width:2px;
  background:var(--brand);border-radius:var(--r-full);
  transform:scaleY(0);transform-origin:top;
  transition:transform .4s var(--ease-out,ease)}
/* HOVER — la rangée s'éclaire et ses chiffres se soulignent. Le survol répond
   sur la ligne entière, pas sur un mot : c'est la ligne qu'on lit. */
.lpx-q:hover{background:var(--surface)}
.lpx-q-a b{border-bottom:1px solid transparent;transition:border-color .2s ease}
.lpx-q:hover .lpx-q-a b{border-bottom-color:var(--brand-line)}
.lpx-q-q{margin:0;font-size:1rem;font-weight:650;letter-spacing:-.02em;
  color:var(--text);text-wrap:pretty}
.lpx-q-a{margin:0;font-size:.9375rem;line-height:1.65;color:var(--text-soft);
  max-width:62ch;text-wrap:pretty}
.lpx-q-a b{font-weight:650;color:var(--text-dim);font-variant-numeric:tabular-nums}

/* La promotion : les questions de la situation choisie passent en tête et
   gagnent un repère. Les autres ne sont NI masquées NI grisées — juste
   déplacées. Griser reviendrait à cacher, et on refuse de cacher. */
/* flex:none + nowrap : sans les deux, la pastille se fait comprimer par la
   question dans un conteneur de 320 px, « Pour vous » passe sur deux lignes et
   la pilule à rayon plein devient une tache. */
.lpx-mark{display:none;flex:none;margin-left:auto;padding:2px 8px;
  font-size:.6875rem;font-weight:650;letter-spacing:.02em;white-space:nowrap;
  align-self:baseline;color:var(--brand-text);background:var(--brand-soft);
  border:1px solid var(--brand-line);border-radius:var(--r-full)}
.lpx-q-head{display:flex;align-items:baseline;gap:.75rem}
.lpx-who:has(input[value="debut"]:checked)   .lpx-q[data-lpx-who="debut"],
.lpx-who:has(input[value="societe"]:checked) .lpx-q[data-lpx-who="societe"],
.lpx-who:has(input[value="abo"]:checked)     .lpx-q[data-lpx-who="abo"]{
  order:1;padding-left:18px}
.lpx-who:has(input[value="debut"]:checked)   .lpx-q[data-lpx-who="debut"]::before,
.lpx-who:has(input[value="societe"]:checked) .lpx-q[data-lpx-who="societe"]::before,
.lpx-who:has(input[value="abo"]:checked)     .lpx-q[data-lpx-who="abo"]::before{
  transform:scaleY(1)}
.lpx-who:has(input[value="debut"]:checked)   .lpx-q[data-lpx-who="debut"]   .lpx-mark,
.lpx-who:has(input[value="societe"]:checked) .lpx-q[data-lpx-who="societe"] .lpx-mark,
.lpx-who:has(input[value="abo"]:checked)     .lpx-q[data-lpx-who="abo"]     .lpx-mark{
  display:inline-block}
/* Sans :has() : aucune promotion, les neuf réponses restent lisibles dans
   l'ordre du source. C'est une dégradation acceptable parce que rien n'est
   masqué — c'était le principe de la section. */

/* --- Replis. Conteneur plutôt que viewport : ce bas de page vit dans une
       colonne de contenu dont la largeur ne suit pas celle de la fenêtre.
       Pose sur les sections reelles (pas de wrapper .lpx commun dans
       landing.html, voir plus haut) : chacune doit etablir elle-meme son
       propre contexte de conteneur pour ses descendants. -- */
.lpx-corr,.lpx-faq{container-type:inline-size;container-name:lpx}
@container lpx (max-width:860px){
  .lpx-step,
  .lpx-step[data-lpx-step="2"],
  .lpx-step[data-lpx-step="3"]{grid-template-columns:2.5rem 1fr;
    grid-template-areas:"n b" ". s"}
  .lpx-step-n{grid-area:n}
  .lpx-step-b{grid-area:b}
  .lpx-slate{grid-area:s;margin-top:1.25rem}
  .lpx-ledger-fill{left:calc(1.25rem - 1px + clamp(1.25rem,3vw,2rem))}
  .lpx-q{grid-template-columns:1fr;gap:.625rem}
}
@container lpx (max-width:460px){
  .lpx-step,
  .lpx-step[data-lpx-step="2"],
  .lpx-step[data-lpx-step="3"]{grid-template-columns:1fr;
    grid-template-areas:"n" "b" "s"}
  .lpx-step-n{padding-top:0}
  .lpx-ledger-fill{display:none}
  .lpx-slate{padding:1rem}
  .lpx-bar{height:32px}
  .lpx-step-n{font-size:1.5rem;margin-left:0}
  .lpx-bar-cut,.lpx-bar-add{font-size:.6875rem}
  .lpx-faq-top{flex-direction:column;align-items:stretch}
  .lpx-who{gap:.375rem}
  .lpx-who label{flex:1;text-align:center}
}
@supports not (container-type:inline-size){
  @media (max-width:900px){
    .lpx-step{grid-template-columns:2.5rem 1fr;grid-template-areas:"n b" ". s"}
    .lpx-step-n{grid-area:n} .lpx-step-b{grid-area:b}
    .lpx-slate{grid-area:s;margin-top:1.25rem}
    .lpx-q{grid-template-columns:1fr;gap:.625rem}
  }
}

/* --- Mouvement réduit : les chiffres se posent sur leur valeur finale, le
       filet est plein, la promotion de la FAQ continue de fonctionner (elle
       n'est pas une animation mais une réorganisation). ------------------- */
/* Mouvement réduit ET sans JS : les barres sont posées sur leur longueur
   finale. Aucun chiffre ne dépend d'une animation — ils sont en texte. */
@media (prefers-reduced-motion:reduce){
  .lpx-bar-fill,.lpx-bar-cut,.lpx-bar-add{transition:none}
  .lpx-bar-fill{width:var(--lpx-w,100%)}
  .lpx-bar-cut{opacity:1}
  .lpx-bar-add{width:var(--lpx-a,0)}
  .lpx-q,.lpx-q::before,.lpx-who label{transition:none}
  /* Les deux boucles sont coupées ; les états de survol, eux, restent — ce
     sont des réponses à une action, pas du mouvement autonome. */
  .reveal.is-visible .lpx-bar-cut,
  .reveal.revealed .lpx-bar-cut,
  .reveal.visible .lpx-bar-cut{animation:none}
}
@media print{
  .lpx-bar-cut{animation:none}
  .lpx-bar-fill{width:var(--lpx-w,100%)}
  .lpx-bar-cut{opacity:1}
  .lpx-bar-add{width:var(--lpx-a,0)}
  .lpx-ledger-fill{height:100%}
}
/* ==========================================================================
   ProfitTrack — landing : la section « 4 tuiles », refaite
   Préfixe .lft-* (landing features). Ne cible aucune classe existante :
   .feature-card / .features sont REMPLACÉES, pas surchargées.
   Tokens uniquement, aucune couleur nouvelle.
   ==========================================================================

   DIAGNOSTIC (détail dans README.md) : quatre cartes identiques qui affirment
   un bénéfice, sans jamais montrer l'écran qui le produit. D'où les deux
   retours du visiteur — « je ne vois pas à quel feature ça fait référence »
   et « on ne comprend pas pourquoi ils sont là ».

   La forme retenue n'est pas une grille de cartes : c'est un RELEVÉ de quatre
   questions, chacune accolée au fragment d'interface réel qui y répond, et
   chaque fragment porte le nom de la fonctionnalité. La preuve et
   l'étiquetage sont dans le même geste.
   -------------------------------------------------------------------------- */

/* container-type et non une media query de viewport : la section peut vivre
   dans une colonne étroite sans que la fenêtre le soit (aperçu, page à
   sidebar, futur gabarit à deux colonnes). Une media query se serait trompée
   dans tous ces cas — et l'aperçu de ce handoff en était la démonstration :
   le bouton « colonne étroite » rétrécit le conteneur, pas la fenêtre. */
.lft{container-type:inline-size;
  --lft-gut:clamp(1.5rem,3.4vw,3rem);
  --lft-hair:1px solid var(--border-soft);
  --lft-cut:3px;                      /* l'angle vif, cf. §langage de forme */
  max-width:1120px;margin:0 auto;padding:clamp(2.5rem,6vw,5rem) 0}

/* --- L'amorce : c'est elle qui donne son rôle à la section ---------------
   Le second retour (« on ne comprend pas pourquoi ils sont là ») est un
   problème de rôle, pas de formulation. Une section ne se justifie pas par
   ses quatre titres : il faut dire, en une ligne, ce qu'elle fait dans la
   page. Ici : elle liste les questions, et désigne l'écran qui répond. */
.lft-lead{max-width:38ch;margin:0 0 clamp(2rem,4vw,3.25rem)}
.lft-kicker{display:block;margin-bottom:.75rem;font-size:.75rem;font-weight:650;
  letter-spacing:.1em;text-transform:uppercase;color:var(--text-head)}
.lft-h{margin:0 0 .625rem;font-size:clamp(1.5rem,3.4vw,2.125rem);font-weight:680;
  line-height:1.12;letter-spacing:-.025em;color:var(--text);text-wrap:balance}
.lft-sub{margin:0;font-size:clamp(.9375rem,1.6vw,1.0625rem);line-height:1.6;
  color:var(--text-dim);text-wrap:pretty}

/* --- Le relevé : quatre rangées empilées, pas une grille ----------------
   Empilées et non côte à côte, pour trois raisons : la question se lit en
   ligne (une grille de 4 la casse en colonnes de 3 mots) ; le fragment
   d'interface a besoin de largeur pour être lisible en tant qu'écran ; et
   l'empilement crée un ordre de lecture, alors que quatre cartes égales
   n'en ont aucun — c'est exactement ce qui rendait la section inerte. */
.lft-rows{display:flex;flex-direction:column;gap:0;
  border-top:1px solid var(--border)}
.lft-row{position:relative;display:grid;
  grid-template-columns:minmax(0,26rem) minmax(0,1fr);
  gap:var(--lft-gut);align-items:center;
  padding:clamp(1.5rem,3vw,2.25rem) 0;
  border-bottom:var(--lft-hair);
  transition:opacity .3s var(--ease-out,ease)}
/* Une rangée sur deux inverse l'ordre : le fragment passe à gauche. Ce n'est
   pas un effet de style, c'est ce qui empêche l'œil de sauter les questions
   pour ne suivre qu'une colonne de mockups. */
.lft-row:nth-child(even) > .lft-ask{order:2}
.lft-row:nth-child(even) > .lft-shard{order:1}

/* HOVER — « une question à la fois ». Survoler une rangée éteint les trois
   autres et fait monter son fragment. C'est la seule réponse au geste de la
   section, et elle dit quelque chose : ces quatre questions se posent une par
   une, jamais en bloc. */
/* Prefixe .lft.is-visible : la regle de reveal (.reveal.is-visible .lft-row
   {opacity:1}, plus bas dans ce fichier) a exactement la meme specificite
   (3 classes) et gagne toujours en arrivant apres dans la cascade - le
   survol n'avait donc jamais d'effet visible. Prefixer porte la
   specificite a 4 classes, sans dependre de l'ordre des regles. */
.lft.is-visible .lft-rows:hover > .lft-row{opacity:.42}
.lft.is-visible .lft-rows:hover > .lft-row:hover{opacity:1}
.lft-row:hover .lft-shard{transform:translateY(-4px)}
/* Repli sur --brand : --brand-text n'est PAS déclaré dans le :root de
   style.css (seulement dans son bloc clair). Sans ce repli, la pastille
   resterait grise en thème sombre — or c'est précisément elle qui répond au
   retour « je ne vois pas à quel feature ça fait référence ». Voir le §2 du
   prompt de portage. */
.lft-row:hover .lft-tag{color:var(--brand-text,var(--brand));
  border-color:var(--brand-line);background:var(--brand-soft)}

/* --- La question, côté texte ------------------------------------------- */
.lft-ask{min-width:0}
.lft-q{margin:0 0 .5rem;font-size:clamp(1.0625rem,2vw,1.3125rem);font-weight:660;
  line-height:1.25;letter-spacing:-.02em;color:var(--text);text-wrap:pretty}
.lft-a{margin:0;font-size:.9375rem;line-height:1.6;color:var(--text-dim);
  text-wrap:pretty}
.lft-a b{font-weight:640;color:var(--text)}

/* --- Le fragment d'interface -------------------------------------------
   Volontairement pas une « carte » : pas de rayon uniforme, un coin vif à
   3 px en bas à gauche, et un débord hors de la colonne (margin négative)
   pour que ça se lise comme un morceau d'écran découpé, pas comme une
   vignette posée dans une grille. */
.lft-shard{position:relative;min-width:0;padding:1rem 1.125rem 1.125rem;
  background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--r-lg,16px) var(--r-lg,16px) var(--r-lg,16px) var(--lft-cut);
  box-shadow:var(--sh-card,0 6px 18px rgba(0,0,0,.24));
  transition:transform .3s var(--ease-out,cubic-bezier(.22,1,.36,1))}
.lft-row:nth-child(odd) > .lft-shard{margin-right:clamp(-2.5rem,-2vw,-.75rem)}
.lft-row:nth-child(even) > .lft-shard{margin-left:clamp(-2.5rem,-2vw,-.75rem);
  border-radius:var(--r-lg,16px) var(--r-lg,16px) var(--lft-cut) var(--r-lg,16px)}

/* Le nom de la fonctionnalité. C'est LA réponse littérale au premier retour :
   le fragment ne se contente pas de prouver, il se nomme. */
/* flex-wrap en filet : la pastille et l'emplacement sont tous deux en nowrap
   (une pilule à rayon plein cassée sur deux lignes devient une tache), et en
   allemand la paire la plus longue — « Mahnungen & Verzugszinsen » +
   « Dashboard → „Unbezahlt“ » — tient de justesse à 390 px. Mieux vaut que
   l'emplacement passe à la ligne que de déborder du fragment. */
.lft-head{display:flex;align-items:center;flex-wrap:wrap;gap:.375rem .625rem;
  margin-bottom:.875rem}
.lft-tag{padding:2px 8px;font-size:.6875rem;font-weight:650;letter-spacing:.02em;
  white-space:nowrap;color:var(--text-dim);background:var(--surface-3,var(--surface));
  border:1px solid var(--border);border-radius:var(--r-full,999px);
  transition:color .2s ease,background-color .2s ease,border-color .2s ease}
.lft-where{margin-left:auto;font-size:.6875rem;letter-spacing:.02em;
  color:var(--text-mute);white-space:nowrap}

/* --- Fragment 1 · Taux horaire net ------------------------------------- */
.lft-rate{display:flex;align-items:flex-end;gap:.875rem;flex-wrap:wrap}
.lft-rate-old{font-size:1.125rem;font-weight:600;color:var(--text-mute);
  text-decoration:line-through;text-decoration-thickness:1px}
.lft-rate-new{font-size:clamp(1.875rem,4vw,2.5rem);font-weight:700;line-height:1;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;color:var(--text)}
.lft-rate-new small{font-size:.4em;font-weight:600;letter-spacing:0;
  color:var(--text-dim)}
.lft-why{display:flex;flex-wrap:wrap;gap:.375rem;margin-top:.875rem}
.lft-chip{padding:2px 7px;font-size:.6875rem;font-weight:600;
  font-variant-numeric:tabular-nums;color:var(--critical-text);
  background:var(--critical-soft);border-radius:var(--r-sm,8px)}

/* --- Fragment 2 · Suivi des heures ------------------------------------
   La seule BOUCLE PERMANENTE de la section, et elle porte l'argument : la
   barre dépasse la limite estimée et continue de pousser. Le débordement
   n'est pas illustré, il a lieu. Une seule rangée boucle, parce qu'une seule
   des quatre questions porte sur quelque chose qui continue d'augmenter. */
/* PAS d'overflow:hidden. La piste est l'estimation ; le dépassement doit
   SORTIR du cadre, sinon il est écrêté pile là où l'argument se joue et la
   barre se contente de remplir sa jauge. C'est le défaut qu'avait la première
   version : un dépassement invisible parce que rangé à l'intérieur. */
.lft-track{position:relative;height:34px;
  margin:1.75rem clamp(2.5rem,5vw,4rem) 0 0;
  background:var(--pd-sunken,var(--surface));border-radius:var(--r-sm,8px)}
/* Le trait d'estimation, et son libellé en VRAI texte : « estimé 12 h » est
   une donnée, donc sélectionnable et traduisible — pas du content: attr(). */
.lft-limit{position:absolute;top:-5px;bottom:-5px;left:48%;width:1px;
  background:var(--border-strong);z-index:3}
.lft-limit b{position:absolute;top:-1.35rem;left:0;transform:translateX(-50%);
  font-size:.6875rem;font-weight:600;white-space:nowrap;color:var(--text-mute)}
.lft-fill{position:absolute;inset:0 auto 0 0;z-index:1;
  border-radius:var(--r-sm,8px) 0 0 var(--r-sm,8px);
  background:linear-gradient(90deg,var(--brand-deep,var(--brand-dim)),var(--brand));
  width:48%}
/* Le dépassement, en warning, hachuré : même vocabulaire que la part retirée
   de « Comment ça marche », donc la page reste cohérente d'une section à
   l'autre. 17 px = une période exacte du motif à 135°, la boucle est
   invisible. 11 s : premier, sans harmonique avec 2,1 / 13 / 19 s. */
/* Le dépassement part du trait d'estimation et va JUSQU'À 58 % — soit 106 %
   de la piste : il sort visiblement par la droite, cap arrondi compris. */
.lft-over{position:absolute;top:0;bottom:0;left:48%;z-index:2;
  border-radius:0 var(--r-sm,8px) var(--r-sm,8px) 0;
  background:repeating-linear-gradient(135deg,
    var(--warning-soft) 0 6px,transparent 6px 12px),var(--warning-soft);
  border-left:2px solid var(--warning-text);
  animation:lft-push 11s ease-in-out infinite,lft-hatch 2.3s linear infinite}
@keyframes lft-push{
  0%{width:0}
  26%{width:58%}
  82%{width:58%}
  100%{width:0}}
@keyframes lft-hatch{from{background-position:0 0,0 0}to{background-position:-17px 0,0 0}}
.lft-hours{display:flex;align-items:baseline;gap:.5rem;margin-top:.75rem;
  font-size:.8125rem;color:var(--text-dim)}
.lft-hours b{font-size:1.0625rem;font-weight:680;font-variant-numeric:tabular-nums;
  color:var(--warning-text)}

/* --- Fragment 3 · Relances & pénalités --------------------------------- */
.lft-due{display:flex;flex-direction:column;gap:.5rem}
.lft-due-r{display:grid;grid-template-columns:minmax(0,1fr) auto auto;
  gap:.75rem;align-items:baseline;padding:.5rem .625rem;
  background:var(--pd-sunken,var(--surface));border-radius:var(--r-sm,8px);
  font-size:.875rem;color:var(--text)}
.lft-due-r span:first-child{overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.lft-late{font-size:.75rem;font-weight:650;font-variant-numeric:tabular-nums;
  color:var(--critical-text)}
.lft-amt{font-weight:650;font-variant-numeric:tabular-nums;color:var(--text)}
.lft-pen{display:flex;align-items:baseline;gap:.5rem;margin-top:.25rem;
  font-size:.8125rem;color:var(--text-dim)}
.lft-pen b{font-weight:680;font-variant-numeric:tabular-nums;
  color:var(--good-text)}

/* --- Fragment 4 · Trésorerie ------------------------------------------
   Un empilement à trois segments, à l'échelle : la part « à vous » est
   petite, et c'est tout le propos. Un camembert aurait montré la même chose
   en la rendant illisible sous 40 px. */
.lft-split{display:flex;height:30px;border-radius:var(--r-sm,8px);overflow:hidden}
.lft-seg{display:flex;align-items:center;justify-content:center;
  font-size:.6875rem;font-weight:650;font-variant-numeric:tabular-nums;
  color:var(--text);white-space:nowrap;overflow:hidden}
.lft-seg--soc{background:var(--critical-soft);color:var(--critical-text)}
.lft-seg--tax{background:var(--warning-soft);color:var(--warning-text)}
.lft-seg--you{background:var(--good-soft);color:var(--good-text)}
.lft-legend{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:.75rem;
  font-size:.75rem;color:var(--text-dim)}
.lft-legend span{display:flex;align-items:center;gap:.375rem}
.lft-dot{width:8px;height:8px;border-radius:var(--r-full,999px);flex:none}
.lft-keep{margin-top:.75rem;font-size:.8125rem;color:var(--text-dim)}
.lft-keep b{font-weight:680;font-variant-numeric:tabular-nums;color:var(--text)}

/* --- La sortie : la section passe la main -------------------------------
   Deuxième moitié de la réponse au retour « on ne comprend pas pourquoi ils
   sont là ». Une section a un rôle quand on voit ce qu'elle amène. */
.lft-out{display:flex;align-items:baseline;gap:.625rem;flex-wrap:wrap;
  margin-top:clamp(1.5rem,3vw,2.25rem);font-size:.9375rem;color:var(--text-dim)}
.lft-out b{font-weight:650;color:var(--text)}

/* --- Reveal ------------------------------------------------------------
   Réutilise le .reveal / IntersectionObserver déjà en place. Les rangées
   arrivent en cascade de 90 ms — assez pour qu'on voie l'empilement se
   construire, pas assez pour qu'on attende la quatrième. */
.lft-row{opacity:0;transform:translateY(14px);
  transition:opacity .5s var(--ease-out,ease),transform .5s var(--ease-out,ease),
    padding .3s ease}
.reveal.is-visible .lft-row,
.reveal.revealed .lft-row,
.reveal.visible .lft-row{opacity:1;transform:none}
.lft-row:nth-child(1){transition-delay:.05s}
.lft-row:nth-child(2){transition-delay:.14s}
.lft-row:nth-child(3){transition-delay:.23s}
.lft-row:nth-child(4){transition-delay:.32s}
/* Le survol doit reprendre la main sur la cascade, sinon la première rangée
   met 320 ms à s'éteindre. */
.lft-rows:hover > .lft-row{transition-delay:0s}

/* --- Étroit ------------------------------------------------------------- */
@container (max-width:700px){
  .lft-row{grid-template-columns:minmax(0,1fr);gap:1.125rem}
  .lft-row:nth-child(even) > .lft-ask,
  .lft-row:nth-child(even) > .lft-shard{order:0}
  .lft-row > .lft-shard{margin:0}
  .lft-row:nth-child(even) > .lft-shard{
    border-radius:var(--r-lg,16px) var(--r-lg,16px) var(--r-lg,16px) var(--lft-cut)}
  /* Sur mobile, éteindre les autres rangées au survol n'a aucun sens : il n'y
     a pas de survol, et un :hover collant laisserait trois rangées grises.
     Meme prefixe .lft.is-visible que la regle activee plus haut, sinon
     celle-ci (specificite plus faible) ne l'emporterait jamais. */
  .lft.is-visible .lft-rows:hover > .lft-row{opacity:1}
}

/* --- Mouvement réduit --------------------------------------------------
   Le dépassement reste VISIBLE, figé sur sa valeur haute : c'est une donnée,
   pas une décoration. Le faire disparaître retirerait l'argument. */
@media (prefers-reduced-motion:reduce){
  .lft-row,.lft-shard,.lft-tag{transition:none}
  .lft-row{opacity:1;transform:none}
  /* 58 % = le sommet du keyframe, donc le dépassement reste SORTI de la piste.
     ⚠ Cette valeur doit suivre lft-push : une version antérieure était restée
     à 37 % (la géométrie d'avant le débord, quand fill valait 63 %), et la
     barre s'arrêtait alors 53 px AVANT le bord — elle remplissait sa jauge
     sans jamais franchir l'estimation, soit l'inverse de la fonctionnalité,
     pour tous les utilisateurs en mouvement réduit. */
  .lft-over{animation:none;width:58%}
}

@media print{
  .lft-over{animation:none;width:58%}
  .lft-row{opacity:1;transform:none}
  .lft-shard{box-shadow:none}
}

/* Type de client (professionnel/particulier) dans le panneau de detail projet.
   Prefixe .pd- comme le reste du bloc, voir CLAUDE.md. Le select est stylé
   pour se fondre dans la ligne .pd-line plutot que d'y poser un controle de
   formulaire pleine largeur, qui casserait l'alignement dt/dd. */
.pd-inline-select{
  appearance:none; -webkit-appearance:none;
  background:var(--surface-2, rgba(255,255,255,.04));
  border:1px solid var(--border-soft);
  border-radius:var(--r-sm, 6px);
  color:var(--text);
  font-size:.8125rem; font-weight:600;
  padding:.25rem 1.5rem .25rem .5rem;
  cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2397a4bb' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right .4rem center;
}
.pd-inline-select:focus-visible{ outline:2px solid var(--brand); outline-offset:1px; }
.pd-inline-select:disabled{ opacity:.55; cursor:progress; }
.pd-note{
  margin:.5rem 0 0; padding:.5rem .625rem;
  border-radius:var(--r-sm, 6px);
  background:var(--warning-bg, rgba(240,180,60,.10));
  color:var(--text-dim);
  font-size:.75rem; line-height:1.45;
}

/* ==========================================================================
   ProfitTrack — landing, « Nouveautés » : 4ᵉ carte, comparaison au marché
   Préfixe .bmk-* (benchmark). Vérifié libre dans tout le dépôt : aucune
   collision avec .ptx-* / .lft-* / .lpx-* / .mkt-* / .pt-* / .bl-*.

   La structure de carte (.ptx-feature, -text, -tag, -caveat, -visual,
   .ptx-mock, .ptx-mock-screen, .ptx-mock-bar, .ptx-mock-body) est REPRISE
   telle quelle, jamais redéclarée. Ce fichier ne contient que l'intérieur du
   mockup : la règle de marché, le badge de verdict et les repères.

   ⚠ CONVENTION DE COULEUR À L'INTÉRIEUR DU MOCKUP — à lire avant de toucher.
   .ptx-mock est peint en dégradés sombres codés en dur (#22304a → #0b1120),
   volontairement : il représente l'app, qui est sombre, et il reste sombre
   dans les deux thèmes du site. Les trois mockups existants en tirent la
   conséquence et utilisent des valeurs claires FIXES à l'intérieur
   (rgba(255,255,255,…) pour .ptx-sk, etc.).
   Ce fichier fait pareil, et ce n'est pas un écart au design system : y
   employer --text, --good-text ou --critical-text produirait l'inverse de ce
   qu'on veut. En thème clair, --text vaut #0f172a et --good-text #15803d —
   du texte quasi noir et du vert foncé posés sur un fond #0b1120, donc
   illisibles. Les seuls tokens utilisés ici sont ceux qui ne basculent pas
   (--r-*) ou dont la valeur claire reste lisible sur fond sombre (--brand).
   -------------------------------------------------------------------------- */

/* --- Encres du mockup ----------------------------------------------------
   Les trois teintes de verdict reprennent les HUES de --good / --warning /
   --critical, remontées en clarté pour tenir sur le châssis sombre. Ce sont
   les mêmes couleurs perçues, pas de nouvelles couleurs de marque. */
.bmk{
  --bmk-ink:rgba(255,255,255,.92);
  --bmk-ink-dim:rgba(255,255,255,.62);
  --bmk-ink-mute:rgba(255,255,255,.42);
  --bmk-line:rgba(255,255,255,.14);
  --bmk-good:#6ee7a8;
  --bmk-warn:#f5c451;
  --bmk-crit:#ff9090;
  display:grid;gap:.875rem}

/* --- Le verdict ---------------------------------------------------------
   Première chose lue, donc premier élément du mockup. Le pictogramme est un
   chevron CSS orienté selon le verdict — pas une icône importée. */
.bmk-verdict{display:flex;align-items:center;gap:.5rem}
.bmk-badge{display:inline-flex;align-items:center;gap:.375rem;
  padding:.25rem .5625rem;font-size:.6875rem;font-weight:700;
  letter-spacing:.02em;white-space:nowrap;border-radius:var(--r-full,999px);
  color:var(--bmk-warn);background:rgba(245,196,81,.14);
  border:1px solid rgba(245,196,81,.32)}
.bmk-badge::before{content:"";width:0;height:0;flex:none;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-bottom:6px solid currentColor}
/* Les deux autres verdicts, prêts à servir : la vraie UI en affiche un des
   trois. Le mockup est figé sur « sous le marché » (voir README). */
.bmk-badge.is-in{color:var(--bmk-good);background:rgba(110,231,168,.13);
  border-color:rgba(110,231,168,.3)}
.bmk-badge.is-in::before{border-bottom:none;width:6px;height:6px;
  border-radius:50%;background:currentColor}
.bmk-badge.is-over{color:var(--bmk-crit);background:rgba(255,144,144,.13);
  border-color:rgba(255,144,144,.3)}
.bmk-badge.is-over::before{border-bottom:none;
  border-top:6px solid currentColor}
.bmk-verdict-sub{font-size:.6875rem;color:var(--bmk-ink-mute);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* --- La règle de marché -------------------------------------------------
   Une échelle unique de 300 à 800 €/j. Tous les repères sont positionnés en
   pourcentage de CETTE échelle, ce qui est ce qui rend la lecture juste :
   la fourchette, la médiane et « vous » sont comparables parce qu'ils sont
   mesurés sur la même règle — comme les trois barres de « Comment ça
   marche ». Changer une borne oblige à recalculer les quatre positions. */
.bmk-rule{position:relative;height:8px;margin:1.5rem 0 0;
  background:rgba(255,255,255,.07);border-radius:var(--r-full,999px)}
/* La fourchette basse → haute */
.bmk-band{position:absolute;top:0;bottom:0;left:30%;width:40%;
  background:linear-gradient(90deg,rgba(74,140,255,.5),rgba(74,140,255,.85));
  border-radius:var(--r-full,999px);
  transform:scaleX(0);transform-origin:35% 50%;
  transition:transform .8s var(--ease-out,cubic-bezier(.22,1,.36,1)) .15s}
/* La médiane : un trait net DANS la fourchette, jamais confondu avec « vous »
   — l'un est vertical et fin, l'autre est une pastille pleine sous la règle. */
.bmk-median{position:absolute;top:-5px;bottom:-5px;left:44%;width:2px;
  background:#fff;border-radius:1px;opacity:0;
  transition:opacity .4s var(--ease-out,ease) .75s}
.bmk-median::after{content:"";position:absolute;inset:0;
  box-shadow:0 0 0 3px rgba(10,16,32,.55)}

/* Le repère « vous » : sous la règle, pastille + tige, donc jamais
   confondable avec la médiane. Ici à 16 % — HORS fourchette, à gauche : le
   mockup illustre du même coup le cas « le repère peut tomber dehors ». */
.bmk-you{position:absolute;top:-4px;left:16%;
  display:grid;justify-items:center;gap:2px;
  transform:translate(-50%,0);
  opacity:0;transition:opacity .5s var(--ease-out,ease) .95s,
    left .8s var(--ease-out,cubic-bezier(.22,1,.36,1)) .55s}
.bmk-you-dot{width:14px;height:14px;border-radius:50%;
  background:var(--bmk-warn);border:3px solid #0d1526;
  box-shadow:0 0 0 1px rgba(245,196,81,.5),0 0 12px rgba(245,196,81,.45)}
.bmk-you-lab{padding:1px 5px;font-size:.625rem;font-weight:700;
  white-space:nowrap;color:#0d1526;background:var(--bmk-warn);
  border-radius:4px}

/* Les bornes chiffrées, alignées sous la règle. Ce sont les positions de la
   fourchette, pas les extrémités de l'échelle : la règle va de 300 à 800,
   la fourchette de 450 à 650. */
.bmk-scale{position:relative;height:1.125rem;margin-top:2.25rem;
  font-size:.625rem;font-variant-numeric:tabular-nums;
  color:var(--bmk-ink-mute)}
/* ⚠ La couleur est posée EXPLICITEMENT sur le <b>, pas héritée de .bmk-scale.
   style.css colore les <b> globalement avec var(--text) : cette règle bat
   l'héritage. En thème sombre --text est clair, donc le défaut ne se voyait
   pas ; en thème clair il vaut #0f172a, et les bornes chiffrées passaient en
   quasi-noir sur le mockup — invisibles. Même piège pour tout <b> ajouté ici. */
.bmk-scale b{position:absolute;transform:translateX(-50%);font-weight:600;
  white-space:nowrap;color:var(--bmk-ink-mute)}
.bmk-scale b.is-med{color:var(--bmk-ink);font-weight:700}

/* --- Les sources --------------------------------------------------------
   « X sources » n'est pas une décoration : c'est ce qui distingue une vraie
   recherche d'un chiffre inventé, donc ça a sa place dans le mockup. Le
   chevron dit que c'est dépliable, sans l'être ici. */
.bmk-src{display:flex;align-items:center;gap:.5rem;
  margin-top:.375rem;padding-top:.625rem;
  border-top:1px solid var(--bmk-line);
  font-size:.6875rem;color:var(--bmk-ink-dim)}
.bmk-src b{font-weight:700;color:var(--bmk-ink);
  font-variant-numeric:tabular-nums}
.bmk-src-chev{margin-left:auto;width:6px;height:6px;flex:none;
  border:solid var(--bmk-ink-mute);border-width:0 1.5px 1.5px 0;
  transform:rotate(45deg) translate(-1px,-1px)}

/* --- Apparition au scroll ------------------------------------------------
   Comme les trois autres cartes : une micro-animation d'apparition, pas de
   boucle permanente. Le déclencheur est le .reveal-scale du conteneur, donc
   les trois noms de classe possibles de l'observer, comme le fait déjà
   style.css (.is-visible / .revealed / .visible).

   L'ordre raconte la lecture réelle du résultat : la fourchette s'établit,
   la médiane se pose, puis « vous » vient s'y situer. Le repère glisse
   depuis la médiane jusqu'à sa vraie valeur — c'est le geste de
   positionnement, et il finit hors de la fourchette. */
.reveal-scale.is-visible .bmk-band,
.reveal-scale.revealed .bmk-band,
.reveal-scale.visible .bmk-band{transform:scaleX(1)}
.reveal-scale.is-visible .bmk-median,
.reveal-scale.revealed .bmk-median,
.reveal-scale.visible .bmk-median{opacity:1}
.reveal-scale.is-visible .bmk-you,
.reveal-scale.revealed .bmk-you,
.reveal-scale.visible .bmk-you{opacity:1}
/* Position de départ du repère : sur la médiane. Il ne « glisse » que parce
   que la valeur de départ diffère — sans cette règle, il apparaît en place. */
.bmk-you{left:44%}
.reveal-scale.is-visible .bmk-you,
.reveal-scale.revealed .bmk-you,
.reveal-scale.visible .bmk-you{left:16%}

/* --- Étroit --------------------------------------------------------------
   Sous 400 px de mockup, les trois bornes chiffrées se chevauchent. La
   médiane reste (c'est la valeur de référence), les deux bornes passent en
   plus petit et se collent aux extrémités de la fourchette. */
@container (max-width:400px){
  .bmk-scale{font-size:.5625rem}
  .bmk-you-lab{font-size:.5625rem}
}
.bmk-wrap{container-type:inline-size}

@media (prefers-reduced-motion:reduce){
  /* Tout est posé à l'état final : la fourchette est déployée, la médiane
     visible, et « vous » à sa VRAIE position (16 %, hors fourchette). Le
     laisser sur sa position de départ (44 %, la médiane) afficherait un
     verdict « sous le marché » avec un repère pile sur la médiane — soit
     l'inverse de ce que le badge annonce. */
  .bmk-band{transform:scaleX(1);transition:none}
  .bmk-median{opacity:1;transition:none}
  .bmk-you{opacity:1;left:16%;transition:none}
}

@media print{
  .bmk-band{transform:scaleX(1)}
  .bmk-median{opacity:1}
  .bmk-you{opacity:1;left:16%}
}

/* ==========================================================================
   ProfitTrack — landing « Nouveautés » : 5ᵉ carte + regroupement de section
   Deux préfixes, vérifiés libres dans tout le dépôt :
     .hyp-*  intérieur du mockup du simulateur (hypothèses)
     .nvx-*  regroupement de la section (nouveautés)
   Aucune classe .ptx-* n'est redéclarée.
   ========================================================================== */

/* ==========================================================================
   PARTIE 2 · LE REGROUPEMENT
   ==========================================================================
   Cinq cartes de même poids, de même forme, qui se suivent : le visiteur
   n'a aucun moyen de savoir combien il en reste ni laquelle le concerne. Le
   problème n'est pas leur nombre, c'est que la section n'a pas de carte du
   territoire — on y entre sans en connaître l'étendue.

   Deux groupes nommés de 3 et 2, chacun annoncé par un titre et la liste de
   ce qu'il contient en liens d'ancrage. Cinq éléments deviennent deux, la
   portée est annoncée, et on peut sauter directement à ce qu'on cherche.
   Rien n'est masqué, rien ne dépend de JS, tout reste dans le DOM.

   ⚠ LE REGROUPEMENT CHANGE LE RANG DES CARTES, DONC LEUR CÔTÉ.
   L'alternance de style.css est calculée sur :nth-child :
     .ptx-feature:nth-child(even) > .ptx-feature-visual { order: -1 }
   En scindant .ptx-features en deux conteneurs, la numérotation repart à 1
   dans le second : la carte 4 y devient rang 1 (impair → visuel à droite)
   alors qu'elle doit garder son visuel à gauche, et la carte 5 rang 2
   (pair → visuel à gauche) alors qu'elle le veut à droite. Les deux sont
   inversées. D'où la règle ci-dessous, qui inverse la parité du SEUL second
   groupe pour que l'alternance visuelle se poursuive à travers la coupure.
   Sans elle, deux cartes consécutives auraient leur visuel du même côté. */
.nvx-grp--b .ptx-feature:nth-child(odd)  > .ptx-feature-visual{order:-1}
.nvx-grp--b .ptx-feature:nth-child(even) > .ptx-feature-visual{order:0}

.nvx-grp{position:relative}
.nvx-grp + .nvx-grp{margin-top:clamp(1.5rem,4vw,3rem)}

/* L'en-tête de groupe : un titre, une ligne de cadrage, et le sommaire.
   Le filet supérieur marque la coupure sans ajouter de surface — la section
   reste une suite de cartes, pas une pile de blocs encadrés. */
.nvx-head{display:grid;gap:.75rem;
  padding-top:clamp(1.5rem,3.5vw,2.5rem);
  border-top:1px solid var(--border)}
.nvx-grp:first-child .nvx-head{border-top:0;padding-top:0}
.nvx-num{font-size:.75rem;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--text-head)}
.nvx-title{margin:0;font-size:clamp(1.125rem,2.2vw,1.5rem);font-weight:660;
  letter-spacing:-.022em;line-height:1.2;color:var(--text)}
.nvx-lead{margin:0;max-width:52ch;font-size:.9375rem;line-height:1.6;
  color:var(--text-dim);text-wrap:pretty}

/* Le sommaire : des ancres, pas des filtres. Un lien d'ancrage fonctionne
   sans JS, se partage, s'indexe, et ne masque rien. */
.nvx-toc{display:flex;flex-wrap:wrap;gap:.5rem;margin:.375rem 0 0;
  padding:0;list-style:none}
.nvx-toc a{display:inline-flex;align-items:center;gap:.4375rem;
  padding:.3125rem .6875rem;font-size:.8125rem;font-weight:600;
  text-decoration:none;color:var(--text-dim);
  background:var(--surface-2);border:1px solid var(--border);
  border-radius:var(--r-full,999px);
  transition:color .2s var(--ease,ease),border-color .2s var(--ease,ease),
    background-color .2s var(--ease,ease),transform .2s var(--ease,ease)}
.nvx-toc a::before{content:"";width:5px;height:5px;flex:none;
  border-radius:50%;background:var(--border-strong);
  transition:background-color .2s var(--ease,ease)}
.nvx-toc a:hover,.nvx-toc a:focus-visible{color:var(--text);
  border-color:var(--brand-line);background:var(--brand-soft);
  transform:translateY(-1px)}
.nvx-toc a:hover::before,.nvx-toc a:focus-visible::before{background:var(--brand)}

/* La carte visée par une ancre s'annonce, sinon on atterrit sans savoir où.
   :target ne dépend pas de JS. */
.ptx-feature:target > .ptx-feature-text{position:relative}
.ptx-feature:target > .ptx-feature-text::before{content:"";
  position:absolute;left:-1rem;top:.25rem;bottom:.25rem;width:2px;
  border-radius:2px;background:var(--brand);
  animation:nvx-hit 2.4s var(--ease-out,ease) forwards}
@keyframes nvx-hit{0%,55%{opacity:1}100%{opacity:0}}
/* scroll-margin : sans lui, l'ancre colle la carte au bord haut du viewport
   et le titre passe sous une éventuelle barre fixe. */
.ptx-feature[id]{scroll-margin-top:clamp(1.5rem,6vh,4.5rem)}

@media (max-width:960px){
  /* En colonne, style.css remet déjà tous les visuels sous le texte
     (order:0). La règle du groupe B doit s'effacer aussi, sinon elle
     rebascule un visuel au-dessus de son texte — on lit d'abord, on voit
     ensuite, sur toutes les cartes. */
  .nvx-grp--b .ptx-feature:nth-child(odd) > .ptx-feature-visual{order:0}
}

@media (prefers-reduced-motion:reduce){
  .nvx-toc a{transition:none}
  .ptx-feature:target > .ptx-feature-text::before{animation:none;opacity:1}
}

/* ==========================================================================
   PARTIE 1 · LE MOCKUP DU SIMULATEUR
   ==========================================================================
   ⚠ CONVENTION DE COULEUR — identique aux quatre mockups existants.
   .ptx-mock est peint en dégradés sombres codés en dur et reste sombre dans
   les DEUX thèmes du site : il représente l'app. Employer --text, --good-text
   ou --critical-text à l'intérieur donnerait, en thème clair, du quasi-noir
   (#0f172a) et du vert foncé (#15803d) sur un fond #0b1120. L'intérieur
   utilise donc des encres claires fixes, aux teintes des tokens d'état.
   Le texte de la carte, lui, est entièrement en tokens. */
.hyp{
  --hyp-ink:rgba(255,255,255,.92);
  --hyp-dim:rgba(255,255,255,.6);
  --hyp-mute:rgba(255,255,255,.4);
  --hyp-line:rgba(255,255,255,.12);
  --hyp-good:#6ee7a8;
  --hyp-warn:#f5c451;
  display:grid;gap:.75rem}

/* ⚠ Tout <b> placé ici DOIT porter sa propre couleur : style.css contient
   `strong, b { color: var(--text) }`, et un sélecteur d'élément bat
   l'héritage. En thème sombre --text est clair, donc le défaut ne se voit
   pas — il n'existe que dans le thème clair. Piège déjà rencontré sur le
   mockup de comparaison au marché. */

/* --- Les deux modes -----------------------------------------------------
   Le cœur de la fonctionnalité, donc en tête du mockup et jamais réduit à
   un seul mode. Les deux onglets portent leur unité (€/projet, €/mois) :
   quatre caractères qui disent en quoi les deux modes diffèrent, sans avoir
   à montrer deux formulaires. */
.hyp-modes{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:3px;
  background:rgba(0,0,0,.3);border-radius:var(--r-sm,8px)}
.hyp-mode{display:grid;gap:1px;padding:.375rem .5rem;text-align:center;
  border-radius:calc(var(--r-sm,8px) - 3px);
  font-size:.6875rem;font-weight:650;color:var(--hyp-mute)}
.hyp-mode small{font-size:.5625rem;font-weight:600;letter-spacing:.02em;
  color:var(--hyp-mute);opacity:.8}
.hyp-mode.is-on{color:var(--hyp-ink);background:rgba(74,140,255,.22);
  box-shadow:inset 0 0 0 1px rgba(74,140,255,.45)}
.hyp-mode.is-on small{color:rgba(255,255,255,.7)}

/* --- Les hypothèses saisies --------------------------------------------- */
.hyp-in{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}
.hyp-f{padding:.4375rem .5rem;background:rgba(255,255,255,.045);
  border:1px solid var(--hyp-line);border-radius:6px;min-width:0}
.hyp-f span{display:block;font-size:.5625rem;letter-spacing:.02em;
  color:var(--hyp-mute);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.hyp-f b{display:block;margin-top:1px;font-size:.8125rem;font-weight:700;
  font-variant-numeric:tabular-nums;color:var(--hyp-ink)}

/* --- Les résultats, en direct -------------------------------------------
   Deux chiffres, pas quatre : le taux horaire et le profit sont ce que la
   fonctionnalité calcule, l'écart à l'objectif est ce qu'elle en conclut.
   Le troisième bloc est donc d'une autre nature, et il est traité comme
   tel — en pastille, pas en tuile. */
.hyp-out{display:grid;grid-template-columns:1fr 1fr;gap:5px}
.hyp-r{padding:.5rem .5625rem;background:rgba(255,255,255,.055);
  border:1px solid var(--hyp-line);border-radius:7px;min-width:0}
.hyp-r span{display:block;font-size:.5625rem;letter-spacing:.02em;
  color:var(--hyp-mute);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.hyp-r b{display:block;margin-top:2px;font-size:1.0625rem;font-weight:700;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;
  color:var(--hyp-ink)}
.hyp-gap{display:flex;align-items:center;gap:.4375rem;
  padding:.375rem .5625rem;border-radius:6px;
  background:rgba(245,196,81,.14);
  box-shadow:inset 0 0 0 1px rgba(245,196,81,.3);
  font-size:.625rem;color:var(--hyp-dim)}
.hyp-gap b{margin-left:auto;font-size:.75rem;font-weight:700;
  font-variant-numeric:tabular-nums;color:var(--hyp-warn)}

/* --- Les deux recommandations -------------------------------------------
   La sortie utile de la fonctionnalité : pas un constat, deux leviers. */
.hyp-rec{display:grid;gap:3px;padding-top:.5rem;
  border-top:1px solid var(--hyp-line)}
.hyp-rec-r{display:flex;align-items:baseline;gap:.4375rem;
  font-size:.625rem;color:var(--hyp-dim)}
.hyp-rec-r::before{content:"";width:4px;height:4px;flex:none;
  border-radius:50%;background:var(--hyp-good)}
.hyp-rec-r b{margin-left:auto;font-size:.6875rem;font-weight:700;
  font-variant-numeric:tabular-nums;color:var(--hyp-good)}

/* --- Apparition au scroll -----------------------------------------------
   Comme les quatre autres cartes : une apparition, pas de boucle.
   L'ordre est causal — les hypothèses d'abord, les résultats ensuite, les
   recommandations en dernier. C'est ce que fait le simulateur : il déduit.
   Un fondu global aurait montré un écran ; cet ordre montre un calcul. */
.hyp-out,.hyp-gap,.hyp-rec{opacity:0;transform:translateY(6px);
  transition:opacity .5s var(--ease-out,ease),transform .5s var(--ease-out,ease)}
.hyp-out{transition-delay:.35s}
.hyp-gap{transition-delay:.5s}
.hyp-rec{transition-delay:.65s}
.reveal-scale.is-visible .hyp-out,.reveal-scale.revealed .hyp-out,
.reveal-scale.visible .hyp-out,
.reveal-scale.is-visible .hyp-gap,.reveal-scale.revealed .hyp-gap,
.reveal-scale.visible .hyp-gap,
.reveal-scale.is-visible .hyp-rec,.reveal-scale.revealed .hyp-rec,
.reveal-scale.visible .hyp-rec{opacity:1;transform:none}

/* Sous 400 px de mockup, trois colonnes d'hypothèses ne tiennent plus :
   les libellés se tronquent avant les valeurs. Deux colonnes, la troisième
   passant sous les deux premières. */
.hyp-wrap{container-type:inline-size}
@container (max-width:400px){
  .hyp-in{grid-template-columns:1fr 1fr}
  .hyp-f:last-child{grid-column:1 / -1}
}

@media (prefers-reduced-motion:reduce){
  .hyp-out,.hyp-gap,.hyp-rec{opacity:1;transform:none;transition:none}
}
@media print{
  .hyp-out,.hyp-gap,.hyp-rec{opacity:1;transform:none}
}

/* ============================================================
   SEQUENCE DE RELANCE (.rsq-*) + MISE EN DEMEURE (.fnx-*)
   + HISTORIQUE (.tlx-*) + VENTILATION DES PENALITES (.pnx-*)
   Prefixes scopes, meme convention que .np-* / .pd-* : ces blocs
   ajoutent des classes de composant et ne doivent jamais entrer en
   collision avec les noms globaux courts (.card, .btn, .row).
   ============================================================ */

.rsq{ margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--border); }
.rsq-head{ margin-bottom: 14px; }

.rsq-step,
.rsq-interval{
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.rsq-interval{ border-bottom: none; align-items: flex-start; }

.rsq-dot{
  width: 9px; height: 9px; flex: 0 0 9px;
  border-radius: 50%;
  background: var(--brand);
}
/* L'etape a validation humaine se distingue a l'oeil des deux etapes
   automatiques : c'est la seule information de ce bloc qu'un utilisateur
   ne doit pas pouvoir confondre. */
.rsq-dot-manual{ background: var(--warning); }

.rsq-step-body{ flex: 1 1 190px; min-width: 0; }
.rsq-step-name{ font-size: 14px; font-weight: 600; color: var(--text); }

.rsq-badge{
  display: inline-block;
  margin-top: 3px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .01em;
}
.rsq-badge-auto{ background: rgba(74, 140, 255, .14); color: #7aa8ff; }
.rsq-badge-manual{ background: var(--warning-soft); color: var(--warning-text, #fbbf24); }

.rsq-days{ display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-mute); }
.rsq-days input{
  width: 72px;
  padding: 7px 9px;
  text-align: center;
  border-radius: var(--r-sm, 8px);
  border: 1px solid var(--border);
  background: var(--page-deep, #05080f);
  color: var(--text);
  font-size: 14px;
}

/* Libelle accessible pour les champs jours : le nom de l'etape est deja
   visible a cote, mais un lecteur d'ecran qui tabule directement sur
   l'input n'entendrait sinon qu'un nombre sans contexte. */
.rsq-sr{
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.rsq-legal{
  margin: 14px 0 0;
  padding: 11px 13px;
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .03);
  border: 1px solid var(--border);
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-mute);
}

.rsq-actions{ display: flex; align-items: center; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.rsq-status{ font-size: 12.5px; color: var(--text-mute); }
.rsq-status-ok{ color: var(--success-text, #4ade80); }
.rsq-status-err{ color: var(--critical-text, #f87171); }

/* ---- Ventilation des penalites, panneau projet ---- */
.pnx{
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: rgba(255, 255, 255, .02);
}
.pnx-title{ font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-mute); margin-bottom: 9px; }
.pnx-row{ display: flex; justify-content: space-between; gap: 12px; font-size: 13px; padding: 4px 0; color: var(--text-dim); }
.pnx-row strong{ color: var(--text); }
.pnx-total{ border-top: 1px solid var(--border); margin-top: 6px; padding-top: 8px; font-size: 14.5px; font-weight: 700; color: var(--text); }
.pnx-note{ margin-top: 9px; font-size: 11.5px; line-height: 1.5; color: var(--text-mute); }

/* ---- Mise en demeure ---- */
.fnx-warn{
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--warning-soft);
  border: 1px solid rgba(242, 176, 30, .3);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--warning-text, #fbbf24);
  margin-bottom: 14px;
}
.fnx-deadline{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 12px 0; font-size: 13px; color: var(--text-dim); }
.fnx-deadline input{
  width: 74px; padding: 7px 9px; text-align: center;
  border-radius: var(--r-sm, 8px); border: 1px solid var(--border);
  background: var(--page-deep, #05080f); color: var(--text); font-size: 14px;
}
.fnx-actions{ display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }

/* ---- Timeline / historique ---- */
.tlx{ margin: 4px 0 0; }
.tlx-item{ position: relative; padding: 0 0 18px 22px; border-left: 2px solid var(--border); }
.tlx-item:last-child{ border-left-color: transparent; padding-bottom: 4px; }
.tlx-item::before{
  content: "";
  position: absolute;
  left: -6px; top: 3px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--text-mute);
  border: 2px solid var(--surface);
}
.tlx-item.is-sent::before{ background: var(--brand); }
.tlx-item.is-notice::before{ background: var(--warning); }
.tlx-item.is-paid::before{ background: var(--success, #22c55e); }
.tlx-when{ font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-mute); }
.tlx-what{ font-size: 14px; font-weight: 600; color: var(--text); margin: 2px 0 3px; }
.tlx-meta{ font-size: 12px; color: var(--text-mute); }
.tlx-toggle{
  margin-top: 6px; padding: 0;
  background: none; border: none;
  color: var(--brand); font-size: 12px; cursor: pointer; text-decoration: underline;
}
.tlx-snapshot{
  display: none;
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--page-deep, #05080f);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  line-height: 1.55;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 260px;
  overflow: auto;
}
.tlx-snapshot.is-open{ display: block; }
.tlx-empty{ font-size: 13px; color: var(--text-mute); font-style: italic; padding: 8px 0; }

@media (max-width: 560px){
  .rsq-step, .rsq-interval{ align-items: flex-start; }
  .rsq-days{ width: 100%; }
  .fnx-actions .btn{ width: 100%; }
}

/* ---- Maquette "relances & pénalités" de la landing (.ptx-seq*) ----
   Reprend le gabarit .ptx-mock des autres cartes de fonctionnalité ; seules
   les classes propres à cette maquette sont ajoutées ici. */
.ptx-seq{ display: grid; gap: 9px; margin-bottom: 12px; }

.ptx-seq-step{ display: flex; align-items: center; gap: 9px; font-size: 10.5px; }
.ptx-seq-dot{
  width: 8px; height: 8px; flex: 0 0 8px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .18);
}
.ptx-seq-step.is-done .ptx-seq-dot{ background: #4a8cff; }
.ptx-seq-step.is-done .ptx-seq-label{ color: var(--text-dim); }
/* L'étape en attente de validation humaine est la seule en ambre : c'est ce
   qui doit rester lisible d'un coup d'œil sur la maquette. */
.ptx-seq-step.is-pending .ptx-seq-dot{
  background: var(--warning, #f2b01e);
  box-shadow: 0 0 0 3px rgba(242, 176, 30, .18);
}
.ptx-seq-step.is-pending .ptx-seq-label{ color: var(--warning-text, #fbbf24); font-weight: 600; }
.ptx-seq-label{ color: var(--text-mute); }

.ptx-seq-late{
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(220, 74, 74, .16);
  color: #f87171;
  font-size: 9.5px;
  font-weight: 700;
}

.ptx-seq-amounts{ border-top: 1px solid rgba(255, 255, 255, .08); padding-top: 9px; display: grid; gap: 6px; }
.ptx-seq-line{ display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 10.5px; color: var(--text-mute); }
.ptx-seq-pen{ color: #f87171; font-weight: 600; }
.ptx-seq-total{
  border-top: 1px solid rgba(255, 255, 255, .08);
  padding-top: 7px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text, #f2f6fb);
}

/* ==========================================================================
   ProfitTrack — landing : les deux garanties du récurrent, fusionnées dans
   le panneau Abonnement de la bascule (.lsw-panel--a)
   Préfixe .lsg-* (landing subscription guarantees), vérifié libre : aucune
   collision avec .lft-* / .lsw-* / .ptx-* / .lpx-* / .hyp-* / .bl-*

   CONTEXTE. La section « Ça marche aussi pour le récurrent » (.rcr-*) est
   retirée : elle précédait immédiatement le panneau Abonnement, donc la page
   présentait deux blocs « abonnement » consécutifs. Ses deux garanties, elles,
   sont conservées et déplacées ICI.

   LE CHOIX DE FORME. Ce ne sont pas deux réponses de plus : ce sont les
   CONDITIONS sous lesquelles les quatre réponses du panneau tiennent. D'où
   un bandeau de prémisse en tête de panneau — subordonné en poids,
   structurant en position — et non deux cartes de plus au même niveau que
   les quatre questions.

   Trois conséquences de ce statut, à ne pas « corriger » :
   - PAS de chrome de carte (ni fond, ni ombre, ni rayon) : deux filets
     horizontaux seulement. Une carte en ferait un contenu ; un filet en fait
     un cadre de lecture.
   - PAS d'animation d'entrée. Une prémisse est là avant ce qu'elle fonde ;
     la faire apparaître après les questions inverserait la logique. C'est
     aussi ce qui la rend visible sans JS, alors que .lft-row démarre à
     opacity:0.
   - Corps de texte plus petit que celui des questions, et titres en 14 px :
     le bandeau doit se lire d'un coup d'œil ou pas du tout.
   -------------------------------------------------------------------------- */

/* Le bandeau ne vit QUE dans le panneau abonnement. Le sélecteur est ancré
   pour que rien ne puisse fuir vers .lsw-panel--u. */
.lsw-panel--a .lsg{
  margin:0 0 clamp(1.5rem,3vw,2.25rem);
  padding:clamp(.875rem,2vw,1.25rem) 0;
  border-top:1px solid var(--border);
  border-bottom:1px solid var(--border-soft)}

/* --- La prémisse ------------------------------------------------------
   Une seule ligne, et c'est elle qui fait la synthèse : les deux garanties
   ne sont pas deux faits indépendants, elles répondent à la même question
   tacite — « ces chiffres tiennent-ils si mes paliers sont hétérogènes et ma
   saisie en retard ? ». Sans cette ligne, le bandeau redevient deux cartes. */
.lsg-premise{display:block;margin:0 0 .875rem;
  font-size:.8125rem;font-weight:600;line-height:1.5;
  color:var(--text-dim);text-wrap:pretty}
.lsg-premise b{font-weight:700;color:var(--brand-text)}

/* --- Les deux clauses -------------------------------------------------
   Un filet vertical entre les deux, pas deux fonds : elles se lisent comme
   deux clauses d'une même phrase. En étroit le filet devient horizontal. */
.lsg-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(1rem,2.4vw,2rem)}
.lsg-c{min-width:0}
.lsg-c + .lsg-c{padding-left:clamp(1rem,2.4vw,2rem);
  border-left:1px solid var(--border-soft)}
.lsg-ct{margin:0 0 .3125rem;font-size:.875rem;font-weight:660;
  letter-spacing:-.01em;line-height:1.3;color:var(--text)}
.lsg-cp{margin:0;font-size:.8125rem;line-height:1.55;color:var(--text-dim);
  text-wrap:pretty}
.lsg-cp b{font-weight:650;color:var(--text)}

/* --- Preuve 1 · la conversion ----------------------------------------
   Les TROIS ABONNÉS NOMMÉS DANS LA RANGÉE 1, avec leur fréquence réelle et
   leur équivalent mensuel. Vérifiable de tête (147/3 = 49 · 348/12 = 29), et
   l'annuel affiche le plus gros prix pour le coût mensuel le plus faible.

   ⚠ CONTRAINTE DE COHÉRENCE, la plus importante de ce bandeau. Les trois
   équivalents (89 / 49 / 29 €) sont EXACTEMENT les trois montants affichés
   par la rangée 1, quatre lignes plus bas sur le même panneau. Ne remettez
   pas de noms de PALIERS ici : trois paliers nommés formeraient un système
   clos dont on peut déduire un MRR qui ne tombera jamais sur les 552 €
   annoncés. Trois ABONNÉS ne ferment rien — ce sont trois cas parmi vingt. */
.lsg-conv{display:grid;gap:2px;margin-top:.75rem;
  font-variant-numeric:tabular-nums}
.lsg-cv{display:grid;grid-template-columns:minmax(0,1fr) 1.25rem minmax(0,auto);
  align-items:center;gap:.5rem;
  padding:.3125rem .5rem;border-radius:var(--r-sm,8px);
  background:var(--pd-sunken,var(--surface-2));
  border:1px solid var(--border-soft)}
.lsg-cv-raw{font-size:.75rem;color:var(--text-dim);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.lsg-cv-arw{position:relative;height:2px;border-radius:1px;
  background:var(--brand)}
.lsg-cv-arw::after{content:"";position:absolute;right:-1px;top:-3px;
  width:0;height:0;border-left:5px solid var(--brand);
  border-top:4px solid transparent;border-bottom:4px solid transparent}
.lsg-cv-eq{font-size:.8125rem;font-weight:700;color:var(--text);
  white-space:nowrap}

/* --- Preuve 2 · l'historique non rétroactif --------------------------
   Trois mois, dont DEUX verrouillés. Le mois en cours doit être verrouillé
   lui aussi : c'est le point le plus contre-intuitif de la garantie. Un
   lecteur qui voit « août » changer croit que sa saisie du jour a déformé
   le mois courant. */
.lsg-mths{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:.25rem;margin-top:.75rem;font-variant-numeric:tabular-nums}
.lsg-m{padding:.375rem .4375rem;border-radius:var(--r-sm,8px);
  background:var(--pd-sunken,var(--surface-2));
  border:1px solid var(--border-soft);text-align:center}
.lsg-m-k{display:block;font-size:.6875rem;color:var(--text-dim)}
.lsg-m-v{display:block;font-size:.9375rem;font-weight:700;color:var(--text)}
.lsg-m-s{display:block;margin-top:.125rem;font-size:.625rem;
  letter-spacing:.02em;color:var(--good-text)}
/* Le mois qui change : bordure de marque, jamais un fond plein — le bandeau
   n'a droit à aucun aplat, sinon il redevient une carte. */
.lsg-m--next{background:var(--brand-soft);border-color:var(--brand-line)}
.lsg-m--next .lsg-m-s{color:var(--brand-text)}
.lsg-drop{display:block;margin-top:.5rem;font-size:.75rem;line-height:1.5;
  color:var(--text-dim)}
.lsg-drop b{font-weight:650;color:var(--text)}

/* --- Étroit ------------------------------------------------------------
   Le filet vertical passe à l'horizontale. En dessous de deux colonnes, un
   filet latéral ne sépare plus rien et devient un liseré décoratif — ce que
   la page bannit explicitement. */
@container (max-width:700px){
  .lsg-pair{grid-template-columns:minmax(0,1fr);gap:1rem}
  .lsg-c + .lsg-c{padding-left:0;padding-top:1rem;
    border-left:0;border-top:1px solid var(--border-soft)}
}
@media (max-width:560px){
  .lsg-pair{grid-template-columns:minmax(0,1fr);gap:1rem}
  .lsg-c + .lsg-c{padding-left:0;padding-top:1rem;
    border-left:0;border-top:1px solid var(--border-soft)}
}

/* Rien à couper en mouvement réduit : le bandeau n'anime rien. C'est une
   propriété du choix de forme, pas un oubli. */

@media print{
  /* Les deux garanties sont du contenu de plein droit : elles s'impriment. */
  .lsw-panel--a .lsg{break-inside:avoid}
}

/* ==========================================================================
   ProfitTrack — landing : bascule « Projet unitaire / Abonnement »
   sur la section des quatre questions (.lft-*)
   Préfixe .lsw-* (landing switch), vérifié libre dans le dépôt.
   Ne redéclare AUCUNE classe .lft-* : la structure de carte est réutilisée
   telle quelle, seul le contenu change. Tokens uniquement.

   MÉCANISME : deux radios cachés + sélecteurs de frère. Aucun JS.
   -------------------------------------------------------------------------- */

.lsw{margin:0 0 clamp(1.75rem,3.5vw,2.75rem)}

/* Les radios : cachés mais FOCUSABLES. Pas display:none, qui les retirerait
   de l'ordre de tabulation et rendrait la bascule inaccessible au clavier. */
.lsw-radio{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  border:0}

/* --- La barre ----------------------------------------------------------
   Deux pilules dans un rail, comme le tableau de bord. */
.lsw-bar{position:relative;display:grid;grid-template-columns:repeat(2,1fr);
  gap:0;padding:4px;width:fit-content;max-width:100%;margin:0 auto;
  background:var(--pd-sunken,var(--surface));
  border:1px solid var(--border);border-radius:var(--r-md,12px)}
/* 50 % se mesure sur la BOITE DE PADDING (padding 4px de chaque cote) :
   calc(50% - 4px) vaut donc exactement une piste, et translateX(100%) tombe
   pile sur le debut de la seconde. Les trois "4px" sont lies au padding
   ci-dessus — les changer ensemble ou pas du tout. */
.lsw-ind{position:absolute;top:4px;bottom:4px;left:4px;z-index:0;
  width:calc(50% - 4px);
  background:var(--surface-2);border:1px solid var(--border-strong);
  border-radius:calc(var(--r-md,12px) - 4px);
  box-shadow:var(--sh-1,0 1px 3px rgba(0,0,0,.2));
  transition:transform .34s var(--ease-out,cubic-bezier(.22,1,.36,1))}
.lsw-btn{position:relative;z-index:1;display:inline-flex;align-items:center;
  justify-content:center;gap:.5rem;
  min-height:54px;padding:.875rem 2rem;
  font-size:.9375rem;font-weight:640;letter-spacing:-.01em;
  color:var(--text-dim);cursor:pointer;white-space:nowrap;
  border-radius:calc(var(--r-md,12px) - 4px);
  transition:color .2s var(--ease-out,ease),
             box-shadow .22s var(--ease-out,ease)}
/* Le gradient est porte par un ::before a z-index:-1 : .lsw-btn cree deja son
   propre contexte d'empilement (z-index:1), donc -1 le place derriere le
   libelle mais DEVANT .lsw-ind (z-index:0, frere). Un background direct sur
   .lsw-btn n'aurait pas pu etre anime en opacite sans clignoter. */
.lsw-btn::before{content:"";position:absolute;inset:0;z-index:-1;
  border-radius:inherit;opacity:0;
  background:linear-gradient(135deg,var(--brand) 0%,var(--brand-deep) 100%);
  transition:opacity .22s var(--ease-out,ease)}
.lsw-btn:hover{color:#fff;box-shadow:0 6px 18px -4px rgba(74,140,255,.45)}
.lsw-btn:hover::before{opacity:1}
.lsw-btn:hover i{background:#fff}
.lsw-btn i{width:7px;height:7px;flex:none;border-radius:2px;
  background:var(--text-mute);
  transition:background-color .2s ease}

/* --- L'état, sans un seul id --------------------------------------------
   nth-of-type sur les radios plutôt que des id : le CSS reste valable quel
   que soit le nom des id du balisage. */
.lsw-radio:nth-of-type(1):checked ~ .lsw-bar .lsw-btn:nth-of-type(1),
.lsw-radio:nth-of-type(2):checked ~ .lsw-bar .lsw-btn:nth-of-type(2){
  color:var(--text)}
.lsw-radio:nth-of-type(1):checked ~ .lsw-bar .lsw-btn:nth-of-type(1) i{
  background:var(--brand)}
.lsw-radio:nth-of-type(2):checked ~ .lsw-bar .lsw-btn:nth-of-type(2) i{
  background:var(--brand)}
.lsw-radio:nth-of-type(2):checked ~ .lsw-bar .lsw-ind{
  transform:translateX(100%)}

/* Anneau de focus porté par la pilule, pas par le radio invisible. */
.lsw-radio:nth-of-type(1):focus-visible ~ .lsw-bar .lsw-btn:nth-of-type(1),
.lsw-radio:nth-of-type(2):focus-visible ~ .lsw-bar .lsw-btn:nth-of-type(2){
  outline:2px solid var(--brand);outline-offset:2px}

/* --- Les deux panneaux ---------------------------------------------------
   Les DEUX sont dans le DOM en permanence : indexables, et présents sans JS.
   ⚠ Des classes explicites, PAS :nth-of-type sur les panneaux : nth-of-type
   compte par TYPE d'élément parmi les frères, et .lsw-bar est un <div> qui
   précède les panneaux — ils seraient div n°2 et n°3, jamais n°1. */
.lsw-panel{display:none}
.lsw-radio:nth-of-type(1):checked ~ .lsw-panel--u,
.lsw-radio:nth-of-type(2):checked ~ .lsw-panel--a{display:block}

/* --- Fragment A1 · Revenu moyen par abonné ------------------------------
   Réutilise .lft-due / .lft-due-r : seule la troisième colonne change de
   sens — ce n'est plus un montant dû mais un revenu par heure de support. */
.lsw-arpu{display:flex;align-items:flex-end;gap:.875rem;flex-wrap:wrap;
  margin-bottom:1rem}
.lsw-arpu-v{font-size:clamp(1.875rem,4vw,2.5rem);font-weight:700;line-height:1;
  letter-spacing:-.03em;font-variant-numeric:tabular-nums;color:var(--text)}
.lsw-arpu-v small{font-size:.4em;font-weight:600;letter-spacing:0;
  color:var(--text-dim)}
.lsw-arpu-n{font-size:.8125rem;color:var(--text-dim)}
.lsw-ph{font-weight:650;font-variant-numeric:tabular-nums;
  color:var(--good-text)}
.lsw-ph--bad{color:var(--critical-text)}
.lsw-sup{font-size:.75rem;font-variant-numeric:tabular-nums;
  color:var(--text-dim)}

/* --- Fragment A2 · Seuil de rentabilité ----------------------------------
   Même mécanisme que la barre du mode unitaire, sens INVERSÉ : là-bas la
   barre franchit l'estimation et c'est mauvais ; ici elle franchit le seuil
   et c'est bon. */
.lsw-track{position:relative;height:34px;
  margin:1.75rem clamp(2.5rem,5vw,4rem) 0 0;
  background:var(--pd-sunken,var(--surface));border-radius:var(--r-sm,8px)}
/* Le remplissage s'arrête AU seuil (33 %), il ne le dépasse pas — la marge
   ne doit jamais lui être superposée. */
.lsw-fill{position:absolute;inset:0 auto 0 0;z-index:1;width:0;
  border-radius:var(--r-sm,8px) 0 0 var(--r-sm,8px);
  background:linear-gradient(90deg,var(--brand-deep,var(--brand-dim)),var(--brand));
  animation:lsw-grow 11s ease-in-out infinite}
@keyframes lsw-grow{
  0%{width:0}
  16%{width:33%}
  86%{width:33%}
  100%{width:0}}
.lsw-mark{position:absolute;top:-5px;bottom:-5px;left:33%;width:1px;
  background:var(--border-strong);z-index:3}
/* --text-dim et NON --text-mute : mesuré à 3,45:1 sur --surface-2 en thème
   sombre à 11 px, sous le seuil AA. Ce libellé nomme le seuil que la barre
   franchit — il ne peut pas être le moins lisible de la carte. Fond de
   référence : --surface-2 (le fragment), le libellé est positionné hors de
   la boîte de son parent (top:-1.35rem). */
.lsw-mark b{position:absolute;top:-1.35rem;left:0;transform:translateX(-50%);
  font-size:.6875rem;font-weight:600;white-space:nowrap;color:var(--text-dim)}
/* La marge PREND LE RELAIS au seuil : elle démarre à 16 % du cycle, l'instant
   exact où lsw-grow atteint le trait. */
.lsw-over{position:absolute;top:0;bottom:0;left:33%;width:0;z-index:2;
  border-radius:0 var(--r-sm,8px) var(--r-sm,8px) 0;
  background:repeating-linear-gradient(135deg,
    var(--good-soft) 0 6px,transparent 6px 12px),var(--good-soft);
  border-left:2px solid var(--good-text);
  animation:lsw-margin 11s ease-in-out infinite,lsw-hatch 2.3s linear infinite}
@keyframes lsw-margin{
  0%,16%{width:0}
  34%,86%{width:50%}
  100%{width:0}}
@keyframes lsw-hatch{from{background-position:0 0,0 0}to{background-position:-17px 0,0 0}}
.lsw-count{display:flex;align-items:baseline;gap:.5rem;margin-top:.75rem;
  font-size:.8125rem;color:var(--text-dim)}
.lsw-count b{font-size:1.0625rem;font-weight:680;
  font-variant-numeric:tabular-nums;color:var(--good-text)}

/* --- Fragment A3 · Évolution des abonnés ---------------------------------
   Colonnes empilées par palier. Le TOTAL monte pendant qu'un palier baisse :
   c'est l'argument, une courbe de total unique l'aurait caché. */
.lsw-cap{margin:0 0 .5rem;font-size:.6875rem;font-weight:600;
  letter-spacing:.03em;text-transform:uppercase;color:var(--text-dim)}
/* 160px, pas 104 : le plus petit segment vaut 10 % de la hauteur, il doit
   rester assez haut pour contenir son nombre (10 % x 160 = 16px). */
.lsw-cols{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:.625rem;align-items:end;height:160px;margin-bottom:0}
.lsw-col{display:flex;flex-direction:column;justify-content:flex-end;
  gap:2px;height:100%}
.lsw-col > i{display:flex;align-items:center;justify-content:center;
  border-radius:2px;overflow:hidden}
/* Le compte dans le segment : c'est lui qui rend la baisse lisible (9, 8, 7,
   6 en descendant la ligne rouge). Ombre portee parce que du blanc sur
   --brand seul ne passe pas le contraste a cette taille. */
.lsw-col > i em{font-style:normal;font-size:.625rem;font-weight:700;
  line-height:1;color:#fff;font-variant-numeric:tabular-nums;
  text-shadow:0 1px 2px rgba(0,0,0,.5)}
.lsw-axis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:.625rem}
.lsw-tot{margin-bottom:.375rem}
.lsw-col-m{margin-top:.375rem;font-size:.6875rem;text-align:center;
  color:var(--text-dim)}
.lsw-col-t{font-size:.8125rem;font-weight:680;text-align:center;
  font-variant-numeric:tabular-nums;color:var(--text)}
/* Deux familles, pas trois couleurs : le palier qui BAISSE en --critical, les
   deux qui montent dans la famille de marque. */
.lsw-seg--e{background:var(--critical)}
.lsw-seg--p{background:var(--brand-deep,var(--brand-dim))}
.lsw-seg--s{background:var(--brand)}
.lsw-key{display:flex;flex-wrap:wrap;gap:.75rem;
  font-size:.75rem;color:var(--text-dim)}
.lsw-key span{display:flex;align-items:center;gap:.375rem}
.lsw-key i{width:8px;height:8px;flex:none;border-radius:2px}
/* La legende porte la variation, pas seulement la couleur : c'est la reponse
   directe a "lequel baisse ?". */
.lsw-key--trend{gap:.5rem 1.125rem;margin-top:.875rem;
  padding-top:.75rem;border-top:1px solid var(--border-soft)}
.lsw-key--trend b{font-weight:680;font-variant-numeric:tabular-nums;
  color:var(--text)}
.lsw-key--trend b.is-down{color:var(--critical-text,var(--critical))}
.lsw-key--trend b.is-up{color:var(--good-text,var(--good))}
/* Le repère factuel — jamais un taux de résiliation calculé. */
.lsw-fact{display:flex;align-items:baseline;gap:.5rem;margin-top:.625rem;
  font-size:.8125rem;color:var(--text-dim)}
.lsw-fact b{font-weight:650;color:var(--critical-text);
  font-variant-numeric:tabular-nums}

/* --- Fragment A4 · Provision sur le récurrent ----------------------------
   Réutilise .lft-split / .lft-seg / .lft-legend du mode unitaire : mêmes
   taux, autre assiette. */
.lsw-year{display:flex;align-items:baseline;gap:.5rem;margin-top:.75rem;
  padding-top:.75rem;border-top:1px solid var(--border-soft);
  font-size:.8125rem;color:var(--text-dim)}
.lsw-year b{font-weight:680;font-variant-numeric:tabular-nums;color:var(--text)}

/* --- Étroit --------------------------------------------------------------- */
@container (max-width:700px){
  .lsw-bar{width:100%}
  .lsw-btn{min-height:48px;padding:.75rem .75rem;font-size:.875rem}
  .lsw-cols{height:132px}
}
@media (max-width:560px){
  .lsw-bar{width:100%}
  /* Sur un ecran de 320px, "Proyecto puntual" ne tient pas sur une ligne :
     on autorise le retour a la ligne plutot que le debordement. */
  .lsw-btn{min-height:48px;padding:.75rem .5rem;font-size:.8125rem;
    min-width:0;white-space:normal;text-align:center;line-height:1.25}
}

/* --- Mouvement réduit ------------------------------------------------------
   ⚠ Les 33 % et les 50 % doivent suivre les sommets de lsw-grow et
   lsw-margin, ET le left de .lsw-mark : quatre nombres liés. */
@media (prefers-reduced-motion:reduce){
  .lsw-ind,.lsw-btn,.lsw-btn::before,.lsw-btn i{transition:none}
  .lsw-fill{animation:none;width:33%}
  .lsw-over{animation:none;width:50%}
}

@media print{
  .lsw-fill{animation:none;width:33%}
  .lsw-over{animation:none;width:50%}
  /* À l'impression, les deux modes sortent l'un après l'autre : un papier n'a
     pas de bascule. */
  .lsw-panel{display:block!important}
  .lsw-bar{display:none}
}

/* --- Deux défauts préexistants corrigés au passage (mesurés, identiques
   dans les deux modes de la bascule ci-dessus, donc antérieurs à elle) --- */
/* 1. Débordement de 18 px en colonne étroite : la remise à zéro (0,2,0) dans
   la requête de conteneur perdait contre la marge négative (0,3,0). */
@container (max-width:700px){
  .lft-row:nth-child(odd)  > .lft-shard,
  .lft-row:nth-child(even) > .lft-shard{ margin: 0 }
}
/* 2. --text-mute échoue en thème sombre à 11 px (4,27:1, sous le seuil AA) sur
   les libellés d'emplacement, l'estimation barrée et le taux annoncé barré —
   passés en --text-dim (6,22:1 sombre, 7,55:1 clair). */
.lft-where,.lft-limit b,.lft-rate-old{color:var(--text-dim)}

/* ==========================================================================
   Mobile — défilement horizontal : deux causes distinctes, mesurées au
   navigateur à 320 / 375 / 390 px puis recoupées avec des captures iPhone.
   Symptôme identique sur la landing, /how-it-works et /suggestions : contenu
   décalé, header s'arrêtant avant le bord droit de l'écran.
   `body{overflow-x:hidden}` (plus haut) ne masquait rien dans le cas 1 — la
   page n'était pas trop large, elle était zoomée.
   ========================================================================== */

/* 1. Auto-zoom iOS — cause des trois captures. Safari sur iPhone zoome la
   page dès qu'un champ prend le focus si sa font-size calculée est < 16 px,
   et une page zoomée devient pannable horizontalement : c'est exactement le
   décalage observé, y compris sur les pages sans le moindre débordement
   (document mesuré à 390 px pour un viewport de 390 px sur la landing).
   Tous les champs étaient sous le seuil : règle de base à .9375rem (15 px),
   simulateur du hero à 11 px, chatbot à 14 px, champs "jours" à 14 px,
   panneau de détail projet à 13 px.
   Restreint à (pointer: coarse) : le desktop garde son échelle d'origine. */
@media (pointer: coarse){
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="number"],
  input[type="search"],
  input[type="tel"],
  input[type="url"],
  input[type="date"],
  select,
  textarea,
  .hc-input,
  .chat-input,
  .fnx-deadline input,
  .rsq-days input,
  .pd-f,
  .pd-inline-select,
  .rem-ta{
    font-size: 16px;
  }
}

/* Le simulateur du hero est une maquette miniature (libellés 8 px, aide
   9,5 px) : avec ses champs remontés à 16 px, l'écart devenait incohérent.
   Sur téléphone cette maquette occupe toute la largeur de l'écran — autant
   la rendre lisible, c'est le premier écran du site. Le bandeau titre passe
   en flex-wrap : à cette largeur le badge "EN DIRECT" n'a plus la place de
   tenir sur la même ligne dans toutes les langues. */
@media (max-width: 640px){
  .hc-field-label{ font-size: 11px; }
  .hc-unit{ font-size: 12px; }
  .hc-compare-label{ font-size: 12px; }
  .hc-compare-value{ font-size: 15px; }
  .hc-compare-row-real .hc-compare-value{ font-size: 20px; }
  .hc-hint{ font-size: 11.5px; }
  .hc-topbar{ flex-wrap: wrap; row-gap: 4px; }
}

/* 2. Seul débordement structurel du lot : .btn est en white-space:nowrap, et
   « ▶ Revoir la visite guidée du formulaire Nouveau projet » (/how-it-works,
   rendu seulement pour un utilisateur connecté) mesure 406 px pour les
   354 px utiles de .app-main à 390 px — 52 px hors cadre, ce qui décale la
   page entière. Le libellé court de la même rangée (299 px) sortait lui
   aussi en dessous de 320 px. On autorise le retour à la ligne sur petit
   écran plutôt que de raccourcir un texte traduit en six langues.
   line-height:1.25 ne change rien aux boutons d'une seule ligne : min-height
   40px les gouverne déjà. */
@media (max-width: 640px){
  .btn{
    white-space: normal;
    max-width: 100%;
    line-height: 1.25;
  }
}
