/*! Longward — personal wealth dashboard
 *  Copyright (C) 2026 Longward
 *  Licensed under the GNU Affero General Public License, version 3 or later.
 *  Source: https://github.com/pierrelouis-fetet/Longward-demo
 *  Distributed WITHOUT ANY WARRANTY. See the LICENSE file for the full terms.
 */

/* ---------------------------------------------------------------
   Manrope, et elle seule pour les titres.

   Une police d'affichage donne un visage a l'application la ou on la
   regarde longtemps : le titre de page, le titre d'une carte, le nom
   sur l'ecran de lancement. Elle ne descend jamais sur les chiffres.
   Ce n'est pas une preference : toute la feuille repose sur
   `tabular-nums` pour que les colonnes de montants s'alignent, et une
   police d'affichage qui n'embarque pas de chiffres tabulaires ferait
   danser la largeur d'un total a chaque frappe. Les montants gardent
   donc la police du systeme, declaree sur `body`.

   Auto-hebergee, sous-ensemble latin, 24,3 Ko. Le fichier vient de
   Google Fonts mais n'est pas servi par lui : un `@import` vers
   fonts.googleapis.com annoncerait chaque visite du tableau de bord a
   un tiers, et l'application est fermee par defaut.

   Le sous-ensemble latin porte U+0000-00FF (tous les accents francais
   et les guillemets obliques), U+2000-206F (l'apostrophe typographique)
   et U+20AC (l'euro). Un glyphe absent, comme le ⤒ des releves, tombe
   sur la police du systeme, caractere par caractere : rien ne casse.

   Aucun `?v=` sur ce fichier, volontairement. Le lien de prechargement
   d'`index.html` et l'`url()` ci-dessous doivent coincider a l'octet
   pres, sinon le navigateur telecharge la police deux fois. Une police
   ne change pas : le jour ou elle changerait, elle changerait de nom.
   --------------------------------------------------------------- */
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 500 800;
  /* `swap` et non `optional` : avec le prechargement, la police est la
     avant le premier rendu dans la quasi-totalite des cas. `optional`
     laisserait le navigateur l'ignorer purement et simplement sur une
     connexion lente, et le titre resterait en police systeme toute la
     session — une identite qui n'apparait qu'une fois sur deux n'en
     est pas une. */
  font-display: swap;
  src: url(manrope-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
                 U+FFFD;
}

:root {
  color-scheme: light;
  --surface-1: #ffffff;
  --surface-2: #f1f4f8;
  --page: #f7f9fb;
  --sidebar: #ffffff;
  --sidebar-ink: #0d1117;
  --sidebar-muted: #55606e;
  --sidebar-faible: #8a94a3;
  --sidebar-hover: rgba(13, 17, 23, .05);
  --sidebar-active: rgba(13, 17, 23, .08);
  --sidebar-border: rgba(13, 17, 23, .10);
  --text-primary: #0d1117;
  --text-secondary: #4d5866;
  /* Le troisieme gris porte le texte le plus petit de l'application : intitules
     de colonnes a 11 px, bulles d'aide a 12, etiquettes de tuiles a 10,5. Il
     doit donc franchir 4,5:1 sur les trois fonds, et non seulement sur la page.
     #7b8794 donnait 3,66:1 sur une carte blanche et 3,32 sur `--surface-2` :
     sous le seuil la ou les caracteres sont les plus fins. #66707d rend 5,03,
     4,76 et 4,56, et garde un ecart net avec `--text-secondary`, a 7,23. */
  --muted: #66707d;
  --grid: #e4e9ef;
  --axis: #c3ccd6;
  --border: rgba(13, 17, 23, .09);
  --border-strong: rgba(13, 17, 23, .18);

  --series-1: #2173c6;
  --series-2: #048681;
  --series-3: #b8417e;
  --series-4: #9a539d;
  --series-5: #017ca7;
  --series-6: #818643;
  --series-7: #028293;
  --series-8: #55873b;
  --series-9: #505ea8;
  --series-10: #4a6d8c;
  --series-11: #5f8722;
  --series-12: #6b6976;

  --good: #0a8f5f;
  --good-text: #06724c;
  --warning: #b07d00;
  /* L'orange sert de texte, et pas seulement d'aplat : les ecarts au budget
     s'ecrivent avec, a 12 et 13 px. #c25f2b ne rendait que 4,23:1 sur blanc et
     3,83 sur `--surface-2`, sous le seuil aux deux endroits ou il s'affiche.
     #ab5022 rend 5,38, 5,10 et 4,88, et reste un orange : il ne glisse pas vers
     le rouge, qui a son propre sens dans cette application. */
  --serious: #ab5022;
  --critical: #cf3a3a;

  /* La zone de gestes du telephone, nommee une fois et declaree ici, hors de
     toute requete media : une variable definie sous un point de rupture ne
     vaudrait rien pour les regles qui vivent sous un autre.

     C'est `env(safe-area-inset-bottom)` : 34 px sur un iPhone a barre de gestes,
     zero partout ailleurs. Passer par une variable plutot que d'appeler `env()`
     a chaque endroit sert a la verifier. `env()` ne se simule pas dans un
     navigateur de bureau ; une variable se force le temps d'un controle, et la
     geometrie de l'iPhone devient mesurable sans iPhone. */
  --zone-geste: env(safe-area-inset-bottom, 0px);

  /* Les deux bandes qui restent en place quand on defile, sur telephone : la
     barre du haut, puis les sous-onglets quand la page en a.

     Un collant qui se pose sous un autre a besoin de connaitre sa hauteur. Elle
     ne se devine pas, donc elle se declare : `--h-sous-onglets` commande la
     hauteur de la barre autant que le decalage du bandeau, et les deux ne
     peuvent plus se desaccorder. */
  --h-barre-dessin: 54px;
  --h-barre: calc(var(--h-barre-dessin) + env(safe-area-inset-top, 0px));
  --h-sous-onglets: 53px;
  /* La barre du haut de l'ordinateur, `.topbar`. Sa hauteur ne se declarait
     nulle part et se subissait — 91 px de remplissage et de texte — pendant
     qu'un bandeau collant la supposait a 72 : il se garait 19 px dessous, le
     titre coupe en deux. Declaree ici, la barre la tient par `min-height`, et
     le bandeau comme le defilement vers une ancre la lisent. */
  --h-entete: 91px;

  /* Une troisieme variable a vecu ici, `--h-colle`, qui portait la hauteur
     reellement clouee en haut selon que la barre etait la ou retiree. Les collants
     s'y accrochaient par leur `top`. Elle est partie avec ce mecanisme : animer le
     `top` d'un element collant fait recalculer sa contrainte de collage a chaque
     image, et le mouvement accrochait. Le collage est desormais fixe et c'est une
     translation qui deplace ces bandes, donc plus rien a faire varier. */

  /* L'accent de l'interface : le violet du logo. Tout ce qui dit « actif,
     survole, selectionne » le porte — le repere du menu, l'onglet courant, la
     pastille de plage retenue, les anneaux de focus, la selection de texte.

     Il ne rejoint pas les series de donnees, et c'est le point : `--series-1`
     peignait a la fois les liquidites et chaque etat actif de l'application.
     Une couleur qui sert de marque et de classe d'actif ne veut plus rien dire,
     et l'interface entiere prenait l'air d'un gabarit dont l'accent est le bleu
     le plus consensuel qui existe, pendant que la marque etait violette. */
  --accent: #6d3fc4;

  /* Le degrade du budget, defini une fois. La jauge de l'accueil le portait en
     dur dans son CSS pendant que les barres par categorie tiraient sur un
     jaune sans rapport : deux ecrans du meme sujet, deux couleurs. Les deux le
     lisent ici, donc ils ne peuvent plus diverger.

     Une seule declaration suffit pour les deux themes : un `var()` dans une
     variable se resout chez qui l'utilise, donc `--series-1` y prend la teinte
     du theme actif. */
  --degrade-budget: linear-gradient(90deg, var(--series-1), var(--series-3));

  --radius: 14px;        /* cartes, fenetres, tuiles */
  --radius-sm: 9px;      /* boutons, champs, controles */
  --radius-xs: 6px;      /* badges, petites pastilles carrees */
  --radius-pill: 999px;  /* pilules : onglets, jauges, etiquettes rondes */
  /* Trois niveaux d'ombre, et pas un par surface. `--shadow` pose (cartes),
     `--ombre-flottante` detache ce qui survole la page (menus, infobulles,
     panneau de notifications), `--ombre-fenetre` porte ce qui la recouvre
     (fenetres, tiroirs). Avant : six ombres noires ecrites en dur, les memes
     dans les deux themes — un voile a 45 % de noir sous un menu en theme
     clair pese trois fois trop lourd, c'est lui qui faisait « sombre colle
     sur clair ». Chaque theme regle desormais sa densite. */
  --shadow: 0 1px 2px rgba(13, 17, 23, .04);
  --ombre-flottante: 0 8px 24px rgba(13, 17, 23, .10);
  --ombre-fenetre: 0 20px 50px rgba(13, 17, 23, .16);
  --ombre-ovale: 0 -14px 34px rgba(13, 17, 23, .10);
  --fw-chiffre: 650;
  /*    La police du systeme d'abord, Inter ensuite. Inter etait demandee en
   premier. Elle est excellente et elle est aussi la police par defaut de tous
   les gabarits web : c'est le premier signal qui fait dire d'une application
   qu'elle sort d'un modele. Avec `system-ui` en tete, elle porte le caractere
   de l'appareil : SF Pro sur iPhone et Mac, Segoe UI Variable sur Windows,
   Roboto sur Android. C'est ce qui fait qu'une application a l'air
   d'appartenir au telephone plutot qu'au web. Inter reste en repli pour les
   systemes sans police d'interface.*/
  --font: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI",
          Roboto, "Inter", ui-sans-serif, sans-serif;
  --font-titre: 'Manrope', var(--font);
  --font-nb: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  --font-xs: 11px;
  --font-sm: 12.5px;
  --font-md: 13px;
  --font-base: 14px;
  --font-lg: 16px;
  --font-xl: 20px;
  --font-2xl: 26px;
  --font-hero: 34px;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --surface-1: #101114;
  --surface-2: #181a1f;
  --page: #08090b;
  --sidebar: #050506;
  --sidebar-ink: #f2f3f5;
  --sidebar-muted: #9a9ca3;
  --sidebar-faible: #5c5e66;
  --sidebar-hover: rgba(255, 255, 255, .05);
  --sidebar-active: rgba(255, 255, 255, .08);
  --sidebar-border: rgba(255, 255, 255, .07);
  --text-primary: #f2f3f5;
  --text-secondary: #9a9ca3;
  /* Meme regle qu'en clair, meme faute corrigee : #6a6c74 rendait 3,61:1 sur une
     carte et 3,32 sur `--surface-2`, aux tailles ou les caracteres sont les plus
     fins. #83858e rend 5,13, 5,42 et 4,73, et reste sous `--text-secondary`
     (6,88) : la hierarchie des trois gris tient toujours. */
  --muted: #83858e;
  --grid: #1e2025;
  --axis: #2c2f36;
  --border: rgba(255, 255, 255, .045);
  --border-strong: rgba(255, 255, 255, .11);

  --accent: #9a72e8;

  --series-1:  #65a7f2;   /* liquidités, monétaire      — azur */
  --series-2:  #35c5be;   /* actions, actifs de marché  — turquoise */
  --series-3:  #e06da4;   /* non coté                   — rose */
  --series-4:  #ca85cb;   /* immobilier, immobilier coté — mauve */
  --series-5:  #46b1e3;   /* crypto                     — bleu ciel */
  --series-6:  #b2b867;   /* métaux précieux            — or olive */
  --series-7:  #3fbbd0;   /* obligations                — cyan */
  --series-8:  #8ebe77;   /* multi-actifs               — vert */
  --series-9:  #8d9be2;   /* biens de valeur */
  --series-10: #6b8ca8;
  --series-11: #8fbf5a;
  --series-12: #9c9aa6;   /* et la teinte des classes sans attribution */

  --good: #26a17b;
  --good-text: #3ec49b;
  --warning: #d0a215;
  --serious: #e08048;
  --critical: #e0574f;

  --shadow: 0 1px 2px rgba(0, 0, 0, .5);
  --ombre-flottante: 0 8px 24px rgba(0, 0, 0, .45);
  --ombre-fenetre: 0 24px 60px rgba(0, 0, 0, .5);
  --ombre-ovale: 0 -14px 34px rgba(0, 0, 0, .3);
}

/* ---------------------------------------------------------------
   Le survol vit sous `@media (hover: hover)`, sans exception.

   Le correctif du 5 aout avait pose ce garde-fou sur une seule regle, celle du
   logo, parce qu'un seul symptome avait ete signale. Les soixante-neuf autres
   avaient le meme defaut et l'ont montre a la premiere occasion. Une regle
   ecrite pour un cas s'applique a tous ceux qui lui ressemblent : c'est la
   lecon que ce fichier note deja pour les bandeaux d'etablissement.

   Chaque bloc est enveloppe **sur place**. Cette feuille se joue a l'ordre
   autant qu'a la specificite — voir `.table-serree`, dont la regle doit rester
   apres `.data-view > table` — et regrouper les survols en fin de fichier
   aurait change qui gagne. Une requete media n'ajoute aucune specificite et ne
   deplace rien.

   Un selecteur qui melangeait survol et focus a ete coupe en deux : le clavier
   ne perd pas son indication en meme temps que le doigt.

   Un test l'exige desormais, et il se derive de la feuille : aucune occurrence
   de `:hover` ne peut vivre hors d'un de ces blocs. L'appui tactile, lui, garde
   ses `:active` et ses classes `.tape`, qui eux se levent. */
* { box-sizing: border-box; }

/* Le fond est porte par les deux : sur iPhone, la zone sous la barre
   d'onglets — encoche du bas, ou l'espace laisse par la barre de Safari qui
   se replie — reste parfois hors du canevas peint par `body` seul, et s'y
   affiche en noir.

   `min-height` sur le corps, et non `height`, et c'est un correctif.

   `height: 100%` plafonnait le corps a la hauteur de l'ecran quelle que soit la
   longueur de la page : 900 px pour un document de 2 062. Or le corps est la
   grille qui porte la barre laterale, donc la zone de grille de cette barre
   faisait 900 px, exactement sa propre hauteur. Un element colle n'a de course
   que dans son bloc conteneur : avec zero de marge, `position: sticky` ne
   collait rien du tout, et la barre s'en allait avec la page. Mesure a 1 280 x
   900 : `top` a -800 px pour un defilement de 800, donc pas un pixel de retenue.

   Ce n'est pas un choix qu'on aurait pu defendre. Le pied de cette barre porte
   le patrimoine net en permanence, au point que l'accueil a perdu une carte
   entiere au motif qu'« il est deja ecrit en permanence au pied de la barre
   laterale ». Un chiffre retire ailleurs parce qu'il est toujours la ne peut pas
   s'en aller au premier defilement.

   Le corps peut donc depasser l'ecran, et il ne descend jamais en dessous : le
   fond reste peint d'un bord a l'autre sur une page courte, ce qui etait la
   raison d'etre de cette ligne. La hauteur posee par `gelerFond()` est un style
   en ligne, elle gagne toujours sur celle-ci — le gel d'une fenetre ouverte
   n'est pas touche.

   Le `min-height` vit dans la regle `body` principale, plus bas, et non dans une
   seconde regle posee ici : la feuille en compte deja une seule, et un controle
   cherche « le bloc dont le selecteur est exactement body » pour verifier que
   les montants gardent la police du systeme. Une deuxieme regle du meme nom
   passait devant et lui faisait lire autre chose que ce qu'il croit lire. */
html { height: 100%; }
/* LE DEFILEMENT VERS UNE ANCRE S'ARRETE SOUS CE QUI EST CLOUE EN HAUT.
   `scrollIntoView` et `focusAnchor` posaient leur cible au bord de la fenetre,
   donc derriere la barre du haut. `scroll-padding-top` le dit une fois, pour le
   conteneur qui defile, et chaque ecran le redit avec ses propres bandes plus
   bas ; `app.js` lit cette valeur au lieu de porter les siennes. */
html { scroll-padding-top: calc(var(--h-entete) + 8px); }
html, body { background: var(--page); }

/* Garde-fou : rien ne doit pouvoir élargir la fenêtre.
   Un seul élément trop large et le navigateur agrandit le viewport de mise en
   page — 375 px devenaient 446 — puis dézoome pour tout montrer. La barre
   d'onglets, posée en `fixed; bottom: 0`, se calait alors sur ce viewport
   gonflé, donc 97 px sous le bord visible : elle disparaissait en arrivant sur
   Marchés. `clip` plutôt que `hidden` : il ne crée pas de conteneur de
   défilement et ne casse donc pas les en-têtes collants. */
html, body { overflow-x: clip; max-width: 100%; }

/* Le zoom au double-tap, lui, se refuse — et seulement lui.

   `manipulation` vaut `auto` moins le zoom au double-tap. C'est celui qu'on
   declenche sans le vouloir : deux appuis rapproches sur un montant, et la page
   se recadre. Le defilement et le **pincement restent permis**, ce qui est la
   difference entre corriger un accident et confisquer un geste : un pincement se
   defait en pincant, un double-tap laisse sur place.

   Marche partout, iPhone compris, la ou le viewport n'y peut rien. Les regles qui
   posent `pan-x` ou `pan-y` sur un carrousel ou le corps d'une fenetre gagnent
   par specificite, et c'est ce qu'il faut : elles disent quelque chose de plus
   precis sur un geste precis. */
html { touch-action: manipulation; }

body {
  margin: 0;
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "cv05" 1;
  background: var(--page);
  color: var(--text-primary);
  display: grid;
  grid-template-columns: 244px 1fr;
  /* Et non `height: 100%`, qui plafonnait le corps a un ecran : cette grille est
     le bloc conteneur de la barre laterale, et une zone de grille exactement
     aussi haute que la barre ne laisse aucune course a son collage. Voir la note
     en tete de fichier, sous `html { height: 100% }`. */
  min-height: 100%;
  font-size: var(--font-base);
  -webkit-font-smoothing: antialiased;
}

/* RIEN NE SE SELECTIONNE DANS L'APPLICATION, SAUF CE QUI S'EDITE.

   Sur iPhone, un appui maintenu sur un montant ouvrait la loupe, posait les deux
   poignees bleues et sortait le menu Copier / Chercher / Traduire. Le geste qui
   sert a lire — poser le doigt et le laisser une seconde — declenchait donc une
   selection dont personne n'avait besoin, sur du mobilier : un titre, un libelle,
   un chiffre affiche. Une application native ne fait pas cela.

   LA REGLE VIT SUR `body` ET NON SUR `html` : tout ce qui s'affiche est dedans —
   ecran de lancement, barre laterale, fiches, barre de navigation, infobulles —
   et `user-select` comme `-webkit-touch-callout` s'heritent, donc une seule
   declaration suffit a couvrir l'arbre entier. Elle remplace une demi-douzaine de
   gardes posees une par une, dont l'une rendait justement la selection aux
   paragraphes et aux tableaux des cartes a graphique : deux regles qui se
   contredisaient, et la plus specifique gagnait.

   `-webkit-touch-callout: none` EN PLUS de `user-select`, et non a sa place : le
   premier supprime le menu contextuel et la loupe d'iOS, que le second laisse
   apparaitre. Il en faut les deux pour faire taire un appui long.

   ET SURTOUT PAS `pointer-events: none`, qui eteindrait du meme coup les boutons,
   les renvois, le survol, la navigation et le doigt sur la courbe. Ce qui est
   coupe ici est la selection native, rien d'autre : aucun evenement ne change de
   chemin, `touch-action` n'est pas touche, le defilement vertical reste au
   navigateur. */
body {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
/* CE QUI S'EDITE GARDE TOUT. Un champ sans selection ne se corrige plus : ni
   double-appui sur un mot, ni poignees pour deplacer le curseur, ni menu Coller.
   `-webkit-touch-callout: default` va avec, sinon iOS retire aussi le menu
   d'edition A L'INTERIEUR du champ. Deux appels a `champ.select()` vivent dans
   `app.js` — quantite d'une ligne, code d'un doublon — et ils ont besoin de cette
   exception pour faire quelque chose.

   `[contenteditable]` n'est employe nulle part aujourd'hui. La ligne est posee
   pour que le jour ou il le sera, il le soit normalement. */
input, textarea,
[contenteditable="true"], [contenteditable=""] {
  -webkit-user-select: text; user-select: text;
  -webkit-touch-callout: default;
}

/* La barre latérale ne défile pas avec la page, et elle défile en elle-même.

   Et il n'y a rien à y gagner. Si la barre du haut se retire sur téléphone,
   c'est que la place verticale y est comptée — 54 px sur 812. Ici la barre coûte
   de la largeur, jamais de la hauteur : la cacher ne rendrait pas une ligne de
   contenu.

   Mais la question désigne un vrai défaut, mesuré : dans une fenêtre de 560 px
   de haut, son contenu en fait 655. Le pied dépassait donc de 95 px, et
   `overflow: visible` sur un élément collé le rendait **inatteignable** — ni le
   patrimoine net, ni le témoin de sauvegarde, et aucun défilement ne pouvait les
   ramener. C'est le cas qu'on ne voit jamais en développant sur un grand écran.

   Elle défile donc en elle-même quand elle ne tient pas. `contain` pour que le
   geste ne se propage pas à la page une fois le bas atteint : sinon lire le pied
   de la barre ferait partir le contenu à côté. */
.sidebar {
  background: var(--sidebar);
  color: var(--sidebar-ink);
  display: flex;
  flex-direction: column;
  padding: 20px 14px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-right: 1px solid var(--sidebar-border);
}

.brand {
  display: flex; gap: 11px; align-items: center; padding: 0 6px 22px;
  text-decoration: none; color: inherit; border-radius: var(--radius-sm);
}
.brand .brand-mark {
  transition: transform .18s cubic-bezier(.34, 1.28, .48, 1),
              box-shadow .18s ease, filter .18s ease;
}
/* Sous `hover: hover` seulement, et c'est un correctif.

   Sur un ecran tactile, `:hover` reste colle apres un appui jusqu'a ce qu'on
   touche ailleurs. Le halo violet restait donc allume sur le T une fois qu'on
   avait ouvert l'accueil — « quand je clique sur le T il reste en
   surbrillance ». Un survol qui ne se leve pas n'est plus un survol, c'est un
   etat.

   L'application connait deja ce garde-fou, pour la croix des notifications :
   une commande qui n'apparait qu'au pointeur n'existe pas sur telephone. Ici
   c'est l'inverse — un retour qui ne part jamais — mais c'est la meme cause et
   le meme remede.

   L'appui, lui, reste : il a une fin, celle du doigt qui se leve. */
@media (hover: hover) {
  .brand:hover .brand-mark {
    transform: translateY(-1px) scale(1.05);
    filter: brightness(1.08);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 22%, transparent);
  }
}
/* L'appui, lui, reste — et il doit se voir.

   `:active` fait donc le travail, et il convient mieux : il finit quand le doigt
   se leve, alors qu'un `:hover` tactile attend qu'on touche ailleurs. Le
   mouvement est un enfoncement franc, pas la levee du survol — on appuie sur une
   touche, on ne la survole pas. */
.brand:active .brand-mark,
.brand.tape .brand-mark {
  transform: scale(.93);
  filter: brightness(1.12);
  box-shadow: 0 0 0 4px color-mix(in oklab, var(--accent) 26%, transparent);
  transition-duration: .06s;
}
.brand:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .brand .brand-mark { transition: none; }
  @media (hover: hover) { .brand:hover .brand-mark { transform: none; } }
  .brand:active .brand-mark,
  .brand.tape .brand-mark { transform: none; }
}
.brand-vue { display: none; }
/* Le rayon suit celui du dessin, mesure sur le fichier source : 204 px de rayon
   sur une tuile de 880, soit 23 %. En pourcentage et non en pixels, parce que la
   meme image sert a 34 px dans la barre laterale et a 38 px dans le tiroir — un
   rayon fixe arrondissait deux fois differemment le meme coin.

   Et sans bordure : la nouvelle icone porte son propre liseré violet. Le
   `1px solid` d'avant existait parce que l'ancienne icone etait un carre plein
   bord a bord, qui avait besoin qu'on lui dessine un cadre. */
.brand-mark {
  width: 34px; height: 34px; border-radius: 23%; flex-shrink: 0; display: block;
  object-fit: cover;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text strong { font-size: var(--font-lg); letter-spacing: -.01em; }
.brand-text span { font-size: var(--font-sm); color: var(--sidebar-faible); }
.brand-sous b { font-weight: 600; color: var(--accent); }

.nav { display: flex; flex-direction: column; gap: 2px; }
.nav-tete { display: none; }
.nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border-radius: var(--radius-sm);
  color: var(--sidebar-muted); text-decoration: none; font-size: var(--font-md); font-weight: 500;
  transition: background .12s, color .12s;
}
.nav a .ic { width: 15px; text-align: center; opacity: .8; font-size: 12px; }
/* Les icones du menu sont des SVG au trait, comme la tabbar : les glyphes
   texte (◈ ◎ ▦) rendaient differemment selon la police du systeme et
   faisaient prototype a cote des vraies icones du bas. Meme classe `ic`,
   pour heriter des regles de largeur et de masquage existantes. */
.nav a svg.ic {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.nav a { position: relative; }
@media (hover: hover) { .nav a:hover { background: var(--sidebar-hover); color: var(--sidebar-ink); } }
.nav a.active { background: var(--sidebar-active); color: var(--sidebar-ink); font-weight: 600; }
@media (hover: hover) { .nav a.active:hover { background: var(--sidebar-active); } }
.nav a.active::before {
  content: ''; position: absolute; left: 0; top: 7px; bottom: 7px; width: 2.5px;
  border-radius: 0 2px 2px 0; background: var(--accent);
}

.nav-groupe {
  margin: 14px 0 4px; padding: 0 11px;
  font-size: var(--font-xs); font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--sidebar-faible);
}
.nav-groupe:first-child { margin-top: 2px; }

.retour { display: none; }

.entete-actions { display: none; }

/* --- l'étiquette de démonstration ----------------------------------------
   Le mot le plus discret qui dise encore quelque chose : pas de fond plein, pas
   de couleur d'alerte, la taille d'une légende. Elle accompagne le nom de la
   page sans lui disputer la place, et elle tient à toutes les largeurs — une
   démonstration doit s'annoncer aussi sur grand écran.

   `[hidden]` explicite : la règle d'affichage qui suit l'emporterait sinon sur
   l'attribut, et l'étiquette resterait visible hors démonstration. */
.etiquette-demo {
  display: inline-block; flex: none; align-self: center;
  padding: 2px 7px; border-radius: var(--radius-pill);
  font-size: var(--font-xs); font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--sidebar-faible);
  border: 1px solid var(--sidebar-border);
  background: none;
  white-space: nowrap;
}
.etiquette-demo[hidden] { display: none; }

/* Sur téléphone, le tiroir « Plus » ne montre que ce que la barre du bas ne
   porte pas : deux lignes, qui n'ont pas besoin de trois en-têtes. La classe
   `dans-barre` est posée par `majOnglets()`, d'après la barre elle-même.

   Les titres restent, eux : « Réglages » dit ce qu'est Données, et sans lui
   l'entrée se lirait comme une suite du portefeuille. Seul se retire le titre
   dont toutes les entrées sont parties — `sans-suite`, posée par le même
   parcours. CSS ne sait pas dire « jusqu'au titre suivant » : `~` porte sur
   tous les frères, donc « Pilotage » aurait vu Comptes, trois lignes plus
   bas, et se serait cru encore utile. */
@media (max-width: 900px) {
  #nav a.dans-barre, #nav .nav-groupe.sans-suite { display: none; }
}

.sidebar-foot { margin-top: auto; display: flex; flex-direction: column; gap: 10px; padding-top: 18px; }
.sb-net {
  background: var(--sidebar-hover); border: 1px solid var(--sidebar-border);
  border-radius: var(--radius-sm); padding: 11px 12px; display: flex; flex-direction: column; gap: 2px;
}
/* Ce bloc portait sa propre garde contre le surlignage, ecrite quand on a cru
   que l'aplat violet du patrimoine net etait une selection — il venait en fait
   d'une animation sans `fill-mode`, et un test le tient depuis. La garde reste
   juste, mais elle n'est plus a elle : la regle de `body` la couvre, et une
   selection tiree depuis ailleurs ne peut plus commencer nulle part. */

.sb-label { font-size: var(--font-xs); color: var(--sidebar-faible); text-transform: uppercase; letter-spacing: .06em; }
.sb-net strong { font-size: var(--font-xl); font-weight: 700; letter-spacing: -.03em; color: var(--sidebar-ink); }
.sb-delta { font-size: var(--font-sm); color: #7d8896; }
.sb-delta.up { color: var(--good-text); }
.sb-delta.down { color: var(--critical); }
.saved { font-size: var(--font-xs); color: #5d6875; text-align: center; transition: color .2s; }
.compte-liens { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 14px; font-size: var(--font-xs); }
.compte-liens a { color: var(--sidebar-faible); }
/* Une valeur qu'on lit et qu'on ne modifie pas. Un champ desactive dirait
   « modifiable, mais pas maintenant » ; celle-ci n'est simplement pas a saisir.
   `break-all` parce qu'une adresse longue ne doit pas elargir sa carte. */
.valeur-fixe { font-size: var(--font-lg); padding: 4px 0 2px; word-break: break-all; }

.demarrage-barre { display: flex; align-items: center; gap: 10px; padding: 12px 15px; }
.demarrage-texte { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.demarrage-texte .sub { display: block; }

.bienvenue { padding: 22px 18px 18px; }
.bienvenue .surtitre { display: block; margin-bottom: 8px; }
.bienvenue-accroche { margin: 0; font-size: var(--font-xl); font-weight: 700; letter-spacing: -.02em; line-height: 1.2; color: var(--text-primary); }
.bienvenue-texte { margin: 8px 0 0; font-size: var(--font-md); color: var(--text-secondary); line-height: 1.5; }
.bienvenue-pas { list-style: none; margin: 16px 0 0; padding: 0; display: grid;
                 grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 10px 14px;
                 font-size: var(--font-sm); color: var(--text-secondary); }
.bienvenue-pas li { display: flex; align-items: center; gap: 8px; min-width: 0; }
@media (min-width: 768px) { .bienvenue-pas { display: flex; flex-wrap: wrap; gap: 6px 18px; } }
.bienvenue-point { width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--border-strong); flex: none; }
.bienvenue-pas li.courant .bienvenue-point { border-color: var(--accent); }
.bienvenue-pas li.fait .bienvenue-point { background: var(--good); border-color: var(--good); }
.bienvenue-pas li.fait { color: var(--muted); }
.bienvenue-actes { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.bienvenue-actes .btn { flex: 1 1 12em; }
@media (max-width: 420px) { .bienvenue-actes .btn { flex-basis: 100%; } }
.apercus-legende { margin: 2px 4px 0; font-size: var(--font-sm); color: var(--muted); line-height: 1.45; }
.apercus-verrous { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.apercu-verrou { padding: 14px 14px 12px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.apercu-verrou .silhouette { display: block; width: 100%; height: 34px; color: var(--border-strong); }
.apercu-verrou .silhouette * { fill: currentColor; stroke: currentColor; }
.apercu-verrou .silhouette [fill="none"] { fill: none; }
.apercu-verrou b { font-size: var(--font-md); font-weight: 600; color: var(--text-primary); }
.apercu-verrou .sub { display: block; line-height: 1.4; }
.apercu-verrou.principal {
  background:
    linear-gradient(180deg, color-mix(in oklab, var(--text-primary) 3%, var(--surface-1)), var(--surface-1)) padding-box,
    linear-gradient(180deg, color-mix(in oklab, var(--text-primary) 18%, var(--border)), var(--border-strong) 60%) border-box;
}
.apercu-verrou.principal .silhouette { color: color-mix(in oklab, var(--text-primary) 28%, var(--border-strong)); }

.familles-titre { margin: 18px 0 8px; font-size: var(--font-xs); font-weight: 600;
                  letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.familles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.famille { display: flex; align-items: center; justify-content: space-between; gap: 8px;
           min-width: 0; min-height: 46px; padding: 9px 12px; border-radius: var(--radius-sm); cursor: pointer;
           border: 1px solid var(--border-strong); background: transparent;
           font: inherit; font-size: var(--font-sm); color: var(--text-primary); text-align: left; }
@media (hover: hover) { .famille:hover { border-color: var(--accent); } }
.famille-nom { min-width: 0; line-height: 1.25; }
.famille-plus { flex: none; color: var(--muted); font-size: var(--font-md); }
.demarrage-barre b { font-size: var(--font-base); font-weight: 600; }
.demarrage-barre .demarrage-chevron { margin-left: auto; color: var(--muted); font-size: 18px; line-height: 1; }

.pas-liste { list-style: none; margin: 0; padding: 0; }
.pas { display: flex; gap: 12px; padding: 10px 0; }
.pas + .pas { border-top: 1px solid var(--border); }
.pas-marque {
  flex: none; width: 24px; height: 24px; border-radius: var(--radius-pill);
  display: grid; place-items: center; font-size: var(--font-sm); font-weight: 650;
  border: 1px solid var(--border-strong); color: var(--muted);
}
.pas.fait .pas-marque { border-color: var(--good); color: var(--good); }
.pas.courant .pas-marque { border-color: var(--accent); color: var(--accent); }
.pas-corps { min-width: 0; }
.pas-corps b { font-size: var(--font-base); font-weight: 600; }
.pas.fait .pas-corps b { color: var(--muted); font-weight: 500; }
.pas-corps p { margin: 5px 0 10px; line-height: 1.5; }
/* IL PASSE A LA LIGNE DE LUI-MEME, ET C'EST AU STYLE DE LE DIRE.

   Les deux branches du pas n'ont pas la meme forme : celle de la question
   pose une rangee de deux boutons, qui occupe sa ligne, et le renvoi tombait
   donc dessous ; celle d'un pas rouvert n'a qu'un bouton, reste en ligne, et
   le renvoi venait se coller a sa droite. Deux places pour un meme element
   selon l'ecran, sans que rien ne le decide.

   `block` tranche pour les deux. `width: fit-content` garde le souligne sous
   le seul texte — un bouton en bloc s'etendrait sur toute la largeur, et son
   libelle se centrerait, ce qui en ferait un troisieme bouton a l'oeil. */
.pas-actes {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
@media (max-width: 767px) {
  .pas-actes { grid-template-columns: 1fr; }
}

.pas-voir {
  display: block; width: fit-content; text-align: left;
  margin-top: 10px; font-size: var(--font-sm);
}

.pas-retour {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 0; border: 0; background: none; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.pas-modifier { font-size: var(--font-sm); color: var(--muted); margin-left: auto; }
@media (hover: hover) { .pas-retour:hover .pas-modifier { color: var(--text-secondary); } }
.pas-retour:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.saved.flash { color: var(--good); }
.fiche-note { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--grid); }
.fiche-note p { margin: 4px 0 0; font-size: var(--font-md); line-height: 1.5; white-space: pre-wrap;
                overflow-wrap: anywhere; }
.fiche-plus { display: block; margin-top: 12px; font-size: var(--font-sm); }
.saved.ko { color: var(--critical); font-weight: 650; }

.burger, .nav-backdrop, .tabbar { display: none; }

.burger {
  width: 38px; height: 38px; flex-shrink: 0; padding: 0;
  border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
  background: var(--sidebar-hover); cursor: pointer;
  place-items: center; position: relative;
}
.burger span, .burger span::before, .burger span::after {
  content: ''; display: block; position: absolute; left: 50%; width: 17px; height: 2px;
  background: var(--sidebar-ink); border-radius: 2px; transform: translateX(-50%);
  transition: transform .18s ease, opacity .18s ease;
}
.burger span { top: 50%; margin-top: -1px; }
.burger span::before { top: -6px; }
.burger span::after  { top: 6px; }
body.nav-open .burger span { background: transparent; }
body.nav-open .burger span::before { transform: translateX(-50%) translateY(6px) rotate(45deg); }
body.nav-open .burger span::after  { transform: translateX(-50%) translateY(-6px) rotate(-45deg); }

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

.topbar {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
  padding: 26px 30px 18px;
  /* Hauteur declaree et non subie : c'est `--h-entete` que lisent les collants
     qui se garent dessous. `min-height` et non `height`, pour qu'une police de
     repli d'un pixel plus haute deborde d'un pixel au lieu de rogner. */
  min-height: var(--h-entete); box-sizing: border-box;
  border-bottom: 1px solid var(--border);
  background: var(--page);
  position: sticky; top: 0; z-index: 20;
}
.topbar h1 { margin: 0; font-size: var(--font-xl); font-weight: 680; letter-spacing: -.02em; }
.topbar .sub { margin: 3px 0 0; color: var(--muted); font-size: var(--font-sm); }
.topbar-actions { display: flex; gap: 8px; flex-shrink: 0; align-items: center; }

/* Pied du menu : les reglages d'affichage, a l'ecart des destinations.
   `margin-top: auto` les pousse tout en bas — d'ou le `flex: 1` sur le menu,
   sans quoi la rangee restait collee sous « Preferences ».

   L'alignement se calcule : le retrait des liens du menu moins le rembourrage
   propre du bouton, pour que le bord gauche des icones tombe sur celui du
   texte au-dessus. */
.nav { flex: 1; }
.nav-outils {
  display: flex; gap: 2px; margin-top: auto; padding-top: 16px;
  padding-left: calc(var(--nav-retrait, 11px) - var(--oeil-marge, 6px));
}
.nav-outils .btn-oeil { opacity: .6; }
#themeToggle .ic { font-size: 15px; line-height: 17px; display: block; width: 17px; text-align: center; }

.btn-oeil {
  background: none; border: none; cursor: pointer;
  color: var(--muted); opacity: .45;
  line-height: 0; padding: 6px;
  border-radius: var(--radius-xs); transition: opacity .15s, color .15s, background .15s;
}
@media (hover: hover) { .btn-oeil:hover { opacity: 1; color: var(--text-secondary); background: var(--surface-2); } }
/* L'œil de la barre du haut partage l'état allumé et l'animation de barre, sans
   partager la géométrie : il est rond, comme la cloche et le profil, et
   `.btn-rond` s'en charge. Étendre les sélecteurs existants plutôt que d'en
   recopier trois — une seconde série de règles pour le même dessin aurait
   divergé au premier retouchage. */
:is(.btn-oeil, .btn-oeil-entete)[aria-pressed="true"] { opacity: 1; color: var(--accent); }

.btn-oeil svg {
  width: 17px; height: 17px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* L'ICONE EST PETITE, SA CIBLE NE L'EST PAS.

   Dix-sept pixels de dessin se pointent a la souris et se manquent au doigt.
   Le `::after` porte donc une zone de quarante-quatre pixels centree sur le
   bouton — la mesure d'Apple pour une cible tactile — sans peindre quoi que ce
   soit ni deplacer une ligne. Le bouton, lui, garde sa taille : une icone de
   quarante-quatre pixels dans une fiche serait un bouton plein, ce qu'il n'est
   pas.

   `margin-left` de dix pixels : colle au texte, l'icone se lirait comme une
   suite du numero, et le doigt qui vise l'un attraperait l'autre. */
.btn-copie {
  position: relative;
  background: none; border: none; cursor: pointer;
  color: var(--muted); opacity: .55;
  line-height: 0; padding: 5px; margin-left: 10px;
  vertical-align: -.35em;
  border-radius: var(--radius-xs); transition: opacity .15s, color .15s, background .15s;
}
/* ANCREE A DROITE DU BOUTON, ET NON CENTREE SUR LUI. Mesure a 390 px, sur un
   numero de vingt-sept caracteres : centree, la zone depassait de neuf pixels
   la boite du bouton — invisible, mais la ligne ne se replie pas et le
   debordement se lisait sur son `scrollWidth`. Ancree a droite, elle s'etend
   vers le texte qu'elle copie, ou le doigt est de toute facon, et la ligne
   retombe juste. */
.btn-copie::after {
  content: ''; position: absolute; right: 0; top: 50%;
  width: 44px; height: 44px; transform: translateY(-50%);
}
@media (hover: hover) {
  .btn-copie:hover { opacity: 1; color: var(--text-secondary); background: var(--surface-2); }
}
.btn-copie:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; }
.btn-copie svg {
  width: 15px; height: 15px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

svg.oeil-masque {
  width: 1.05em; height: 1.05em; vertical-align: -.15em; display: inline-block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .72;
}

:is(.btn-oeil, .btn-oeil-entete) .o-barre {
  stroke-dasharray: 22; stroke-dashoffset: 22;
  transition: stroke-dashoffset .22s ease;
}
:is(.btn-oeil, .btn-oeil-entete)[aria-pressed="true"] .o-barre { stroke-dashoffset: 0; }
:is(.btn-oeil, .btn-oeil-entete)[aria-pressed="true"] .o-pupille { opacity: 0; transition: opacity .15s; }

.barre-etat { display: flex; justify-content: flex-end; margin: -8px 0 -10px; }

.etat-cours {
  display: inline-flex; align-items: center; gap: 7px;
  background: none; border: none; cursor: pointer;
  padding: 6px 9px; border-radius: var(--radius-sm);
  font-size: var(--font-sm); font-weight: 500; letter-spacing: .01em;
  color: var(--muted);
  transition: color .15s, background .15s;
}
@media (hover: hover) { .etat-cours:hover:not(:disabled) { color: var(--text-secondary); background: var(--surface-2); } }
.etat-cours:disabled { cursor: default; }

.etat-cours .pt {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--muted);
}
.etat-cours.frais .pt { background: var(--pos, var(--series-2)); }
.etat-cours.tiede .pt { background: var(--warn, #d9a441); }
.etat-cours.hs .pt { background: var(--neg, #d4574e); opacity: .7; }
.etat-cours.encours .pt { animation: pouls 1s ease-in-out infinite; }
@keyframes pouls { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

.etat-maj { font-size: var(--font-md); line-height: 1; opacity: .45; transition: opacity .15s; }
@media (hover: hover) { .etat-cours:hover .etat-maj { opacity: 1; } }
.etat-cours.tiede .etat-maj, .etat-cours.hs .etat-maj { opacity: .9; }
.etat-cours.encours .etat-maj { animation: tourne .9s linear infinite; opacity: 1; }
@keyframes tourne { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .etat-cours.encours .etat-maj { animation: none; } }

/* Les champs de saisie portent des nombres bruts : ils ne passent pas par
   le formatage, donc le masque doit les couvrir en CSS. Les points de
   `text-security` gardent la largeur du champ, la valeur reste modifiable. */
body.discret input[type="number"],
body.discret input.acct-nom + input,
body.discret .acct-val {
  -webkit-text-security: disc; text-security: disc;
  color: var(--muted);
}

/* Le contenu s'arrete de grandir, la page continue de se centrer.

   Sur un ecran de 2 000 px, la colonne de contenu en faisait 1 750, et une
   courbe de patrimoine a douze points s'y etirait sur toute la largeur : des
   pixels depenses a eloigner les reperes les uns des autres, pas a montrer
   davantage. Les cartes a deux colonnes devenaient deux bandes, et l'oeil
   parcourait 1 700 px pour aller d'un intitule a son montant.

   1 480 px visait une grille a trois colonnes confortable. Cette grille n'existe
   plus : la page la plus large porte une grille de quatre tuiles, et les autres
   sont des listes. Le plafond gardait donc de la place pour un usage disparu,
   pendant que les lignes s'etiraient.

   1 100 px, et le chiffre vient d'une mesure, pas d'un gout. Le tableau le plus
   large de l'application — neuf colonnes, les lignes de titres — demande 891 px
   pour tenir sur une ligne ; il en recoit 1 002 une fois le remplissage retire.
   Les tuiles du budget passent de 330 a 248 px de large, ce qui reste au-dessus
   d'une carte de telephone. Aucun ecran ne deborde, a aucune des largeurs
   essayees.

   Ce qu'on y gagne se lit sur une rangee : un intitule et son montant etaient
   separes de 1 328 px sur un 1 680, pour un tableau de trois colonnes. Le
   regard n'a pas a faire ce chemin, et deux comptes voisins se comparent
   d'autant mieux que leurs chiffres sont proches l'un de l'autre.

   En dessous, `margin: 0 auto` ne fait rien ; au-dela, il centre. */
.view {
  padding: 22px 30px 64px; display: flex; flex-direction: column; gap: 18px;
  max-width: 1100px; margin: 0 auto; width: 100%;
}

/* Les titres portent Manrope. La regle vise les elements, pas une liste de
   classes : `.topbar h1`, `.card-head h2`, `.modal-head h2`, `.acct-head h3`
   auraient fait quatre selecteurs a tenir a jour, et le titre ajoute demain
   serait reste en police systeme sans que rien ne le signale. Un titre est
   un titre parce qu'il est un `h`, pas parce qu'on a pense a l'inscrire.

   Les deux ecritures du nom s'ajoutent explicitement — c'est meme la raison
   d'etre de cette police. L'ecran de lancement le pose en grand une seconde ;
   la barre laterale le porte en permanence, et c'est celui-la qu'on voit. Les
   deux, ou aucun : une marque qui change de police d'un ecran a l'autre n'en
   est plus une.

   Rien d'autre. Aucun de ces elements ne porte de montant — verifie : les
   seules valeurs interpolees dans un titre sont un millesime (« Objectif a
   fin 2026 ») et un rang (« Allocation 2 »), qui ne s'alignent en colonne
   nulle part. Le jour ou un titre afficherait un total, il faudrait le
   sortir d'ici ou verifier que Manrope porte bien `tnum`. */
h1, h2, h3,
.lancement-marque span,
.brand-text strong {
  font-family: var(--font-titre);
}

.t-value, .hero-value, .acct-value, .jour-eur, .dep-somme,
td, th, .sb-net strong {
  font-variant-numeric: tabular-nums;
}
.t-value { letter-spacing: -.02em; }

/* Safari iOS peint le texte des <button> en bleu, comme un lien, et le fait
   par `-webkit-text-fill-color` — qui l'emporte sur `color`. Depuis que les
   tuiles et les cartes sont cliquables, tous les montants qu'elles portent
   virraient au bleu sur iPhone, alors qu'ils restaient normaux ailleurs.
   On rend donc la couleur du remplissage a `color`, et on coupe l'habillage
   natif que Safari applique par-dessus notre propre style. */
button, [role="button"] {
  -webkit-text-fill-color: currentColor;
  -webkit-appearance: none; appearance: none;
  -webkit-tap-highlight-color: transparent;
}

/* Ceinture et bretelles avec le `format-detection` de l'en-tete : si Safari
   enveloppe malgre tout un montant dans un lien telephonique, celui-ci ne
   doit pas se distinguer du texte autour. */
a[href^="tel:"], a[x-apple-data-detectors] {
  color: inherit !important; -webkit-text-fill-color: currentColor !important;
  text-decoration: none !important; font: inherit !important;
  pointer-events: none;
}

.btn {
  font: inherit; font-size: var(--font-md); font-weight: 550;
  padding: 8px 14px; border-radius: var(--radius-sm);
  border: 1px solid transparent; background: var(--text-primary); color: var(--page);
  cursor: pointer; transition: opacity .12s, background .12s;
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
}
@media (hover: hover) { .btn:hover { opacity: .85; } }
/* Le meme anneau que les vingt-cinq autres composants focalisables de
   l'application. Sans lui, le bouton s'en remettait a l'anneau par defaut du
   navigateur : la moitie de l'interface disait « tu es ici » en violet, l'autre
   moitie dans le dialecte du systeme, et un bouton plein clair sur fond clair
   n'y montrait presque rien. `outline-offset` positif : l'anneau se pose autour
   du bouton, pas dedans, ou il rognerait le libelle. */
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn.ghost { background: var(--surface-1); color: var(--text-primary); border-color: var(--border-strong); }
@media (hover: hover) { .btn.ghost:hover { background: var(--surface-2); opacity: 1; } }
.btn.sm { padding: 6px 11px; font-size: var(--font-sm); }
/* Pleine largeur, pour l'action principale d'une fenetre. Le bouton de photo du
   jour etait un `sm ghost` pose au-dessus de douze champs a remplir : discret
   devant une grille, il se lisait comme une option alors qu'il fait tout le
   travail. Une action qui remplace douze gestes doit occuper la largeur des
   douze. */
.btn.pleine { display: block; width: 100%; padding: 11px 14px; font-size: var(--font-md); }
.btn.danger { background: color-mix(in oklab, var(--critical) 90%, #000); color: #fff; }
.btn.icon { padding: 4px 8px; background: transparent; color: var(--muted); border-color: transparent; font-size: var(--font-base); }
@media (hover: hover) { .btn.icon:hover { color: var(--critical); background: var(--surface-2); } }
/* La cible du doigt, plus grande que le dessin. Un bouton-icone mesurait 22 a
   29 px selon l'endroit, quand le minimum tenable au doigt est 24 : sous cette
   taille, on atteint son voisin une fois sur trois. L'agrandir pour de vrai
   ferait un aplat la ou il faut un signe discret — le seul moyen d'avoir les
   deux est de separer la boite cliquable du dessin.

   `inset` negatif plutot qu'un remplissage : le remplissage pousserait les
   voisins et deplacerait la rangee entiere. Le pseudo-element, lui, ne prend
   aucune place dans le flux. Il faut un `position: relative` sur le bouton, et
   surtout pas de fond : c'est une surface a viser, pas a voir. */
.btn.icon { position: relative; }
/* Six pixels, et pas huit, y compris pour les `xs` de 22 px : a huit, la croix
   de la carte de rappel mordait de deux pixels sur le bord de « Plus tard », son
   voisin a six pixels d'ecart. Une cible etendue ne doit jamais empieter sur une
   autre — et surtout pas quand c'est l'action destructive qui gagne du terrain
   sur celle qui ne fait que reporter. Six suffisent : 34 px pour le plus petit
   des boutons-icones, contre 24 demandes. */
.btn.icon::after { content: ''; position: absolute; inset: -6px; }
.btn.icon:is([data-action^="del-"], [data-action^="supprimer"], [data-action^="retirer"]) {
  color: color-mix(in oklab, var(--critical) 72%, var(--muted));
}
@media (hover: hover) { .btn.icon:is([data-action^="del-"], [data-action^="supprimer"], [data-action^="retirer"]):hover {
  color: #fff; background: color-mix(in oklab, var(--critical) 90%, #000);
} }

.champ-vider {
  position: fixed; z-index: 60;
  width: 20px; height: 20px; padding: 0;
  display: grid; place-items: center;
  font: inherit; font-size: var(--font-lg); line-height: 1;
  border: none; border-radius: 50%; cursor: pointer;
  background: var(--surface-2); color: var(--text-secondary);
  box-shadow: 0 0 0 1px var(--border-strong);
}
@media (hover: hover) { .champ-vider:hover { color: var(--critical); box-shadow: 0 0 0 1px var(--critical); } }
input[aria-invalid="true"] { border-color: var(--critical); }
input.champ-vidable::-webkit-outer-spin-button,
input.champ-vidable::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input.champ-vidable[type="number"] { -moz-appearance: textfield; }

input, select, textarea {
  font: inherit; font-size: var(--font-md); color: var(--text-primary);
  background: var(--surface-1); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 7px 9px; width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
input[type=number] { text-align: right; font-variant-numeric: tabular-nums; }
/* Le premier `textarea` de l'application. Deux endroits en avaient besoin : une
   adresse et la note d'un mois, deux textes qu'une ligne coupait en plein
   milieu — l'adresse du fixture demande 359 px pour 311 disponibles, et rien ne
   disait qu'il en manquait.

   `resize: vertical` et non `both` : elargir un champ au-dela de sa carte le
   ferait deborder d'une mise en page qui ne l'attend pas. La hauteur, elle, se
   regle sans rien casser. */
textarea { resize: vertical; line-height: 1.45; }

/* `hidden` doit primer : une règle de classe comme `.field { display: flex }`
   l'emporte sinon sur le `display: none` du navigateur, et l'élément reste
   visible alors que le code le croit masqué. */
[hidden] { display: none !important; }

.field { display: flex; flex-direction: column; gap: 5px; }
.field label { font-size: var(--font-sm); color: var(--text-secondary); font-weight: 550; }
.champ-par-part { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px;
                  margin-top: 7px; padding-left: 10px; border-left: 2px solid var(--grid);
                  font-size: var(--font-sm); color: var(--text-secondary); }
.champ-par-part input { font-size: var(--font-md); padding: 6px 8px; width: 6.5em; max-width: 6.5em; flex: none; }
.champ-par-part .formule-val { color: var(--text-primary); font-weight: 550; white-space: nowrap; }
.champ-par-part .formule-op { color: var(--muted); }
.champ-par-part .formule-unite { color: var(--muted); white-space: nowrap; margin-left: -2px; }
.champ-par-part .formule-sous { flex: 1 0 100%; margin-top: 1px; }
.valeur-figee {
  margin: 0; align-self: start; padding: 7px 0;
  font-size: var(--font-md); color: var(--text-secondary); font-style: italic;
}

/*      Un menu deroulant sait deja quelle largeur il lui faut : celle de sa
        plus longue option. C'est `.field`, colonne flex, qui l'etirait de
        force sur toute la carte — d'ou « 1 000 € » dans une boite de 320 px.
        On lui rend sa largeur naturelle. Un menu de montants se serre, un
        menu de noms de comptes reste large, sans qu'on ait a les distinguer.
        Le plafond reste, pour l'option a rallonge — celui d'un texte libre, et
        non 20 em : « Oui, chercher les cours automatiquement » demande 21,3 em
        et « Automatique (place de reference du titre) » 22,5, donc les deux se
        coupaient au milieu d'un mot. Un menu natif tronque sans rien dire ;
        `text-overflow` ajoute l'ellipse quand le conteneur, lui, se resserre.

        `min(28em, 100%)` et non 28 em seuls : un menu prend la largeur de sa
        plus longue option sans regarder ou il est pose, et 28 em valent 364 px
        quand la carte n'en offre que 311 a 375 px. Il sortait alors de l'ecran,
        ou les pixels au-dela du bord sont perdus. */
.field select {
  width: auto; align-self: start;
  max-width: min(28em, 100%); text-overflow: ellipsis;
}
/* Un menu se dimensionne sur ce qu'il montre, pas sur sa plus longue option.

   C'est le principe ; la mise en oeuvre demande deux regles parce qu'aucune ne
   suffit seule. `width: auto` prend la largeur de la plus longue option, et une
   liste de quarante-quatre places de cotation etale donc son menu sur 292 px pour
   afficher « Automatique », onze caracteres. A l'inverse, un plafond serre pour
   tout le monde couperait « Oui, chercher les cours automatiquement », qui est la
   valeur choisie et non une option lointaine. Le CSS ne connait pas l'option
   selectionnee : il ne peut pas trancher entre ces deux cas.

   Le plafond general reste donc large — la valeur choisie doit tenir — et les
   listes qui portent une option nettement plus longue que ce qu'on y choisit
   d'ordinaire prennent `menu-serre`. Douze em valent 192 px sur telephone, ou le
   navigateur impose 16 px aux controles de formulaire, et 156 sur ecran : de quoi
   ecrire « Euronext Paris » en entier. Les deux ou trois noms plus longs de la
   liste s'y coupent, avec l'ellipse pour le dire — c'est la contrepartie assumee,
   et la liste deroulee, elle, reste dessinee par le systeme a sa vraie largeur.

   `select.menu-serre` et non `.menu-serre` seul : a specificite egale avec
   `.field select`, c'est l'ordre qui tranche, et une classe nue perdrait. */
select.menu-serre { max-width: min(12em, 100%); }
.field input[type="text"], .field input:not([type]) { max-width: 28em; }
.field :is(input[type="number"], input[type="date"]) { max-width: 9em; }
.field input[maxlength] { max-width: 11em; }
table .field :is(input, select) { max-width: none; }

/* Dans une grille, un champ remplit sa cellule.

   Les plafonds ci-dessus ont leur raison hors grille : un champ seul sur une
   ligne n'a pas besoin d'une boite de 320 px pour « 1 000 € ». Mais dans une
   rangee, ils donnaient quatre largeurs differentes sur une meme fiche — 117 px
   pour un nombre, 160 pour un menu, 299 pour un texte serre, 364 pour un texte
   libre — et rien ne s'alignait avec rien. C'est la grille qui decide de la
   largeur d'une cellule ; le champ, lui, l'occupe.

   Le resultat tient les deux : deux champs d'une `g-2` sont egaux, trois d'une
   `g-3` aussi, et les rangees successives s'alignent puisqu'elles partagent la
   largeur de la carte. Un champ hors grille garde son plafond.

   `align-self` est remis a son defaut : `.field select` le passait a `start`,
   ce qui laissait le menu a sa largeur naturelle meme avec `width: 100%`.

   Enfant direct seulement, et c'est essentiel : une cellule de grille est un
   enfant de la grille, jamais un descendant. Le combinateur descendant attrapait
   le moindre champ pose dans une carte posee dans une grille, et « Français »
   s'affichait alors dans un menu de 442 px sur la page Preferences. Un champ
   n'est etire que s'il est lui-meme la cellule. */
.grid > .field :is(input, select, textarea) {
  max-width: none; width: 100%; align-self: stretch;
}
/* Et le controle se pose au BAS de sa cellule.

   Deux champs cote a cote, deux libelles de hauteur differente : « Valeur
   estimee aujourd'hui (€) » passe a la ligne quand « Prix d'acquisition (€) »
   tient sur une, et les deux saisies se retrouvaient decalees de la hauteur
   d'une ligne. C'est la meme faute que la paire quantite / prix de revient,
   corrigee la sur le conteneur ; ici les cellules sont deja etirees a la hauteur
   de la rangee, donc c'est a l'interieur du champ que l'espace doit se placer.

   `margin-bottom: auto` sur le LIBELLE et non `margin-top: auto` sur la saisie :
   l'espace se pose ainsi entre les deux, le libelle reste en haut, et ce qui
   suivrait la saisie garde sa place au lieu d'etre pousse avec elle. */
.grid > .field > label { margin-bottom: auto; }

.card {
  background:
    linear-gradient(var(--surface-1), var(--surface-1)) padding-box,
    linear-gradient(180deg,
      color-mix(in oklab, var(--text-primary) 9%, var(--border)),
      var(--border) 42%) border-box;
  border: 1px solid transparent;
  border-radius: var(--radius); padding: 18px;
  box-shadow: var(--shadow);
}
.card.tight { padding: 14px 16px; }
.card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
}
.card-head h2 {
  margin: 0; font-size: var(--font-lg); font-weight: 680; letter-spacing: -.015em;
  color: var(--text-primary);
}
.card-head .hint { font-size: var(--font-sm); color: var(--muted); }

/* La base d'un pourcentage, sous le titre de sa carte et non a cote.

   Elle vivait dans l'en-tete, donc a droite du titre : deux textes de longueurs
   tres differentes que `space-between` poussait aux deux bords. En francais,
   « en % de ton patrimoine financier · 66 182 EUR » et « Repartition globale »
   se rejoignaient au milieu et se chevauchaient sur un telephone.

   Sous le titre, elle se lit comme ce qu'elle est : la legende de la carte, pas
   une commande. Et le probleme de place disparait, puisqu'il n'y a plus deux
   textes a loger sur une ligne.

   `:has()` plutot qu'une classe de variante sur l'en-tete : c'est la presence
   de la legende qui resserre le titre, et la regle le dit dans ce sens-la. */
.card-head:has(+ .tete-legende) { margin-bottom: 2px; }
.tete-legende { margin: 0 0 14px; font-size: var(--font-sm); color: var(--muted); }
/* Une bascule posee dans un en-tete de carte se cale au bord droit, et y reste
   quand elle passe a la ligne.

   `space-between` distribue ligne par ligne : seule sur la seconde, elle partait
   a gauche. Le cas se produit en francais, ou « Evolution du patrimoine » prend
   179 des 311 px d'une carte a 375 px quand l'anglais « Wealth over time » n'en
   prend que 126 — la meme carte se replie dans une langue et pas dans l'autre.
   C'est la meme regle que pour la bascule du grand chiffre, et c'est la meme
   raison : le bord droit est celui des commandes. */
.card-head > .segmented { margin-left: auto; }
/* Un intitule et sa precision, empiles dans l'en-tete.

   Un compte pose a cote du titre, avant les commandes, n'appartient ni a l'un ni
   aux autres : il flotte, et l'oeil le prend pour une troisieme commande. Sous
   le titre, il se lit comme ce qu'il est.

   `align-items: baseline` sur l'en-tete aligne les commandes sur la premiere
   ligne de ce bloc, donc sur le titre : la geometrie ne change pas pour les
   voisins. `min-width: 0` pour qu'un titre long se tronque au lieu de pousser
   les commandes hors de la carte. */
.card-head .tete-titre {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}

/* LA GRAMMAIRE D'UN EN-TETE DE CARTE, et ce qui descend dessous.

   Sur la ligne du titre, a droite, UNE chose au plus : une information
   discrete, ou l'action de la carte, ou une bascule. Deux boutons de meme
   poids s'y disputaient l'attention, et une precision posee a cote d'un bouton
   flottait entre les deux. Une precision qui accompagne une action se range
   sous le titre (`.tete-titre`).

   Ce qui regle la liste descend en dessous, en deux rangees partagees par
   toutes les cartes qui en ont besoin : les filtres d'abord (`.carte-filtres`),
   puis le compte et le tri (`.carte-meta`), juste au-dessus de ce qu'ils
   rangent. Le compte a gauche, le tri au bord droit, comme la commande d'un
   en-tete. */
.carte-filtres {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  margin: -4px 0 12px;
}
.carte-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 0 0 8px;
}
.carte-meta > :last-child:not(:first-child) { margin-left: auto; }
.total-vus { display: none; }
@media (max-width: 767px) {
  .total-vus {
    display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    margin: 0 0 8px; font-size: var(--font-sm); color: var(--text-secondary);
  }
  .total-vus b {
    color: var(--text-primary); font-weight: 650;
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }
}
.card-head > .btn.sm { margin-block: -4px; }
@media (max-width: 480px) {
  .carte-filtres:has(> :nth-child(2)) > .seg-mini,
  .carte-filtres:has(> :nth-child(2)) > select { flex: 1 1 auto; }
  .carte-filtres:has(> :nth-child(2)) > .seg-mini { display: flex; }
  .carte-filtres:has(> :nth-child(2)) > .seg-mini > button { flex: 1 1 0; }
}

/* Deux boutons du meme geste passent a la ligne ensemble, ou pas du tout, et
   mesurent pareil.

   En-tete de carte souple, ils tombaient un par un : « + Rentree » restait en
   haut a droite contre le selecteur d'annee, « + Depense » descendait seul a
   gauche sous le sous-titre, a l'autre bout de la rangee. Deux entrees de la
   meme paire lues comme deux commandes sans rapport. Un seul element de flex,
   donc un seul point de rupture.

   Une grille a colonnes egales, et non un flex, pour la meme raison que les
   rangees de boutons d'une fiche : en flex, la longueur du libelle decide de la
   largeur, et deux boutons du meme geste finissaient a 77 et 120 px l'un
   au-dessus de l'autre. Des colonnes en `1fr` dans un conteneur de largeur
   naturelle prennent toutes la taille de la plus large : les deux boutons
   mesurent celui qui en demande le plus, sans qu'aucun libelle se replie. */
.paire-btn {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px;
}

.sous-titre-carte {
  margin: 22px 0 10px; font-size: var(--font-sm); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-secondary);
  border-top: 1px solid var(--grid); padding-top: 16px;
}

.lien-nu {
  padding: 0; border: 0; background: none;
  font: inherit; color: var(--accent); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
@media (hover: hover) { .lien-nu:hover { opacity: .75; } }
.lien-nu:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.champ-lecture {
  margin: 0; padding: 9px 2px; font-size: var(--font-md);
  color: var(--text-secondary);
}

/* UNE CASE ET SON LIBELLE FONT UNE LIGNE, et il fallait l'ecrire. La classe
   existait dans le gabarit sans aucune regle en face : la case tombait donc
   sur sa propre ligne, au-dessus du texte qu'elle commande, et le centrage du
   pied de fenetre la posait au milieu. On lisait un intitule sans savoir a quoi
   la case se rapportait.
   `align-items: start` et non `center` : le libelle porte une bulle d'aide en
   dessous, donc il fait deux lignes, et une case centree sur ces deux lignes
   flotte a cote de rien. Elle s'aligne sur la premiere. */
.field-case {
  display: flex; align-items: start; gap: 9px;
  padding: 7px 2px; cursor: pointer; text-align: left;
}
/* `width: auto` : la regle generique des champs met tout `input` a pleine
   largeur, case a cocher comprise, et le libelle etait repousse hors de la
   fenetre, replie sur quatre lignes. */
.field-case > input { flex: none; width: auto; padding: 0; margin: 3px 0 0; }
.field-case > span { display: block; }

/* L'etablissement sous le nom d'un champ de releve. `min-height` plutot que
   rien : la ligne garde sa place quand elle est vide — le cas d'un nom qui
   porte deja sa banque — et les douze champs de la grille restent alignes.
   Sans elle, un seul libelle sur deux lignes decalait son champ d'un cran. */
.f-etab {
  display: block; min-height: 1.15em; line-height: 1.15;
  font-weight: 500; font-size: var(--font-xs); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.avert {
  margin: 0 0 14px; padding: 9px 12px;
  border-left: 3px solid var(--warning);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: color-mix(in oklab, var(--warning) 9%, transparent);
  color: var(--text-primary); font-size: var(--font-sm); line-height: 1.45;
}
.avert-liste { margin: 4px 0 6px; padding-left: 18px; }
.avert-liste li { overflow-wrap: anywhere; }
.dest-auto { margin: 0; font-weight: 550; color: var(--text-primary); }

.grid { display: grid; gap: 16px; }
/* Un enfant de grille refuse par defaut de descendre sous sa largeur
   min-content : une carte au contenu large (un libelle qui ne se coupe pas,
   un tableau) poussait alors toute la page en debordement horizontal. Les
   `minmax(0, …)` ci-dessous protegent les colonnes, pas leur contenu — d'ou
   ce plancher leve explicitement. La regle existait deja cote mobile ; elle
   manquait sur grand ecran, ou le debordement passait plus inapercu. */
.grid > *, .card, .table-wrap { min-width: 0; }
.g-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g-auto { grid-template-columns: repeat(auto-fit, minmax(172px, 1fr)); }
/* `align-items: start` : chaque carte prend sa hauteur, pas celle de sa
   voisine. Par defaut une grille etire ses elements, si bien que « Depenses du
   mois en cours » — un grand chiffre et trois ecarts — s'allongeait sur les
   480 px de la carte d'a cote et montrait surtout du vide. Deux cartes de
   hauteurs differentes se lisent mieux qu'une carte a moitie remplie. */
.g-hero { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); align-items: start; }
.g-2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

.hero {
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--surface-2) 70%, var(--surface-1)), var(--surface-1));
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 24px 26px;
  display: flex; flex-direction: column; gap: 16px;
}
/* Elle est fille de la carte, pas de la colonne de lecture : c'est ce qui lui
   rend toute la largeur, et donc le bord droit ou sa bascule se cale.
   `margin-bottom: -10px` reprend une part de l'ecart de seize pixels de la
   carte : l'intitule appartient au chiffre qu'il nomme, il ne flotte pas a
   mi-chemin entre lui et le bord. */
.hero-label {
  font-size: var(--font-sm); text-transform: uppercase; letter-spacing: .08em; color: var(--text-secondary);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: -10px;
}
.hero-label > .segmented { margin-left: auto; }

/* La version reduite du controle segmente vit plus bas, apres `.segmented`.
   Elle etait ecrite ici et n'a jamais rien fait : voir son commentaire. */
.hero-sous { font-size: var(--font-sm); margin-top: 2px; text-transform: none; letter-spacing: 0; }

.bien { padding: 12px 0; border-bottom: 1px solid var(--grid); }
.bien:last-child { border-bottom: none; }
.bien-tete { display: flex; align-items: baseline; gap: 12px; margin-bottom: 8px; }
.bien-tete b { margin-left: auto; font-size: var(--font-lg); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bien .kv { margin: 0 0 10px; }
.pret-vif {
  margin: 4px 0 12px; padding: 8px 10px;
  background: var(--surface-2); border-radius: var(--radius-sm);
}
.pret-vif .kv { margin: 6px 0 0; }
.pret { padding: 12px 0; border-bottom: 1px solid var(--grid); }
.pret:last-of-type { border-bottom: none; }
.pret .goal-bar { margin-top: 4px; }

/* Montant qu'on peut masquer d'un geste. Il ne se deguise pas en bouton :
   rien ne change au repos, seuls le curseur et un leger voile au survol
   disent qu'il repond.

   Ce bloc precede volontairement les regles qui donnent leur taille aux
   montants : `font: inherit` neutralise le style que le navigateur impose
   aux <button>, et doit donc ceder ensuite a `.hero-value`. Place apres, il
   ramenait le patrimoine de 52 px a 14 px. */
/* Le montant est un bouton : il masque les chiffres au clic. Ce qu'on touche
   pour agir ne doit pas se comporter comme du texte — c'est desormais vrai de
   toute l'application, par la regle posee sur `body`, et ce bouton n'a plus de
   garde a lui. */
.val-masquable {
  background: none; border: none; padding: 0; cursor: pointer;
  color: inherit; font: inherit; letter-spacing: inherit; text-align: left;
  display: block; transition: opacity .15s;
}
@media (hover: hover) { .val-masquable:hover { opacity: .72; } }
.val-masquable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

.hero-value { font-size: clamp(38px, 5vw, 54px); font-weight: 600; letter-spacing: -.035em; line-height: 1; margin: 8px 0 0; }
.hero-barre {
  display: flex; height: 10px; border-radius: var(--radius-pill); overflow: hidden;
  background: var(--grid);
}
.hero-barre > i { display: block; height: 100%; flex: 0 0 auto; }
.hero-barre > i:last-child { flex: 1 1 auto; }

/* --- LE HAUT DU HERO, EN DEUX COLONNES --------------------------------------

   La lecture reste a gauche : intitule, montant, variation, periode. La courbe
   prend le vide de droite, et rien d'autre ne bouge — la barre de composition
   garde toute la largeur, sous les deux colonnes.

   `align-items: end` : la courbe se pose sur la ligne de base de la periode
   plutot que de flotter en haut, ou elle aurait concurrence le grand chiffre.
   `min-width: 0` sur la colonne gauche, sans quoi un montant a sept chiffres
   refuserait de se laisser comprimer et pousserait la courbe hors de la carte. */
/* MOITIE-MOITIE, ET LE PARTAGE NE DEPEND PLUS DU CONTENU.

   Deux tentatives ont precede celle-ci et chacune laissait le contenu decider.
   `3fr 2fr` donnait toujours ses trois cinquiemes a la colonne de gauche, meme
   quand « 49 517 € » n'en demandait que la moitie : le reste devenait un blanc
   entre le texte et la courbe. `fit-content(60%)` a corrige ce blanc, mais en
   rendant a la courbe TOUT ce que le texte ne prenait pas — sur un ecran large,
   « 50 578 € » ne demande pas grand-chose, et la courbe s'etirait sur les trois
   quarts de la carte pour illustrer un chiffre ecrit a cote.

   Deux fractions egales tranchent : chaque colonne prend la moitie de ce qui
   reste APRES l'ecart, a toutes les largeurs, quelle que soit la longueur du
   montant. Mesure a 1440 : la courbe passe de 750 a 485 px.

   `min-content` EST LA SEULE EXCEPTION, ET ELLE EST MESUREE. Le `clamp()` du
   grand chiffre suit la largeur de la fenetre, pas celle de sa colonne : a
   375 px il reste a 34 px de police, et « 4 200 909 € » demande alors 166 px
   la ou la moitie n'en donne que 148 — les separateurs de milliers sont
   insecables, rien ne se replie. Avec `minmax(0, 1fr)` le montant debordait de
   dix-huit pixels ; avec `minmax(min-content, 1fr)` la colonne de lecture
   grandit juste assez (166/129) et seulement dans ce cas. Un patrimoine
   ordinaire garde ses deux moities exactes. */
.hero-haut {
  display: grid; grid-template-columns: minmax(min-content, 1fr) minmax(0, 1fr);
  align-items: end; gap: 16px;
}
/* `min-width: 0` reste indispensable : sans lui, une cellule de grille refuse
   de descendre sous la largeur de son contenu, et un montant a sept chiffres
   ferait deborder sa colonne au lieu de s'y ajuster. */
.hero-gauche { min-width: 0; }
/* La colonne de droite prend la moitie de la rangee. `min-width: 0` pour la
   meme raison que sa voisine : une cellule de grille ne descend pas sous son
   contenu sans lui.

   ELLE GARDE SA PROPRE GARDE CONTRE LA SELECTION, alors que la regle de `body`
   la couvre deja, et c'est le seul endroit du fichier ou cette redite est
   voulue : ici, l'appui maintenu N'EST PAS un geste parasite, c'est le geste de
   lecture lui-meme. Une regle racine qu'on restreindrait un jour ne doit pas
   emporter celle-la sans qu'on s'en apercoive. Le defilement vertical reste au
   navigateur par le `touch-action: pan-y` pose a la construction, et rien ici
   ne le touche. */
.hero-spark {
  min-width: 0;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
/* SOUS 560 PX, ELLE S'EFFACE, ET LA RAISON EST UNE PROPORTION, PAS UNE CASSE.
   Mesure faite a 375 : la colonne de lecture tombe de 311 a 167 px, la courbe
   prend son plancher de 120. Rien ne deborde et le montant tient encore sur une
   ligne, meme a sept chiffres — `clamp()` le ramene a 38 px et « 4 200 909 EUR »
   y entre. Ce qui ne tient pas, c'est le partage : quarante pour cent de la
   colonne ou l'on vient lire un chiffre, donnes a l'illustration de ce meme
   chiffre, ecrit en toutes lettres deux lignes plus haut. Sur l'ecran ou cette
   carte se lit vraiment, l'illustration cede.
   Rien ne reste a sa place : la colonne gauche reprend toute la largeur, et la
   carte retrouve exactement la mise en page qu'elle avait. */
/* ELLE RESTE A DROITE A TOUTES LES LARGEURS, telephone compris. Deux essais
   l'ont precede et aucun ne tenait : l'effacer sous 560 px la faisait
   simplement manquer, la passer sous la lecture l'eloignait du chiffre qu'elle
   illustre. A 375 px la lecture tombe a 167 px et la courbe prend son plancher
   de 120 : rien ne deborde, et le montant tient sur une ligne meme a sept
   chiffres, `clamp()` le ramenant a 38 px.

   AUCUNE HAUTEUR IMPOSEE EN CSS SUR LE SVG, ET C'EST UN PIEGE MESURE. Poser
   `height` ne l'aplatit pas : sa `viewBox` se recentre pour tenir dans la
   boite, donc le dessin RETRECIT EN LARGEUR et flotte au milieu de son
   conteneur — trois cent onze pixels de place, deux cent cinquante-quatre de
   trace. La hauteur se decide a la construction, dans `sparkline()`, ou nulle
   part. */

.hero-deltas { display: flex; gap: 20px; flex-wrap: wrap; }
.hero-delta { display: flex; flex-direction: column; gap: 1px; }
.hero-delta span { font-size: var(--font-xs); color: var(--muted); }
.hero-delta b { font-size: var(--font-base); font-weight: 600; }
/* Le pourcentage vit DANS le montant, et pas a cote.

   Il y est pour deux raisons. La couleur d'abord : `.hero-delta span` grise
   tout ce qui suit le montant, et un pourcentage gris a cote d'un montant vert
   dirait deux choses differentes du meme mouvement. En heritant du `b`, il
   prend la couleur de hausse, de baisse ou de neutre sans qu'on la repete.
   Le rang ensuite : l'oeil doit prendre le montant en premier. Un point plus
   petit, un demi-cran de graisse en moins et un voile d'opacite suffisent a le
   mettre second sans le rendre secondaire.

   Le selecteur passe par `b` pour battre `.hero-delta span`, qui est plus
   specifique que la classe seule. */
.hero-delta b .hero-pct { font-size: var(--font-md); font-weight: 550; color: inherit;
                          opacity: .82; margin-left: 7px; letter-spacing: 0; }

.up { color: var(--good-text); }
.down { color: var(--critical); }
.flat { color: var(--muted); }

/* Un depassement leger, celui qui se rattrape sur le mois suivant. Meme
   graduation que les barres du graphique, qui la tiennent de
   `niveauDepassement()` : un mois orange sur le dessin et rouge dans le tableau
   juste en dessous, ce serait deux jugements sur le meme mois. */
.tiede { color: var(--serious); }

/* Un credit en cours n'est pas une alerte. Le rouge de `.down` dit « quelque
   chose ne va pas » — un depassement de budget, une moins-value — et il
   s'affichait sur le capital restant du, qui n'est ni bon ni mauvais : c'est
   une soustraction choisie, dans une addition qui la mentionne. Le signe moins
   porte deja tout le sens, la couleur n'a rien a ajouter. */
.dette { color: var(--text-secondary); }

.tile {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 15px 16px; display: flex; flex-direction: column; gap: 5px;
  position: relative; overflow: hidden;
  transition: border-color .15s ease, transform .15s ease, background .15s ease;
}
.tile::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: var(--tile-color, var(--series-1));
  opacity: .85; transition: opacity .15s ease;
}
.tile .t-label {
  font-size: var(--font-xs); font-weight: 650; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); display: flex; align-items: center;
}
.tile .t-value { font-size: var(--font-2xl); font-weight: var(--fw-chiffre); letter-spacing: -.02em; line-height: 1.05; }
.tile .t-meta { font-size: var(--font-sm); color: var(--text-secondary); display: flex; align-items: center; gap: 8px; }
.tile-link {
  font: inherit; text-align: left; cursor: pointer; color: var(--text-primary);
}
@media (hover: hover) { .tile-link:hover {
  border-color: var(--border-strong);
  background: color-mix(in oklab, var(--tile-color, var(--series-1)) 5%, var(--surface-1));
  transform: translateY(-1px);
} }
@media (hover: hover) { .tile-link:hover::before { opacity: 1; } }
.tile-link:active { transform: translateY(0); }
.tile-link:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.tile .t-go {
  position: absolute; top: 11px; right: 12px; font-size: var(--font-lg); color: var(--muted);
  opacity: 0; transition: opacity .15s ease, transform .15s ease;
}
@media (hover: hover) { .tile-link:hover .t-go { opacity: 1; transform: translateX(2px); } }

.pf-corps { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px 32px; }

.pf-total, .pf-mesure {
  display: flex; flex-direction: column; align-items: flex-start;
  background: none; border: 0; padding: 0; font: inherit; text-align: left;
  color: var(--text-primary);
}
button.pf-total, button.pf-mesure { cursor: pointer; }
.pf-total { gap: 2px; }
.pf-mesure { gap: 3px; }
.pf-total > b {
  font-size: var(--font-2xl); font-weight: var(--fw-chiffre); letter-spacing: -.02em;
  line-height: 1.05;
}
.pf-total > span { font-size: var(--font-sm); color: var(--text-secondary); }
.pf-mesures {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px 28px; margin-left: auto; flex: 1 1 300px; max-width: 420px;
}
.pf-lab {
  font-size: var(--font-xs); font-weight: 650; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
.pf-val { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.pf-mesure b {
  font-size: var(--font-xl); font-weight: var(--fw-chiffre); letter-spacing: -.02em;
  line-height: 1.1;
}
.pf-pct, .pf-sous { font-size: var(--font-sm); color: var(--text-secondary); }
.pf-muet b { font-size: var(--font-md); font-weight: 600; color: var(--muted); }
.pf-muet .pf-sous { color: var(--muted); }
.pf-jour-muet { grid-column: 1 / -1; margin: 0; font-size: var(--font-xs); color: var(--muted); }

.repart-synthese .repart-ligne { padding: 8px 0; }
.repart-autres .repart-haut { padding-left: 17px; }
.repart-autres .repart-nom { font-weight: 500; color: var(--text-secondary); }
.repart-chev { display: inline-block; margin-left: 6px; color: var(--muted); transition: transform .15s ease; }
.repart-autres[aria-expanded="true"] .repart-chev { transform: rotate(90deg); }
/* CHAQUE CATEGORIE SE TOUCHE. La rangee entiere est la cible ; le chevron a
   droite dit qu'elle s'ouvre, et le fond au toucher confirme le geste. Le fond
   deborde de 8 px de chaque cote sur un pseudo-element, pour ne deplacer ni les
   filets ni les colonnes : `isolation` le garde sous le texte de la rangee. */
.repart-synthese .repart-ligne {
  position: relative; isolation: isolate; -webkit-tap-highlight-color: transparent;
}
.repart-synthese .repart-ligne::before {
  content: ''; position: absolute; inset: 1px -8px; z-index: -1;
  border-radius: var(--radius-sm); background: var(--surface-2); opacity: 0;
  transition: opacity .12s;
}
.repart-synthese .repart-ligne:active::before { opacity: 1; }
@media (hover: hover) { .repart-synthese .repart-ligne:hover::before { opacity: .6; } }
.repart-synthese .repart-ligne:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-xs); }
@media (prefers-reduced-motion: reduce) { .repart-synthese .repart-ligne::before { transition: none; } }
.repart-autres-detail { padding-left: 17px; }
.repart-autres-detail:not(:last-child) { border-bottom: 1px solid var(--border); }
.repart-autres:has(+ .repart-autres-detail[hidden]:last-child) { border-bottom: none; }
@media (prefers-reduced-motion: reduce) { .repart-chev { transition: none; } }

.apercu-pied { margin: 0; text-align: center; font-size: var(--font-sm); }
.apercu-edition-tete { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.apercu-edition-tete > div { min-width: 0; }
.apercu-edition-tete p { line-height: 1.45; }
.apercu-edition-tete .btn { flex: none; }
.apercu-edition-tete h2:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: var(--radius-xs); }
.apercu-liste { list-style: none; margin: 0; padding: 0; }
.apercu-liste > .apercu-ligne:first-child { border-top: none; }
.apercu-ligne {
  display: flex; align-items: center; gap: 12px; min-height: 52px;
  padding: 6px 0; border-top: 1px solid var(--grid);
}
.apercu-nom { flex: 1 1 auto; min-width: 0; font-weight: 600; overflow-wrap: break-word; }
.apercu-nom .sub { display: block; margin-top: 1px; font-weight: 400; font-size: var(--font-sm); color: var(--muted); }
.apercu-ligne.masquee .apercu-nom { color: var(--text-secondary); }
.apercu-commandes { flex: none; display: flex; align-items: center; gap: 6px; }
.apercu-fleche { width: 36px; height: 36px; padding: 0; display: inline-grid; place-items: center; }
.apercu-fleche[disabled] { opacity: .35; cursor: default; }
.bascule.apercu-vu { width: auto; padding: 6px 0 6px 6px; border: none; background: none; }
.apercu-edition-pied {
  display: flex; justify-content: end; margin-top: 4px; padding-top: 12px;
  border-top: 1px solid var(--border);
}
.hors-ecran {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
@media (max-width: 899px) {
  .apercu-fleche { width: 44px; height: 44px; }
  .bascule.apercu-vu { min-height: 44px; }
}
@media (hover: hover) {
  button.pf-total:hover > b, button.pf-mesure:hover b {
    text-decoration: underline; text-underline-offset: 4px;
  }
}
button.pf-total:focus-visible, button.pf-mesure:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
  border-radius: var(--radius-xs);
}

@keyframes flash-target {
  0%, 100% { box-shadow: var(--shadow); }
  15%, 60% { box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 55%, transparent), var(--shadow); }
}
.flash-target { animation: flash-target 1.6s ease; border-radius: var(--radius); }
@media (prefers-reduced-motion: reduce) { .flash-target { animation: none; outline: 2px solid var(--accent); } }

