@charset "UTF-8";
/* ==========================================================================
   carimbo-app.css — L'APP, TRADUITE POUR LE WEB.

   Demande du propriétaire, 19 septembre 2026 : « fait en sorte que toutes
   les pages web ressemblent à l'app, c'est plus beau pour moi — le panneau
   admin, même les mentions légales et tout ».

   LA SOURCE, C'EST L'APP, PAS LE SITE. Chaque valeur ci-dessous vient de
   Tampon-app/natif : App/Sources/Theme.swift (les jetons), Composants.swift
   (capsule, rond, champ), FondVivant.swift (la lumière), et les écrans qui
   les assemblent. natif/DESIGN.md dit pourquoi. Le détail jeton par jeton,
   avec le fichier et la ligne, est dans traduction.md.

   CE FICHIER EST SERVI TEL QUEL, À LA RACINE : /carimbo-app.css (plan.md,
   19/09). Aucune ligne de server.js à changer (extension .css dans la
   liste blanche, Cache-Control: no-cache, CSP style-src 'self').
   QUI LE CHARGE (<link rel="stylesheet" href="/carimbo-app.css">) :
     - admin.html, hors-ligne.html, diagnostic.html,
       inscription-commercant.html (qui quittent interface.css) ;
     - les cinq pages de legal.js et les trois gabarits de server.js
       (404, erreur, /confirmer-adresse).
   QUI NE PEUT PAS LE CHARGER, et le recopie (c'est alors le NUANCIER) :
     - inscription, retrouver, nouveau-code : AUCUNE feuille permise
       (tests/page-client-ambiance) ; nouveau-mot-de-passe suit sa sœur ;
     - scanner.html : tous ses var() doivent être déclarés dans la page
       (tests/echelle-espacement) ; index.html et affiche.html sont
       autonomes exprès ;
     - espace.html + interface.css : l'espace garde interface.css (cinq
       mille lignes de balisage en dépendent, et douze tests en lisent le
       texte) ; le lot de l'espace y recopie les valeurs d'ici.
   RÈGLE POUR UNE PAGE QUI LE CHARGE (tests/echelle-espacement) : tout
   var(--x) écrit DANS la page (style en ligne, attribut style, script)
   doit aussi être déclaré DANS la page. Une page qui charge cette feuille
   n'écrit donc que des CLASSES d'ici ; si elle a besoin d'un jeton dans
   son propre <style>, elle le redéclare, à la même valeur, dans son :root.
   JAMAIS AVEC interface.css SUR LA MÊME PAGE : les deux posent le fond de
   <body> et partagent .verre, .marque, .grand-titre, .lecture-seule,
   .chevron, .feuille*.
   NE PAS LE RECOPIER EN BLOC DANS interface.css : ses noms génériques
   (.entete, .feuille, .tuile, .message, .pastille, .code, .barre,
   .option, .aide, .chevron, .verre, .marque…) existent déjà dans
   l'espace et le panneau, avec d'autres rôles.
   LA FEUILLE DU BAS suit la structure de web/raccourci.js (section 10) :
   <div class="feuille" id="…"><div class="feuille-boite">…</div></div>,
   ouverte par ouvrirFeuille(id) qui pose .show et html.feuille-ouverte.

   CE QUE LES TESTS IMPOSENT EN RECOPIANT (corrigé ici le 19/09) :
     - JETONS DE COULEUR en #rrggbb ou rgba(r,g,b,a) à virgules : les
       tests de contraste ne lisent rien d'autre. Les RÈGLES peuvent
       garder rgb(r g b / a).
     - margin et gap en px : multiples de 4 (echelle-espacement). Les
       2 px mesurés dans l'app passent en padding, qui est libre.
     - Jamais « both » sur une animation qui part de opacity:0
       (mouvement-reduit) ; « backwards » fait la même chose sans risque.
     - Aucun nom d'état que le panneau emploie en variante
       (tableau-de-bord) : erreur succes attention vert orange rouge long
       ok manque bloque sursis essai-finit silencieux ajour essai demo
       aujourdhui absente — d'où .du-jour au lieu de .aujourdhui.
     - Aucun antislash : ce qui en est recopié dans legal.js vit dans un
       gabarit JS, où une séquence d'échappement (antislash suivi d'un
       zéro) est une erreur de syntaxe. Le point médian de .liens est
       donc écrit en clair (le fichier est en UTF-8, voir @charset).
     - Sur inscription, retrouver, nouveau-code SEULEMENT : --doux ≤ .3s,
       aucune animation/transition non infinie au-delà de .3s (donc pas
       la transition d'humeur de .8s du fond), aucun « both », champs à
       16 px, anneau de focus à l'or, et la palette de page-client-ambiance.
     - Ne pas recopier html{font-size:106.25%} dans interface.css : ses
       tailles sont en px et ses rem grandiraient tous.

   LES RÈGLES QUE LE CSS NE PEUT PAS FAIRE RESPECTER — à tenir à la main :
     1. UN SEUL AXE PAR ÉCRAN : tout sur la marge de gauche, ou tout centré
        (.axe-centre). Jamais les deux, sauf la pilule de retour.
     2. UN SEUL BOUTON PRINCIPAL (.capsule.principale) PAR ÉTAT D'ÉCRAN.
     3. DEUX SURFACES DE VERRE AU PLUS PAR ÉCRAN (Theme.swift:34-35), et
        jamais de verre sur du verre. Le principal compte : c'est un verre
        teinté. Dans une feuille et dans une annonce, le CSS éteint lui-même
        le verre des commandes posées dessus.
     4. TROIS RÔLES DE COULEUR : agir (blanc chaud), encre gagnée (or),
        problème (saumon). L'ocre n'est qu'une LUMIÈRE, et la marque.
     5. LES ESPACEMENTS : 4, 8, 12, 16, 24, 32, 48, 64. Aucune autre valeur.
   ========================================================================== */


/* ==========================================================================
   1. LES JETONS
   ========================================================================== */

/* Enregistrés pour que l'humeur du fond GLISSE au lieu de sauter
   (FondVivant.swift:115-117, 0,8 s). Sans @property, elle change d'un coup :
   rien ne casse. */
@property --lumiere {
  syntax: "<color>";
  inherits: true;
  initial-value: #8fb37a;
}
@property --force-haut {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 20%;
}
@property --force-bas {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 30%;
}

:root {
  /* L'app n'existe qu'en sombre (CarimboApp.swift:55). Le navigateur dessine
     donc en sombre ce qu'il dessine seul : barres de défilement, listes
     déroulantes, sélecteurs de date, remplissage automatique. */
  color-scheme: dark;

  /* --- Couleurs : Color(red:green:blue:) de SwiftUI, en sRGB, arrondies
         en 8 bits (ce que l'écran affiche de toute façon). En #rrggbb :
         c'est le seul format que lisent les tests de contraste. Les
         valeurs au centième sont en commentaire. ------------------------ */
  --encre:        #151311;                   /* rgb(8.2% 7.5% 6.7%)    Theme.swift:43 */
  --sapin:        #25331f;                   /* rgb(14.5% 20% 12.2%)   Theme.swift:50 — lumière seulement */
  --ocre:         #8fb37a;                   /* rgb(56.1% 70.2% 47.8%) Theme.swift:51 — lumière et marque */
  --action:       #f7f2e8;                   /* rgb(97% 95% 91%)       Theme.swift:58 — AGIR */
  --sur-action:   var(--encre);              /*                        Theme.swift:60 */
  --or:           #edb342;                   /* rgb(93% 70% 26%)       Theme.swift:69 — ENCRE GAGNÉE */
  --alerte:       #e58579;                   /* rgb(89.8% 52.2% 47.5%) Theme.swift:74 — PROBLÈME */

  /* Les mêmes, en canaux, pour les voiles : rgb(var(--or-canaux) / .1). */
  --encre-canaux:  8.2% 7.5% 6.7%;
  --or-canaux:     93% 70% 26%;
  --alerte-canaux: 89.8% 52.2% 47.5%;

  /* Le blanc, en quatre forces — et pas une de plus. */
  --texte:        #ffffff;                   /* Theme.swift:80 */
  --texte-doux:   rgba(255,255,255,.62);     /* Theme.swift:81 — 7,6:1 sur l'encre */
  --texte-faible: rgba(255,255,255,.42);     /* Theme.swift:85 — éteint, décor. 4,1:1 : JAMAIS pour un texte à lire
                                                (sous 4,5:1 ; les tests des pages client exigent 4,5) */
  --invite:       rgba(255,255,255,.55);     /* Theme.swift:91 — l'exemple d'un champ vide */
  --rangee:       rgba(255,255,255,.07);     /* Theme.swift:78 — le fond d'une ligne */

  /* Ce que l'app dérive de ces couleurs, calculé une fois (oklab, comme
     Color.mix de SwiftUI). */
  --or-profond:     #77581C;  /* or mêlé de 40 % de noir — OngletClients.swift:227 ; blanc dessus 6,6:1 */
  --alerte-profond: #8E5049;  /* saumon mêlé de 30 % de noir — OngletClients.swift:242 ; blanc dessus 6,2:1 */
  --or-clair:       #F4CE8E;  /* or mêlé de 35 % de blanc — Graphiques.swift:105 */

  /* Les traits. */
  --trait:        rgba(255,255,255,.12);     /* liseré d'un champ, « ou », jauge vide — Composants.swift:257-261 */
  --trait-fin:    rgba(255,255,255,.07);     /* entre deux rangées d'un tableau — EcranFormules.swift:1326 */
  --separateur:   rgba(84,84,88,.6);         /* entre deux rangées d'une liste : le séparateur d'iOS en sombre (système, pas dans le code) */

  /* L'accent du navigateur : le blanc chaud, comme .tint(Theme.action)
     (CarimboApp.swift:56-60). Sans lui, cases et curseurs prendraient le
     bleu du système — une quatrième couleur. */
  accent-color: var(--action);
  --accent-choix: var(--or);                 /* le liseré d'une option choisie */

  /* --- Texte ------------------------------------------------------------
     Les styles d'Apple à la taille par défaut : grand titre 34, en-tête 17
     semi-gras, texte 17, sous-titre 15, note 13, légende 12, étiquette 11.
     En rem, et 1 rem = 17 px (voir html plus bas) : le texte suit la taille
     choisie dans le navigateur, et sur iPhone celle des Réglages, comme
     l'app (Theme.swift:111-120). Plafonné vers la taille « accessibility2 »
     de l'app (CarimboApp.swift:118). Les « instruments » restent fixes. */
  --t-heros:       112px;                    /* Theme.swift:103 — fixe */
  --t-heros-sur:   34px;                     /* Theme.swift:105 — fixe */
  --t-recompense:  40px;                     /* Theme.swift:107 — fixe */
  --t-touche:      34px;                     /* Theme.swift:134 — fixe */
  --t-grand-titre: min(2rem, 48px);          /* 34  .largeTitle — Theme.swift:124 */
  --t-titre:       min(1.6471rem, 42px);     /* 28  .title — EcranFormules.swift:291, FeuilleActivite.swift:216 */
  --t-titre-3:     min(1.1765rem, 39px);     /* 20  .title3 — champs, OngletReglages.swift:140 */
  --t-corps:       min(1rem, 33px);          /* 17  .body / .headline — Theme.swift:126-128 */
  --t-precision:   min(.8824rem, 29px);      /* 15  .subheadline — Theme.swift:130 */
  --t-legende:     min(.7647rem, 25px);      /* 13  .footnote */
  --t-legende-2:   min(.7059rem, 23px);      /* 12  .caption — FeuilleActivite.swift:222 */
  --t-etiquette:   min(.6471rem, 21px);      /* 11  .caption2 — Theme.swift:132 */

  /* Les interlignes d'Apple (taille / interligne du tableau des styles). */
  --i-grand-titre: 1.206;  /* 41/34 */
  --i-titre:       1.214;  /* 34/28 */
  --i-titre-3:     1.25;   /* 25/20 */
  --i-corps:       1.294;  /* 22/17 */
  --i-precision:   1.333;  /* 20/15 */
  --i-legende:     1.385;  /* 18/13 */
  --i-legende-2:   1.333;  /* 16/12 */
  --i-etiquette:   1.182;  /* 13/11 */
  --i-lecture:     1.5;    /* LA SEULE EXCEPTION : un texte long (mentions, conditions). L'app n'en a pas. */

  --police: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", system-ui,
            "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --police-titre: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro", system-ui,
            "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* L'arrondi est réservé aux chiffres et à ce qu'on tape (Theme.swift:122-123).
     Hors d'Apple, il n'existe pas : on retombe sur la police du système. */
  --police-arrondie: ui-rounded, "SF Pro Rounded", -apple-system, BlinkMacSystemFont,
            system-ui, "Segoe UI", Roboto, sans-serif;
  --police-fixe: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* --- Espace : la grille de 8, subdivisée en 4 (Theme.swift:141-148).
         ATTENTION : interface.css appelait 20 px « --e-5 ». Ici e5 = 24. -- */
  --e1: 4px;
  --e2: 8px;
  --e3: 12px;
  --e4: 16px;
  --e5: 24px;
  --e6: 32px;
  --e7: 48px;
  --e8: 64px;
  --marge: var(--e5);                        /* UNE seule marge, 24 — DESIGN.md:34-36 */

  /* --- Formes ----------------------------------------------------------- */
  --rayon-carte: 28px;                       /* Theme.swift:154 */
  --rayon-champ: 18px;                       /* Theme.swift:155 — un champ de plusieurs lignes */
  --rayon-option: 18px;                      /* EcranFormules.swift:998 */
  --rayon-tuile: 16px;                       /* FeuilleActivite.swift:233 */
  --rayon-groupe: 28px;                      /* iOS le dessine ; Theme.rayonCarte, la valeur la plus proche (voir traduction.md) */
  --hauteur-bouton: 56px;                    /* Theme.swift:170 — capsule, champ */
  --rond: 56px;                              /* Theme.swift:171 */
  --touche-minimale: 44px;                   /* Theme.swift:160 */
  --touche-clavier: 80px;                    /* Theme.swift:164 */

  /* --- Mesures de colonne (web seulement) ------------------------------- */
  --mesure-telephone: 392px;                 /* le plus grand iPhone (440) moins deux marges */
  --mesure-lecture: 640px;                   /* ~70 signes par ligne à 17 px */
  --mesure-large: 1040px;                    /* le panneau d'administration */

  /* --- Mouvement : les ressorts de l'app, échantillonnés (voir plus bas,
         linear()). Ici, les courbes de repli des vieux navigateurs. ------- */
  --courbe-doux: cubic-bezier(.2, .8, .2, 1);     /* Theme.doux  — .smooth(0,32 s), Theme.swift:175 */
  --courbe-vif: cubic-bezier(.25, .9, .3, 1);     /* Theme.vif   — .snappy(0,22 s), Theme.swift:176 */
  --courbe-tampon: cubic-bezier(.3, 1.6, .5, 1);  /* Theme.tampon — ressort 0,32 / 0,52, Theme.swift:180 */
  /* .32 dans l'app. Sur inscription, retrouver et nouveau-code, le test
     exige un jeton --doux ≤ .3s : y écrire --doux:.3s, pas ce jeton-ci. */
  --duree-doux: .32s;
  --duree-vif: .22s;
  --duree-tampon: .6s;

  /* --- Le verre, sur l'encre --------------------------------------------
     Les noms sont ceux de la recette .verre du site (--verre-fond, -bord,
     -reflet, -ombre, -repli) ; les valeurs sont celles d'un verre posé sur
     une encre chaude et une lumière ocre. */
  --verre-fond:   rgba(255,255,255,.07);
  --verre-voile:  linear-gradient(180deg, rgba(255,255,255,.09), rgba(255,255,255,.02));
  --verre-bord:   rgba(255,255,255,.14);
  --verre-reflet: rgba(255,255,255,.28);
  --verre-ombre:  rgba(0,0,0,.45);
  --verre-flou:   blur(24px) saturate(180%);
  --verre-repli:  #2A2826;                   /* l'encre + 9 % de blanc, opaque */

  /* --- La barre du bas --------------------------------------------------- */
  --barre-fond: var(--encre);                /* EcranBienvenue.swift:645-646 */
  --barre-voile: 16px;                       /* EcranBienvenue.swift:647-654 */
}

/* LES RESSORTS, TELS QUELS. SwiftUI n'a pas de courbe : il a des ressorts.
   linear() les reproduit point par point (calculés : x(t) d'un ressort de
   réponse r et d'amortissement z). Theme.doux atteint 98,6 % en 0,32 s ;
   Theme.vif dépasse de 0,6 % ; Theme.tampon rebondit à 115 %. */
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    --courbe-doux: linear(0, .131 10%, .358 20%, .562 30%, .715 40%, .821 50%, .89 60%, .934 70%, .96 80%, .977 90%, 1);
    --courbe-vif: linear(0, .138 10%, .388 20%, .618 30%, .786 40%, .895 50%, .956 60%, .988 70%, 1.002 80%, 1.006 90%, 1);
    --courbe-tampon: linear(0, .139 5%, .431 10%, .733 15%, .964 20%, 1.098 25%, 1.146 30%, 1.135 35%, 1.095 40%, 1.05 45%, 1.012 50%, .989 55%, .979 60%, .979 65%, .984 70%, .991 75%, .997 80%, 1.001 85%, 1.003 90%, 1.003 95%, 1);
  }
}


/* ==========================================================================
   2. LA BASE
   ========================================================================== */

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

html {
  /* 1 rem = 17 px : le texte de l'iPhone à sa taille par défaut. */
  font-size: 106.25%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* L'encre sous tout, y compris quand iOS tire la page au-delà de ses bords. */
  background: var(--encre);
  color: var(--texte);
  scrollbar-color: rgb(255 255 255 / .24) transparent;
}

/* SUR IPHONE, LA TAILLE CHOISIE DANS RÉGLAGES › AFFICHAGE › TAILLE DU TEXTE.
   -apple-system-body vaut 17 px au réglage par défaut et grandit avec lui :
   c'est le Dynamic Type de l'app. -webkit-touch-callout n'existe que sur
   iOS/iPadOS — sur un Mac, le corps du système fait 13 px, et la page
   rétrécirait. */
@supports (font: -apple-system-body) and (-webkit-touch-callout: none) {
  html { font: -apple-system-body; }
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--police);
  font-size: var(--t-corps);
  line-height: var(--i-corps);
  color: var(--texte);
  background: transparent;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-wrap: break-word;
  caret-color: var(--action);
  /* Les deux lumières du fond se peignent sous le contenu, jamais dessus. */
  isolation: isolate;
}

h1, h2, h3, h4, h5, h6, p, ul, ol, dl, dd, figure, blockquote { margin: 0; }
h1 {
  font: 700 var(--t-grand-titre) / var(--i-grand-titre) var(--police-titre);
  text-wrap: balance;
}
h2 {
  font: 700 var(--t-titre-3) / var(--i-titre-3) var(--police-titre);
  text-wrap: balance;
}
h3, h4, h5, h6 {
  font: 600 var(--t-corps) / var(--i-corps) var(--police);
}
strong, b { font-weight: 600; }

img, video { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
button { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }

/* Un lien n'est jamais bleu : c'est la quatrième couleur que l'app refuse
   (CarimboApp.swift:56-59). Du blanc, souligné. */
a {
  color: var(--texte);
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--texte-faible);
  text-underline-offset: 3px;
}
@media (hover: hover) {
  a:hover { text-decoration-color: currentColor; }
}

::selection { background: rgb(247 242 232 / .3); }
::placeholder { color: var(--invite); opacity: 1; }
option { background: #211F1D; color: var(--texte); }

[hidden] { display: none !important; }

/* Visible des lecteurs d'écran seulement. */
.lecture-seule {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* LE CLAVIER VOIT OÙ IL EST. Un anneau au blanc chaud de l'accent, écarté
   de la forme qu'il entoure (il suit l'arrondi des capsules). Jamais retiré :
   seuls les champs le remplacent par leur propre liseré, plus bas. */
:focus-visible {
  outline: 2px solid var(--action);
  outline-offset: 3px;
}

/* 44 POINTS POUR TOUT CE QUI SE TOUCHE (DESIGN.md:187). Quand le dessin ne
   doit pas grandir, seule la zone sensible grandit. */
.cible-44 { position: relative; }
.cible-44::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: max(100%, 44px);
  height: max(100%, 44px);
  transform: translate(-50%, -50%);
}


/* ==========================================================================
   3. LE FOND VIVANT (FondVivant.swift)

   Deux lumières, chacune avec un rôle. Une douce en haut à gauche,
   derrière le titre ; une plus serrée en bas, derrière la barre et le
   bouton — c'est elle que le verre réfracte. Toujours en surimpression,
   JAMAIS dans la mise en page : deux pseudo-éléments fixes, qui ne
   prennent pas de place et ne se touchent pas.

   Dans l'app, un dégradé maillé 3 × 3 (MeshGradient) : l'encre partout, la
   lumière au coin haut gauche et au milieu du bas. Ici, deux dégradés
   radiaux posés sur les mêmes points. La lumière est posée en transparence
   sur l'encre, avec une force calibrée (× 0,87) pour retomber sur les
   couleurs exactes du maillage à ses points de contrôle, à 1/255 près :
     calme  haut #2B2F24 · bas #363D2E · coin bas droit #22231C
   (voir traduction.md pour les trois humeurs).
   ========================================================================== */

body {
  --lumiere: var(--ocre);
  --lumiere-canaux: 56.1% 70.2% 47.8%;       /* pour les navigateurs sans color-mix() */
  --force-haut: 20%;                         /* FondVivant.swift:62 */
  --force-bas: 30%;                          /* FondVivant.swift:72 */
  /* L'humeur glisse en 0,8 s (FondVivant.swift:117). */
  transition:
    --lumiere .8s var(--courbe-doux),
    --force-haut .8s var(--courbe-doux),
    --force-bas .8s var(--courbe-doux);
}
/* Gain : un moment de fête, pas une page de lecture (le texte doux y
   descend à 4,2:1 sous la lumière du haut). */
body[data-humeur="gain"] {
  --lumiere: var(--or);
  --lumiere-canaux: 93% 70% 26%;
  --force-haut: 40%;                         /* FondVivant.swift:63 */
  --force-bas: 26%;                          /* FondVivant.swift:73 */
}
body[data-humeur="alerte"] {
  --lumiere: var(--alerte);
  --lumiere-canaux: 89.8% 52.2% 47.5%;
  --force-haut: 30%;                         /* FondVivant.swift:64 */
  --force-bas: 14%;                          /* FondVivant.swift:74 */
}

body::before,
body::after {
  content: "";
  position: fixed;
  z-index: -1;
  pointer-events: none;
  will-change: transform;
}

/* LA LUMIÈRE DU HAUT. Le bord du haut passe de 20 % (coin) à 9 % (milieu,
   × 0,45) puis à rien ; le bord de gauche de 20 % à 6 % (à 45 % de la
   hauteur, × 0,30) puis à rien (FondVivant.swift:126-141). */
body::before {
  inset: 0;
  transform-origin: 0 0;
  background: radial-gradient(100vw 70vh at 0 0,
    color-mix(in srgb, var(--lumiere) calc(var(--force-haut) * .87), transparent) 0%,
    color-mix(in srgb, var(--lumiere) calc(var(--force-haut) * .87 * .72), transparent) 25%,
    color-mix(in srgb, var(--lumiere) calc(var(--force-haut) * .87 * .45), transparent) 50%,
    color-mix(in srgb, var(--lumiere) calc(var(--force-haut) * .87 * .205), transparent) 75%,
    transparent 100%);
  animation: respiration-haut 14s ease-in-out infinite alternate;
}

/* LA LUMIÈRE DU BAS, plus serrée, centrée à 55 % de la largeur ; le coin
   bas droit garde 40 % de sa force. Elle dérive en s'étirant depuis le coin
   bas droit, qui ne bouge pas : aucun bord ne se découvre, et rien ne
   déborde de l'écran. */
body::after {
  inset: 0;
  transform-origin: 100% 100%;
  background:
    radial-gradient(45vw 45vh at 100% 100%,
      color-mix(in srgb, var(--lumiere) calc(var(--force-bas) * .87 * .4), transparent) 0%,
      transparent 100%),
    radial-gradient(55vw 58vh at 55% 100%,
      color-mix(in srgb, var(--lumiere) calc(var(--force-bas) * .87), transparent) 0%,
      color-mix(in srgb, var(--lumiere) calc(var(--force-bas) * .87 * .668), transparent) 25%,
      color-mix(in srgb, var(--lumiere) calc(var(--force-bas) * .87 * .379), transparent) 50%,
      color-mix(in srgb, var(--lumiere) calc(var(--force-bas) * .87 * .144), transparent) 75%,
      transparent 100%);
  animation: respiration-bas 14s ease-in-out infinite alternate;
}

/* UNE RESPIRATION DE QUATORZE SECONDES, aller et retour, en douceur
   (FondVivant.swift:94). Les points du maillage dérivent de quelques pour
   cent : la lumière du haut s'étire vers la droite et remonte, celle du bas
   glisse vers la gauche (son centre passe de 55 à 46 % de la largeur) et
   se resserre (FondVivant.swift:123-130). */
@keyframes respiration-haut {
  to { transform: scale(1.12, .88); }
}
@keyframes respiration-bas {
  to { transform: scale(1.2, .85); }
}

/* SANS color-mix() : la même lumière, en canaux. L'humeur change d'un coup. */
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  body::before {
    background: radial-gradient(100vw 70vh at 0 0,
      rgb(var(--lumiere-canaux) / calc(var(--f-haut, .2) * .87)) 0%,
      rgb(var(--lumiere-canaux) / calc(var(--f-haut, .2) * .87 * .72)) 25%,
      rgb(var(--lumiere-canaux) / calc(var(--f-haut, .2) * .87 * .45)) 50%,
      rgb(var(--lumiere-canaux) / calc(var(--f-haut, .2) * .87 * .205)) 75%,
      transparent 100%);
  }
  body::after {
    background:
      radial-gradient(45vw 45vh at 100% 100%,
        rgb(var(--lumiere-canaux) / calc(var(--f-bas, .3) * .87 * .4)) 0%, transparent 100%),
      radial-gradient(55vw 58vh at 55% 100%,
        rgb(var(--lumiere-canaux) / calc(var(--f-bas, .3) * .87)) 0%,
        rgb(var(--lumiere-canaux) / calc(var(--f-bas, .3) * .87 * .668)) 25%,
        rgb(var(--lumiere-canaux) / calc(var(--f-bas, .3) * .87 * .379)) 50%,
        rgb(var(--lumiere-canaux) / calc(var(--f-bas, .3) * .87 * .144)) 75%,
        transparent 100%);
  }
  body[data-humeur="gain"]   { --f-haut: .40; --f-bas: .26; }
  body[data-humeur="alerte"] { --f-haut: .30; --f-bas: .14; }
}

/* LE FOND DE L'ÉCRAN QUI VEND, et de lui seul (EcranFormules.swift:638-672) :
   une encre neutre #0B0B10 → #14141C, une lueur champagne à 7,5 % en haut à
   gauche, rien qui respire derrière des prix. Le propriétaire trouvait
   l'ocre « chelou » à cet endroit. Pour la page des tarifs, pas ailleurs. */
body.fond-formules {
  --texte: #ECE6D8;                          /* os */
  --texte-doux: #B6B0A3;                     /* os doux */
  --accent-choix: #D4AF5A;                   /* champagne */
  --barre-fond: #14141C;
  --barre-voile: 24px;                       /* EcranFormules.swift:476-484 */
}
body.fond-formules::before {
  transform: none;
  animation: none;
  background:
    radial-gradient(420px circle at 30% 0, rgb(212 175 90 / .075), rgb(212 175 90 / 0)),
    linear-gradient(180deg, #0B0B10, #14141C);
}
body.fond-formules::after { display: none; }


/* ==========================================================================
   4. LA MISE EN PAGE
   ========================================================================== */

/* LA COLONNE. Sur un téléphone : la marge de 24, à gauche comme à droite.
   Sur un grand écran : une colonne centrée, à la largeur d'un iPhone pour
   un formulaire, à la largeur de lecture pour un texte long, plus large
   pour l'administration. */
.colonne {
  --mesure: var(--mesure-telephone);
  width: 100%;
  max-width: calc(var(--mesure) + 2 * var(--marge));
  margin-inline: auto;
  padding-left: max(var(--marge), env(safe-area-inset-left));
  padding-right: max(var(--marge), env(safe-area-inset-right));
  padding-top: calc(var(--e4) + env(safe-area-inset-top));
  padding-bottom: calc(var(--e7) + env(safe-area-inset-bottom));
}
.colonne.lecture { --mesure: var(--mesure-lecture); }
.colonne.large   { --mesure: var(--mesure-large); }
.colonne:has(> .barre-du-bas) { padding-bottom: 0; }

/* UN SEUL AXE PAR ÉCRAN (DESIGN.md:18-33). Par défaut, tout est calé à
   gauche. Un écran de code, de verdict, d'attente : tout centré. */
.axe-centre { text-align: center; }
.axe-centre.pile, .axe-centre .pile { align-items: center; }

/* Une pile verticale, à un écart de la grille. */
.pile {
  display: flex;
  flex-direction: column;
  gap: var(--ecart, var(--e3));
}
.pile > * { min-width: 0; }
.pile.e1 { --ecart: var(--e1); }
.pile.e2 { --ecart: var(--e2); }
.pile.e3 { --ecart: var(--e3); }
.pile.e4 { --ecart: var(--e4); }
.pile.e5 { --ecart: var(--e5); }
.pile.e6 { --ecart: var(--e6); }
.pile.e7 { --ecart: var(--e7); }
.pile.e8 { --ecart: var(--e8); }

/* Une capsule et son rond, côte à côte (la barre du comptoir). */
.duo {
  display: flex;
  align-items: center;
  gap: var(--e3);
}
.duo > .capsule { flex: 1; }
.duo > .rond { flex: none; }

/* L'EN-TÊTE : le retour en haut à gauche, sur la marge (DESIGN.md:26-28),
   un titre court au centre, un rond à droite. */
.entete {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--e3);
  min-height: var(--touche-minimale);
  margin-top: calc(var(--e2) - var(--e4));   /* 8 sous le haut de l'écran — EcranMotDePasse.swift:189 */
  margin-bottom: var(--e5);                   /* 24 avant le contenu — EcranMotDePasse.swift:77 */
}
.entete > .gauche { grid-column: 1; justify-self: start; }
.entete > .entete-titre { grid-column: 2; }
.entete > .droite { grid-column: 3; justify-self: end; }
.entete-titre {
  font: 600 var(--t-corps) / var(--i-corps) var(--police);   /* titre réduit, 17 semi-gras — DESIGN.md:297 */
  text-align: center;
  margin: 0;
}

/* LA BARRE DU BAS (EcranBienvenue.swift:576-655). Le bouton principal, le
   refus au-dessus de lui, les petits liens dessous. Sur un téléphone, elle
   reste sous le pouce : collée au bas, opaque, et le contenu s'éteint sous
   elle par un voile de 16 points. Sur un grand écran, elle reprend sa place
   dans la page. */
.barre-du-bas {
  position: sticky;
  bottom: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: var(--e1);
  margin-inline: calc(-1 * var(--marge));
  padding: var(--e2) var(--marge) calc(var(--e2) + env(safe-area-inset-bottom));
  background: var(--barre-fond);
}
.barre-du-bas::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--barre-voile);
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--barre-fond) 94%, transparent));
  pointer-events: none;
}
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .barre-du-bas::before { background: linear-gradient(180deg, rgb(var(--encre-canaux) / 0), rgb(var(--encre-canaux) / .94)); }
}
html:has(.barre-du-bas) { scroll-padding-bottom: 180px; }


/* ==========================================================================
   5. LE TEXTE
   ========================================================================== */

/* LE MOT-MARQUE « CARIMBO », au-dessus du titre des écrans d'entrée : en
   capitales, 11 gras, espacé de 2,2, à l'ocre — la seule exception voulue
   à la règle des trois couleurs (EcranBienvenue.swift:314-317, DESIGN.md:61). */
.marque {
  display: block;
  font: 700 var(--t-etiquette) / var(--i-etiquette) var(--police);
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--ocre);
}

/* UNE ÉTIQUETTE EN CAPITALES (« CODE DE CETTE CARTE », « RÉCOMPENSE ») :
   11 gras, espacée de 1, en doux (FicheCarte.swift:288-293). */
.etiquette {
  display: block;
  font: 700 var(--t-etiquette) / var(--i-etiquette) var(--police);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--texte-doux);
}

/* LE TITRE D'UN ÉCRAN : 34 gras, sans l'arrondi (Theme.swift:122-124). */
.grand-titre {
  font: 700 var(--t-grand-titre) / var(--i-grand-titre) var(--police-titre);
  color: var(--texte);
  text-wrap: balance;
  margin: 0;
}
/* 28 gras : un titre qui partage sa ligne avec un rond (EcranFormules.swift:284-295). */
.titre {
  font: 700 var(--t-titre) / var(--i-titre) var(--police-titre);
  text-wrap: balance;
  margin: 0;
}
/* 20 gras : le nom du commerce en tête des Réglages (OngletReglages.swift:140). */
.titre-3 {
  font: 700 var(--t-titre-3) / var(--i-titre-3) var(--police-titre);
  margin: 0;
}
/* 17 semi-gras : un nom, un libellé de bouton, le titre d'une feuille. */
.intitule {
  font: 600 var(--t-corps) / var(--i-corps) var(--police);
}
/* Ce qu'on lit vraiment : 17. */
.corps {
  font: 400 var(--t-corps) / var(--i-corps) var(--police);
}
/* Une précision sous un titre : 15, en doux (Theme.swift:129-130). */
.precision {
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
/* Une note : 13, en doux. */
.legende {
  font: 400 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
}

.texte-doux   { color: var(--texte-doux); }
.texte-faible { color: var(--texte-faible); }
.texte-or     { color: var(--or); }
.texte-alerte { color: var(--alerte); }

/* LES CHIFFRES : l'arrondi, à chasse fixe — un compte qui change ne fait
   pas danser ce qui l'entoure (Theme.swift:99-103). */
.chiffres {
  font-family: var(--police-arrondie);
  font-variant-numeric: tabular-nums;
}
.code {
  font-family: var(--police-fixe);
  font-variant-numeric: tabular-nums;
}

/* LES INSTRUMENTS : le compte « 4 », « / 10 », la récompense. Tailles fixes,
   comme sur l'écran verrouillé (Theme.swift:99-109). */
.heros {
  font: 700 var(--t-heros) / 1 var(--police-arrondie);
  font-variant-numeric: tabular-nums;
}
.heros-sur {
  font: 600 var(--t-heros-sur) / 1 var(--police-arrondie);
  font-variant-numeric: tabular-nums;
  color: var(--texte-doux);
}
.recompense {
  font: 700 var(--t-recompense) / 1.1 var(--police-arrondie);
  color: var(--or);
  text-wrap: balance;
}


/* ==========================================================================
   6. LE VERRE

   « Liquid Glass applies to the topmost layer of the interface, where you
   define your navigation. » (Theme.swift:22-23). Le verre est un ÉTAGE,
   pas une texture : ce qu'on touche (capsule, rond, pilule, barre), jamais
   ce qu'on lit. Il floute et sature ce qu'il y a derrière — la lumière du
   fond —, un reflet fin s'allume sur l'arête haute, une ombre douce le
   décolle.

   .verre est la recette nue, pour une surface flottante qui n'est pas une
   commande (une barre d'onglets, par exemple). Les commandes l'ont déjà.
   Les trois replis obligatoires sont à la fin du fichier (section 13).

   ATTENTION, POUR LES PAGES QUI GARDENT « verre » DANS LEUR BALISAGE MAIS
   EN FONT UN BLOC (le panneau : tuiles, cartes repliables, listes) : la
   surcharge qui éteint le verre doit éteindre les CINQ choses, pas quatre.
   Le fond, le flou, le bord et l'ombre se posent sur l'élément ; le REFLET
   se pose sur son ::before (la tache radiale ci-dessous). Une surcharge
   qui oublie « .x::before { display: none } » laisse un reflet de verre en
   haut à gauche d'un bloc plein — du verre partout, en plus discret.
   Modèle : .capsule.plate (section 7) et .feuille-boite (section 10).
   ========================================================================== */

.verre,
.capsule,
.pilule,
.rond {
  position: relative;
  isolation: isolate;
  background: var(--verre-voile), var(--verre-fond);
  -webkit-backdrop-filter: var(--verre-flou);
  backdrop-filter: var(--verre-flou);
  border: 1px solid var(--verre-bord);
  box-shadow:
    inset 0 1px 0 var(--verre-reflet),
    inset 0 -1px 0 rgb(255 255 255 / .04),
    0 8px 24px -10px var(--verre-ombre);
}
.verre::before,
.capsule::before,
.pilule::before,
.rond::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(120% 90% at 18% -20%, var(--verre-reflet), transparent 55%);
  opacity: .5;
}
.verre { border-radius: var(--rayon-carte); }


/* ==========================================================================
   7. LES COMMANDES
   ========================================================================== */

/* LA CAPSULE (Composants.swift:34-87) : tout bouton à texte. 56 de haut,
   toute la largeur qu'on lui donne — la largeur est DANS le style
   (DESIGN.md:87-88). Du verre neutre, libellé blanc, 17 semi-gras. */
.capsule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--e2);
  width: 100%;
  min-height: var(--hauteur-bouton);
  margin: 0;
  padding: var(--e2) var(--e5);
  border-radius: 999px;
  font: 600 var(--t-corps) / var(--i-corps) var(--police);
  color: var(--texte);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition:
    transform var(--duree-vif) var(--courbe-vif),
    background-color var(--duree-vif) var(--courbe-vif),
    filter var(--duree-vif) var(--courbe-vif);
}
.capsule svg { width: 1.15em; height: 1.15em; flex: none; }

/* LE BOUTON PRINCIPAL : le blanc chaud, UN par état d'écran — le seul objet
   vraiment clair, celui que l'œil trouve sans chercher (Theme.swift:55-60).
   Dans l'app, un verre teinté de ce blanc ; sur l'encre, il se lit plein. */
.capsule.principale {
  background: var(--action);
  color: var(--sur-action);
  border-color: rgb(255 255 255 / .6);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .8),
    inset 0 -1px 0 rgb(var(--encre-canaux) / .1),
    0 10px 28px -12px rgb(0 0 0 / .7);
}
.capsule.principale::before {
  background: radial-gradient(120% 90% at 18% -20%, rgb(255 255 255 / .9), transparent 55%);
  opacity: .6;
}

/* CE QUI DÉTRUIT, en capsule (FeuilleSuppression.swift:233) : le saumon,
   assez profond pour que le libellé blanc se lise (6,2:1). */
.capsule.danger {
  background: var(--alerte-profond);
  border-color: rgb(var(--alerte-canaux) / .45);
  color: var(--texte);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* PLATE : LA MÊME CAPSULE, SANS VERRE (ajoutée le 19/09). Hors barre
   d'onglets, UNE capsule de verre visible par écran ; toutes les autres
   sont .plate — blanc 10 % plein, sans flou, sans reflet, sans ombre :
   exactement ce que la feuille du bas fait déjà aux commandes posées sur
   elle (section 10). L'app a une ou deux capsules par écran ; le panneau en
   a dix, l'espace vingt-neuf : sans cette variante, la règle des deux
   verres (Theme.swift:34-35) tombe toute seule. Vaut aussi pour un rond et
   une pilule. */
.capsule.plate,
.pilule.plate,
.rond.plate {
  background: rgb(255 255 255 / .10);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-color: transparent;
  box-shadow: none;
}
.capsule.plate::before,
.pilule.plate::before,
.rond.plate::before { display: none; }

/* ÉTEINTE, ELLE N'EST PLUS EN VERRE : une capsule à peine marquée, au trait.
   Un bouton qu'on ne peut pas toucher n'est pas une commande
   (Composants.swift:29-33, 60-69, 78-80). */
.capsule:disabled,
.capsule[aria-disabled="true"] {
  background: rgb(255 255 255 / .05);
  border-color: var(--trait);
  color: var(--texte-faible);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
  cursor: not-allowed;
}
.capsule:disabled::before,
.capsule[aria-disabled="true"]::before { display: none; }

/* PENDANT L'ENVOI : ALLUMÉE, MAIS FERMÉE (Composants.swift:91-119). La
   capsule garde son blanc et sa roue ; le doigt ne l'atteint plus. */
.capsule[aria-busy="true"] { pointer-events: none; }

/* Dans un état vide, l'action n'occupe pas toute la largeur (OngletClients.swift:104-105). */
.capsule.etroite { max-width: 280px; margin-inline: auto; }

/* LE ROND (Composants.swift:148-166) : 56 pour une icône, un vrai cercle. */
.rond {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: var(--rond);
  height: var(--rond);
  margin: 0;
  padding: 0;
  border-radius: 50%;
  font: 600 var(--t-corps) / 1 var(--police);
  color: var(--texte);
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
  transition: transform var(--duree-vif) var(--courbe-vif), background-color var(--duree-vif) var(--courbe-vif);
}
.rond svg { width: 22px; height: 22px; }
/* 44 : le menu « … », la croix d'une feuille (EcranPause.swift:213-221). */
.rond.petit { width: var(--touche-minimale); height: var(--touche-minimale); }
.rond.petit svg { width: 18px; height: 18px; }
.rond:disabled {
  color: var(--texte-faible);
  cursor: not-allowed;
}

/* LA PILULE DE VERRE « ‹ Retour » (EcranMotDePasse.swift:164-190) : en haut
   à gauche, loin du pouce qui tape. 44 de haut, 16 de chaque côté, 15
   semi-gras. */
.pilule {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  min-height: var(--touche-minimale);
  margin: 0;
  padding: 0 var(--e4);
  border-radius: 999px;
  font: 600 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  touch-action: manipulation;
  transition: transform var(--duree-vif) var(--courbe-vif), background-color var(--duree-vif) var(--courbe-vif);
}
.pilule svg { width: 1.05em; height: 1.05em; flex: none; }

/* L'APPUI. Le verre interactif d'Apple réagit tout seul ; ici, une capsule
   qui s'enfonce à peine, au ressort vif. Rien avec « Réduire les
   animations » (section 13). */
.capsule:active:not(:disabled):not([aria-disabled="true"]),
.pilule:active,
.rond:active:not(:disabled) {
  transform: scale(.97);
}
@media (hover: hover) {
  .capsule:not(.principale):not(.danger):not(:disabled):not([aria-disabled="true"]):hover,
  .pilule:hover,
  .rond:not(:disabled):hover {
    --verre-fond: rgb(255 255 255 / .12);
  }
  .capsule.principale:not(:disabled):not([aria-disabled="true"]):hover,
  .capsule.danger:not(:disabled):hover {
    filter: brightness(1.05);
  }
}

/* LA ROUE D'ATTENTE, dans une capsule : à l'encre sur le principal
   (Theme.surAction), en blanc ailleurs. Elle tourne toujours : c'est
   l'information que quelque chose se passe. */
.roue {
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  opacity: .8;
  animation: roue .8s linear infinite;
}
@keyframes roue { to { transform: rotate(360deg); } }

/* LES LIENS (EcranBienvenue.swift:612-627, 666-681 ; EcranMotDePasse.swift:387-425). */
/* « J'ai déjà un compte » : 15 semi-gras, en doux, souligné, 44 de haut. */
.lien {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--touche-minimale);
  margin: 0;
  padding: 0 var(--e2);
  border: 0;
  background: none;
  font: 600 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
  text-align: center;
  text-decoration-line: underline;
  text-decoration-color: currentColor;
  cursor: pointer;
}
/* Après un refus, la suite possible passe en blanc (EcranMotDePasse.swift:393). */
.lien.fort { color: var(--texte); }
/* « Confidentialité », « J'avais un code… » : 13 semi-gras, sans soulignement,
   36 visibles, 44 au toucher. */
.lien.discret {
  position: relative;
  min-height: 36px;
  font-size: var(--t-legende);
  line-height: var(--i-legende);
  text-decoration: none;
}
.lien.discret::after {
  content: "";
  position: absolute;
  inset: -4px 0;
}
.lien.a-gauche { justify-content: flex-start; text-align: left; padding-inline: 0; }

/* Une rangée de petits liens séparés d'un point (EcranFormules.swift:592-628). */
.liens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.liens > .lien { width: auto; }
.liens > * + *::before {
  content: "·";
  display: inline-block;
  padding: 0 var(--e2);
  color: var(--texte-doux);
  opacity: .6;
  text-decoration: none;
}


/* ==========================================================================
   8. LA SAISIE
   ========================================================================== */

/* LE CHAMP (Composants.swift:239-267) : PLEIN, PAS EN VERRE — c'est du
   contenu, pas une commande. La même capsule de 56 que les boutons, blanc
   7 %, liseré blanc 12 % ; le liseré dit l'état : blanc 90 % sur 2 points
   quand on tape, saumon quand ça n'a pas marché. Ce qu'on tape est en 20
   arrondi semi-gras (EcranBienvenue.swift:399).

   Deux écritures : <label class="champ"><svg/><input></label>, avec un
   symbole et, au besoin, un bouton « œil » ; ou <input class="champ">. */
.champ {
  display: flex;
  align-items: center;
  gap: var(--e3);
  width: 100%;
  min-height: var(--hauteur-bouton);
  margin: 0;
  padding: var(--e2) var(--e5);
  border-radius: 999px;
  border: 1px solid var(--trait);
  background: var(--rangee);
  /* Le second point du liseré est une ombre : rien ne bouge quand il vient. */
  box-shadow: inset 0 0 0 0 transparent;
  font: 600 var(--t-titre-3) / var(--i-titre-3) var(--police-arrondie);
  color: var(--texte);
  cursor: text;
  transition:
    border-color var(--duree-vif) var(--courbe-vif),
    box-shadow var(--duree-vif) var(--courbe-vif);
}
.champ > input,
.champ > select {
  flex: 1;
  min-width: 0;
  min-height: 38px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  outline: none;
}
/* Le symbole, à gauche : en doux (EcranBienvenue.swift:388-391). */
.champ > svg {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--texte-doux);
}
/* L'ŒIL : 44 au toucher, qui débordent dans la marge de la capsule au lieu
   de la grandir (EcranBienvenue.swift:479-497). */
.champ > button {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--touche-minimale);
  height: var(--touche-minimale);
  margin: calc(-1 * var(--e1)) calc(-1 * var(--e3)) calc(-1 * var(--e1)) 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--texte-doux);
  cursor: pointer;
}
.champ > button svg { width: 20px; height: 20px; }

.champ:focus-within,
:is(input, textarea, select).champ:focus {
  border-color: rgb(255 255 255 / .9);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .9);
  outline: none;
}
/* L'erreur l'emporte sur le focus, comme dans l'app. */
.champ.erreur,
.champ:has(:is(input, select, textarea)[aria-invalid="true"]),
:is(input, textarea, select).champ[aria-invalid="true"] {
  border-color: var(--alerte);
  box-shadow: inset 0 0 0 1px var(--alerte);
}
/* Le bouton « œil » garde son anneau à lui, le liseré du champ suffit au reste. */
.champ > button:focus-visible { outline-offset: -4px; }

/* Plusieurs lignes : pas une capsule, un rectangle arrondi à 18
   (Theme.rayonChamp), et le texte au corps de lecture — on relit ce qu'on
   écrit. */
textarea.champ {
  display: block;
  min-height: 120px;
  padding: var(--e3) var(--e4);
  border-radius: var(--rayon-champ);
  font: 400 var(--t-corps) / 1.4 var(--police);
  resize: vertical;
}
select.champ {
  -webkit-appearance: none;
  appearance: none;
  padding-right: var(--e7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-opacity='.62' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--e5) center;
  background-size: 16px;
  cursor: pointer;
}
input.champ { cursor: text; }

/* LA RECHERCHE, LE TRI, LES FORMULAIRES DENSES : 17 RÉGULIER (ajouté le
   19/09). Le 20 arrondi semi-gras est la saisie des écrans d'entrée de l'app
   (EcranBienvenue.swift:399) ; sa recherche est celle du système, 17
   régulier. Un champ « Chercher un commerce… », un <select> de tri, ou neuf
   champs d'une fiche d'administration en 20 semi-gras pèsent lourd :
   .champ.dense pour ceux-là. */
input[type="search"].champ,
.champ:has(> input[type="search"]),
select.champ,
.champ.dense,
.lecture select:not(.champ) {
  font: 400 var(--t-corps) / var(--i-corps) var(--police);
}

/* Le remplissage automatique du navigateur ne repeint pas la capsule. */
.champ input:-webkit-autofill,
input.champ:-webkit-autofill {
  -webkit-text-fill-color: var(--texte);
  caret-color: var(--action);
  transition: background-color 600000s 0s, color 600000s 0s;
}

/* UN CHAMP QUI NE SE MODIFIE PAS LE MONTRE (ajouté le 19/09 : l'adresse
   posée par le paiement, inscription-commercant.html). Le fond se retire,
   le liseré passe en pointillé, le curseur reste ordinaire ; le texte,
   lui, reste pleinement lisible — c'est un champ déjà rempli, pas un
   champ éteint. */
input.champ[readonly],
textarea.champ[readonly],
.champ:has(> input[readonly]) {
  background: rgb(255 255 255 / .03);
  border-style: dashed;
  cursor: default;
}
input.champ[readonly]:focus,
.champ:has(> input[readonly]):focus-within {
  border-color: var(--trait);
  box-shadow: none;
}

/* Sous un champ : la règle, en doux ; le refus, en saumon — 15
   (EcranBienvenue.swift:533-536). */
.aide {
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
.aide.erreur { color: var(--alerte); }

/* LE MESSAGE D'UN REFUS, au-dessus du bouton : il ne prend de place que
   quand il parle (EcranBienvenue.swift:540-572). .reserve lui garde sa
   ligne quand rien ne doit sauter (FeuillesAcces.swift:48-51). */
.message {
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--alerte);
  text-align: left;
}
/* JAMAIS display:none quand il est vide (corrigé le 19/09) : ces messages
   portent role="status" ou aria-live, et un lecteur d'écran n'annonce pas
   ce qui arrive dans une zone qui n'était pas dans l'arbre d'accessibilité.
   Un <p> vide ne mesure rien de toute façon ; .reserve lui garde sa ligne. */
.message.reserve { min-height: 20px; }
.message.neutre { color: var(--texte-doux); }
.message.succes { color: var(--texte); }

/* LA JAUGE DU MOT DE PASSE : trois traits de 28 × 4 à l'encre d'or, un mot
   (EcranBienvenue.swift:510-538). <div class="jauge" data-force="2">
   <span></span><span></span><span></span><b>Bon</b></div> */
.jauge {
  display: flex;
  align-items: center;
  gap: var(--e2);
}
.jauge > span {
  width: 28px;
  height: 4px;
  border-radius: 2px;
  background: var(--trait);
  transition: background-color var(--duree-vif) var(--courbe-vif);
}
.jauge > span + span { margin-left: calc(var(--e1) - var(--e2)); }
.jauge[data-force="1"] > span:nth-of-type(-n+1),
.jauge[data-force="2"] > span:nth-of-type(-n+2),
.jauge[data-force="3"] > span:nth-of-type(-n+3) { background: var(--or); }
.jauge > b {
  font: 700 var(--t-etiquette) / var(--i-etiquette) var(--police);
  color: var(--or);
}
.jauge[data-force="0"] > b { color: var(--texte-doux); }

/* « ou », entre deux traits (EcranBienvenue.swift:341-367). */
.ou {
  display: flex;
  align-items: center;
  gap: var(--e3);
  padding: var(--e2) 0;
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
.ou::before,
.ou::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--trait);
}

/* L'INTERRUPTEUR, teinté d'or (OngletReglages.swift:311, 472). Le dessin est
   celui d'iOS (51 × 31) ; la zone sensible monte à 45. */
input.bascule {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  flex: none;
  width: 51px;
  height: 31px;
  margin: 0;
  border-radius: 999px;
  background: rgb(120 120 128 / .32);
  cursor: pointer;
  transition: background-color var(--duree-vif) var(--courbe-vif);
}
input.bascule::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 3px 8px rgb(0 0 0 / .15), 0 3px 1px rgb(0 0 0 / .06);
  transition: transform var(--duree-vif) var(--courbe-vif);
}
input.bascule::after {
  content: "";
  position: absolute;
  inset: -7px 0;
}
input.bascule:checked { background: var(--or); }
input.bascule:checked::before { transform: translateX(20px); }
input.bascule:disabled { opacity: .42; cursor: not-allowed; }

/* LE SÉLECTEUR SEGMENTÉ (le Picker .segmented du système — CadrageDuLogo,
   OngletRelances). Ses valeurs sont celles d'iOS, relevées, pas dans le code. */
.segments {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: 999px;
  background: rgb(118 118 128 / .24);
}
.segments > * {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 36px;
  margin: 0;
  padding: 0 var(--e3);
  border: 0;
  border-radius: 999px;
  background: none;
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--duree-vif) var(--courbe-vif);
}
.segments > *::after {
  content: "";
  position: absolute;
  inset: -4px 0;
}
.segments > .actif,
.segments > [aria-pressed="true"],
.segments > [aria-selected="true"],
.segments > [aria-checked="true"] {
  background: #636366;
  box-shadow: 0 3px 8px rgb(0 0 0 / .12);
}


/* ==========================================================================
   9. LE CONTENU
   ========================================================================== */

/* LE GROUPE DE RANGÉES : la liste groupée des Réglages de l'iPhone
   (OngletReglages.swift). Un titre en capitales au-dessus, des rangées sur
   le blanc 7 % de Theme.rangee, un pied en note dessous. */
.groupe + .groupe { margin-top: var(--e6); }
/* Le titre et le pied s'alignent sur le texte de ce qu'ils coiffent : 16
   dans une boîte de rangées, 24 dans un bloc, la marge sinon (un tableau
   nu, des tuiles). Un seul axe. */
.groupe-titre {
  display: block;
  margin: 0 0 var(--e2);
  padding: 0;
  font: 400 var(--t-legende) / var(--i-legende) var(--police);
  text-transform: uppercase;
  color: var(--texte-doux);
}
.groupe-titre:has(+ .rangees) { padding: 0 var(--e4); }
.groupe-titre:has(+ .bloc) { padding: 0 var(--e5); }
.groupe-pied {
  margin: var(--e2) 0 0;
  padding: 0;
  font: 400 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
}
.rangees + .groupe-pied { padding: 0 var(--e4); }
.bloc + .groupe-pied { padding: 0 var(--e5); }
.groupe-pied.centre { text-align: center; }

.rangees {
  --hauteur-rangee: var(--touche-minimale);
  --retrait: var(--e4);
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--rangee);
  border-radius: var(--rayon-groupe);
  overflow: hidden;
}
/* 56 : les totaux de l'Activité (FeuilleActivite.swift:186). 80 : la liste
   des clients (OngletClients.swift:202). */
.rangees.moyennes { --hauteur-rangee: 56px; }
.rangees.grandes  { --hauteur-rangee: 80px; }
/* Avec un symbole devant chaque rangée, le trait part du texte
   (OngletReglages.swift:377-379). */
.rangees.icones { --retrait: calc(var(--e4) + 28px + var(--e3)); }
.rangees > * { position: relative; }
.rangees > * + *::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--retrait);
  right: 0;
  height: 1px;
  background: var(--separateur);
  transform-origin: 0 0;
  pointer-events: none;
}
@media (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2) {
  .rangees > * + *::before { transform: scaleY(.5); }
}

.rangee {
  display: flex;
  align-items: center;
  gap: var(--e3);
  width: 100%;
  min-height: var(--hauteur-rangee, var(--touche-minimale));
  margin: 0;
  padding: var(--e2) var(--e4);
  border: 0;
  background: none;
  font: 400 var(--t-corps) / var(--i-corps) var(--police);
  color: var(--texte);
  text-align: left;
  text-decoration: none;
}
a.rangee, button.rangee, label.rangee, summary.rangee {
  cursor: pointer;
  transition: background-color var(--duree-vif) var(--courbe-vif);
}
a.rangee:active, button.rangee:active, label.rangee:active { background: rgb(255 255 255 / .06); }
@media (hover: hover) {
  a.rangee:hover, button.rangee:hover, label.rangee:hover { background: rgb(255 255 255 / .04); }
}
.rangee:focus-visible { outline-offset: -3px; }

/* Le symbole : à l'accent (le blanc chaud), comme les Label d'une liste. */
.rangee-icone {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  color: var(--action);
}
.rangee-icone svg { width: 22px; height: 22px; }
.rangee-corps {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;                                  /* l'écart mesuré de l'app — OngletReglages.swift:173 */
}
.rangee-titre { color: var(--texte); }
.rangee-sous {
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
/* La valeur d'un LabeledContent : en doux, à droite. */
.rangee-valeur {
  flex: none;
  max-width: 55%;
  color: var(--texte-doux);
  text-align: right;
}
/* Un nombre : arrondi, semi-gras, en blanc (FeuilleActivite.swift:242-246). */
.rangee-valeur.chiffres { color: var(--texte); font-weight: 600; }
/* Le chevron : note semi-grasse, en faible, à 2 points du bord mesurés sur
   les chevrons du système (OngletReglages.swift:186-192). */
.chevron {
  flex: none;
  width: 14px;
  height: 14px;
  margin-right: 2px;
  color: var(--texte-faible);
}
/* Ce qui détruit, en dernier, en saumon (DESIGN.md:131-139). */
.rangee.danger,
.rangee.danger .rangee-icone { color: var(--alerte); }
/* Une ligne présente mais hors formule : grisée, jamais verrouillée
   (OngletReglages.swift:331-364). */
.rangee.eteinte,
.rangee.eteinte .rangee-icone { color: var(--texte-faible); }

/* UNE CARTE DE CONTENU : le rayon de 28, le fond d'une rangée, 24 de marge
   intérieure (FicheCarte.swift:259-268). Du contenu, jamais du verre. */
.bloc {
  position: relative;
  padding: var(--e5);
  border-radius: var(--rayon-carte);
  background: var(--rangee);
}
/* La récompense due : or 10 %, liseré or 35 % (FicheCarte.swift:261-268). */
.bloc.or {
  background: rgb(var(--or-canaux) / .10);
  border: 1px solid rgb(var(--or-canaux) / .35);
}
/* Le même bloc, pour un problème qui demande un geste (extrapolé du bloc or). */
.bloc.alerte {
  background: rgb(var(--alerte-canaux) / .10);
  border: 1px solid rgb(var(--alerte-canaux) / .35);
}
/* LE PAPIER : un QR a besoin de sa marge blanche — c'est du contenu, pas du
   verre (MonQR.swift:102-143 ; ombre 24 pt → 48 px de flou CSS). */
.bloc.papier {
  padding: var(--e4);
  background: #fff;
  color: var(--encre);
  box-shadow: 0 8px 48px rgb(0 0 0 / .35);
}

/* UNE OPTION À CHOISIR (les rangées des formules, EcranFormules.swift:985-1095) :
   72 de haut au moins, rayon 18, un rond à cocher ; la choisie s'éclaircit
   (blanc 9 %) et prend un liseré or d'un point et demi.
   <label class="option"><input type="radio" name="…"><span class="option-rond"></span>…</label> */
.option {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--e3);
  width: 100%;
  min-height: 72px;
  margin: 0;
  padding: var(--e3) var(--e4);
  border-radius: var(--rayon-option);
  border: 1px solid rgb(255 255 255 / .08);
  background: var(--rangee);
  box-shadow: inset 0 0 0 0 transparent;
  font: 400 var(--t-corps) / var(--i-corps) var(--police);
  color: var(--texte);
  text-align: left;
  cursor: pointer;
  transition:
    transform var(--duree-vif) var(--courbe-vif),
    background-color var(--duree-vif) var(--courbe-vif),
    border-color var(--duree-vif) var(--courbe-vif),
    box-shadow var(--duree-vif) var(--courbe-vif);
}
.option:active { transform: scale(.985); }   /* EcranFormules.swift:676-686 */
.option > input[type="radio"],
.option > input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.option:has(> input:focus-visible) {
  outline: 2px solid var(--action);
  outline-offset: 3px;
}
.option.choisie,
.option[aria-pressed="true"],
.option[aria-checked="true"],
.option:has(> input:checked) {
  background: rgb(255 255 255 / .09);
  border-color: var(--accent-choix);
  box-shadow: inset 0 0 0 .5px var(--accent-choix);
}
.option-rond {
  position: relative;
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid rgb(255 255 255 / .4);
}
.option.choisie .option-rond,
.option[aria-pressed="true"] .option-rond,
.option[aria-checked="true"] .option-rond,
.option:has(> input:checked) .option-rond {
  border-color: var(--accent-choix);
  background: var(--accent-choix);
}
.option.choisie .option-rond::after,
.option[aria-pressed="true"] .option-rond::after,
.option[aria-checked="true"] .option-rond::after,
.option:has(> input:checked) .option-rond::after {
  content: "";
  position: absolute;
  left: 6.5px;
  top: 3.5px;
  width: 5px;
  height: 9px;
  border: solid var(--encre);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* UNE PASTILLE : « Conseillée », « Actuelle », un état (EcranFormules.swift:1109-1125). */
.pastille {
  display: inline-flex;
  align-items: center;
  gap: var(--e1);
  padding: var(--e1) var(--e2);
  border-radius: 999px;
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  white-space: nowrap;
  background: var(--rangee);
  color: var(--texte-doux);
}
.pastille.or      { background: var(--accent-choix); color: var(--encre); }
.pastille.contour { background: none; box-shadow: inset 0 0 0 1px rgb(255 255 255 / .35); }
.pastille.alerte  { background: rgb(var(--alerte-canaux) / .16); color: var(--alerte); }

/* LES TUILES (FeuilleActivite.swift:205-237) : un titre, un grand chiffre,
   une unité, rien d'autre. Rayon 16, 16 de marge intérieure, 12 entre elles. */
.tuiles {
  display: grid;
  grid-template-columns: repeat(var(--par-rangee, 3), minmax(0, 1fr));
  gap: var(--e3);
}
.tuiles.quatre { --par-rangee: 2; }
.tuile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
  margin: 0;
  padding: var(--e4);
  border: 0;
  border-radius: var(--rayon-tuile);
  background: var(--rangee);
  color: var(--texte);
  text-align: left;
  text-decoration: none;
}
button.tuile, a.tuile { cursor: pointer; transition: transform var(--duree-vif) var(--courbe-vif); }
button.tuile:active, a.tuile:active { transform: scale(.985); }
.tuile-titre {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
}
.tuile-nombre {
  font: 700 var(--t-titre) / var(--i-titre) var(--police-arrondie);
  font-variant-numeric: tabular-nums;
}
.tuile-unite {
  font: 400 var(--t-legende-2) / var(--i-legende-2) var(--police);
  color: var(--texte-doux);
}

/* LE TABLEAU (la comparaison des formules, EcranFormules.swift:1297-1398) :
   pas de grille, un trait blanc 7 % au-dessus de chaque rangée, l'en-tête
   en note semi-grasse et en doux, des rangées de 44. Pour l'administration,
   les cellules descendent d'un cran (15) : l'app en a trois colonnes, le
   panneau davantage. Sur un téléphone, il défile de côté dans son cadre,
   jamais la page. */
.tableau-cadre {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  margin-inline: calc(-1 * var(--marge));
  padding-inline: var(--marge);
}
.tableau-cadre.bloc { margin-inline: 0; padding: var(--e4) var(--e5); }
.tableau,
.lecture table {
  width: 100%;
  border-collapse: collapse;
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte);
  font-variant-numeric: tabular-nums;
}
.tableau caption,
.lecture table caption {
  caption-side: top;
  padding: 0 0 var(--e3);
  text-align: left;
  font: 700 var(--t-etiquette) / var(--i-etiquette) var(--police);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--texte-doux);
}
.tableau thead th,
.lecture table thead th {
  padding: 0 var(--e3) var(--e3);
  vertical-align: bottom;
  text-align: left;
  white-space: nowrap;
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
}
.tableau tbody :is(td, th),
.lecture table tbody :is(td, th) {
  padding: var(--e3);
  vertical-align: middle;
  text-align: left;
  border-top: 1px solid var(--trait-fin);
}
.tableau tbody th,
.lecture table tbody th { font-weight: 600; }
.tableau :is(td, th):first-child,
.lecture table :is(td, th):first-child { padding-left: 0; }
.tableau :is(td, th):last-child,
.lecture table :is(td, th):last-child { padding-right: 0; }
/* Un nombre : arrondi, semi-gras, à droite, à chasse fixe. */
.tableau .nombre {
  text-align: right;
  white-space: nowrap;
  font-family: var(--police-arrondie);
  font-weight: 600;
}
.tableau .doux { color: var(--texte-doux); }
/* Une rangée qui ouvre une fiche. */
.tableau tbody tr.cliquable { cursor: pointer; }
@media (hover: hover) {
  .tableau tbody tr.cliquable:hover { background: rgb(255 255 255 / .04); }
}

/* LES SEPT DERNIERS JOURS (Graphiques.swift:73-165) : des barres arrondies à
   6, 62 % de leur case, en blanc 34 % ; celle du jour en or (or clair en
   haut) ; une ligne de base blanc 16 %, pas de grille. La hauteur de chaque
   barre se pose en style : style="height: 40%". */
.barres {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: end;
  height: var(--hauteur-barres, 124px);
  border-bottom: 1px solid rgb(255 255 255 / .16);
}
.barre {
  justify-self: center;
  width: 62%;
  min-height: 3px;
  border-radius: 6px;
  background: rgb(255 255 255 / .34);
}
.barre.aujourdhui { background: linear-gradient(180deg, var(--or-clair), var(--or)); }
.barres-jours {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  padding-top: var(--e2);
  text-align: center;
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
}
.barres-jours .aujourdhui { font-weight: 700; color: var(--or); }

/* L'ÉCRAN D'UN ÉTAT (EcranPause.swift:98-138) : centré, un symbole de 64 au
   trait fin, le titre de 34, une phrase en doux, 16 entre chaque. Pour une
   page expirée, une panne, un espace fermé. */
.etat-vide {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--e4);
  min-height: 60vh;
  min-height: 60dvh;
  padding: var(--e4) 0;
  text-align: center;
}
.etat-vide-symbole {
  width: 64px;
  height: 64px;
  color: var(--texte-doux);
  stroke-width: 1.2;
  animation: respiration-symbole 3s ease-in-out infinite;
}
.etat-vide.alerte .etat-vide-symbole { color: var(--alerte); }
.etat-vide p { color: var(--texte-doux); max-width: 32em; }
.etat-vide .capsule { max-width: 280px; }
/* Le symbole qui respire (.symbolEffect(.breathe)) : une approximation. */
@keyframes respiration-symbole {
  50% { transform: scale(1.06); opacity: .8; }
}


/* ==========================================================================
   10. CE QUI PASSE AU-DESSUS
   ========================================================================== */

/* L'ANNONCE (CarimboApp.swift:131-227) : une pastille de verre en haut, de
   la marge de gauche à celle de droite, aux coins de carte (28), teintée
   d'encre — l'encre à 92 % dessous, sinon le titre se lisait à travers.
   15 semi-gras ; un symbole or pour une information, saumon pour une erreur.
   Elle reste 3,2 s, 6 s pour une erreur (au script de la retirer), et se
   déclare aux lecteurs d'écran : role="status" (erreur : role="alert"). */
.annonce {
  position: fixed;
  z-index: 90;
  top: calc(env(safe-area-inset-top) + var(--e1));
  left: 50%;
  width: min(calc(100% - 2 * var(--marge)), var(--mesure-telephone));
  display: flex;
  align-items: center;
  gap: var(--e3);
  min-height: var(--touche-minimale);
  padding: var(--e2) var(--e4);
  border-radius: var(--rayon-carte);
  border: 1px solid var(--verre-bord);
  background: rgb(var(--encre-canaux) / .92);
  -webkit-backdrop-filter: blur(24px) saturate(160%);
  backdrop-filter: blur(24px) saturate(160%);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .14), 0 12px 32px -12px rgb(0 0 0 / .7);
  font: 600 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte);
  transform: translateX(-50%);
  animation: annonce-arrive var(--duree-doux) var(--courbe-doux) backwards;
}
.annonce > svg:first-child {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--or);
}
.annonce.erreur > svg:first-child { color: var(--alerte); }
.annonce > span { flex: 1; min-width: 0; }
.annonce.part { animation: annonce-part var(--duree-doux) var(--courbe-doux) forwards; }
@keyframes annonce-arrive {
  from { transform: translate(-50%, -120%); opacity: 0; }
}
@keyframes annonce-part {
  to { transform: translate(-50%, -120%); opacity: 0; }
}

/* LA FEUILLE : une tâche courte (DESIGN.md:154-156). Sur un téléphone, elle
   monte du bas ; sur un grand écran, elle se pose au centre. Dans iOS 26,
   la feuille EST le verre ; les commandes posées dessus n'en ont donc plus
   (jamais de verre sur du verre). Titre au centre en 17 semi-gras, la croix
   ronde à gauche, 24 sous le titre (FeuillesAcces.swift:61-74,
   EcranFormules.swift:1279-1282).

   STRUCTURE : CELLE DE web/raccourci.js (corrigé le 19/09 — la première
   version avait sa propre structure, incompatible avec le panneau et
   l'espace, dont les feuilles sont ouvertes par ouvrirFeuille(id)) :
     <div class="feuille" id="feuille-x">            le voile, plein écran
       <div class="feuille-boite">                    la feuille elle-même
         <div class="feuille-poignee"></div>
         <h2 class="feuille-titre">…</h2>
         <p class="feuille-sous">…</p>
         …
         <div class="feuille-actions">…</div>
       </div>
     </div>
   ouvrirFeuille(id) pose .show sur .feuille et .feuille-ouverte sur <html> ;
   fermerFeuille les retire ; le glissement lit .feuille-boite. */
.feuille {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
  align-items: flex-end;
  justify-content: center;
  background: rgb(0 0 0 / .4);
  opacity: 0;
  transition: opacity var(--duree-doux) var(--courbe-doux);
}
.feuille.show { display: flex; opacity: 1; }
/* Tant qu'une feuille est ouverte, plus rien ne défile derrière. */
.feuille-ouverte, .feuille-ouverte body { overflow: hidden; }
.feuille-ouverte .contenu-scroll { overflow: hidden; }
.feuille-boite {
  position: relative;
  width: 100%;
  max-width: 100%;
  max-height: calc(100dvh - env(safe-area-inset-top) - var(--e2));
  margin: 0;
  padding: 0 var(--e5) calc(var(--e5) + env(safe-area-inset-bottom));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  border: 0;
  border-top: 1px solid var(--trait);
  border-radius: var(--rayon-carte) var(--rayon-carte) 0 0;
  background: rgb(33 31 29 / .82);           /* l'encre + 5 % de blanc */
  -webkit-backdrop-filter: blur(40px) saturate(180%);
  backdrop-filter: blur(40px) saturate(180%);
  box-shadow: 0 -12px 40px -16px rgb(0 0 0 / .6);
  color: var(--texte);
  transform: translateY(18px);
  transition: transform var(--duree-doux) var(--courbe-doux);
}
.feuille.show .feuille-boite { transform: translateY(0); }
.feuille-poignee {
  width: 36px;
  height: 5px;
  margin: var(--e2) auto var(--e5);
  border-radius: 3px;
  background: rgb(255 255 255 / .3);
}
/* Le titre au centre ; la croix ou « Annuler », un rond de 44 à gauche du
   titre, se pose dans une .feuille-tete quand il y en a un. */
.feuille-titre {
  margin: 0 0 var(--e2);
  text-align: center;
  font: 600 var(--t-corps) / var(--i-corps) var(--police);
}
.feuille-sous {
  margin: 0 0 var(--e5);
  text-align: center;
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
.feuille-tete {
  display: grid;
  grid-template-columns: var(--touche-minimale) 1fr var(--touche-minimale);
  align-items: center;
  gap: var(--e2);
  margin-bottom: var(--e5);
}
.feuille-tete > .gauche { grid-column: 1; }
.feuille-tete > .droite { grid-column: 3; }
.feuille-tete > .feuille-titre { grid-column: 2; margin: 0; }
.feuille-actions { display: flex; gap: var(--e2); margin-top: var(--e4); }
.feuille-actions > * { flex: 1; }
.feuille-boite :is(.rond, .pilule, .capsule:not(.principale):not(.danger):not(:disabled)),
.annonce :is(.rond, .pilule, .capsule:not(.principale):not(.danger):not(:disabled)) {
  background: rgb(255 255 255 / .10);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border-color: transparent;
  box-shadow: none;
}
.feuille-boite :is(.rond, .pilule, .capsule)::before,
.annonce :is(.rond, .pilule, .capsule)::before { display: none; }
@keyframes fondu {
  from { opacity: 0; }
}

/* LE MODE D'EMPLOI DU RACCOURCI (fabriqué par web/raccourci.js dans la
   feuille « feuille-raccourci » du panneau et de l'espace) : trois étapes
   numérotées, en rangées. Les noms sont ceux de raccourci.js — tuto-texte
   compris, qu'espace.html ne doit jamais redéfinir. */
.tuto-etapes {
  list-style: none;
  counter-reset: etape;
  margin: 0 0 var(--e2);
  padding: 0;
}
.tuto-etape {
  counter-increment: etape;
  display: flex;
  gap: var(--e3);
  align-items: flex-start;
  padding: var(--e3) 0;
}
.tuto-etape + .tuto-etape { border-top: 1px solid var(--separateur); }
.tuto-etape::before {
  content: counter(etape);
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--action);
  color: var(--encre);
  font: 700 var(--t-etiquette) / 1 var(--police);
}
.tuto-texte { font: 400 var(--t-precision) / var(--i-precision) var(--police); }
.tuto-texte b { font-weight: 700; }
.tuto-glyphe {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  vertical-align: -5px;
  margin: 0 var(--e1);
  border-radius: 5px;
  background: rgb(255 255 255 / .12);
}
.tuto-glyphe svg { width: 13px; height: 13px; stroke: var(--texte); fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }

/* LE SEUL LANGAGE D'APPARITION : LE FONDU FLOUTÉ (DESIGN.md:108-110). Un
   message, un bloc qui arrive, une page qui remplace l'autre. */
.apparait { animation: flou-arrive var(--duree-doux) var(--courbe-doux) backwards; }
@keyframes flou-arrive {
  from { opacity: 0; filter: blur(8px); }
}

/* LA SECOUSSE D'UNE ENTRÉE REFUSÉE : −14, 12, −8, 5, 0, 60 ms chacun
   (EcranBienvenue.swift:729-738). Au script de poser la classe, puis de la
   retirer. */
.secoue { animation: secousse .3s linear; }
@keyframes secousse {
  20% { transform: translateX(-14px); }
  40% { transform: translateX(12px); }
  60% { transform: translateX(-8px); }
  80% { transform: translateX(5px); }
}


/* ==========================================================================
   11. LES TEXTES LONGS (mentions légales, conditions, confidentialité,
       assistance, suppression). L'app n'en a pas : c'est la seule page où
       l'interligne passe à 1,5 et où la colonne s'élargit à ~70 signes.
       Tout le reste — couleurs, tailles, marque, liens, formulaires — est
       celui de l'app.
   ========================================================================== */

.lecture {
  font: 400 var(--t-corps) / var(--i-lecture) var(--police);
  color: var(--texte);
}
.lecture > .marque { margin: 0 0 var(--e2); }
.lecture h1 { margin: 0 0 var(--e2); }
/* « Mise à jour le … » : une précision. */
.lecture .maj {
  margin: 0 0 var(--e6);
  font: 400 var(--t-precision) / var(--i-precision) var(--police);
  color: var(--texte-doux);
}
.lecture h2 { margin: var(--e7) 0 var(--e3); }
.lecture h3 { margin: var(--e5) 0 var(--e2); }
.lecture :is(p, ul, ol, dl, table, details:last-of-type, .bloc) { margin: 0 0 var(--e4); }
.lecture :is(ul, ol) { padding-left: var(--e5); }
.lecture li { margin: 0 0 var(--e2); }
.lecture li::marker { color: var(--texte-doux); }
/* PAS DE MOT ORPHELIN (ajouté le 19/09) : un « ; » seul sur la dernière
   ligne de la confidentialité, un « ! » seul sous un champ de Relancer. Le
   texte ne change pas (il est figé) : c'est le navigateur qui équilibre les
   dernières lignes. Ignoré là où text-wrap n'existe pas ; rien ne casse. */
.lecture p, .lecture li, .aide, .champ-aide { text-wrap: pretty; }
.lecture dt { margin-top: var(--e3); font-weight: 600; }
.lecture dd { margin: 0; color: var(--texte-doux); }
.lecture hr {
  height: 1px;
  margin: var(--e6) 0;
  border: 0;
  background: var(--trait);
}
.lecture code { font-family: var(--police-fixe); font-size: .9em; }
.lecture em { font-style: italic; color: var(--texte-doux); }

/* Les questions qui se déplient : des rangées, un chevron qui tourne. */
.lecture details {
  border-top: 1px solid var(--separateur);
}
.lecture details:last-of-type { border-bottom: 1px solid var(--separateur); }
.lecture summary {
  display: flex;
  align-items: center;
  gap: var(--e3);
  min-height: var(--touche-minimale);
  padding: var(--e2) 0;
  list-style: none;
  font-weight: 600;
  cursor: pointer;
}
.lecture summary::-webkit-details-marker { display: none; }
.lecture summary::after {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  margin: 0 var(--e1) 0 auto;
  border: solid var(--texte-faible);
  border-width: 0 2px 2px 0;
  transform: rotate(-45deg);
  transition: transform var(--duree-vif) var(--courbe-vif);
}
.lecture details[open] > summary::after { transform: rotate(45deg); }
.lecture details > :not(summary) { color: var(--texte-doux); }
.lecture details > :not(summary):last-child { padding-bottom: var(--e3); }

/* Les formulaires d'une page de texte : les champs de l'app, sans classe à
   ajouter. Au-dessus de chaque champ, son nom en 15 semi-gras. */
.lecture label:not(.champ):not(.option):not(.rangee) {
  display: block;
  margin: var(--e4) 0 var(--e2);
  font: 600 var(--t-precision) / var(--i-precision) var(--police);
}
.lecture :where(input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="submit"]):not([type="button"]), select, textarea):not(.champ):not(.bascule) {
  display: block;
  width: 100%;
  min-height: var(--hauteur-bouton);
  margin: 0;
  padding: var(--e2) var(--e5);
  border-radius: 999px;
  border: 1px solid var(--trait);
  background: var(--rangee);
  box-shadow: inset 0 0 0 0 transparent;
  font: 600 var(--t-titre-3) / var(--i-titre-3) var(--police-arrondie);
  color: var(--texte);
  transition: border-color var(--duree-vif) var(--courbe-vif), box-shadow var(--duree-vif) var(--courbe-vif);
}
.lecture select:not(.champ):not(.bascule) {
  -webkit-appearance: none;
  appearance: none;
  padding-right: var(--e7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-opacity='.62' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--e5) center;
  background-size: 16px;
}
.lecture textarea:not(.champ):not(.bascule) {
  min-height: 150px;
  padding: var(--e3) var(--e4);
  border-radius: var(--rayon-champ);
  font: 400 var(--t-corps) / 1.4 var(--police);
  resize: vertical;
}
.lecture :is(input, select, textarea):not(.champ):not(.bascule):not([type="checkbox"]):not([type="radio"]):focus {
  outline: none;
  border-color: rgb(255 255 255 / .9);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .9);
}
.lecture :is(input, select, textarea)[aria-invalid="true"]:not(.champ):not(.bascule):not([type="checkbox"]):not([type="radio"]) {
  border-color: var(--alerte);
  box-shadow: inset 0 0 0 1px var(--alerte);
}
.lecture .capsule { margin-top: var(--e5); }

/* LE PIED DES PAGES DE TEXTE : les petits liens, 13 semi-gras, en doux. */
.pied {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--e4);
  margin-top: var(--e7);
  padding-top: var(--e4);
  border-top: 1px solid var(--trait);
}
.pied a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touche-minimale);
  font: 600 var(--t-legende) / var(--i-legende) var(--police);
  color: var(--texte-doux);
  text-decoration: none;
}
@media (hover: hover) {
  .pied a:hover { color: var(--texte); }
}


/* ==========================================================================
   12. LES GRANDS ÉCRANS
   ========================================================================== */

@media (min-width: 768px) {
  .colonne { padding-top: calc(var(--e8) + env(safe-area-inset-top)); }
  /* Sur iPhone, le retour se glisse à 8 sous la barre d'état ; sur un écran,
     remonté de 56 il collerait au haut de la fenêtre alors que le contenu
     commence à 64. Il reste à sa place (corrigé le 19/09). */
  .entete { margin-top: 0; }
  /* La barre du bas n'a plus de pouce à suivre : elle reprend sa place. */
  .barre-du-bas {
    position: static;
    margin-inline: 0;
    padding-inline: 0;
    padding-top: var(--e5);
    background: none;
  }
  .barre-du-bas::before { display: none; }
  .tuiles.quatre { --par-rangee: 4; }
  /* Un sélecteur de 1 000 points ne se lit plus comme un choix. */
  .segments { width: fit-content; max-width: 100%; }
  .segments > * { flex: 0 0 auto; padding: 0 var(--e4); }
  .tableau-cadre { margin-inline: 0; padding-inline: 0; }
  .tableau-cadre.bloc { padding: var(--e4) var(--e5); }
  /* La feuille se pose au centre. */
  .feuille { align-items: center; padding: var(--e6); }
  .feuille-boite {
    max-width: 540px;
    border: 1px solid var(--trait);
    border-radius: var(--rayon-carte);
    transform: scale(.96);
  }
  .feuille.show .feuille-boite { transform: scale(1); }
  .feuille-poignee { display: none; }
}


/* ==========================================================================
   13. LES REPLIS — OBLIGATOIRES. Le vrai verre d'Apple s'adapte seul aux
       réglages d'accessibilité (DESIGN.md:188-190) ; une imitation doit le
       faire à la main.
   ========================================================================== */

/* REPLI 1 — SANS backdrop-filter : un verre qui ne floute pas laisse lire
   ce qu'il y a derrière. Une surface opaque, et lisible. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .verre,
  .capsule:not(.principale):not(.danger):not(:disabled):not([aria-disabled="true"]),
  .pilule,
  .rond { background: var(--verre-repli); }
  .annonce { background: var(--encre); }
  .feuille-boite { background: #211F1D; }
}

/* REPLI 2 — « RÉDUIRE LA TRANSPARENCE » : plus de flou, plus de reflet qui
   laisse passer le fond. */
@media (prefers-reduced-transparency: reduce) {
  .verre,
  .capsule:not(.principale):not(.danger):not(:disabled):not([aria-disabled="true"]),
  .pilule,
  .rond {
    background: var(--verre-repli);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .verre::before, .capsule::before, .pilule::before, .rond::before { display: none; }
  .annonce { background: var(--encre); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .feuille-boite { background: #211F1D; -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* REPLI 3 — « RÉDUIRE LES ANIMATIONS » : la lumière reste, la dérive s'arrête
   (FondVivant.swift:90-93) ; rien ne s'enfonce, rien ne glisse, rien ne
   tremble ; ce qui apparaît apparaît en fondu, sur place. La roue d'attente,
   elle, tourne : c'est une information, pas un décor. */
@media (prefers-reduced-motion: reduce) {
  body::before,
  body::after { animation: none; }
  .capsule, .pilule, .rond, .option, .tuile, .rangee, .champ, input.bascule, input.bascule::before {
    transition-property: background-color, border-color, box-shadow, color, filter;
  }
  .capsule:active:not(:disabled), .pilule:active, .rond:active:not(:disabled),
  .option:active, button.tuile:active, a.tuile:active { transform: none; }
  .annonce { animation-name: fondu; }
  .annonce.part { animation-name: fondu-part; }
  .feuille-boite { transform: none; transition-property: opacity; }
  .apparait { animation-name: fondu; }
  .secoue, .etat-vide-symbole { animation: none; }
  .lecture summary::after { transition: none; }
}
@keyframes fondu-part {
  to { opacity: 0; }
}

/* « AUGMENTER LE CONTRASTE » : le verre devient opaque, les traits se
   voient, le texte doux remonte. */
@media (prefers-contrast: more) {
  :root {
    --texte-doux: rgb(255 255 255 / .8);
    --texte-faible: rgb(255 255 255 / .62);
    --invite: rgb(255 255 255 / .7);
    --verre-bord: rgb(255 255 255 / .5);
    --trait: rgb(255 255 255 / .4);
    --trait-fin: rgb(255 255 255 / .24);
    --separateur: rgb(255 255 255 / .3);
  }
  .verre,
  .capsule:not(.principale):not(.danger):not(:disabled):not([aria-disabled="true"]),
  .pilule,
  .rond {
    background: var(--verre-repli);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* LES COULEURS FORCÉES (contraste élevé de Windows) : le système repeint
   tout ; on éteint la lumière et on garde les contours. */
@media (forced-colors: active) {
  body::before, body::after { display: none; }
  .capsule, .pilule, .rond, .champ, .option, .rangees, .bloc, .tuile, .annonce, .feuille-boite {
    border: 1px solid CanvasText;
  }
  .capsule.principale { border: 2px solid ButtonText; }
  .jauge > span, .barre, input.bascule { forced-color-adjust: none; }
}

/* L'IMPRESSION : le navigateur jette les fonds, et un texte blanc sur une
   page blanche ne s'imprime pas. Une conditions générales doit pouvoir se
   poser sur papier : encre noire, rien de ce qui se touche. */
@media print {
  :root {
    --texte: #000;
    --texte-doux: #444;
    --texte-faible: #666;
    --trait: #bbb;
    --trait-fin: #ddd;
    --separateur: #ccc;
    --rangee: transparent;
  }
  html, body { background: #fff; color: #000; }
  body::before, body::after { display: none; }
  .verre, .capsule, .pilule, .rond, .annonce, .feuille-boite {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  .barre-du-bas, .annonce, .feuille, .entete .pilule, .entete .rond { display: none !important; }
  .colonne { max-width: none; padding: 0; }
  .marque { color: #000; }
  a { color: #000; }
  .bloc, .rangees, .tuile { border: 1px solid #ccc; }
}

/* POUR VÉRIFIER À L'ŒIL : <html class="montrer-la-grille"> pose la grille de
   8 sur la page. À ne jamais laisser en production. */
.montrer-la-grille body {
  background-image:
    repeating-linear-gradient(0deg, rgb(255 0 128 / .12) 0 1px, transparent 1px 8px),
    repeating-linear-gradient(90deg, rgb(255 0 128 / .12) 0 1px, transparent 1px 8px);
}
