/* Charte ValuAI (ADR-0001 Q5, Q6).
 *
 * Feuille **commune** aux trois pages, servie en fichier plutôt que recopiée :
 * deux copies auraient divergé, comme divergent toujours les tables de libellés
 * que l'on finit par réunir après coup.
 *
 * Les couleurs, polices et l'arrondi sont des variables surchargées au rendu par
 * le bloc de thème (marque blanche). Rien n'est écrit en dur ailleurs : une
 * teinte codée dans une règle jurerait chez le premier client qui change sa
 * charte.
 *
 * Responsive de premier jour, trois paliers : téléphone (< 640), tablette
 * (< 1024), poste. La navigation passe de colonne à barre horizontale, les
 * grilles de cartes se resserrent, les tableaux larges défilent dans leur propre
 * boîte - jamais la page.
 */

:root {
  /* Charte par défaut. Le bloc `#theme` injecté en fin de <head> gagne. */
  --paper: #0a1526;
  --card: #0f1d33;
  --surface: #14243d;
  --ink: #eef4fb;
  --muted: #8ba0bd;
  --line: #1e3253;
  --clay: #00e8ff;
  /* Dérivées par color-mix : un seul réglage d'accent les entraîne toutes. */
  --clay-deep: color-mix(in srgb, var(--clay) 65%, white);
  --clay-tint: color-mix(in srgb, var(--clay) 12%, transparent);
  --clay-ink: #06121f;
  --ok: #00f9c3;
  --alerte: #ffb020;
  --erreur: #ff4d6a;
  --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 10px 30px rgba(0, 0, 0, .45);
  --font-titre: "Iowan Old Style", Georgia, "Times New Roman", serif;
  --font-corps: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;
  --radius-scale: 1;
  --field-h: 40px;
  --nav-l: 224px;
  /* Longueur de ligne confortable. Sans elle, une phrase court sur les
     966 px de la zone de contenu, soit environ cent cinquante caractères :
     l'oeil perd la ligne suivante en revenant à la marge. */
  --mesure: 78ch;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.55 var(--font-corps);
  -webkit-font-smoothing: antialiased;
}

/* `hidden` doit toujours cacher : sans cette règle, n'importe quel `display`
   explicite le neutralise en silence - piège rencontré cinq fois chez le frère. */
[hidden] { display: none !important; }

h1, h2, h3 { font-family: var(--font-titre); font-weight: 600; letter-spacing: -.01em; margin: 0; }
h1 { font-size: 22px; }
h2 { font-size: 18px; }
h3 { font-size: 15px; }
a { color: var(--clay-deep); }

/* --- Ossature ------------------------------------------------------------- */

/* La fenêtre est le cadre : l'application en occupe la hauteur exacte, et c'est
   le **contenu** qui défile, pas la page. Sur `min-height: 100vh`, une page de
   résultat plus haute que l'écran allongeait la barre latérale d'autant : le
   menu descendait avec le contenu, ses onglets sortaient du champ, et l'on
   remontait tout l'écran pour changer de vue.
   `dvh` avant `vh` : sur un téléphone, `100vh` compte la barre d'URL qui
   n'est pas toujours là, et le bas de l'écran passait sous elle. */
.app {
  display: flex;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  align-items: stretch;
}

.nav {
  flex: none;
  width: var(--nav-l);
  background: var(--card);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  padding: 20px 14px 14px;
  gap: 18px;
  /* Un menu plus haut que la fenêtre - écran court, cinq onglets et un pied -
     défile chez lui plutôt que de pousser la page. */
  overflow-y: auto;
}

.nav-marque { display: flex; flex-direction: column; gap: 2px; padding: 0 8px; }
.nav-marque .nom { font-family: var(--font-titre); font-size: 20px; font-weight: 600; }
/* Le logo remplace le nom : borné en hauteur pour qu'une image de mille pixels
   ne pousse pas la barre, et libre en largeur pour respecter ses proportions. */
.logo-marque { max-height: 34px; max-width: 100%; width: auto; object-fit: contain; }
.nav-marque .tagline { color: var(--muted); font-size: 12.5px; line-height: 1.25; }

.tabs { display: flex; flex-direction: column; gap: 4px; }
.tab {
  display: inline-flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  border: none; background: none; color: var(--muted); cursor: pointer;
  font: inherit; font-weight: 600; font-size: 14px;
  padding: 10px 12px; border-radius: calc(9px * var(--radius-scale));
  transition: background .15s ease, color .15s ease;
}
.tab:hover:not(.active) { color: var(--clay); background: var(--clay-tint); }
.tab.active { background: var(--clay); color: var(--clay-ink); }
.tab .glyph { width: 17px; height: 17px; flex: none; }
.tab-count {
  margin-left: auto; font-size: 11.5px; font-weight: 700; padding: 1px 8px;
  border-radius: calc(20px * var(--radius-scale));
  background: var(--clay-tint); color: var(--clay-deep);
}
.tab.active .tab-count { background: rgba(6, 18, 31, .22); color: inherit; }

.nav-pied { margin-top: auto; display: flex; flex-direction: column; gap: 10px; }
.nav-identite { display: flex; align-items: center; gap: 9px; padding: 0 8px; min-width: 0; }
.nav-identite .avatar {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  background: var(--clay-tint); color: var(--clay-deep);
  display: grid; place-items: center; font-weight: 700; font-size: 12.5px;
}
.nav-identite .qui { min-width: 0; }
.nav-identite .nom { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav-identite .role { color: var(--muted); font-size: 12px; }

/* `min-height: 0` n'est pas un ornement : un enfant de flex refuse par défaut de
   descendre sous la hauteur de son contenu, et sans lui la zone déborderait au
   lieu de laisser `.contenu` défiler. */
.main { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* L'en-tête reste visible en défilant : il porte la ligne de statut, et sur un
   écran long on agit en bas. La confirmation d'un geste lourd - « 11'175
   multiplicateurs recalculés » - s'affichait hors du champ de vision et
   pouvait n'être jamais vue. */
.main-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 20px 26px 12px;
  position: sticky; top: 0; z-index: 5;
  background: var(--paper);
}
.main-head .titre { flex: 1; min-width: 0; }
/* Un message long prend sa place et passe à la ligne, il n'écrase pas le titre.
   Sans cette règle, une erreur de cent vingt caractères pliait « Descriptions de
   secteur » en trois lignes ; et un message d'erreur porte des blocs insécables
   (une URL, un code SSL) qu'il faut autoriser à se couper. */
.main-head .message { flex: 1 1 320px; min-width: 0; overflow-wrap: anywhere; }
/* C'est ici que ça défile, et nulle part ailleurs : l'en-tête reste posé au-dessus
   sans avoir besoin de `sticky`, et la barre latérale ne bouge pas.
   Colonne de flex plutôt que bloc, pour qu'un écran puisse **rendre sa hauteur**
   à une boîte au lieu de la laisser grandir avec son contenu (`.vue-cadre`). */
.contenu {
  flex: 1; min-height: 0; overflow-y: auto; padding: 18px 26px 30px;
  display: flex; flex-direction: column;
}
.vue { display: flex; flex-direction: column; gap: 18px; }
/* Une vue garde sa hauteur naturelle et laisse défiler `.contenu` - `flex: none`
   est **écrit**, et non déduit du `min-height: auto` d'un enfant de flex, qui
   dépendrait de ce que chaque vue contient. Une vue-cadre, elle, prend la
   hauteur disponible et la passe à ses boîtes, qui défilent chez elles. */
.contenu > .vue { flex: none; }
.contenu > .vue-cadre { flex: 1; min-height: 0; }

/* --- Boîtes --------------------------------------------------------------- */

.boite {
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(14px * var(--radius-scale));
  box-shadow: var(--shadow);
}
.boite-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 15px 18px; border-bottom: 1px solid var(--line);
}
.boite-head h2, .boite-head h3 { flex: 1; min-width: 0; }
.boite-corps { padding: 18px; }
.invite { color: var(--muted); font-size: 14px; margin: 0; max-width: var(--mesure); }
/* Écart d'empilement des blocs d'une boîte. Cette règle est écrite **après**
   `.invite` à dessein : les deux ont la même spécificité, donc la dernière
   gagne. Dans l'autre ordre, quatre paragraphes d'accueil se retrouvaient
   collés les uns aux autres, faute d'écart - défaut visible sur cinq écrans. */
.boite-corps > * + * { margin-top: 14px; }
/* Titre d'une section **dans** une boîte, quand découper en plusieurs boîtes
   couperait aussi ce qui les gouverne - le sélecteur de période de l'analytics
   commande les deux sections qu'il surplombe. */
.sous-titre { font-family: var(--font-titre); font-size: 17px; margin: 0; }
/* Un sous-titre ouvre une section : il lui faut plus d'air que l'interligne
   commun des blocs, sinon il colle à l'aide du bloc précédent. */
.boite-corps > .sous-titre { margin-top: 26px; }

/* --- Boîte à hauteur fixe -------------------------------------------------
   Une boîte par écran, de hauteur constante, dont le **contenu** défile : c'est
   le patron des paramètres de StreamAI, repris ici pour la même raison. Sept
   volets qui empilaient chacun deux ou trois boîtes donnaient sept longueurs de
   page différentes, et l'Enregistrer d'un volet long attendait au bas d'un
   défilement - donc hors du champ pendant qu'on remplit les champs qu'il valide.

   La hauteur ne se chiffre pas : elle est celle que la vue reçoit de la fenêtre
   (`.vue-cadre`). Un nombre de pixels serait juste sur un écran et faux sur les
   autres, et il faudrait le tenir à jour à chaque ajout de sous-onglet. */
.boite-cadre { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
/* C'est de là que vient la hauteur : la vue reçoit celle de la fenêtre, la
   boîte prend ce qui reste. D'où une hauteur **identique d'un sous-onglet à
   l'autre**, sans qu'aucun pixel soit écrit nulle part. */
.vue-cadre > .boite-cadre { flex: 1; }
/* En-tête et pied ne défilent pas : ils portent ce qui doit rester sous les
   yeux - de quoi changer de courriel en haut, de quoi enregistrer en bas. */
.boite-cadre > .boite-head, .boite-cadre > .boite-pied { flex: none; }
.boite-defile { flex: 1; min-height: 0; overflow-y: auto; padding: 18px; }
.boite-defile > * + * { margin-top: 14px; }
.boite-pied {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 14px 18px; border-top: 1px solid var(--line);
}
/* L'en-tête d'une boîte-cadre n'accueille que des sous-onglets, qui portent
   déjà leur cadre : le double filet ferait deux traits pour une séparation. */
.boite-cadre > .boite-head:has(> .subtabs) { padding: 12px; border-bottom: none; }

/* Section d'une boîte : un titre souligné d'un filet, et rien d'autre. Le trait
   appartient au titre et non à la section, sinon la première section porterait
   un filet en haut de boîte, à deux pixels de la bordure. */
.section-titre {
  font-family: var(--font-titre); font-size: 16px; font-weight: 600;
  margin: 0 0 14px; padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
/* Une section suivante s'ouvre avec de l'air : son filet la sépare déjà de ce
   qui précède, mais collé, il se lirait comme le bas du bloc d'au-dessus. */
.boite-defile > .section + .section { margin-top: 28px; }
.section > * + * { margin-top: 14px; }

.grille { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }

/* Carte cliquable d'une société ou d'une valorisation. */
.carte {
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(14px * var(--radius-scale));
  padding: 20px 16px; text-align: center; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: inherit; font: inherit;
  transition: border-color .15s ease, transform .15s ease;
}
.carte:hover { border-color: var(--clay); transform: translateY(-1px); }
.carte .glyph { width: 40px; height: 40px; color: var(--clay); }
.carte .titre { font-weight: 600; }
.carte .detail { color: var(--muted); font-size: 13px; }
/* La carte de création se distingue de celles qui portent une donnée : un trait
   discontinu dit « ici, il n'y a rien encore, et c'est à vous de le poser ».
   Elle ne prend en revanche **pas** la taille d'une carte de données : c'est une
   seule action, et lui donner 230 × 175 px en faisait le plus gros objet de
   l'écran. Elle se cale en haut de sa cellule et tient sur une rangée. */
.carte-ajout {
  border-style: dashed; color: var(--muted); font-weight: 600;
  flex-direction: row; justify-content: center; gap: 8px;
  /* Pas de `align-self` : l'élément de grille s'étire, donc la carte prend la
     hauteur de la rangée et fait la taille d'une carte de société dès qu'elle
     en côtoie une. Seule sur sa rangée, elle se contente de son contenu. */
  min-height: 0; padding: 12px 16px;
}
.carte-ajout::before { content: "+"; font-size: 17px; line-height: 1; }
/* Le trait devient plein au survol : le pointillé dit « rien ici pour
   l'instant », le plein dit que le geste est armé. */
.carte-ajout:hover { color: var(--clay); border-style: solid; }
/* Les actions d'une carte restent au bas, quelle que soit la longueur du nom.
   Elles s'empilent en deux rangs : l'action principale sur toute la largeur,
   les deux discrètes en dessous. À plat, les trois libellés demandaient 261 px
   pour 196 disponibles, et « Supprimer » tombait seul à la ligne. */
.carte .actions { margin-top: auto; width: 100%; flex-direction: column; gap: 8px; }
.carte .actions > .btn { width: 100%; }
.carte .actions-secondaires { display: flex; gap: 6px; justify-content: center; width: 100%; }

/* --- Contrôles ------------------------------------------------------------ */

label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; }

/* `date` et `file` sont dans la liste comme les autres : laissés au navigateur,
   ils rendaient un contrôle de 22 px en Arial, avec un bouton gris système et
   un libellé anglais au milieu d'un écran français. */
input[type="text"], input[type="email"], input[type="tel"], input[type="number"],
input[type="password"], input[type="date"], input[type="file"], input[type="url"],
select, textarea {
  width: 100%; min-height: var(--field-h); padding: 8px 12px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: calc(9px * var(--radius-scale));
  font: inherit;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--clay); }
/* La flèche native se colle au bord droit du champ, sans la marge que porte le
   texte à gauche : le contrôle paraît coupé. On dessine la nôtre, à la même
   distance du bord que le texte de l'autre côté. */
select {
  appearance: none;
  padding-right: 34px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) calc(50% + 2px), calc(100% - 12px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
/* Les flèches d'incrémentation ne servent aucun de nos champs : on ne saisit pas
   un chiffre d'affaires en cliquant deux mille fois, et sur un montant elles
   volent la place du dernier chiffre tout en offrant un geste qui fait dériver
   la valeur d'une unité sans qu'on l'ait voulu. Le champ reste `number` - le
   clavier numérique du téléphone et la validation du navigateur en dépendent. */
input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { appearance: none; margin: 0; }
/* Le sélecteur de date garde son calendrier natif, qu'aucune réécriture ne vaut,
   mais son icône est teintée pour ne pas rester un carré blanc sur fond sombre. */
input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(.55); cursor: pointer;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator { filter: invert(1) opacity(.9); }
/* Le champ fichier natif est **remplacé** par un bouton et un libellé à nous : le
   contrôle du navigateur écrit son propre texte, dans sa propre langue et sa
   propre police, et aucune feuille de style ne le traduit. Il reste dans la
   page, invisible mais atteignable au clavier, parce que c'est lui qui porte le
   fichier ; on ne l'enlève pas, on cesse de le montrer. */
/* Le dépôt n'est plus un champ : un bouton ne se remplit pas, il s'actionne, et
   son cadre l'annonçait comme une zone de saisie restée vide. */
/* `relative` pour la même raison que `.case-bascule` : le champ fichier
   ci-dessous est en absolu sans coordonnées, et sans porteur positionné il se
   poserait dans le bloc englobant du **document**, échapperait au découpage de
   l'application et étirerait la page jusqu'à sa position dans le flux. */
.depot {
  position: relative;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  min-height: var(--field-h);
}
.depot input[type="file"] {
  position: absolute; width: 1px; height: 1px; min-height: 0; padding: 0;
  opacity: 0; overflow: hidden; border: none;
}
/* Le champ étant invisible, c'est la boîte qui montre qu'il a le focus, sinon
   la tabulation traverse le contrôle sans que rien ne bouge à l'écran. */
.depot:focus-within { border-color: var(--clay); }
/* Témoin d'état : une pastille et un mot. La couleur seule ne dit rien à qui ne
   la distingue pas - le texte porte le sens, la pastille le rend visible d'un
   coup d'oeil. Elle emprunte `--ok`, la teinte de réussite de la charte, qui
   suit le thème du client au lieu d'un vert écrit en dur. */
.temoin { display: inline-flex; align-items: center; gap: 8px; color: var(--ok); font-size: 13px; }
.temoin-point {
  width: 9px; height: 9px; flex: none; border-radius: 50%; background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
}
.temoin-texte { font-weight: 600; }
/* Choisir un fichier puis extraire sont deux gestes **successifs** : ils
   s'empilent. Côte à côte, ils se lisaient comme deux options d'une même
   rangée, et le bouton s'étirait sur toute sa colonne - la largeur d'une action
   principale, qu'« Extraire les chiffres » n'est pas. Il prend son libellé. */
.depot-bloc { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.depot-bloc .champ { width: 100%; max-width: 520px; }
input[type="color"] {
  width: 100%; height: var(--field-h); padding: 4px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: calc(9px * var(--radius-scale)); cursor: pointer;
}
/* Sans cela, le navigateur pose sa propre bordure claire **dans** la pastille,
   qui trahit le contrôle système au milieu de champs qui n'en ont pas. */
input[type="color"]::-webkit-color-swatch { border: none; border-radius: calc(6px * var(--radius-scale)); }
input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }

.champ { min-width: 0; }
.champ + .champ { margin-top: 14px; }
/* Un champ hors grille prenait toute la largeur de l'écran quel que soit son
   contenu : une clé de trente-neuf caractères dans un champ de 1070 px annonce
   un paragraphe, et un sélecteur à trois choix n'a pas besoin d'un kilomètre. */
.champ-moyen { max-width: 520px; }
.champ-court { max-width: 280px; }
.champs { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
/* Trois colonnes **comptées**, là où la rangée porte un sens : le formulaire
   société se lit en deux lignes (ce qui identifie, puis ce qui classe), et
   `auto-fit` en donnait quatre sur un écran large, qui coupait la seconde
   rangée en deux. Le repli à deux puis une colonne suit les règles communes. */
.champs-trois { grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* Deux colonnes comptées, même raison : le profil veut le mobile **sous** le
   prénom et l'adresse de connexion sous le nom. `auto-fit` alignait les quatre
   champs sur une seule rangée, et les paires ne voulaient plus rien dire. */
.champs-deux { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* Dans une grille, l'écart est déjà tenu par `gap` : la marge d'empilement
   décalait le champ suivant vers le bas, si bien que « Prénom » et « Nom »
   n'étaient pas alignés côte à côte. Règle posée sur **toute** grille de champs,
   et non sur la première rencontrée : le défaut a reparu à l'identique dans la
   grille des couleurs, deux écrans plus loin. Invisible aux tests, évident à l'œil. */
.champs > .champ + .champ,
.couleurs > .champ + .champ { margin-top: 0; }
.aide { color: var(--muted); font-size: 12.5px; margin: 6px 0 0; max-width: var(--mesure); }

.case { display: flex; align-items: flex-start; gap: 10px; color: var(--ink); font-size: 14px; }
.case input[type="checkbox"] { width: 17px; height: 17px; margin: 2px 0 0; accent-color: var(--clay); flex: none; }
.case label { margin: 0; color: inherit; font-size: inherit; }

/* Interrupteur : pour un réglage qui **bascule un état** - ici l'apparition d'un
   champ et le calcul de l'EBITDA le plus ancien - plutôt qu'une case, qui dit
   « je fais partie d'une liste ». La case reste le contrôle réel, seulement
   sortie du champ visuel : le clavier l'atteint, les aides techniques la
   nomment, et `:checked` continue de commander l'apparence. Un div piloté au
   script aurait perdu les trois. */
/* **Le porteur est positionné, et cette ligne n'est pas décorative.** La case
   ci-dessous est en absolu sans coordonnées : elle se pose donc à sa position
   statique, dans le bloc englobant de son plus proche ancêtre positionné. Sans
   ce `relative`, il n'y en avait aucun - le bloc englobant devenait celui du
   document, la case échappait au `overflow: hidden` de `.app` et **étirait la
   page entière** jusqu'à sa propre hauteur dans le flux.
   Le défaut dormait depuis le 2026-08-17 : l'unique interrupteur d'alors était à
   708 px, dans la fenêtre, donc il n'étirait rien. Le second, posé le 2026-08-18
   au bas d'un formulaire long, s'est retrouvé à 2180 px : le document s'est mis
   à défiler de mille deux cent quatre-vingts pixels, la barre latérale partait
   vers le haut, et l'écran qui devait tenir dans la fenêtre s'est mis à glisser. */
.case-bascule { position: relative; }
.case-bascule input[type="checkbox"] {
  position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0;
}
.case-bascule label { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; }
.case-bascule .curseur {
  position: relative; flex: none; width: 38px; height: 22px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; transition: background .15s ease, border-color .15s ease;
}
.case-bascule .curseur::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--muted); transition: transform .15s ease, background .15s ease;
}
.case-bascule input:checked + label .curseur { background: var(--clay); border-color: var(--clay); }
.case-bascule input:checked + label .curseur::after {
  transform: translateX(16px); background: var(--clay-ink);
}
/* Le focus se voit **sur le curseur**, la case étant hors du champ visuel : sans
   cela, la tabulation traverse le réglage sans que rien ne bouge à l'écran. */
.case-bascule input:focus-visible + label .curseur { border-color: var(--clay); box-shadow: 0 0 0 3px var(--clay-tint); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: var(--field-h); padding: 0 18px;
  background: var(--clay); color: var(--clay-ink);
  border: 1px solid var(--clay); border-radius: calc(9px * var(--radius-scale));
  font: inherit; font-weight: 600; cursor: pointer;
  /* Un bouton peut être un `<a>` (« Administration ») : sans cela il garde le
     soulignement des liens et détonne au milieu des vrais boutons. */
  text-decoration: none;
  /* Un libellé ne se casse jamais : « Retour au portail » se pliait en trois
     lignes dans la barre étroite du téléphone. */
  white-space: nowrap;
  transition: filter .15s ease;
}
.btn:hover:not(:disabled) { filter: brightness(1.08); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.secondaire { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn.secondaire:hover:not(:disabled) { border-color: var(--clay); color: var(--clay-deep); }
.btn.discret {
  background: none; border-color: transparent; color: var(--muted);
  min-height: 32px; padding: 0 10px; font-size: 13px; font-weight: 600;
}
.btn.discret:hover:not(:disabled) { color: var(--clay-deep); background: var(--clay-tint); }
.btn.danger { background: var(--erreur); border-color: var(--erreur); color: #fff; }
.btn.compact { min-height: 32px; padding: 0 12px; font-size: 13px; }
.btn .glyph { width: 16px; height: 16px; flex: none; }
.actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* Ce qui détruit se range à l'opposé de ce qu'on vient chercher : la marge
   automatique pousse le dernier bouton au bord, et l'on ne rencontre plus
   « Supprimer » en visant « Modifier les données ». */
.actions-partagees > .btn:last-child { margin-left: auto; }

/* --- Tableaux ------------------------------------------------------------- */

/* Un tableau large défile **dans sa boîte**, jamais en poussant la page. */
.table-cadre { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--line); vertical-align: middle; }
th { color: var(--muted); font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
tbody tr:last-child td { border-bottom: none; }
td.actions-cellule { white-space: nowrap; }
/* Deux écarts distincts : dix pixels quand les actions tiennent côte à côte,
   quatre seulement quand elles se replient. Avec un écart unique de dix, deux
   libellés longs empilés portaient la rangée à cent pixels pour deux liens ;
   en les interdisant de se replier, « Fermer le compte » sortait du cadre. */
td .actions { gap: 4px 10px; }
td .actions .btn {
  min-height: 26px; padding: 3px 8px; font-size: 12.5px;
  /* Dans un tableau, un libellé long se plie plutôt que d'élargir sa colonne :
     « Retirer les droits d'administrateur » d'un seul tenant poussait la table
     au-delà de son cadre et coupait l'action voisine. La règle générale du
     bouton (jamais de coupure) vaut pour les barres, pas pour les cellules. */
  white-space: normal; text-align: left;
}

/* Un tableau long garde son en-tête sous les yeux : passé la trentième des deux
   cents lignes de multiplicateurs, on ne sait plus quelle colonne on lit.
   L'en-tête ne peut coller qu'à un conteneur qui **défile verticalement** :
   `.table-cadre` seul ne défile qu'en largeur, et un `sticky` y resterait sans
   effet. D'où la hauteur plafonnée, posée là où le tableau est long, et nulle
   part ailleurs - un tableau de deux lignes dans une boîte à défilement serait
   une gêne pour rien. */
.table-cadre.long { max-height: min(70vh, 620px); overflow-y: auto; }
.table-cadre.long thead th {
  position: sticky; top: 0; z-index: 1; background: var(--card);
  /* `border-collapse: collapse` perd les bordures d'une cellule collante :
     l'ombre intérieure les remplace, au pixel près. */
  border-bottom: none; box-shadow: inset 0 -1px 0 var(--line);
}
/* La colonne d'une description prend la place disponible : sans largeur imposée,
   le tableau la dimensionne sur la taille par défaut d'un `textarea` (une
   soixantaine de caractères), et on relit une phrase de quarante mots par une
   fenêtre de trois lignes alors que l'écran est large. */
td.cellule-description textarea { width: 100%; display: block; }

.pastille {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: calc(20px * var(--radius-scale));
  font-size: 12px; font-weight: 600;
  background: var(--surface); color: var(--muted); border: 1px solid var(--line);
}
.pastille.accent { background: var(--clay-tint); color: var(--clay-deep); border-color: transparent; }
.pastille.ok { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); border-color: transparent; }
.pastille.erreur { background: color-mix(in srgb, var(--erreur) 16%, transparent); color: var(--erreur); border-color: transparent; }

/* --- Messages ------------------------------------------------------------- */

.message { font-size: 14px; line-height: 1.5; margin: 0; }
.message.ok { color: var(--ok); }
.message.alerte { color: var(--alerte); }
.message.erreur { color: var(--erreur); }
.bandeau {
  padding: 12px 16px; border-radius: calc(11px * var(--radius-scale));
  border: 1px solid var(--alerte); color: var(--alerte);
  background: color-mix(in srgb, var(--alerte) 10%, transparent);
  font-size: 14px;
}
.vide { color: var(--muted); font-size: 14px; text-align: center; padding: 28px 12px; }
/* L'accueil échappe à la longueur de ligne commune : c'est une page de
   bienvenue qui remplit sa boîte. Ses deux appels s'alignent à gauche comme les
   actions de tous les autres écrans - centrés, ils flottaient au milieu d'un
   texte aligné à gauche, et l'oeil devait aller les chercher. */
.accueil .invite { max-width: none; }
.accueil .actions { margin-top: 20px; }

/* Ligne de contexte sous le titre d'un écran : de quelle société parle ce
   formulaire. Ce n'est ni une aide ni une invite, c'est le sujet. */
.contexte { margin: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
/* Un bloc et sa légende se tiennent : `.vue` sépare ses enfants de 18 px, ce qui
   plaçait la note à égale distance du bloc qu'elle explique et de la boîte
   suivante - donc rattachée à rien. */
.bloc-legende { display: grid; gap: 8px; }

/* --- Sous-onglets --------------------------------------------------------- */

.subtabs {
  display: flex; gap: 4px; flex-wrap: wrap; padding: 4px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(11px * var(--radius-scale));
}
.subtab {
  border: none; background: none; color: var(--muted); cursor: pointer;
  font: inherit; font-weight: 600; font-size: 13.5px;
  padding: 8px 14px; border-radius: calc(8px * var(--radius-scale));
}
.subtab:hover:not(.active) { color: var(--clay); }
.subtab.active { background: var(--clay); color: var(--clay-ink); }

/* --- Modale de confirmation ----------------------------------------------- */

/* Une modale à nous, jamais `confirm()` : le harnais de vérification supprime
   le natif (il rend `false`), donc une porte bâtie dessus est intestable. */
.modale-fond {
  position: fixed; inset: 0; z-index: 20;
  background: rgba(4, 10, 20, .7);
  display: grid; place-items: center; padding: 20px;
}
.modale {
  width: 100%; max-width: 460px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(14px * var(--radius-scale)); box-shadow: var(--shadow);
  padding: 22px;
}
.modale h2 { margin-bottom: 10px; }
.modale .consequences { color: var(--muted); font-size: 14px; margin: 0 0 18px; }
.modale .consequences li { margin-bottom: 4px; }
.modale .actions { justify-content: flex-end; }

/* --- Sélecteur de langue -------------------------------------------------- */

.langue-choix { display: flex; align-items: center; gap: 8px; }
.langue-choix select { width: auto; min-width: 130px; }

/* --- Écran de connexion --------------------------------------------------- */

body.page-connexion { display: flex; align-items: center; justify-content: center; padding: 24px; }
.connexion-boite {
  width: 100%; max-width: 430px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(16px * var(--radius-scale)); padding: 30px;
}
.connexion-boite .marque { text-align: center; margin-bottom: 22px; }
.connexion-boite .marque .nom { font-family: var(--font-titre); font-size: 30px; font-weight: 600; }
.connexion-boite .marque .tagline { color: var(--muted); font-size: 13.5px; }
.connexion-boite h1 { font-size: 19px; text-align: center; margin-bottom: 6px; }
.connexion-boite .invite { text-align: center; margin-bottom: 20px; }
.connexion-boite .btn { width: 100%; margin-top: 14px; }
.connexion-boite .message { margin-top: 16px; }
.connexion-boite .langue-choix { justify-content: center; margin-top: 22px; }

/* --- Tablette ------------------------------------------------------------- */

@media (max-width: 1024px) {
  :root { --nav-l: 190px; }
  .main-head { padding: 16px 18px 0; }
  .contenu { padding: 14px 18px 26px; }
}

/* --- Téléphone ------------------------------------------------------------
   La navigation passe **au-dessus** du contenu, en rangée défilante : une
   colonne de 190 px sur un écran de 375 ne laisserait rien à lire. Les libellés
   restent visibles (une barre d'icônes seules se devine mal), et la rangée
   défile horizontalement si les cinq onglets ne tiennent pas. */

@media (max-width: 640px) {
  .app { flex-direction: column; }
  /* Deux rangées, pas quatre. Le pied se repliait sous la marque et sous les
     onglets, si bien que la barre prenait **202 px sur les 812 d'un téléphone**,
     soit un tiers de l'écran avant la première ligne de contenu. Marque et
     utilitaires partagent maintenant la première rangée, les onglets ont la
     seconde, et l'ensemble tient en une centaine de pixels. */
  .nav {
    width: 100%; border-right: none; border-bottom: 1px solid var(--line);
    padding: 9px 14px; gap: 9px;
    position: sticky; top: 0; z-index: 10;
    display: grid; grid-template-columns: auto 1fr;
    grid-template-areas: "marque outils" "tabs tabs";
    align-items: center;
    /* En rangée, la barre prend la hauteur de ses deux lignes et rien de plus :
       le défilement vertical du mode colonne lui donnerait une barre inutile. */
    flex: none; overflow-y: visible;
  }
  /* Sur téléphone, la barre de navigation est déjà collée en haut : un second
     bloc collant lui passerait dessous et masquerait le contenu. */
  .main-head { position: static; padding: 14px 14px 0; }
  .nav-marque { grid-area: marque; flex-direction: row; align-items: baseline; gap: 8px; padding: 0; }
  /* Le sous-titre prend 160 px sur 375 pour une information que la page de
     connexion donne déjà en grand : il cède la place aux utilitaires. */
  .nav-marque .tagline { display: none; }
  .tabs {
    grid-area: tabs;
    flex-direction: row; overflow-x: auto; gap: 6px;
    scrollbar-width: none; margin: 0 -14px; padding: 0 14px;
    /* La rangée déborde (806 px de contenu pour 375 visibles côté console) :
       le dégradé de droite dit qu'il y a une suite, là où un bord net laissait
       croire que « Profil » et « Paramètres » n'existaient pas. */
    mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
  }
  .tabs::-webkit-scrollbar { height: 0; }
  .tab { width: auto; flex: none; white-space: nowrap; }
  .nav-pied {
    grid-area: outils; margin-top: 0;
    flex-direction: row; align-items: center; justify-content: flex-end; gap: 6px;
  }
  /* L'identité sort de la barre : l'onglet Profil la porte en entier, et ses
     34 px décidaient à eux seuls du repli de la rangée. */
  .nav-identite { display: none; }
  .nav-pied .langue-choix select { min-width: 96px; }
  .contenu { padding: 14px 14px 24px; }
  .grille { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
  .champs, .champs-trois, .champs-deux { grid-template-columns: 1fr; }
  h1 { font-size: 19px; }
  /* Un tableau de gestion ne tient pas sur 375 px : il défile dans sa boîte. */
  .table-cadre table { min-width: 560px; }
  /* Sauf les tableaux de saisie : le grand ne montre qu'une colonne à la fois, et
     celui des ajustements n'a que trois lignes courtes. Leur imposer 560 px les
     faisait défiler pour rien - un réglage qui défile est un réglage qu'on ne
     voit pas. */
  .table-cadre table.saisie-bascule,
  .table-cadre table.saisie-fluide { min-width: 0; }
}

/* --- Formulaire de valorisation et page de résultat ------------------------ */

/* Sections repliables : `details` natif plutôt qu'un pliage écrit à la main, qui
   aurait fallu clavier, `aria-expanded` et son propre état. */
details.boite > summary { cursor: pointer; list-style: none; }
details.boite > summary::-webkit-details-marker { display: none; }
details.boite > summary::after {
  content: "\203A"; margin-left: auto; color: var(--muted);
  transition: transform .15s ease; font-size: 20px; line-height: 1;
}
details.boite[open] > summary::after { transform: rotate(90deg); }

/* --- Info-bulle d'un libellé de champ ---------------------------------------
   Le porteur est **focalisable** : au clavier comme au doigt, on atteint
   l'explication sans souris. Un `title` natif n'aurait ni l'un ni l'autre, ni
   la charte - et posé sur un contrôle il en devient le nom accessible, leçon
   déjà payée sur les boutons d'une ligne d'utilisateur. */
/* Aucun soulignement : la pastille « ? » dit déjà qu'il y a une explication, et
   le pointillé sous le libellé faisait lire le mot comme un lien, ou comme une
   faute que le navigateur signale. Un libellé ne se souligne nulle part ici. */
.avec-bulle {
  position: relative; display: inline-flex; align-items: center; gap: 5px;
  cursor: help;
}
.avec-bulle::after {
  content: "?"; flex: none;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--clay-tint); color: var(--clay-deep);
  font-size: 10px; font-weight: 700; line-height: 14px; text-align: center;
}
.avec-bulle:focus { outline: none; }
.avec-bulle.bulle-ouverte::after,
.avec-bulle:hover::after { background: var(--clay); color: var(--clay-ink); }
/* **Position fixe, et c'est ce qui la sauve deux fois.** En absolu, la bulle
   restait dans le flux même masquée (`visibility: hidden` occupe la place) et
   gonflait en permanence la zone défilante de son conteneur : le tableau des
   valeurs financières portait une barre de défilement verticale de cinquante
   et un pixels, celui des ajustements de trente-deux, sans que rien ne dépasse
   à l'oeil. Et une fois ouverte sur une dernière ligne, elle se faisait rogner
   par ce même conteneur, donc illisible là où on en a le plus besoin.
   Un élément en position fixe ne compte dans le défilement d'aucun ancêtre et
   n'est rogné par aucun `overflow` - il échappe aux deux d'un coup. Le prix est
   que sa place se calcule (`placerBulle`), le repère du parent ne valant plus. */
.bulle {
  /* **Ancrée en haut à gauche tant qu'elle n'a pas servi**, et ce n'est pas une
     coquetterie. Avec `top`/`left` laissés à `auto`, un élément fixe se pose à
     sa position statique - mesurée ici à y ≈ 2000 dans une fenêtre de 900, donc
     très en dehors. Invisible, mais quatorze à dix-huit éléments fixes garés
     hors champ dépendent alors du moteur de rendu pour ne rien casser, et un
     seul d'entre eux qui compterait dans la zone défilante ferait défiler la
     page entière : la barre latérale part, et l'écran qui devait tenir dans la
     fenêtre se met à glisser. `placerBulle` écrase ces deux valeurs à chaque
     ouverture ; avant cela, la bulle ne pèse nulle part. */
  position: fixed; top: 0; left: 0; z-index: 30;
  /* Bornée par la fenêtre autant que par elle-même : sur 375 px, une bulle de
     280 px posée près du bord droit sortirait de l'écran. */
  width: max-content; max-width: min(300px, calc(100vw - 40px));
  padding: 10px 12px; border-radius: calc(10px * var(--radius-scale));
  background: var(--card); border: 1px solid var(--line); box-shadow: var(--shadow);
  color: var(--ink); font-size: 12.5px; font-weight: 400; line-height: 1.45;
  text-transform: none; letter-spacing: normal; white-space: normal;
  visibility: hidden; opacity: 0; transition: opacity .12s ease;
}
.avec-bulle:hover > .bulle,
.avec-bulle.bulle-ouverte > .bulle { visibility: visible; opacity: 1; }

/* --- Sections d'un formulaire long -----------------------------------------
   Un filet gris pleine largeur et un titre : sans eux, « déposer un PDF »,
   « l'exercice est long » et treize rubriques de saisie se lisaient comme une
   seule suite de champs sans rapport. Le filet se pose **avant** chaque section
   sauf la première, si bien qu'il n'y a jamais de trait orphelin en tête ni en
   pied de boîte, quel que soit le nombre de sections. */
.section-formulaire + .section-formulaire {
  margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--line);
}
.section-formulaire > * + * { margin-top: 14px; }
.titre-section {
  font-family: var(--font-titre); font-size: 15px; font-weight: 600;
  color: var(--muted); text-transform: uppercase; letter-spacing: .05em;
  margin: 0;
}
/* Un bouton posé dans une grille de champs s'aligne sur la ligne de saisie des
   champs voisins, et non sur leur étiquette. */

/* --- Attente d'un travail long ---------------------------------------------
   Un appel qui prend des dizaines de secondes laisse l'écran figé : sans rien
   qui tourne, on le lit comme cassé et on reclique. */
.attente {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px; border-radius: calc(11px * var(--radius-scale));
  background: var(--surface); border: 1px solid var(--line);
}
.attente-texte { color: var(--muted); font-size: 13.5px; min-width: 0; }
.rouet {
  width: 18px; height: 18px; flex: none; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--clay);
  animation: rouet 900ms linear infinite;
}
@keyframes rouet { to { transform: rotate(360deg); } }
/* Une animation perpétuelle est une gêne pour qui la refuse : le rouet devient
   alors un point fixe, et c'est le texte qui porte l'information. */
@media (prefers-reduced-motion: reduce) {
  .rouet { animation: none; border-top-color: var(--clay); opacity: .7; }
}

/* Tableau de saisie : les rubriques en **lignes**, les exercices en colonnes,
   comme un état financier se lit. En trois colonnes de champs, chaque libellé se
   répétait une fois par exercice - trente-neuf étiquettes sur un écran - et deux
   montants d'une même rubrique ne s'alignaient pas d'une année sur l'autre.
   L'en-tête de ligne joint à celui de colonne nomme aussi chaque cellule pour un
   lecteur d'écran, ce qu'aucun `aria-label` fabriqué n'aurait fait mieux. */
.saisie { table-layout: fixed; }
.saisie thead th { font-size: 12px; }
/* Largeurs **fixes**, et non des pourcentages : à un seul exercice ouvert, les
   70 % laissés par le libellé revenaient tous à l'unique colonne, qui donnait un
   champ de sept cents pixels posé loin de sa rubrique. Un montant s'écrit dans
   la même case qu'il y ait un ou trois exercices ; c'est la colonne d'appoint,
   à droite, qui absorbe la place restante. */
/* La largeur se déclare sur la **première rangée** : `table-layout: fixed` n'y
   lit que celle-là, et la poser sur les `th[scope="row"]` du corps ne changeait
   rien - la colonne des rubriques s'étirait quand même. */
.saisie thead th:first-child, .saisie th[scope="row"] { width: 260px; }
.saisie th[scope="row"] {
  min-width: 148px; padding: 10px 16px 10px 0;
  font-size: 13px; font-weight: 500; color: var(--muted);
  text-transform: none; letter-spacing: normal; white-space: normal;
}
.saisie [data-rang] { width: 190px; }
.saisie .reste { width: auto; padding: 0; }
/* Un tableau de gestion sépare ses lignes par un filet parce qu'on y compare des
   enregistrements. Une grille de saisie, non : les champs se délimitent
   eux-mêmes, et treize filets faisaient un quadrillage de tableur au milieu d'un
   formulaire. L'en-tête gardait le sien, censé séparer les titres des valeurs ;
   il partait à son tour le 2026-08-18 (demande de Luca). Le titre d'un exercice
   est déjà en capitales et en gris au-dessus de champs encadrés : le filet ne
   séparait rien qui ne le fût déjà, et il traversait les deux tableaux de part
   en part là où la grille, elle, n'a plus une seule ligne. */
.saisie td, .saisie th[scope="row"], .saisie thead th { border-bottom: none; }
/* Les ajustements portent **les mêmes largeurs** que les données financières :
   les deux tableaux se lisent l'un sous l'autre, colonne d'exercice au-dessus de
   colonne d'exercice, et deux gabarits différents cassaient l'alignement d'un
   bloc à l'autre. C'est sous 768 px que celui-ci redevient fluide, où il n'a pas
   la bascule qui resserre le grand. */
/* L'en-tête d'une colonne porte son titre **et** ses commandes : la croix qui la
   retire, le bouton qui ouvre la suivante. Le flex vit dans un porteur à
   l'intérieur de la cellule et non sur le `th` lui-même : `display: flex` sur
   une cellule la sort du tableau, et `table-layout: fixed` n'a plus de rangée
   sur laquelle lire ses largeurs. */
.entete-exercice { display: flex; align-items: center; gap: 8px; min-height: 34px; }
/* La croix ne prend pas la place d'un bouton : c'est un signe posé à côté d'un
   titre, qui ne doit pas peser plus que lui. Elle vit dans le gris du texte
   secondaire et ne passe au rouge qu'au survol, où l'on saura ce qu'elle fait. */
.retirer-exercice {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none; padding: 0;
  border: none; border-radius: calc(6px * var(--radius-scale));
  background: none; color: var(--muted); cursor: pointer;
  font: inherit; font-size: 16px; line-height: 1;
}
.retirer-exercice:hover { color: var(--erreur); background: color-mix(in srgb, var(--erreur) 14%, transparent); }
.retirer-exercice:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }
.entete-exercice .ajouter-exercice { margin-left: auto; }
/* Une colonne fermée disparaît, sauf si son en-tête porte le bouton d'ajout :
   c'est là que le prochain exercice s'ouvrira, et le montrer vaut mieux que de
   le faire deviner. La cellule reste, ses champs restent cachés. */
.saisie thead th[data-rang].colonne-fermee:has(.ajouter-exercice:not([hidden]))
  { display: table-cell; }
/* Le bouton d'ajout ne réclame pas la largeur d'une colonne : il tient dans la
   sienne, et son libellé se coupe plutôt que de déborder sur la voisine. */
.entete-exercice .ajouter-exercice { min-width: 0; max-width: 100%; }
.entete-exercice { min-width: 0; }
.entete-exercice .titre-exercice { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Une colonne fermée n'a pas de titre à porter : elle n'existe à l'écran que
   pour montrer où le prochain exercice ira, et « Exer… » tronqué à côté du
   bouton nommait une colonne qui n'est pas encore là. */
.saisie thead th.colonne-fermee .titre-exercice { display: none; }
.saisie th[scope="row"] .aide { margin-top: 3px; }
/* Une colonne fermée sort de la vue à toutes les largeurs. Elle reste dans le
   DOM, sinon ouvrir puis fermer effacerait les saisies voisines au passage.
   L'invariant qui l'accompagne vit dans le script : une colonne fermée n'est
   jamais la colonne active de la bascule, donc les deux règles d'affichage ne
   se disputent jamais la même cellule. */
.saisie [data-rang].colonne-fermee { display: none; }
/* Un montant s'aligne à droite : c'est ainsi qu'une colonne de chiffres se lit,
   unités sous unités, et qu'un ordre de grandeur se compare d'un coup d'oeil.
   Les champs de texte libre - le motif d'un ajustement - gardent la gauche. */
.montant { text-align: right; }
/* Un champ que la soumission attend et qui est resté vide. Le cadre suffit :
   une couleur de fond rendrait la saisie illisible sur un formulaire entier. */
.champ-manquant { border-color: var(--erreur); }
.champ-manquant:focus { border-color: var(--erreur); }
.saisie td { padding: 7px 8px 7px 0; }
.saisie td:last-child { padding-right: 0; }
.saisie td input { min-height: 34px; }

/* La bascule ne sert qu'aux écrans étroits : au-dessus, les trois colonnes
   tiennent côte à côte et une bascule serait un contrôle sans objet. */
.bascule { display: none; gap: 8px; margin-bottom: 16px; }

.badges { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }

/* Le chiffre que l'écran existe pour donner. Il porte la même romaine que les
   titres : c'est une conclusion, pas une donnée de tableau. */
.chiffre-cle { display: flex; flex-direction: column; gap: 1px; }
.chiffre-cle .etiquette {
  color: var(--muted); font-size: 12.5px;
  text-transform: uppercase; letter-spacing: .03em;
}
.chiffre-cle .valeur {
  font-family: var(--font-titre); font-size: 30px; font-weight: 600;
  line-height: 1.15; overflow-wrap: anywhere;
}

.tuile {
  background: var(--card); border: 1px solid var(--line);
  border-radius: calc(14px * var(--radius-scale));
  padding: 16px; display: flex; flex-direction: column; gap: 6px;
}
.tuile .titre { font-size: 19px; font-weight: 600; }
.tuile .detail { color: var(--muted); font-size: 12.5px; text-transform: uppercase; letter-spacing: .03em; }
.tuile { position: relative; }
.graphique { width: 100%; height: auto; margin-top: 10px; overflow: visible; }
.graphique .trait {
  fill: none; stroke: var(--clay); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
}
.graphique .point { fill: var(--clay); stroke: var(--card); stroke-width: 1.5; }
/* Cible de survol : invisible mais large, parce que viser un disque de trois
   unités au doigt ou à la souris demande une précision que personne n'a. */
.graphique .cible-point { fill: transparent; cursor: pointer; }
.graphique .cible-point:hover + .point { fill: var(--clay-deep); }
/* Les tailles sont en unités du `viewBox`, donc **relatives à la tuile** : le
   texte grandit avec elle. À 10 unités sur 170 de haut, une graduation rendait
   13 px sur un poste et dominait la courbe. Sept unités tiennent de 8 à 11 px
   réels, du téléphone au grand écran. */
.graphique .annee,
.graphique .graduation { fill: var(--muted); font-size: 8px; font-family: var(--font-corps); }
.graphique .graduation { font-size: 7px; }
.graphique .axe { stroke: var(--line); stroke-width: 1; }
.graphique .grille-graphique { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 4; }
/* Le zéro n'est pas une graduation comme les autres : c'est la ligne qui sépare
   un bénéfice d'une perte. */
.graphique .zero-graphique { stroke: var(--muted); stroke-width: 1; opacity: .55; }

/* L'info-bulle d'un point : du HTML, pour qu'elle prenne la charte sans qu'on
   redessine un cadre, une ombre et un repli de ligne en SVG. */
.bulle-point {
  position: absolute; z-index: 4; transform: translate(-50%, calc(-100% - 10px));
  padding: 5px 9px; border-radius: calc(8px * var(--radius-scale));
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow);
  color: var(--ink); font-size: 12px; font-weight: 600; white-space: nowrap;
  pointer-events: none;
}

/* --- Logo et polices déposés (repris de StreamAI) --------------------------
 *
 * L'aperçu montre le logo **tel qu'il sera servi**, sur un damier : sur un fond
 * de carte, un logo à fond blanc et un logo transparent se ressemblent, et l'un
 * des deux paraîtra sale dans une barre de navigation sombre. */
.depot-marque { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.apercu-logo {
  display: grid; place-items: center; flex: none;
  width: 132px; height: 56px; padding: 6px;
  border: 1px solid var(--line); border-radius: calc(10px * var(--radius-scale));
  background:
    linear-gradient(45deg, var(--surface) 25%, transparent 25%, transparent 75%, var(--surface) 75%),
    linear-gradient(45deg, var(--surface) 25%, var(--card) 25%, var(--card) 75%, var(--surface) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
}
.apercu-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
.apercu-vide { color: var(--muted); font-size: 12px; }

/* --- Jingle d'amorçage du portail (concept retenu le 2026-08-18) -----------
 *
 * Trois barres montent, puis une quatrième pousse à la seconde même où le nom
 * s'écrit. Les barres ne s'effacent pas pour laisser la place à une icône :
 * elles sont la marque, et le nom vient se poser dessous.
 *
 * **L'overlay est caché par défaut**, et c'est le script qui l'autorise en
 * posant son verdict sur le `<html>` avant la peinture. L'inverse - visible par
 * défaut, masqué par le script - montrerait le jingle une fraction de seconde à
 * ceux qui reviennent de la console, ce qui est exactement le clignotement
 * qu'on veut éviter. */
#jingle { display: none; }
:root[data-jingle="joue"] #jingle {
  display: grid; place-items: center;
  position: fixed; inset: 0; z-index: 60;
  background: var(--paper); cursor: pointer;
  /* Garde de dernier recours : si le script meurt entre le verdict et la
     sortie, l'overlay s'escamote quand même. Un ornement ne condamne jamais
     l'accès à l'outil - c'est le filet le plus important des trois. */
  animation: jgl-garde .5s ease 4.5s both;
}
@keyframes jgl-garde { to { opacity: 0; visibility: hidden; } }
/* La sortie voulue gagne sur la garde : sans `animation: none`, les deux se
   disputeraient la même propriété et la garde reprendrait la main à 4,5 s. */
#jingle.jgl-sortie {
  animation: none; opacity: 0; visibility: hidden;
  transition: opacity .8s ease, visibility 0s linear .8s;
}
.jgl-dessin { width: min(760px, 86vw); height: auto; display: block; overflow: visible; }

/* L'application monte pendant que l'overlay s'éteint : un fondu enchaîné se lit
   comme une transition, deux fondus successifs comme deux écrans. */
body.jgl-revele > :not(#jingle) { animation: jgl-fondu .8s ease both; }
@keyframes jgl-fondu { from { opacity: 0; } to { opacity: 1; } }

.jgl-socle { fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; }
.jgl-barre, .jgl-resultat { fill: var(--clay); }
.jgl-halo { fill: var(--clay); opacity: 0; filter: blur(26px); }
/* Le nom s'écrit en fil d'accent, puis l'encre s'y dépose : la signature
   héritée de StreamAI et de SearchAI, qui fait la famille des trois produits.
   Le pointillé vaut par **glyphe** et non par mot : sa longueur ne dépend donc
   pas du nom, et un client au nom long s'écrit au même rythme. */
.jgl-nom {
  font: 600 46px/1 var(--font-titre); letter-spacing: .5px;
  fill: var(--ink); fill-opacity: 0;
  stroke: var(--clay); stroke-width: 1.1;
  stroke-dasharray: 260; stroke-dashoffset: 260;
}
.jgl-tagline {
  font: 400 15px/1 var(--font-corps); letter-spacing: 2.4px;
  fill: var(--muted); opacity: 0; text-transform: uppercase;
}

.jgl-joue .jgl-socle { animation: jgl-apparait .4s ease .05s both; }
.jgl-joue .jgl-barre,
.jgl-joue .jgl-resultat {
  transform-box: fill-box; transform-origin: center bottom; transform: scaleY(0);
}
.jgl-joue .jgl-barre { animation: jgl-monte .5s cubic-bezier(.3, 1.2, .5, 1) both; }
.jgl-joue .jgl-barre:nth-of-type(1) { animation-delay: .20s; }
.jgl-joue .jgl-barre:nth-of-type(2) { animation-delay: .38s; }
.jgl-joue .jgl-barre:nth-of-type(3) { animation-delay: .56s; }
/* La quatrième pousse plus haut et plus longtemps : elle n'est pas un exercice
   de plus, elle est ce que les trois valent. */
.jgl-joue .jgl-resultat { animation: jgl-pousse .85s cubic-bezier(.35, .9, .3, 1) .95s both; }
@keyframes jgl-monte { to { transform: scaleY(1); } }
@keyframes jgl-pousse { to { transform: scaleY(1); } }

/* Le nom part à la même seconde que la quatrième barre. L'encre se dépose
   ensuite, quand le fil a fini d'écrire : lancées ensemble, le plein
   rattrapait le trait avant qu'il ait atteint le A. */
.jgl-joue .jgl-nom {
  animation: jgl-trace .95s ease-out .95s both, jgl-encre .5s ease 1.55s both;
}
.jgl-joue .jgl-halo { animation: jgl-souffle 1.1s ease .95s both; }
.jgl-joue .jgl-tagline { animation: jgl-tagline-monte .55s ease 2.05s both; }
@keyframes jgl-trace { to { stroke-dashoffset: 0; } }
@keyframes jgl-encre {
  0%, 55% { fill-opacity: 0; stroke-opacity: 1; }
  100% { fill-opacity: 1; stroke-opacity: 0; }
}
@keyframes jgl-souffle { 0% { opacity: 0; } 40% { opacity: .32; } 100% { opacity: .12; } }
@keyframes jgl-tagline-monte {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes jgl-apparait { from { opacity: 0; } to { opacity: 1; } }

/* Qui a demandé moins d'animation n'en reçoit aucune. Le script prend déjà la
   même décision et retire l'overlay ; cette règle est le filet du cas où il
   n'aurait pas tourné. */
@media (prefers-reduced-motion: reduce) {
  :root[data-jingle="joue"] #jingle { display: none; }
  body.jgl-revele > :not(#jingle) { animation: none; }
}

/* --- Éditeur de gabarit de courriel (ADR-0006) -----------------------------
 *
 * La barre d'outils vit **au-dessus** du champ et non dedans : un
 * `contenteditable` qui contiendrait ses propres boutons les ferait entrer dans
 * le HTML enregistré, donc dans le courriel. */
.barre-outils {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
  padding: 6px; margin-bottom: -1px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: calc(10px * var(--radius-scale)) calc(10px * var(--radius-scale)) 0 0;
}
.btn-outil {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 30px; padding: 0 8px;
  background: transparent; border: 1px solid transparent;
  border-radius: calc(7px * var(--radius-scale));
  color: var(--ink); font: 600 14px/1 var(--font-corps); cursor: pointer;
}
.btn-outil:hover { background: var(--clay-tint); border-color: var(--line); }
.btn-outil:focus-visible { outline: 2px solid var(--clay); outline-offset: 1px; }
.btn-outil .glyph { width: 17px; height: 17px; }
.separateur-outil { width: 1px; height: 20px; background: var(--line); margin: 0 3px; }
.choix-outil { height: 30px; min-height: 0; padding: 0 26px 0 8px; font-size: 13px; width: auto; }

/* Le champ riche imite un `textarea` : mêmes bords, même fond, même hauteur
   minimale. Ce n'en est pas un, et rien ne doit le laisser voir. */
.editeur-riche {
  min-height: 200px; padding: 12px 14px; overflow-y: auto;
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 0 0 calc(10px * var(--radius-scale)) calc(10px * var(--radius-scale));
  font: 15px/1.55 var(--font-corps);
}
.editeur-riche:focus { outline: none; border-color: var(--clay); }
.editeur-riche p { margin: 0 0 10px; }
.editeur-riche ul, .editeur-riche ol { margin: 0 0 10px; padding-left: 22px; }
.editeur-riche img { max-width: 100%; height: auto; }
.editeur-riche a { color: var(--clay); }

/* Une étiquette de groupe qui n'est pas un `<label>` : elle nomme un ensemble de
   boutons, et un `for` qui viserait l'un des trois mentirait sur les deux autres. */
.etiquette-champ {
  display: block; margin-bottom: 6px;
  font-size: 13px; font-weight: 600; color: var(--muted);
}

/* L'aperçu montre le courriel **tel qu'il partira**, donc sur un fond blanc :
   une boîte de réception n'a pas la charte du produit, et le montrer sur fond
   sombre ferait croire à une lisibilité qu'il n'aura pas. */
.apercu-sujet { margin: 0 0 8px; font-weight: 600; }
.apercu-courriel {
  padding: 18px 20px; background: #ffffff; color: #111111;
  border: 1px solid var(--line); border-radius: calc(10px * var(--radius-scale));
  font: 15px/1.55 Arial, Helvetica, sans-serif;
  overflow-x: auto;
}
.apercu-courriel a { color: #1155cc; }
.apercu-courriel img { max-width: 100%; height: auto; }
.apercu-texte {
  margin: 8px 0 0; padding: 12px 14px; max-height: 220px; overflow: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: calc(10px * var(--radius-scale));
  color: var(--muted); font: 13px/1.5 ui-monospace, "SF Mono", Menlo, monospace;
  white-space: pre-wrap; word-break: break-word;
}

/* La carte emprunte `.tuile`, dont l'étiquette est en capitales : c'est juste
   pour « COMPTES ACTIFS », qui nomme une grandeur, et faux pour « logo.png »,
   qui est un nom de fichier. Un nom se rend tel qu'il a été donné. */
.carte-image { display: flex; flex-direction: column; gap: 8px; }
.carte-image .detail {
  text-transform: none; letter-spacing: 0; word-break: break-word;
}
/* Le damier dit qu'une image est transparente : sur fond de carte, un logo à
   fond blanc et un logo transparent se ressemblent, et l'un des deux paraîtra
   sale dans une boîte de réception sombre. */
.vignette-image {
  width: 100%; height: 92px; object-fit: contain;
  border-radius: calc(8px * var(--radius-scale)); padding: 6px;
  background:
    linear-gradient(45deg, var(--surface) 25%, transparent 25%, transparent 75%, var(--surface) 75%),
    linear-gradient(45deg, var(--surface) 25%, var(--card) 25%, var(--card) 75%, var(--surface) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
}

/* --- Séries de l'analytics (amendement du 2026-08-17 à l'ADR-0004) ----------
 *
 * Les couleurs de série sont un **dégradé de l'accent vers l'encre**, jamais
 * une palette. La charte n'a qu'une couleur d'accent et la marque blanche la
 * change : une palette écrite ici jurerait chez le premier client. Le mélange
 * vers l'encre marche dans les deux sens de charte pour la même raison que
 * `--clay-deep` - l'encre est par définition ce qui se lit sur le papier, donc
 * claire sur charte sombre et sombre sur charte claire.
 *
 * Seul l'échec garde `--erreur` : c'est une valeur sémantique, pas une série
 * parmi d'autres, et une extraction échouée doit se voir comme telle. */
.serie-1 { --serie: var(--clay); }
.serie-2 { --serie: color-mix(in srgb, var(--clay) 62%, var(--ink)); }
.serie-3 { --serie: color-mix(in srgb, var(--clay) 32%, var(--ink)); }
.serie-echec { --serie: var(--erreur); }

.graphique-serie { width: 100%; height: auto; display: block; overflow: visible; }
.graphique-serie .barre-serie { fill: var(--serie); }
.graphique-serie .courbe-serie {
  fill: none; stroke: var(--clay); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
.graphique-serie .point-serie { fill: var(--clay); stroke: var(--card); stroke-width: 1; }
.graphique-serie .axe-serie { stroke: var(--line); stroke-width: 1; }
.graphique-serie .grille-serie { stroke: var(--line); stroke-width: 1; stroke-dasharray: 3 4; }
/* Le texte d'un SVG suit l'agrandissement de son `viewBox` : ces tailles sont en
   unités de dessin, pas en pixels d'écran. */
.graphique-serie .graduation-serie,
.graphique-serie .abscisse-serie {
  fill: var(--muted); font-size: 9px; font-family: var(--font-corps);
}
/* La zone de survol couvre toute la hauteur du jour : viser un segment de trois
   pixels pour lire une valeur n'est pas une interaction, c'est un jeu d'adresse. */
.graphique-serie .survol-serie { fill: transparent; }
.graphique-serie .survol-serie:hover { fill: var(--clay-tint); }

.graphique-vide { margin: 18px 0; font-style: italic; }

.legende-series { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 8px; }
.legende-serie { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; }
.pastille-serie {
  width: 11px; height: 11px; border-radius: 3px; background: var(--serie); flex: none;
}

/* Un titre de graphique, plus discret qu'un `h3` de section : il nomme une
   vignette, il n'ouvre pas un chapitre. */
.titre-graphique {
  margin: 0 0 2px; font: 600 13px/1.3 var(--font-corps); color: var(--ink);
}
.bloc-graphique { min-width: 0; }

/* Deux graphiques par rangée, et un seul sous 900 px : quatre de front les
   rendaient illisibles, et les axes n'y auraient pas tenu. */
.grille-graphiques { display: grid; gap: 16px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 900px) {
  .grille-graphiques { grid-template-columns: 1fr; }
}
/* Deux colonnes sur la tablette : à trois, un nom de pays ou d'activité n'entre
   plus dans sa liste. La borne basse est **écrite**, et ce n'est pas un ornement :
   ce bloc vit après celui de 640 px, et à spécificité égale c'est la dernière
   règle écrite qui gagne - sans elle, le téléphone repassait à deux colonnes. */
@media (min-width: 641px) and (max-width: 900px) {
  .champs-trois { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 768px) {
  /* Trois colonnes de treize rubriques ne tiennent pas sur un téléphone : le
     grand tableau n'en montre qu'une, choisie par la bascule. L'en-tête de
     colonne se retire, la bascule disant déjà de quel exercice il s'agit. */
  .bascule { display: flex; }
  /* L'en-tête reste, vidé de ses titres : la bascule dit déjà quel exercice on
     saisit, mais c'est cette rangée qui porte le bouton d'ajout. Le masquer en
     bloc, comme avant, emportait le bouton avec lui. */
  .saisie-bascule thead .titre-exercice { display: none; }
  .saisie-bascule thead th { padding: 0; border: none; }
  .saisie-bascule [data-rang] { display: none; }
  .saisie-bascule [data-rang].colonne-active { display: table-cell; }
  /* Les largeurs fixes du poste ne tiennent pas sur 375 px : la rubrique et sa
     valeur se partagent la ligne, et la colonne d'appoint s'efface. La largeur
     se pose ici encore sur la **première rangée**, celle que `table-layout:
     fixed` lit : la poser sur le seul corps laissait les 260 px du poste tenir,
     et la colonne de saisie tombait à cinq pixels. */
  /* Vaut pour les **deux** tableaux de saisie : posée sur le seul tableau à
     bascule, elle laissait les 260 px du poste à celui des ajustements, dont
     les colonnes tombaient à vingt-cinq pixels. */
  .saisie thead th:first-child,
  .saisie th[scope="row"] { width: 46%; }
  /* Sans bascule, le tableau des ajustements montre ses trois colonnes : à
     190 px chacune il déborderait de la boîte et la ferait défiler. */
  .saisie-fluide [data-rang] { width: auto; }
  .saisie-fluide .reste { display: none; }
  .saisie [data-rang] { width: auto; }
  .saisie .reste { display: none; }
  /* Au poste, la case vide sous « Exercice 2 » se comprend d'un coup d'oeil, la
     colonne d'à côté étant remplie. Seule, elle ne se comprendrait plus. */
  .saisie-bascule tbody tr.ligne-hors-rang { display: none; }
  /* La colonne d'accueil se réduit à la place d'un signe : « Ajouter un
     exercice » ne tient pas dans les cinquante pixels que la bascule lui laisse,
     et son libellé débordait sur la colonne saisie. Le nom reste dit par
     `aria-label`, posé sur le bouton à sa construction. */
  .saisie thead th.colonne-fermee:has(.ajouter-exercice:not([hidden])) { width: 42px; }
  .entete-exercice .ajouter-exercice .libelle { display: none; }
  .entete-exercice .ajouter-exercice::before { content: "+"; font-size: 16px; line-height: 1; }
  .entete-exercice .ajouter-exercice { padding: 0 10px; }
}
