/* Sizzle — direction visuelle du § 11.
   Entre Pinterest, le carnet de cuisine et le magazine culinaire.
   Jamais un dashboard fitness : pas de néon, pas de dégradé violet. */

/* Palette de marque. Les cinq premières valeurs viennent de la charte ;
   les autres en sont dérivées pour couvrir les besoins d'interface
   (texte secondaire, filets, alertes) sans introduire de teinte étrangère. */
:root {
  --cream:    #F7ECDF;   /* fond — le beige de l’accueil sur toutes les pages, 14/09 */
  --sand:     #DCC9AE;   /* aplats secondaires */
  --terra:    #C65A2E;   /* accent principal */
  /* ⚠️ **Le brun profond est passé à la terre cuite du logo — 12/09.** Même
     jeton, donc tout ce qui l'employait suit : liens, icônes des raccourcis,
     onglet actif. */
  --cinnamon: #A94A2A;   /* terre cuite profonde */
  --sage:     #7C8B63;   /* vert */
  /* ⚠️ **Le seul bleu de la palette, et il ne sert qu'à l'eau — 04/09.** Tout
     le reste est chaud (crème, terre, cannelle, sauge) ; le bleu est ce qui dit
     « eau » sans un mot, et c'est la seule raison de l'introduire. Désaturé
     exprès pour tenir à côté de la terre, plutôt que le bleu franc de la
     maquette qui y jurerait. ⛔ Ne pas s'en servir pour autre chose : un accent
     qui désigne deux choses n'en désigne plus aucune. */
  --bleu:     #4F7CA6;   /* eau, et rien d'autre */

  --card:     #FFFFFF;
  /* La barre d'onglets porte le beige du fichier « footer Navbar » (12/09),
     mesuré sur la bande : ni `--card` (trop blanc) ni `--cream` (le fond des
     pages, sur lequel elle doit se lire). Opaque — § 6. Et c'est ce beige
     exact que porte le contour de la cocotte du ➕, découpée avec lui : sur
     la barre le liseré se fond, au-dessus d'elle il détache le dessin du
     contenu qui défile. */
  --barre:    #FEFAF3;
  /* Les cœurs, partout : pastille allumée, carte des favoris, coin de photo. */
  --coeur:    #A94A2A;
  /* Le pied du bandeau d'accueil — c'est lui qui prolonge l'image vers le bas. */
  --accueil-fond: #F7ECDF;
  /* L'encadré « Une idée pour ce soir » — la couleur donnée par Luca le 13/09.
     ⚠️ **Le texte y change de couleur, et c'est mesuré** : la cannelle y rend
     3,15 de contraste et le gris 2,2 — sous la barre AA. Brun sombre et
     terre profonde y rendent 5,75 et 5,11. */
  --idee-fond: #BFC689; --idee-texte: #4A3E33; --idee-lien: #7A3118;
  --espresso: #2E241D;
  --muted:    #8C7F70;
  --line:     #E7DCCA;
  --amber:    #D9A441;
  --danger:   #A6412F;

  /* ── Le bouton plein a ses propres jetons — R05, 21/09 ─────────────────────
     ⛔ **`--cinnamon` est une couleur de *texte*, pas un fond de bouton.** Le
     jeton a été éclairci pour le thème sombre le 10/09, parce qu'un brun foncé
     ne se lit pas sur du chocolat ; `.btn-primary` s'en servait comme fond, et
     le libellé blanc du bouton « Analyser » de P14 tombait alors à **2,19:1**
     en sombre. Une couleur qui sert de fond *et* d'encre doit aller dans deux
     directions à chaque changement de thème : elle finit par en rater une.

     → **Deux jetons, et le couple se mesure.** `--bouton-fond` reste terre
     cuite dans les deux thèmes ; `--bouton-texte` est ce qui se lit dessus.
     Relevé sur les couleurs calculées : 5,68:1 en clair, 4,99:1 en sombre, et
     le fond garde 5,68:1 / 3,16:1 avec la carte qui le porte — un bouton doit
     aussi se voir avant de se lire.

     ⚠️ **Le libellé grisé n'est plus un blanc à 55 %.** `opacity` mélangeait le
     blanc au terre cuite et donnait 2,3:1 : un bouton d'attente qu'on ne sait
     plus lire. L'état inactif a donc son propre couple, franchement plus pâle
     que le bouton actif (donc reconnaissable d'un coup d'œil) et lisible —
     4,83:1 en clair, 5,02:1 en sombre. */
  --bouton-fond: #A94A2A;
  --bouton-texte: #FFFFFF;
  --bouton-inactif-fond: #E7DCCA;
  --bouton-inactif-texte: #6B5B45;
  /* L'encre des commandes **sans** fond plein : `.btn-ghost`, `.chip` au repos,
     les onglets. ⛔ **Ce n'est pas `--muted`.** Le gris des textes secondaires
     rendait 3,90:1 sur la carte et 3,75:1 sur la barre d'onglets : lisible pour
     une phrase d'appoint, pas pour le mot qui *est* le bouton. Même famille,
     assez foncée pour être lue : 6,55:1 sur la carte, 6,29:1 sur la barre,
     6,21:1 en thème sombre. */
  --bouton-second-texte: #6B5B45;
  /* Le rouge qui s'écrit n'est pas le rouge qui se remplit — même partage que
     dans `admin.css`. `--danger` reste un fond de pastille ; posé en texte sur
     la carte sombre il tombait à 2,57:1 (« Retirer » de la relecture). */
  --danger-texte: #A6412F;
  /* Le vert de « valider la relecture » : `--sage` rendait 3,66:1 sous du
     blanc. Même famille, assez foncé pour porter une encre claire — 6,69:1 en
     clair, 4,74:1 en sombre. */
  --bouton-valide-fond: #556042;

  /* ── Le carnet : les jetons des menus C01–C30 — DA01, 21/09 ────────────────
     La direction demandée par Luca (`design/nouveau-visuel/CADRE_MENUS.md`) est
     une **surface** propre aux feuilles : papier ivoire, cartes distinctes à
     filet pêche, sauge pour la sélection. Ce n'est pas la palette des écrans —
     ceux-ci gardent `--cream` / `--card` — et c'est voulu : une fenêtre qui se
     pose par-dessus une page doit s'en détacher. D'où un jeu **préfixé**, posé
     ici et redéfini dans les deux blocs sombres, plutôt que trente valeurs
     recopiées feuille par feuille (le cadre l'interdit explicitement).

     ⚠️ **Le papier sombre est plus foncé que les `#302820` du cadre, et c'est
     une mesure, pas un goût.** Le pied d'une feuille porte `.btn-primary`, dont
     le fond `--bouton-fond` (#BC4C2B en sombre) doit garder 3:1 contre ce qui
     l'entoure — R05 : un bouton se voit avant de se lire. Sur #302820 il tombe
     à **2,90:1** ; sur #2B231C il rend **3,09:1**. Les cartes gardent l'écart
     annoncé avec le papier. */
  --carnet-papier: #FFF9F1;
  --carnet-papier-bas: #FBF1E5;
  --carnet-carte: #FFFDF8;
  --carnet-filet: #ECDCCB;
  --carnet-filet-vif: #CFA789;     /* survol et pointeur, jamais un état */
  /* Cacao : 11,13:1 sur la carte, 10,82:1 sur le papier. Il remplace
     `--espresso` **dans les feuilles seulement** — l'encre d'un carnet est
     brune, celle d'un écran reste celle des écrans. */
  --carnet-encre: #513329;
  /* Le sous-texte d'une carte : 5,43:1 sur la carte, 5,28:1 sur le papier.
     ⛔ Pas `--muted` (3,84:1 sur la carte ivoire). */
  --carnet-encre-douce: #786558;
  /* La sélection — bascule active, cran retenu, option courante. L'encre du
     carnet y rend 9,55:1, son sous-texte 4,66:1, le trait sauge 5,19:1. */
  --carnet-sauge: #E9EEDB;
  --carnet-sauge-trait: #53683C;
  --carnet-sauge-filet: #A0AC83;
  --carnet-ombre: 0 14px 34px rgba(58, 38, 27, .18);
  /* Le rouge **qui se remplit** — le bouton d'une suppression, pas le mot
     rouge (`--danger-texte`). `--danger` seul ne suffisait pas : il n'est pas
     redéfini en sombre et rendait 2,51:1 contre le papier brun, soit un bouton
     de suppression qu'on ne voit plus. Le couple est mesuré dans les deux
     thèmes — 6,14:1 / 5,87:1 en clair, 5,47:1 / 5,16:1 en sombre. */
  --danger-fond: #A6412F;
  --danger-fond-texte: #FFFFFF;

  --radius: 22px;
  --shadow: 0 10px 30px rgba(48, 41, 35, .07);
  --tabbar-h: 68px;
  /* Largeur de la colonne de l'app. Un jeton et non un nombre recopié : tout
     ce qui se pose en `position: fixed` doit se recadrer dessus, et une
     seconde valeur écrite en dur ailleurs finirait par en dévier. */
  --app-w: 720px;
  /* Largeur du rail de navigation, **nulle tant qu'il n'existe pas**. C'est ce
     qui permet au recadrage des couches fixes ci-dessous de s'écrire une seule
     fois : sur téléphone il vaut zéro et la formule se réduit au centrage
     d'avant, sur ordinateur il décale tout ce qui est fixe de la même quantité
     que la colonne. Une seconde formule pour le grand écran aurait divergé au
     premier ajout — c'est la leçon de `--app-w` lui-même. */
  --rail-w: 0px;
  /* Largeur de lecture d'un écran, et **elle n'est pas celle de la coque**. Un
     écran qui liste (recettes, journal, courses) se remplit sur toute la
     largeur ; un écran qui se lit ou se remplit (objectif, édition, relecture)
     ne doit pas s'étirer avec la fenêtre — une ligne de saisie de 1 100 px et un
     paragraphe de 180 caractères ne se lisent plus. Le défaut vaut donc la coque
     entière, et le registre du § « tablette et bureau » nomme les écrans qui la
     réduisent. */
  --lecture-w: var(--app-w);

  /* ── Une seule écriture pour toute l'interface — R12, 21/09 ────────────────
     Demande de Luca : la police du cadran des macros (`.mleg-nom`) partout.
     Elle **remplace** l'ancien couple serif de titre / sans d'interface, qui
     faisait cohabiter trois écritures sur un même écran. `--serif` et `--sans`
     restent définis juste en dessous, pointant ici : c'est le filet de sécurité
     d'une déclaration oubliée ailleurs ou d'un style posé en JavaScript — sans
     lui, un coin de l'application resterait en Palatino sans que rien ne le
     dise. ⛔ Ne plus les employer dans du code neuf.

     Le repli n'est pas une deuxième écriture manuscrite : les polices
     manuscrites du système (Segoe Script, Bradley Hand) n'ont ni le même
     dessin ni la même largeur, et l'interface changerait de personnalité selon
     l'appareil. C'est la pile système d'avant, **remise à l'échelle** par
     `size-adjust` pour que les tailles calibrées pour Caveat ne deviennent pas
     énormes le temps du chargement, ou définitivement si la fonte échoue. */
  --ecriture: "Caveat Gourde", "Sizzle repli", system-ui, -apple-system,
              "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --serif: var(--ecriture);   /* ⛔ hérité, ne plus employer */
  --sans:  var(--ecriture);   /* ⛔ hérité, ne plus employer */

  /* ── L'échelle de texte, calibrée pour Caveat ───────────────────────────────
     Les tailles de la pile système ne se recopient pas : le cadre des menus le
     dit, « le manuscrit à 12,5 px serait trop petit ». La correspondance
     appliquée aux 946 déclarations existantes est compressive — ×1,50 jusqu'à
     16 px, puis `24 + 0,60·(s−16)` jusqu'à 30 px, puis ×1,08 —, calibrée sur
     les valeurs déjà écrites en Caveat ici (`.puces-jour button` : 14 px
     système → 22 px Caveat) et sur la table du cadre des menus.

     Ces jetons nomment les degrés obtenus, pour que la direction artistique des
     menus (DA01) compose avec une échelle et non trente valeurs libres.
     ⚠️ Ce sont des **degrés**, pas un `!important` : une règle plus précise
     garde le dernier mot, et la hiérarchie des écrans existants est conservée. */
  --t-titre:      32.5px;  /* titre d'écran ou de feuille (30–34 au cadre) */
  --t-titre-sec:  27.5px;  /* titre de section */
  --t-carte:      24px;    /* texte d'une carte, libellé d'une commande */
  --t-carte-sous: 20px;    /* sous-texte d'une carte */
  --t-corps:      22.5px;  /* corps de texte, formulaire */
  --t-champ:      24px;    /* saisie — ⚠️ jamais sous 16 px (zoom iOS) */
  --t-appoint:    19.5px;  /* mention secondaire, aide, unité d'une liste */
  --t-micro:      16.5px;  /* étiquette minuscule ; plancher de l'échelle */
  --t-nombre:     43px;    /* le nombre qu'on vient lire (36–48 au cadre) */
  --t-unite:      24px;    /* son unité, posée à côté */

  /* Caveat monte plus haut et descend plus bas qu'une linéale : l'interligne
     absolu d'avant suffit, il n'a pas à croître avec la taille. */
  --lh-titre: 1.1;
  --lh-carte: 1.2;
  --lh-corps: 1.3;
  /* 400 est fin pour une écriture manuscrite à petite taille ; 500 est le poids
     de lecture, 600–700 restent l'accent. ⚠️ Et pas 600 par défaut : au-delà,
     les bancs de contraste basculent sur le seuil « grand texte » (3:1), qui
     n'est pas celui d'un libellé. */
  --g-corps: 500;
  --g-accent: 600;
  --g-titre: 700;
}

/* ── La fonte, servie par le dépôt ─────────────────────────────────────────────
   Aucune requête à Google Fonts : le fichier est dans `frontend/fonts/`, la CSP
   n'autorise que `font-src 'self'`, et la licence OFL est conservée à côté.

   ⚠️ **`?v=` à la main, et c'est nécessaire.** `ASSET_VERSION` ne s'applique
   qu'aux URL écrites dans les documents HTML (`index.html`, `admin.html`,
   `sw.js`) ; celle-ci est dans la feuille de style, donc elle ne serait jamais
   invalidée. Le sous-ensemble a été étendu le 21/09 (`œ Œ ’ « » – … € ≈ ≤ ≥`,
   accents, fractions) **sous le même nom de fichier** : sans ce compteur, un
   navigateur qui avait déjà la version U+0020–00FF continuerait d'afficher le
   repli système sur ces caractères. Incrémenter à chaque régénération de la
   fonte, et le noter dans `fonts/caveat/README.md`. */
@font-face {
  font-family: "Caveat Gourde";
  src: url("/static/fonts/caveat/caveat-latin.woff2?v=2") format("woff2");
  font-weight: 400 700; font-style: normal; font-display: swap;
}
/* Le repli, à l'échelle de Caveat. Toutes les tailles de cette feuille sont
   calibrées pour une manuscrite étroite ; rendues avec une linéale, elles
   seraient 1,5 fois trop grosses et l'application se disloquerait pendant le
   chargement (`swap`) ou pour de bon si la fonte n'arrive pas. `size-adjust`
   rend au repli l'échelle qu'il avait avant R12.
   ⚠️ `local()` seulement : aucun téléchargement, aucune requête externe. Si
   aucun de ces noms n'existe, la face est invalide et la pile de `--ecriture`
   passe simplement au suivant — `system-ui`, sans ajustement. */
@font-face {
  font-family: "Sizzle repli";
  src: local("Helvetica Neue"), local("Roboto"), local("Segoe UI"),
       local("Arial"), local("DejaVu Sans"), local("Liberation Sans");
  size-adjust: 67%;
}
/* Le gras du repli : sans cette seconde face, un `font-weight: 700` demandé à
   une face déclarée sur toute la plage sortirait en maigre. Déclarées à 400 et
   700, les deux laissent le navigateur choisir — et synthétiser si le système
   n'a que la romaine. */
@font-face {
  font-family: "Sizzle repli";
  src: local("Helvetica Neue Bold"), local("Roboto Bold"), local("Segoe UI Bold"),
       local("Arial Bold"), local("DejaVu Sans Bold"), local("Liberation Sans Bold");
  size-adjust: 67%;
  font-weight: 700;
}

/* Mode sombre en tons chocolat, jamais en gris neutre (§ 11).
   Trois états : choix explicite clair, choix explicite sombre, et défaut
   système. Les tokens sont définis en clair sur :root nu, puis redéfinis
   pour chacun des deux cas sombres — sinon le bascule ne gagne que dans
   un sens. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --cream: #1F1813; --card: #2A211A; --espresso: #F3EADD;
    --muted: #B0A08D; --line: #3C3027; --sand: #6B5B45;
    --terra: #D9743F; --sage: #93A278; --bleu: #7FA9CE;
    /* ⚠️ **Le brun profond manquait à la palette sombre.** Mesuré le 10/09 :
       « Explorer les recettes » rendait **1,71** de contraste sur le bandeau de
       découverte — un brun foncé sur du chocolat ne se lit pas. Il n'était pas
       redéfini ici alors que la terre, la sauge et le bleu l'étaient : le trou ne
       s'était pas vu parce qu'aucun texte de cette couleur n'occupait encore une
       place où on le cherche. */
    --cinnamon: #E0A276;
    --coeur: #D9743F; --accueil-fond: #1F1813;
    --idee-fond: #3B4029; --idee-texte: #D9CFB9; --idee-lien: #E0A276;
    --barre: #2A211A;
    /* R05 : le bouton reste terre cuite, mais un ton qui porte du blanc.
       ⛔ Ce n'est pas `--cinnamon` : lui est passé à la pêche claire pour être
       lu *sur* le chocolat, ce qui est l'inverse du besoin d'un fond. */
    --bouton-fond: #BC4C2B;
    --bouton-inactif-fond: #3C3027; --bouton-inactif-texte: #B0A08D;
    --bouton-second-texte: #B0A08D;
    /* ⚠️ **#E0785F rendait 4,49:1 sur la carte du carnet — mesuré au banc
       du 21/09 (03d-1), sur sept entrées « Supprimer… ».** La carte ivoire
       du carnet sombre (#372D25) est plus claire que la carte des écrans
       (#2A211A), où le même rouge tenait 5,28:1 : le passage des menus au
       papier a mangé le centième qui manquait. Ce ton-ci rend **5,10:1**
       sur la carte du carnet, 5,87 sur son papier et 5,99 sur la carte des
       écrans — il améliore les trois.
       ⛔ **Et `--danger-fond` ne bouge pas** : c'est un rouge de
       remplissage, mesuré avec son encre brune (5,47:1 / 5,16:1). Les deux
       portaient la même valeur par coïncidence ; les confondre aurait
       déplacé un bouton pour corriger un mot. */
    --danger-texte: #E68669;
    --bouton-valide-fond: #6A7854;
    /* Le carnet en sombre — DA01. Papier brun, cartes un ton au-dessus, filet
       chaud. Le papier est plus foncé que le #302820 du cadre pour que le fond
       du bouton principal garde ses 3:1 (voir le bloc clair). */
    --carnet-papier: #2B231C; --carnet-papier-bas: #262019;
    --carnet-carte: #372D25; --carnet-filet: #5B4A3E; --carnet-filet-vif: #7A6453;
    --carnet-encre: #FFF3E4; --carnet-encre-douce: #D8C4AD;
    --carnet-sauge: #3F4A2E; --carnet-sauge-trait: #D4E2B5;
    --carnet-sauge-filet: #6D7F4E;
    --carnet-ombre: 0 14px 34px rgba(0, 0, 0, .45);
    /* ⚠️ **Un fond clair porte une encre foncée**, jamais le blanc du thème
       clair : c'est la règle du cadre pour le sombre, et c'est ce que le
       calcul dit — #FFFFFF sur cette pêche ne rendrait que 1,69:1. */
    --danger-fond: #E0785F; --danger-fond-texte: #2E1B16;
    --shadow: 0 10px 30px rgba(0, 0, 0, .32);
  }
}
:root[data-theme="dark"] {
  --cream: #1F1813; --card: #2A211A; --espresso: #F3EADD;
  --muted: #B0A08D; --line: #3C3027; --sand: #6B5B45;
  --terra: #D9743F; --sage: #93A278; --bleu: #7FA9CE;
  /* ⚠️ **Le brun profond manquait à la palette sombre.** Mesuré le 10/09 :
     « Explorer les recettes » rendait **1,71** de contraste sur le bandeau de
     découverte — un brun foncé sur du chocolat ne se lit pas. Il n'était pas
     redéfini ici alors que la terre, la sauge et le bleu l'étaient : le trou ne
     s'était pas vu parce qu'aucun texte de cette couleur n'occupait encore une
     place où on le cherche. */
  --cinnamon: #E0A276;
  --coeur: #D9743F; --accueil-fond: #1F1813;
  --idee-fond: #3B4029; --idee-texte: #D9CFB9; --idee-lien: #E0A276;
  --barre: #2A211A;
  /* R05 : le bouton reste terre cuite, mais un ton qui porte du blanc. */
  --bouton-fond: #BC4C2B;
  --bouton-inactif-fond: #3C3027; --bouton-inactif-texte: #B0A08D;
  --bouton-second-texte: #B0A08D;
    /* ⚠️ **#E0785F rendait 4,49:1 sur la carte du carnet — mesuré au banc
       du 21/09 (03d-1), sur sept entrées « Supprimer… ».** La carte ivoire
       du carnet sombre (#372D25) est plus claire que la carte des écrans
       (#2A211A), où le même rouge tenait 5,28:1 : le passage des menus au
       papier a mangé le centième qui manquait. Ce ton-ci rend **5,10:1**
       sur la carte du carnet, 5,87 sur son papier et 5,99 sur la carte des
       écrans — il améliore les trois.
       ⛔ **Et `--danger-fond` ne bouge pas** : c'est un rouge de
       remplissage, mesuré avec son encre brune (5,47:1 / 5,16:1). Les deux
       portaient la même valeur par coïncidence ; les confondre aurait
       déplacé un bouton pour corriger un mot. */
    --danger-texte: #E68669;
    --bouton-valide-fond: #6A7854;
  /* Le carnet en sombre — DA01. Papier brun, cartes un ton au-dessus, filet
     chaud. Le papier est plus foncé que le #302820 du cadre pour que le fond
     du bouton principal garde ses 3:1 (voir le bloc clair). */
  --carnet-papier: #2B231C; --carnet-papier-bas: #262019;
  --carnet-carte: #372D25; --carnet-filet: #5B4A3E; --carnet-filet-vif: #7A6453;
  --carnet-encre: #FFF3E4; --carnet-encre-douce: #D8C4AD;
  --carnet-sauge: #3F4A2E; --carnet-sauge-trait: #D4E2B5;
  --carnet-sauge-filet: #6D7F4E;
  --carnet-ombre: 0 14px 34px rgba(0, 0, 0, .45);
  /* ⚠️ **Un fond clair porte une encre foncée**, jamais le blanc du thème
     clair : c'est la règle du cadre pour le sombre, et c'est ce que le
     calcul dit — #FFFFFF sur cette pêche ne rendrait que 1,69:1. */
  --danger-fond: #E0785F; --danger-fond-texte: #2E1B16;
  --shadow: 0 10px 30px rgba(0, 0, 0, .32);
}

* { box-sizing: border-box; }

/* `display: flex` ou `grid` dans une règle de classe l'emporte sur la valeur
   par défaut de l'attribut `hidden`, qui n'est qu'un `display: none` de la
   feuille de style du navigateur. Sans ce garde-fou, tout élément masqué en
   JS mais mis en flex reste visible — c'est ce qui affichait « Filtres : »
   sans aucun filtre actif. */
[hidden] { display: none !important; }

/* ⚠️ **Sur l'accueil, le fond de la page est blanc — 12/09.** Safari sur iOS
   récent ignore `theme-color` et peint la zone de l'heure et de la batterie
   avec le fond du document ; c'est aussi lui qu'on voit en tirant l'écran vers
   le bas. Le haut du bandeau d'accueil étant blanc, le crème y dessinait une
   bande. La classe est posée par `barreEtat`, et chaque page du carrousel porte
   son propre fond pour que ce blanc ne se voie nulle part ailleurs. */
html.sur-accueil, html.sur-accueil body { background: #FFFFFF; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--espresso);
  font-family: var(--ecriture);
  font-size: var(--t-champ);
  font-weight: var(--g-corps);
  /* ⚠️ **1,55 devenait 1,55 × 1,5 — R12.** L'interligne est un multiple de la
     taille : la remise à l'échelle de la fonte l'aurait multiplié lui aussi, et
     chaque paragraphe aurait pris deux fois la hauteur d'avant. Caveat monte et
     descend beaucoup, mais son encre tient dans 0,92 em : 1,3 lui laisse de
     l'air et rend à un paragraphe une hauteur proche de celle de l'ancienne
     linéale à 1,55. */
  line-height: var(--lh-corps);
  -webkit-font-smoothing: antialiased;
  /* Deuxième des quatre verrous d'échelle (voir la balise `viewport`), et il en
     fait deux à lui seul.

     `pan-x pan-y` autorise exactement les deux gestes dont l'app a besoin — le
     défilement vertical d'une page, le glissement horizontal du carrousel — et
     **rien d'autre** : ni pincement, ni double-tap pour zoomer, puisque ce
     dernier n'est conservé que pour `auto` et `manipulation`.

     Et non `manipulation`, qui laisse passer le pincement : il fallait alors le
     refuser en JavaScript, par un écouteur `touchmove` non passif sur
     `document` — c'est-à-dire en s'interposant à chaque déplacement de doigt, ce
     qui faisait accrocher le défilement de toute l'application. La règle CSS
     obtient le même verrou sans jamais se mettre sur le chemin du geste. */
  touch-action: pan-x pan-y;
}

/* Le zoom d'un onglet en arrière-plan, ou un iPhone tourné en paysage, fait
   grossir les textes longs de son propre chef. Sur une coque à hauteur fixe,
   ça déborde toutes les barres. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* Le défilement quitte la fenêtre pour descendre dans chaque page. C'est ce
   qui permet au carrousel de faire glisser deux pages côte à côte : tant que
   c'était la fenêtre qui défilait, on ne pouvait pas en montrer deux à la fois
   sans perdre la position de lecture de la première. */
html, body { height: 100%; }
body { overflow: hidden; }

#app {
  max-width: var(--app-w);
  margin: 0 auto;
  height: 100vh;
  height: 100dvh;                 /* barres d'adresse mobiles comprises */
  display: flex; flex-direction: column; overflow: hidden;
  /* M01, 21/09 — la bande « Pas de connexion » **pousse** la colonne au lieu
     de la couvrir : posée par-dessus, elle interceptait les quatre boutons de
     la couverture d'une fiche (banc des menus, D1). `--hors-reseau-h` vaut la
     hauteur mesurée de la bande tant qu'elle paraît, rien sinon
     (`peindreHorsReseau`). */
  padding-top: var(--hors-reseau-h, 0px);
}
.screen[hidden] { display: none; }

/* ── Recadrage des couches fixes ─────────────────────────────────────────────

   `position: fixed` se calcule sur la **fenêtre**, jamais sur l'ancêtre : une
   couche posée comme ça ignore le `max-width` de `#app` et s'étale sur tout
   l'écran. La barre d'onglets faisait 1280 px de large devant une colonne de
   720, ses cinq cellules réparties sur la fenêtre entière : l'accueil se
   retrouvait à gauche du contenu, le profil à droite, et seul le ➕ tombait
   encore en face de quelque chose. Le mode cuisine et le scan d'assiette, eux,
   couvraient toute la page pendant que le reste de l'app restait en colonne.

   Un seul recadrage, appliqué partout où il manquait. Les fonds de feuille en
   sont exclus à dessein : un voile ne couvre pas une colonne, il couvre
   l'écran, sinon on voit la page vive de chaque côté. */
/* Le bandeau de mise à jour. Fixe, donc **recadré** comme les autres couches
   fixes : sans ça il ferait 1280 px de large devant une colonne de 720 (§ 2).
   Posé en haut parce que le bas appartient à la barre d'onglets, et discret —
   c'est une information, pas une alerte : rien n'est cassé, la page est
   seulement en retard. */
.maj-dispo {
  /* Au-dessus de **tout**, y compris de l'écran de connexion et des feuilles :
     c'est la seule couche qui ne parle pas du contenu mais de l'application
     elle-même. Vu à la vérification — posée à 60 comme `.auth-wrap`, elle
     passait dessous et n'existait que dans le DOM. */
  position: fixed; top: 0; z-index: 70;
  border: none; border-radius: 0 0 12px 12px;
  padding: 9px 14px; font: inherit; font-size: 19.5px;
  background: var(--espresso); color: var(--cream);
  box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
  cursor: pointer;
}
/* ⚠️ **Sauf sous une fenêtre ouverte — revue du 21/09.** `isolerLeFond` la
   neutralise (`inert`) comme le reste du fond, et c'est voulu : recharger la
   page au milieu d'une saisie emporterait la saisie. Mais à z-index 70 elle
   restait peinte **au-dessus** du voile, parfaitement lisible et parfaitement
   morte — la pire des deux moitiés. Le `[inert]` que pose le socle sert ici de
   signal : elle repasse sous le voile (50) et sous la feuille (51). ⛔ Sans
   `display: none` : une bande qui disparaît puis revient à la fermeture est
   plus déroutante qu'une bande atténuée derrière le voile. */
.maj-dispo[inert] { z-index: 40; }

/* La bande « pas de connexion » — P40, 20/09.

   Même bord et même rang que `.maj-dispo`, et c'est voulu : les deux parlent de
   l'application elle-même, pas du contenu. ⛔ **Elles ne s'affichent jamais
   ensemble** — le § *deux couches fixes au même bord se recouvrent* se règle
   ici par le code plutôt que par un décalage : « toucher pour recharger » hors
   ligne mènerait à la page de repli, donc ferait perdre l'application. C'est
   `peindreHorsReseau` qui tranche.

   ⚠️ **Pas de `position: sticky` dans `#app`** : la bande doit couvrir aussi
   l'écran de connexion et les feuilles, comme sa voisine. */
.hors-reseau {
  position: fixed; top: 0; z-index: 70;
  border-radius: 0 0 12px 12px;
  padding: 9px 14px; font-size: 19.5px; text-align: center;
  background: var(--espresso); color: var(--cream);
  box-shadow: 0 2px 10px rgb(0 0 0 / 18%);
}

/* ⚠️ **Et le centre n'est pas toujours celui de la fenêtre.** Dès qu'un rail de
   navigation occupe le bord gauche (§ « tablette et bureau »), la colonne de
   contenu n'est plus centrée sur l'écran mais sur ce qu'il en reste : une couche
   recadrée sur 50 % passerait à moitié sous le rail. `--rail-w` vaut zéro tant
   que le rail n'existe pas, donc la formule est **la même** sur téléphone — et
   c'est tout l'intérêt : une seconde règle pour le grand écran aurait oublié la
   couche suivante. */
.tabbar, .plate-screen, .cook-screen, .maj-dispo, .fs-panier, .hors-reseau {
  left: calc(50% + var(--rail-w) / 2); right: auto;
  transform: translateX(-50%);
  width: min(var(--app-w), calc(100% - var(--rail-w)));
}

/* ── Carrousel des onglets ───────────────────────────────────────────────── */

/* L'aimantation est native : le doigt entraîne la page en temps réel, le
   navigateur gère l'inertie, le rebond aux extrémités et la fluidité. Aucun
   JavaScript ne s'interpose — c'est ce qui fait la différence entre « ça bascule
   après coup » et « je pousse une carte ». */
#pager {
  flex: 1; min-height: 0;
  display: flex;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  /* Sans ça, arriver au bout du carrousel déclencherait le geste de retour du
     navigateur ou le rebond de la page. */
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Indispensable, et pas seulement cosmétique : sans lui, Chrome mobile
     laisse le débordement horizontal des pages voisines élargir la fenêtre de
     mise en page — 977 px au lieu de 390 — puis **dézoome toute l'interface**
     pour la faire tenir. Tout l'écran rapetissait d'un facteur 2,5. Un
     `overflow: hidden` sur `html` ou `body` n'y change rien ; c'est le
     confinement de peinture qui empêche la propagation. */
  contain: paint;
}
#pager::-webkit-scrollbar { display: none; }

.pane {
  flex: 0 0 100%; width: 100%; min-width: 0;
  background: var(--cream);
  scroll-snap-align: start;
  /* Un élan ne saute pas trois onglets : on avance d'une page à la fois. */
  scroll-snap-stop: always;
  /* `clip` et non `hidden`, et ce mot fait toute la différence : `hidden`
     crée malgré tout un conteneur de défilement horizontal, qui absorbe le
     geste et ne le transmet jamais au carrousel — la page ne bougeait pas d'un
     pixel. `clip` découpe sans devenir scrollable. Et on ne peut pas laisser
     l'axe en `visible` : accolé à un `auto` vertical, il se calcule en `auto`.
     Les trois valeurs se ressemblent, une seule fonctionne. */
  overflow-y: auto; overflow-x: clip;
  overscroll-behavior-y: contain;
  padding: 28px 20px calc(var(--tabbar-h) + 32px);
}

/* Les écrans de détail vivent hors du carrousel et le recouvrent. */
.screen:not(.pane) {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 28px 20px calc(var(--tabbar-h) + 32px);
}

h1, h2, .kcal-value { font-family: var(--ecriture); font-weight: 400; }

/* Cibles tactiles ≥ 44 px et focus visible — contrainte AA du § 11. */
button, input, textarea, select { font: inherit; }
button { min-height: 44px; }

/* ── La zone tactile, quand le dessin est plus petit qu'elle ─────────────────

   La ligne au-dessus est la contrainte AA du § 11, et quatre familles la
   réécrivent **à dessein** : une puce de filtre à 44 px de haut ferait des
   rangées deux fois plus épaisses, et un bouton-icône rond de 44 px écrase les
   en-têtes. Le dessin a raison ; ce qui a tort est que la **cible** lui soit
   égale — le doigt ne vise pas ce qu'il voit, il vise à peu près.

   D'où ce pseudo-élément : centré, transparent, il porte les 44 px que le doigt
   cherche **sans rien changer à la mise en page**, puisqu'il est hors du flux.
   Une pastille de 38 px se touche comme si elle en faisait 44, et l'écran est
   pixel pour pixel le même.

   Relevé au viewport iPhone 14, tous écrans démasqués : **119 cibles sous 44 px,
   dont 85 dans ces familles** — boutons-icônes (36 et 40 px), puces (38 et 40),
   avatar (42), boutons ronds (42), barre du plein écran (38), et les trois
   commandes posées sur la photo de fiche (40).

   ⛔ **Les liens de texte en sont exclus, et c'est voulu.** `.link-btn` porte
   `min-height: 0` depuis le § 8 : un lien posé dans une phrase n'est pas un
   bouton, et lui donner 44 px de zone lui ferait avaler les mots voisins — donc
   leur voler le tap. Ce que le § 8 interdit est l'inverse : poser en `.link-btn`
   une commande qui est un vrai bouton, comme la porte vers la pellicule l'a été.

   ⛔ **Et `.info-dot` non plus**, pour la même raison en pire : 20 px au fil du
   texte, sa zone déborderait de 12 px de chaque côté sur des mots cliquables. */
.icon-btn, .chip, .avatar-btn, .btn-round, .fs-bar-btn, .shop-more { position: relative; }

/* ⚠️ `.hero-back`, `.hero-fav` et `.hero-menu` sont **déjà** `position: absolute`
   sur la photo : leur poser `relative` les décrocherait de leur coin. Un élément
   absolu est déjà le repère de ses enfants, ils n'ont donc rien à recevoir. */
/* ── Le second lot, 22/08 ──────────────────────────────────────────────────
   Relevé au banc, viewport iPhone 14, tous écrans démasqués : 61 cibles sous
   44 px, dont **42 liens de texte** — exclus depuis le 19/08 et toujours, parce
   qu'une zone de 44 px autour d'un lien dans une phrase volerait le tap des mots
   voisins. Restaient 19 cibles réparties en sept familles ; les quatre traitées
   ici sont celles dont la zone déborde de **1 à 4 px seulement**, donc sans
   risque pour ce qui les entoure :

   | | Avant | Ce qui manquait |
   |---|---|---|
   | `.stepper button` (± portions, ×8) | 40×40 | 4 px |
   | `.segmented button` (×2) | 168×40 | 4 px de haut |
   | `.field-eye` | 36×44 | 8 px de large |
   | `#steps-more` | 42×44 | 2 px de large |

   **`.info-dot` (20×20, ×3) y est finalement**, et sur sa structure plutôt que
   sur une mesure au banc — les trois écrans qui la portent demandent un état
   qu'un compte jetable n'a pas (un objectif posé, une fiche calculée), et la
   mesure faite en démasquant tous les écrans à la fois ne vaut rien : elle
   superpose des écrans qui ne coexistent jamais. Ce qui tranche est que ses
   voisins **ne sont pas des cibles** : la pastille est frère d'un `<span>` de
   texte dans `.today-goal-line`, dans `.kcal-line`, ou dans un `<h3>`. Le risque
   nommé le 19/08 — « voler le tap des mots voisins » — vise un lien **dans une
   phrase**, pas une pastille en bout de ligne à côté de texte inerte.

   ⛔ **`.precision` (20×6) reste dehors, et c'est un refus assumé.** C'est une
   **bande**, pas un bouton : 6 px de haut, et une zone de 44 px déborderait de
   19 px au-dessus, c'est-à-dire sur la ligne des kcal et sa propre pastille
   `#kcal-detail`. Deux zones qui se recouvrent se volent le tap l'une à l'autre
   — le défaut qu'on cherche à éviter, obtenu en le corrigeant.

   ⛔ **Et les trois `<input>` non plus** — deux `.interrupteur` et `#veg-switch`.
   Un pseudo-élément ne s'applique pas à un élément **remplacé** : la règle
   passerait au vert dans la feuille et ne peindrait rien. */
.icon-btn::after, .chip::after, .avatar-btn::after, .btn-round::after,
.fs-bar-btn::after, .hero-back::after, .hero-fav::after, .hero-menu::after,
.hero-collect::after, .hero-share::after, .recipe-temps-plus::after,
.stepper button::after, .segmented button::after, .field-eye::after,
#steps-more::after, .info-dot::after, .cook-etape-lien::after, .plan-retirer::after,
.shop-more::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: max(100%, 44px); height: max(100%, 44px);
}

/* Troisième verrou d'échelle, et le seul qui traite la cause la plus fréquente.
   **iOS agrandit la page dès qu'on met au point un champ dont le texte fait
   moins de 16 px**, puis la laisse zoomée *et décalée* quand le clavier se
   referme — c'est le décentrage qu'on voyait sans comprendre d'où il venait.
   Ni `user-scalable=no` ni `touch-action` n'y changent quoi que ce soit : le
   seuil est sur la taille du texte, et rien d'autre.

   D'où ce plancher, qui vaut pour tous les champs de l'app. Les règles qui
   descendaient à 14 ou 15 px ont été remontées une par une plutôt que forcées
   ici : une seule déclaration de trop et les rangées de relecture, qui sont la
   grille la plus serrée de l'app, se seraient mises à déborder en silence. */
input, textarea, select { font-size: 24px; }
:focus-visible { outline: 2.5px solid var(--terra); outline-offset: 2px; border-radius: 8px; }

.hint { color: var(--muted); font-size: 20px; margin: 0 0 12px; }
.form-error { margin: 12px 2px 0; color: var(--danger); font-size: 21px; }

.link-btn {
  border: none; background: none; color: var(--cinnamon);
  cursor: pointer; padding: 4px 2px; font-size: 21px; min-height: 0;
}

.btn-primary {
  border: none; border-radius: 14px; cursor: pointer;
  background: var(--bouton-fond); color: var(--bouton-texte);
  padding: 11px 20px; font-weight: 600;
}
/* ⚠️ **Grisé, pas transparent** — R05. `opacity: .55` délavait le libellé
   *et* le fond dans la page : le blanc tombait à 2,3:1 sur le terre cuite
   éclairci, et le bouton « Analyser » en cours d'envoi devenait illisible au
   moment précis où il dit ce qu'il fait. Le couple inactif est franchement
   plus pâle que le bouton actif — on voit d'un coup d'œil qu'il ne répond
   pas — et il se lit encore. `cursor: progress` reste : ces boutons-là sont
   désactivés le temps d'un envoi. */
.btn-primary:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  cursor: progress;
}

.btn-ghost {
  border: 1.5px solid var(--line); background: transparent;
  color: var(--bouton-second-texte);
  border-radius: 999px; padding: 9px 20px; cursor: pointer;
  /* Certains porteurs de cette classe sont des liens et non des boutons —
     l'accès à l'administration en est un, parce qu'il change de page. Sans ces
     trois lignes, il sort souligné et plus bas que ses voisins, et se lit comme
     un objet d'une autre nature alors qu'il fait le même geste. */
  text-decoration: none; display: inline-flex; align-items: center;
  justify-content: center; min-height: 44px;
}
/* ⚠️ **Le même refus de l'opacité pour les commandes sans fond — revue du
   21/09.** Elle restait en sept endroits (`opacity: .35` à `.55`), et une encre
   délavée n'est pas mesurable comme un couple : à .35 le libellé d'un bouton de
   navigation du mode cuisine tombait sous 2:1 sur le crème. `--bouton-inactif-texte`
   est franchement plus pâle que l'encre active tout en restant lisible ; c'est
   le filet, lui, qui dit que la commande ne répond pas. */
.btn-ghost:disabled, .cook-nav .btn-ghost:disabled,
.chip:disabled, .sheet-item:disabled, .link-btn:disabled,
.identite-ligne .link-btn:disabled, .av-outil-go:disabled,
#screen-bloques .bloque-debloquer:disabled {
  color: var(--bouton-inactif-texte);
  border-color: var(--line);
  background: var(--bouton-inactif-fond);
}
/* Une ligne de feuille et un lien nus n'ont pas de fond au repos : leur donner
   le fond inactif dessinerait une pastille là où il n'y a jamais eu de forme. */
.sheet-item:disabled, .link-btn:disabled, .identite-ligne .link-btn:disabled {
  background: none;
}

/* ── Authentification — P38, 19/09 ──────────────────────────────────────────
   Une couche fixe (§ 2 : elle est dans `PLEIN_ECRAN_ASSUME`, c'est un écran
   entier sans `#app`), qui porte deux choses : le welcome, et la carte des
   états. Une seule couche, une seule règle de recadrage : ce qui est ici ne
   se recopie pas pour le second écran. */
.auth-wrap {
  /* ⚠️ **Une surface claire dans les deux thèmes**, donc ses jetons d'encre
     aussi : sans cette ligne, `--bouton-second-texte` passait au beige clair en
     thème sombre et « Demander un nouveau lien » rendait 2,54:1 sur du blanc
     (R05, 21/09). Les fonds de la carte sont déjà écrits en dur ici. */
  --bouton-second-texte: #6B5B45; --bouton-fond: #A94A2A; --bouton-texte: #FFFFFF;
  /* ⚠️ **Et l'état désactivé avec eux — revue du 21/09.** Ils manquaient à cette
     portée : en thème sombre, « Se connecter » encore grisé devenait une
     pastille presque noire (#3C3027) sur la carte crème de P38, c'est-à-dire
     l'inverse exact de ce que la surface claire impose. */
  --bouton-inactif-fond: #E7DCCA; --bouton-inactif-texte: #6B5B45;
  /* 80 : au-dessus des bandeaux (60) et de tout empilement de feuilles — c'est
     un écran entier, rien de l'application ne doit paraître par-dessus. */
  position: fixed; inset: 0; z-index: 80; overflow-y: auto;
  display: flex; flex-direction: column;
  /* Blanc sur téléphone, comme la carte qui remplit tout — ce qui dépasse sous
     la barre d'état (`viewport-fit=cover`) doit être de la même couleur
     qu'elle. Le crème ne sert qu'autour d'une carte centrée, sur large écran. */
  background: var(--card);
  /* ⚠️ **L'accès reste clair, quel que soit le thème** — Luca, 19/09, en
     sombre : « tu veux bien remettre le reste de la page en blanc ». Le welcome
     est une photo claire et la carte s'accorde à sa barre d'état blanche ; les
     jetons du thème clair sont donc reposés ici, et tout ce qui vit dans la
     couche les hérite — pas de seconde feuille de règles pour le sombre. */
  --cream: #F7ECDF; --card: #FFFFFF; --espresso: #2E241D; --muted: #8C7F70;
  --line: #E7DCCA; --sand: #DCC9AE; --terra: #C65A2E; --cinnamon: #A94A2A;
  --coeur: #A94A2A; --danger: #A6412F;
  color: var(--espresso);
  color-scheme: light;
  /* Un écran d'accès est **figé** (Luca, 19/09 : « ça devrait pas être
     scrollable de bas en haut ») : rien ne rebondit quand tout tient, et il ne
     défile que là où il ne tient pas (un iPhone SE sur l'inscription). */
  overscroll-behavior: contain;
}

/* ── Le welcome — le mockup « welcome screen P38 » ─────────────────────────
   Le fond est **la page** (§ *une image qui porte une disposition doit être
   la page*) : bord à bord, `cover`, ancré en bas pour garder la table et le
   carnet quand l'écran est court — c'est le haut, le mur nu, qui se coupe.
   La casserole et le logo se posent sur le mur ; la nappe crème du bas porte
   les points et les deux boutons, comme sur le mockup. */
.welcome {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  /* ⚠️ **iOS peint la barre d'état avec le bord haut de la page tel qu'il
     était au chargement** — mesuré sur les captures de Luca le 19/09 : la bande
     faisait exactement (244, 230, 217), le mur de la photo, et elle restait
     beige au-dessus d'une carte blanche, `theme-color` blanc compris. Le fond
     commence donc **sous** la zone sûre, sur un blanc qui est aussi celui de la
     carte : ce que la barre échantillonne est blanc, sur tous les écrans de la
     couche. */
  padding-top: env(safe-area-inset-top, 0px);
  background-color: #FFFFFF;
  background-image: url("/static/img/welcome-fond.webp");
  background-position: center bottom; background-size: cover; background-repeat: no-repeat;
  background-origin: content-box; background-clip: content-box;
  /* ⚠️ Le fond est une photo claire quelle que soit la préférence de thème :
     ce qui se pose dessus garde ses couleurs fixes, sinon le texte du thème
     sombre disparaît (vu au banc sur la pilule « Passer » de P29). */
  color: #2E241D;
}
.welcome-defile {
  flex: 1; min-height: 0; display: flex;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
  /* Glisser, dans les deux sens — une page défile en hauteur sur un écran
     court — et jamais pincer (§ 1). */
  touch-action: pan-x pan-y;
}
.welcome-defile::-webkit-scrollbar { display: none; }
@media (prefers-reduced-motion: reduce) { .welcome-defile { scroll-behavior: auto; } }
.welcome-diapos-pages { display: contents; }
.welcome-diapo {
  flex: none; width: 100%; scroll-snap-align: start; scroll-snap-stop: always;
  display: flex; flex-direction: column; align-items: center;
  padding: 18px 20px 12px;
  overflow-y: auto;
}
/* La marque : la casserole détourée au-dessus du logo détouré, centrés sur le
   mur. Un seul nombre commande la scène — la largeur du logo —, la casserole
   s'en déduit. Ni l'un ni l'autre n'est redessiné. */
.welcome-marque {
  --logo-w: min(72vw, 300px);
  display: flex; flex-direction: column; align-items: center; gap: 0;
  margin-top: 2vh;
}
.welcome-casserole { width: calc(var(--logo-w) * .58); height: auto; display: block; margin-bottom: -6px; }
.welcome-logo { width: var(--logo-w); height: auto; display: block; }

/* Les pages de présentation : une carte crème translucide sur la scène —
   opaque à 94 %, parce que ce qui se pose sur une photo redevient opaque,
   jamais translucide (§ *un décor qui porte quelque chose*). Le titre serif
   et les quatre cartes sont ceux de P29. */
.welcome-page-carte {
  width: 100%; max-width: 440px; margin-top: 8px;
  padding: 18px 16px 16px; border-radius: 22px;
  background: rgb(255 252 246 / 94%);
  box-shadow: 0 8px 30px rgb(46 36 29 / 12%);
}
#auth-welcome .welcome-titre {
  margin: 0 0 6px; text-align: center;
  font-family: var(--ecriture); font-weight: 400; font-size: 32.5px; line-height: 1.1; color: #2E241D;
}
#auth-welcome .welcome-sous { margin: 0 0 6px; text-align: center; font-size: 24.5px; color: #2E241D; }
#auth-welcome .welcome-lead { margin: 0 auto 14px; text-align: center; max-width: 38ch; font-size: 22px; color: #6F6257; }

/* La nappe du bas : coins très arrondis en haut, comme le mockup, les deux
   boutons pleine largeur, 52 px chacun. Elle **réserve** sa place dans la
   colonne — rien ne vit dessous. */
.welcome-pied {
  flex: none; position: relative; z-index: 1;
  padding: 10px 24px calc(22px + env(safe-area-inset-bottom, 0px));
  border-radius: 40px 40px 0 0;
  background: #FFFCF6;
  box-shadow: 0 -10px 40px rgb(30 24 20 / 12%);
  display: flex; flex-direction: column; gap: 10px;
}
.welcome-pied .welcome-points {
  list-style: none; display: flex; justify-content: center; gap: 2px; margin: 0; padding: 0;
}
.welcome-pied .welcome-points button {
  width: 30px; height: 44px; padding: 0; border: none; background: none; cursor: pointer;
  display: grid; place-items: center;
}
.welcome-pied .welcome-points button::after {
  content: ""; width: 8px; height: 8px; border-radius: 50%; background: #DCC9AE;
}
.welcome-pied .welcome-points button.on::after { width: 22px; border-radius: 999px; background: #C65A2E; }
.welcome-pied .btn-primary, .welcome-pied .btn-ghost {
  width: 100%; min-height: 52px; font-size: 24.5px; font-weight: 600; border-radius: 999px;
}
/* ⚠️ **Écrit en dur, parce que l'écran d'entrée ne suit pas le thème** : il
   porte sa nappe claire et ses encres brunes dans les deux cas. `#C65A2E` y
   rendait 4,28:1 sous du blanc — la terre cuite du logo est un fond d'accent,
   pas un fond de bouton (R05). Le brun profond du même logo rend 5,68:1. */
.welcome-pied .btn-primary { background: #A94A2A; color: #fff; }
.welcome-pied .btn-ghost { background: #FFFCF6; color: #A94A2A; border-color: #E7DCCA; }

/* Sur un écran large, le welcome devient une carte centrée sur le fond : la
   scène ne s'étire pas sur 1 440 px (§ 10 — on borne l'objet, pas la page). */
@media (min-width: 820px) {
  .welcome {
    inset: auto; position: relative; margin: auto;
    width: 430px; height: min(900px, calc(100vh - 40px));
    border-radius: 28px; overflow: hidden;
    box-shadow: 0 20px 60px rgb(30 24 20 / 18%);
  }
  .auth-wrap:has(#auth-welcome:not([hidden])) { justify-content: center; padding: 20px 0; }
}

/* ── La carte des états P38-2 à P38-8 ──────────────────────────────────────
   Pleine hauteur sur téléphone, le chevron en haut à gauche, le logo, le
   titre serif, la phrase, le formulaire. Sur large écran elle redevient une
   carte. */
.auth-card {
  width: min(430px, 100%); margin: 0 auto; flex: 1;
  background: var(--card);
  padding: calc(10px + env(safe-area-inset-top, 0px)) 26px max(16px, env(safe-area-inset-bottom, 0px));
  text-align: center; position: relative;
}
@media (min-width: 560px) {
  /* Sur large écran la carte redevient une carte, pas une feuille collée bas. */
  .auth-wrap { justify-content: center; padding: 40px 0; background: var(--cream); }
  .auth-card { flex: none; border-radius: var(--radius); padding-top: 18px; box-shadow: var(--shadow); }
}
.auth-retour { display: block; margin: 0 0 2px -4px; }

/* Le logo est un WebP à fond transparent : il se pose sur le blanc du thème
   clair comme sur le brun du thème sombre sans avoir deux fichiers. */
.auth-wordmark { width: 196px; height: auto; margin: 0 auto 8px; display: block; }
.auth-titre {
  margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400;
  font-size: 32.5px; line-height: 1.1; color: var(--espresso);
}
.auth-sub { color: var(--muted); margin: 0 0 16px; font-size: 22.5px; line-height: 1.45; }
.auth-enveloppe { display: block; width: 132px; height: auto; margin: 8px auto 14px; }
[data-etat="sent"] .auth-titre, [data-etat="sentNoMail"] .auth-titre { margin-top: 4px; }

/* Inscriptions fermées (P38-8) : le message dans un panneau, sous le titre. */
.auth-fermees { margin: 6px 0 4px; }
.auth-fermees-texte {
  margin: 0; padding: 12px 14px; border-radius: 14px;
  background: var(--cream); border: 1px solid var(--line);
  color: var(--espresso); font-size: 22.5px; line-height: 1.5;
}

/* La règle du mot de passe, avec sa coche : vide tant qu'elle n'est pas
   tenue, verte ensuite — P38-6. Le texte reste, seule la coche change. */
.auth-regles {
  list-style: none; margin: -2px 0 0; padding: 0 4px; text-align: left;
  color: var(--muted); font-size: 20px; display: grid; gap: 4px;
}
.auth-regles li { display: flex; align-items: center; gap: 8px; }
.auth-regle-coche {
  flex: none; width: 16px; height: 16px; border-radius: 50%;
  border: 1.5px solid var(--sand); display: grid; place-items: center;
}
.auth-regles li.ok { color: var(--espresso); }
.auth-regles li.ok .auth-regle-coche { border-color: var(--sage); background: var(--sage); }
.auth-regles li.ok .auth-regle-coche::after {
  content: ""; width: 8px; height: 4px; border: 2px solid #fff; border-top: none; border-right: none;
  transform: translateY(-1px) rotate(-45deg);
}

/* L'erreur sous les champs (P38-7) : rouge, avec sa pastille « ! », et les
   champs fautifs bordés de la même couleur. */
.auth-error {
  display: flex; align-items: flex-start; gap: 8px; margin: 2px 2px 0; text-align: left;
  font-size: 21px; line-height: 1.4;
}
.auth-error::before {
  content: "!"; flex: none; width: 18px; height: 18px; border-radius: 50%; margin-top: 1px;
  background: var(--danger); color: #fff; font-size: 19px; font-weight: 700;
  display: grid; place-items: center;
}
.field.field-erreur input { border-color: var(--danger); }
.field.field-erreur .field-icon { stroke: var(--danger); }
.auth-relien { display: flex; width: 100%; margin-top: 12px; }
.auth-vers-accueil { display: flex; width: 100%; margin-top: 18px; }
.auth-vers-connexion { display: block; margin: 14px auto 0; min-height: 44px; font-size: 22.5px; font-weight: 600; }

/* ── Champs ──────────────────────────────────────────────────────────────── */

.auth-card form { display: grid; gap: 10px; }

.field { position: relative; display: flex; align-items: center; }
.field-icon {
  position: absolute; left: 15px; width: 20px; height: 20px;
  stroke: var(--muted); fill: none; pointer-events: none;
}
.field input {
  width: 100%;
  border: 1px solid var(--line); border-radius: 14px;
  /* 44px à gauche pour l'icône, 46px à droite pour l'œil. */
  padding: 13px 46px 13px 44px;
  background: var(--card); color: inherit; min-height: 52px; font-size: 24px;
}
.field input:focus {
  outline: none; border-color: var(--terra);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 18%, transparent);
}
.field-eye {
  position: absolute; right: 8px; display: grid; place-items: center;
  width: 36px; height: 36px; border: none; background: none; cursor: pointer;
  border-radius: 10px; color: var(--muted);
}
.field-eye svg { width: 20px; height: 20px; stroke: currentColor; fill: none; }
.field-eye:hover { color: var(--espresso); }

.auth-card .btn-primary { margin-top: 4px; min-height: 52px; font-size: 24px; }

/* ── Liens sous le formulaire ────────────────────────────────────────────── */

.auth-switch { margin: 12px 0 0; font-size: 22px; color: var(--muted); }
.auth-switch .link-btn { font-weight: 600; }
.auth-forgot { display: block; margin: 2px auto 0; min-height: 44px; font-size: 22px; color: var(--cinnamon); font-weight: 600; }

.auth-hint { color: var(--muted); font-size: 19.5px; margin: 14px 0 0; }
/* La ligne qui renvoie à la politique de confidentialité, sous les fournisseurs.
   ⚠️ **13 px, et c'est une exception nommée au § 1** : le plancher de 16 px vise
   les **champs de saisie**, parce qu'iOS agrandit la page à la mise au point.
   Un paragraphe ne se mets pas au point ; ce qui le gouverne est la lisibilité,
   et le lien qu'il contient est une cible tactile — pas une entrée de texte. */
.auth-legal {
  color: var(--muted); font-size: 19.5px; line-height: 1.5;
  margin: 10px 0 0; text-align: center;
}
.auth-legal a { color: var(--muted); text-decoration: underline; }
.auth-note {
  margin: 14px 0 0; padding: 12px 14px; border-radius: 12px;
  background: var(--cream); border: 1px solid var(--line);
  color: var(--muted); font-size: 19.5px; line-height: 1.5; text-align: left;
}
.auth-note strong { color: var(--espresso); }

/* ── Fournisseurs ────────────────────────────────────────────────────────── */

.auth-or {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0 12px; color: var(--muted); font-size: 19.5px;
}
.auth-or::before, .auth-or::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

.provider-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; padding: 13px 14px; border-radius: 14px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--espresso); font-size: 22.5px; font-weight: 600;
  text-decoration: none; cursor: pointer;
}
.provider-btn:hover { background: var(--cream); }
.provider-btn + .provider-btn { margin-top: 8px; }
/* Le logo Google impose ses couleurs officielles : elles ne se dérivent pas de
   la charte, et les altérer contreviendrait à ses règles d'usage. */
.provider-btn svg { flex: none; }

/* Fond noir, texte blanc : variante imposée par les règles d'Apple sur fond
   clair. En thème sombre elle s'inverse, pour rester détachée du fond. */
.provider-apple {
  background: #000; border-color: #000; color: #fff;
}
.provider-apple:hover { background: #1a1a1a; }

/* Deux écritures nécessaires, comme pour les jetons de couleur : la media
   query couvre le réglage « système », le sélecteur couvre le choix explicite.
   `:not([data-theme="light"])` seul s'appliquerait AUSSI en thème clair. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .provider-apple {
    background: #fff; border-color: #fff; color: #000;
  }
  :root:not([data-theme="light"]) .provider-apple:hover { background: #eee; }
}
:root[data-theme="dark"] .provider-apple {
  background: #fff; border-color: #fff; color: #000;
}
:root[data-theme="dark"] .provider-apple:hover { background: #eee; }

/* Sur l'accès, toujours clair (voir `.auth-wrap`) : le bouton Apple garde sa
   variante noire, quelle que soit la préférence de thème. */
.auth-wrap .provider-btn.provider-apple { background: #000; border-color: #000; color: #fff; }
.auth-wrap .provider-btn.provider-apple:hover { background: #1a1a1a; }
.provider-btn.disabled { opacity: .4; cursor: not-allowed; }
.provider-btn.disabled:hover { background: var(--card); }
.provider-apple.disabled:hover { background: #000; }
:root[data-theme="dark"] .provider-apple.disabled:hover { background: #fff; }
.provider-note {
  margin: 8px 2px 0; color: var(--muted); font-size: 19px; text-align: center;
}

/* ── Bandeau éphémère ────────────────────────────────────────────────────── */

/* ⚠️ **Au-dessus des feuilles empilées, quoi qu'elles empilent — 20/09.** La
   profondeur d'une feuille vaut `51 + 2n` : au troisième étage elle atteignait
   55, c'est-à-dire l'égalité avec ce bandeau, que l'ordre du document tranchait
   alors au hasard. Un message d'erreur passé sous la feuille qui l'a provoquée
   n'existe pas. La borne est posée avant qu'un chemin à trois étages existe. */
.toast {
  position: fixed; z-index: 60;
  /* Même recadrage que la feuille (M02) : le message répond à un geste fait
     dans la colonne, pas au milieu de la fenêtre. Sur téléphone `--rail-w`
     vaut zéro, la formule ne change rien. */
  left: calc(50% + var(--rail-w) / 2); transform: translateX(-50%) translateY(20px);
  bottom: calc(var(--tabbar-h) + 16px);
  width: min(420px, calc(100% - 28px));
  background: var(--espresso); color: var(--cream);
  border-radius: 16px; padding: 13px 17px; font-size: 20px; line-height: 1.45;
  box-shadow: 0 12px 34px rgba(30, 24, 20, .3);
  opacity: 0; pointer-events: none; transition: opacity .22s ease, transform .22s ease;
}
.toast.on { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Ranger au frigo depuis le scanner — cahier § 7. Le champ à gauche, le bouton
   à droite : on lit « combien » avant « ajouter », qui est l'ordre de la phrase.
   ⚠️ **16 px sur le champ** (§ 1) — clavier décimal, donc zoom iOS. */
.food-au-frigo { display: flex; align-items: center; gap: 10px; }
.food-au-frigo .btn-ghost { flex: 1; }
.fap-qte {
  display: flex; align-items: center; gap: 6px;
  font-size: 20px; color: var(--muted); flex: none;
}
.fap-qte input {
  width: 56px; font-size: 24px; padding: 10px 6px; text-align: center;
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
}

/* ── Bascule Recettes / Collections ──────────────────────────────────────── */

.segmented {
  display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 4px; margin-bottom: 18px;
}
.segmented button {
  position: relative;   /* repère de la zone de 44 px */
  border: none; background: none; color: var(--muted); cursor: pointer;
  padding: 9px 8px; border-radius: 10px; font-size: 21px; min-height: 40px;
}
.segmented button.on { background: var(--cinnamon); color: #FFFFFF; font-weight: 600; }

/* ── Aujourd'hui — journal ───────────────────────────────────────────────── */

/* Sur un écran étroit, la pastille, les deux flèches et « Vue d'ensemble » ne
   tiennent pas sur une ligne : le retour à la ligne vaut mieux qu'un libellé
   coupé, et il ne se produit que là où c'est nécessaire. */
.day-nav {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 6px; row-gap: 8px; margin-bottom: 16px;
}
.day-nav .icon-btn { width: 36px; height: 36px; min-height: 36px; font-size: 26px; }
/* ⛔ **Une flèche est un libellé, pas un tracé — revue de 03b.** Ces trois
   boutons portent « ‹ » et « › » en **texte** : `opacity: .3` les délavait
   jusqu'à 2,54:1 en sombre, et le banc les voit désormais comme n'importe quel
   libellé de commande. Le couple inactif du fichier (R05) dit l'indisponibilité
   par la pastille pâle et garde la flèche lisible. */
.day-nav .icon-btn:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--line); cursor: default;
}
/* La date est une pastille encadrée, comme sur la maquette : elle se lit comme
   un bouton, ce qu'elle est — elle ouvre le sélecteur natif du téléphone. */
.day-pill {
  flex: 0 1 auto; min-width: 0;
  display: flex; align-items: center; gap: 7px;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso);
  cursor: pointer; font-size: 22px; font-weight: 600;
  padding: 8px 13px; border-radius: 999px;
  overflow: hidden; white-space: nowrap;
}
.day-pill .icon { width: 16px; height: 16px; color: var(--muted); flex: none; }
/* ⚠️ **L'encre de Caveat déborde de sa chasse** — P24, reproduit par 04, corrigé
   en 05C. Le « i » final d'« Aujourd'hui » dépasse de 2,8 px sa boîte d'avance
   (mesuré par `measureText` : encre 84,2 px pour une chasse de 81,4) ; le
   `overflow: hidden` qui porte les points de suspension le coupait, et la
   pastille lisait « Aujourd'hu » sans rien signaler — `scrollWidth` égalait
   `clientWidth`. La marge de fin du libellé donne sa place à l'encre ; le
   rembourrage de la pastille la rend, pour que la largeur ne change pas. */
.day-pill span { overflow: hidden; text-overflow: ellipsis; padding-right: .2em; }
/* `capitalize` mettrait une majuscule à chaque mot — « Samedi 8 Août ». En
   français, jours et mois restent en minuscules ; seule la première lettre du
   libellé se capitalise. Il faut un `inline-block` : `::first-letter` ne
   s'applique pas à une boîte purement en ligne. */
#day-label-text { display: inline-block; }
#day-label-text::first-letter { text-transform: uppercase; }
.day-caret { color: var(--muted); font-size: 19.5px; line-height: 1; flex: none; }
/* Pas de `margin-left: auto` : poussée à droite, la puce se retrouvait seule et
   comme égarée dès que la ligne passait à deux. Centrée avec le reste, le
   retour à la ligne se lit comme voulu. */
.day-overview { display: flex; align-items: center; gap: 6px; color: var(--terra); }
.day-overview .icon { width: 15px; height: 15px; }
.day-home .icon { width: 16px; height: 16px; }

/* Le champ de date reste dans le document — il porte le sélecteur natif — mais
   ne s'affiche pas : c'est le libellé qui l'ouvre. `display: none` le rendrait
   inatteignable par `showPicker()`. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%);
}

/* La carte de tête : le budget à gauche, les trois macros à droite. Deux
   colonnes plutôt qu'un anneau centré — l'anneau segmenté par repas disait de
   quoi la journée était faite, la barre ne dit que son avancement, mais elle
   libère la moitié droite pour les macros qui étaient en dessous. */
/* Titre à gauche, comme sur la maquette. `.pane-head` est une grille
   symétrique `1fr auto 1fr` faite pour un titre centré entre deux groupes de
   boutons ; cet en-tête n'en a qu'un, et la troisième colonne restée vide
   prenait la moitié de la largeur — « Compteur de calories » se coupait en
   deux pour tenir dans 145 px. */
/* `.pane-head.today-head` et non `.today-head` seul : à une classe contre une,
   c'est l'ordre du fichier qui tranche, et `.pane-head` est déclaré 550 lignes
   plus bas. La règle était juste et n'avait simplement aucun effet — même
   mécanique que l'anneau du jour ramené à 96 px par `.ring`. */
.pane-head.today-head { grid-template-columns: minmax(0, 1fr) auto; }
.today-head .head-title { text-align: left; }
.today-head .page-title { font-size: 29px; }

/* La colonne de droite passe devant, et c'est mesuré : la gauche porte un
   nombre et « / 2 100 kcal » (≈ 96 px), la droite un anneau de 44 px **plus**
   un nom et un pourcentage sur la même ligne (≈ 109 px). Le partage était
   l'inverse, hérité d'une époque où la droite n'affichait pas de pourcentage. */
.today-card {
  display: grid; grid-template-columns: minmax(0, .86fr) minmax(0, 1.14fr);
  gap: 12px; align-items: start;
}
/* ⚠️ **Une colonne quand les macros sont sorties.** L'écran du jour garde les
   deux — son anneau et son budget se lisent ensemble ; l'accueil, lui, donne aux
   macros leur propre carte (planche du 03/09), et la seconde colonne y serait
   restée vide. Un modificateur plutôt qu'une seconde carte : c'est la même,
   avec un contenu de moins. */
.today-solo { grid-template-columns: minmax(0, 1fr); }

/* ── L'encadré du jour et ses trois puces — demandé le 05/09 ─────────────────
   **Trois cartes empilées sont devenues une carte à trois puces.** Ce qui change
   n'est pas le contenu — les trois panneaux sont ceux d'avant, à la ligne près —
   c'est qu'un seul se voit à la fois et qu'il a donc toute la largeur : la
   compaction écrite le 04/09 pour des colonnes d'un tiers d'écran n'a plus
   d'objet, et elle s'en va avec elles.

   ⚠️ **Deux attributs décident, et ils ne se marchent pas dessus.** `hidden`
   dit « la donnée n'est pas arrivée » et vient du code qui peint ; `data-puce`
   dit « ce n'est pas la puce ouverte » et vit ici. D'où le `:not([hidden])` :
   sans lui, la feuille de style rouvrirait un panneau que le chargement n'a pas
   encore rempli — deux écritures sur la même question, § 3. */
/* ⚠️ **Le même cadre crème porte la scène kcal de P24** (R13, 22/09) : mêmes
   couleurs nutrition, même marge, même conteneur — les tailles de la scène
   sont en `cqi`, donc relatives à ce cadre, et ne tombent juste qu'avec lui. */
.card.encadre-jour,
.card.cadre-kcal {
  --nutrition-proteines: #C36039;
  --nutrition-glucides: #DDA23D;
  --nutrition-lipides: #87934F;
  --nutrition-fond: color-mix(in srgb, var(--card) 44%, var(--cream));
  --nutrition-doux: color-mix(in srgb, var(--cream) 80%, var(--sand));
  --nutrition-secondaire: color-mix(in srgb, var(--espresso) 70%, var(--nutrition-fond));
  padding: 12px; border-radius: 25px;
  width: 100%; max-width: 660px; margin-inline: auto;
  background: var(--nutrition-fond);
  border: 1px solid color-mix(in srgb, var(--card) 78%, var(--sand));
  box-shadow: 0 5px 18px #6F482616, inset 0 1px 0 color-mix(in srgb, var(--card) 70%, transparent);
  container-type: inline-size;
}
/* Barre crème et onglet terre cuite arrondis, référence du 20/09.
   La police manuscrite locale est partagée avec les inscriptions de la gourde. */
.segmented.puces-jour {
  grid-template-columns: repeat(3, 1fr);
  max-width: none;
  margin-bottom: 10px; padding: 0; border-radius: 999px; gap: 0;
  background: var(--nutrition-doux); border: 0;
}
.puces-jour button {
  position: relative; padding: 5px 6px; min-height: 44px; border-radius: 999px;
  font: 700 clamp(22px, 5.7cqi, 29px)/1.15 var(--ecriture);
  color: var(--espresso);
}
.puces-jour button.on {
  background: linear-gradient(145deg, #CE7746, #BC5228);
  color: #FFF8ED; box-shadow: 0 3px 8px #AC51231A;
}
.puces-jour button + button:not(.on)::before {
  content: ""; position: absolute; left: 0; top: 24%; bottom: 24%;
  width: 1px; background: var(--sand);
}
.puces-jour button.on + button::before { display: none; }
.puce-pane { display: none; margin-bottom: 0; }
.encadre-jour[data-puce="kcal"] #accueil-jour:not([hidden]),
.encadre-jour[data-puce="macros"] #accueil-macros-carte:not([hidden]),
.encadre-jour[data-puce="eau"] #accueil-eau:not([hidden]) { display: grid; }

/* « Voir le détail » appartient à l'encadré entier. La vue Macros ajoute la
   même porte vers la saisie d'un repas que la vue Calories. */
.encadre-pied {
  display: flex; align-items: center; justify-content: center; gap: 4px;
  margin-top: 14px; padding: 3px; border: 0; border-radius: 999px;
  background: var(--nutrition-doux);
}
.encadre-pied #accueil-detail {
  flex: 1; border: 0; background: transparent; color: var(--espresso);
  padding: 6px 8px; font: 700 clamp(21px, 5.6cqi, 28px)/1.15 var(--ecriture);
}
.encadre-pied #accueil-detail span { margin-left: 6px; font: 400 31px/1 var(--ecriture); }
#accueil-macros-ajout { display: none; }
.encadre-jour[data-puce="macros"] #accueil-macros-ajout { display: inline-flex; }

/* Le pied d'une carte du haut : ce qu'on lit à gauche, ce qu'on fait à droite.
   ⚠️ **Il s'enroule** — « 1 240 kcal au-dessus de votre repère » et « Voir le
   détail » font 330 px pour 302 dans une carte à 390 pt, et un bouton poussé
   hors de la carte est le § 2 dans sa version la plus discrète. */
.today-pied {
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-top: 12px;
}
.today-pied .today-eaten { flex: 1 1 auto; }
/* ⚠️ **Et il reste à droite quand la ligne s'enroule.** « 1 240 kcal au-dessus
   de votre repère » prend la rangée entière à 390 pt : sans cette ligne le
   bouton repart au bord gauche, sous un texte qu'il n'accompagne plus. */
.today-pied .btn-mini { margin-left: auto; }
/* Une porte de carte n'est pas une action de page : elle se dimensionne comme
   ce qu'elle vaut. ⛔ Mais jamais sous 44 px — le § 1 passe avant. */
.btn-ghost.btn-mini { padding: 6px 16px; font-size: 20px; }

.today-budget { display: grid; gap: 2px; align-content: start; }
/* ⚠️ **Pas `display: flex` ici**, et c'est le second décalage du 16/08.
   « Calories consommées » passe à la ligne dans une colonne de 160 px ; en flex,
   le libellé prend alors toute la largeur disponible et le « i » se retrouve
   **à droite du bloc de deux lignes**, centré entre elles — détaché de son
   texte, il se lit comme un badge posé là. En flux normal il suit le dernier
   mot, ce qui est le seul endroit où un « i » veut dire quelque chose. */
.today-heading {
  margin: 0; color: var(--espresso); font-size: 21px; font-weight: 600;
}

/* ── La pastille d'un titre de carte — planche du 03/09 ───────────────────────
   Les trois cartes du haut portent le même objet : un pictogramme dans un rond
   teinté, puis le titre en clair. ⚠️ **La couleur est celle de la carte, pas du
   titre** — la flamme est terre, la goutte bleue, l'anneau des macros ambre —,
   et c'est ce qui les distingue d'un coup d'œil sans écrire trois nuances de
   texte : le § *un signal qui ne distingue rien ne compte pas* appliqué à une
   couleur. Le titre, lui, reste espresso partout.

   ⚠️ **`inline-flex` et `vertical-align`** : le titre passe sur deux lignes dans
   une carte étroite, et une pastille en `flex` s'y détacherait de son mot —
   c'est le défaut du « i » du 16/08, noté juste au-dessus. */
.tete-icone {
  display: inline-grid; place-items: center; vertical-align: -7px;
  width: 26px; height: 26px; border-radius: 9px; margin-right: 7px;
  background: color-mix(in srgb, currentColor 13%, var(--card));
}
.tete-icone .icon { width: 15px; height: 15px; }
.tete-flamme { color: var(--terra); }
.tete-macros { color: var(--amber); }
/* Le « i » porte l'avertissement qui compte : la cible contient déjà l'activité,
   le sport ne s'y rajoute pas. Discret, mais jamais absent.

   ⚠️ **Il y en avait deux règles**, à 800 lignes d'écart et de tailles
   différentes (17 px ici, 20 px plus bas) — donc la seconde gagnait, et corriger
   celle-ci ne changeait rien à l'écran. Trouvé le 16/08 en corrigeant le
   décalage du « i » : la correction était juste et sans effet. Une seule
   désormais, et les deux appelants (`#today-info`, `#kcal-info`) la partagent. */
.info-dot {
  /* `inline-flex` et non `flex` : il suit le dernier mot du libellé au lieu de
     se poser à côté du bloc. Le `vertical-align` le cale sur le texte plutôt
     que sur le bas de la boîte, où il pendait. (Sans effet dans un parent flex,
     où c'est `align-items` qui décide — donc sans risque pour `#kcal-info`.) */
  position: relative;   /* repère de la zone de 44 px — voir en tête de fichier */
  display: inline-flex; align-items: center; justify-content: center;
  vertical-align: -4px;
  width: 20px; height: 20px; min-height: 20px; flex: none; padding: 0;
  border: 1px solid var(--line); border-radius: 50%; background: none;
  color: var(--muted); font-size: 16.5px; font-style: italic; line-height: 1;
  cursor: pointer;
}
/* L'écart au texte n'est posé que là où le « i » suit un mot : dans le `h3` des
   filtres, c'est le `gap` du parent flex qui s'en charge, et les deux
   s'ajouteraient. */
/* L'écart est porté par le `gap` de `.today-goal-line`. */
.today-kcal {
  font-family: var(--ecriture); font-size: 49.5px; line-height: 1.05;
  color: var(--terra); font-weight: 400;
}
/* La cible et son « i » sur une ligne. `flex` ici est sans danger — contrairement
   à `.today-heading`, cette ligne ne passe jamais à deux : « / 2 100 kcal » fait
   96 px pour 128 disponibles, et le point en occupe 20. */
.today-goal-line {
  margin: 0; display: flex; align-items: center; gap: 6px; min-width: 0;
}
.today-target { color: var(--muted); font-size: 21px; min-width: 0; }
.today-track {
  height: 8px; border-radius: 999px; background: var(--line);
  margin: 10px 0 8px; overflow: hidden;
}
.today-track > div {
  /* ⚠️ **Terre, et non sauge.** La planche peint la jauge des calories dans
     l'accent de la marque ; le vert y désignait un état — « dans la cible » —
     que rien ne change jamais, puisque la barre plafonne et que **rien ne change
     de couleur au-dessus de la cible** (voir `renderDayBudget`). Une couleur qui
     ne varie pas ne dit rien : autant qu'elle soit celle de la carte.
     ⛔ L'eau garde la sienne — la gourde, `.gourde-eau`, le seul bleu du dépôt. */
  height: 100%; border-radius: 999px; background: var(--terra);
  transition: width .45s ease;
}
.today-eaten {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: 5px; flex-wrap: wrap;
  margin: 0; color: var(--muted); font-size: 19.5px;
}
.today-eaten b { color: var(--terra); font-weight: 600; }
.eaten-flame { display: flex; color: var(--terra); }
.eaten-flame .icon { width: 14px; height: 14px; }

/* ── Le rond de la journée — planches des 03 et 04/09 ─────────────────────────
   Un anneau pour les trois macros, une légende, et la ligne qui dit ce que
   l'anneau découpe. Le détail du choix — pourquoi des calories et pas des
   grammes — est en tête de `renderDayMacros`. */
.macro-bloc { display: grid; gap: 10px; justify-items: center; }
.macro-donut { width: 88px; height: 88px; flex: none; transform: rotate(-90deg); }
.mdonut-piste, .mdonut-part {
  fill: none; stroke-width: 11;
  /* ⛔ **Bout droit, jamais arrondi.** Un `round` déborde de sa part des deux
     côtés : la dernière recouvrirait la première, et une part de 2 % s'y
     peindrait plus large qu'elle n'est. */
  stroke-linecap: butt;
}
.mdonut-piste { stroke: var(--line); }
.mdonut-part { transition: stroke-dasharray .45s ease; }

.macro-legende { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; width: 100%; }
.macro-legende li { display: flex; align-items: center; gap: 7px; min-width: 0; }
.mleg-point { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.mleg-nom { font-size: 19px; font-weight: 600; min-width: 0; }
/* Les chiffres se poussent au bout de la ligne, et **ne se compriment pas** :
   c'est le nom qui a de la marge. Chasse fixe, sinon trois nombres empilés ne
   s'alignent sur rien. */
.mleg-val {
  margin-left: auto; flex: none; color: var(--muted); font-size: 19px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mleg-val b { color: var(--espresso); font-weight: 600; font-size: 21px; }
/* ⚠️ **La note réserve sa ligne même vide** — elle apparaît dès la première
   calorie notée, et sans hauteur réservée toute la carte sauterait à ce
   moment-là. Le § *une hauteur qui apparaît est une hauteur réservée*. */
.macro-note {
  margin: 0; min-height: 1.2em; color: var(--muted); font-size: 16.5px;
  text-align: center;
}

/* Une carte par repas. La version précédente les réunissait dans une carte
   dense pour qu'ils tiennent tous sans défiler ; la vignette par ligne rend ce
   pari caduc, et des cartes distinctes se relisent mieux. */
/* `minmax(0, 1fr)` et non `1fr` : un élément de grille vaut `min-width: auto`,
   donc un libellé long — « Pain complet ou intégral (à la farine T150) » —
   élargit la piste au-delà de son conteneur au lieu d'être coupé. Les cartes
   sortaient de l'écran par la droite, sans que la page défile pour autant. */
.meals-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.meal-card { padding: 14px 16px; min-width: 0; }

.meal-head { display: flex; align-items: center; gap: 10px; }
.meal-icon {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: #FFFFFF;
}
.meal-icon .icon { width: 18px; height: 18px; }
.meal-name { flex: 1; font-size: 24px; font-weight: 600; font-family: var(--ecriture); }
.meal-kcal { color: var(--terra); font-size: 20px; font-weight: 600; white-space: nowrap; }
.meal-add { width: 32px; height: 32px; min-height: 32px; }
.meal-add .icon { width: 15px; height: 15px; }

/* ── Le cadran des calories — P24 et accueil, 15/09 ─────────────────────────────
   Un anneau qui se remplit vers le repère et plafonne, le total au centre. Né sur
   l'écran du jour (P24), posé sur l'accueil le même jour à la demande de Luca :
   un seul dessin, donc des règles qui ne dépendent d'aucun écran. */
.jr-anneau { position: relative; width: 100%; aspect-ratio: 1; margin: 0 auto; }
.jr-anneau-svg { width: 100%; height: 100%; transform: rotate(-90deg); display: block; }
.jr-anneau-piste, .jr-anneau-arc { fill: none; stroke-width: 12; }
.jr-anneau-piste { stroke: color-mix(in srgb, var(--sage) 25%, var(--card)); }
.jr-anneau-arc { stroke: color-mix(in srgb, var(--sage) 70%, #3F6B2F); stroke-linecap: round; stroke-dasharray: 0 400; transition: stroke-dasharray .45s ease; }
.jr-anneau-centre { position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; text-align: center; padding: 16px; }
.jr-anneau-centre .today-kcal { font-family: var(--ecriture); font-weight: 800; font-size: clamp(31px, 8.76vw, 39px); color: var(--espresso); line-height: 1.05; }
.jr-anneau-centre .today-goal-line { gap: 3px; justify-content: center; }
.jr-anneau-centre .today-target { font-size: 19px; color: var(--espresso); font-weight: 600; white-space: nowrap; }

/* Cadre nutrition — références de Luca du 20/09. Le dessin est décoratif ;
   les totaux et les jauges restent du HTML/SVG. Le centre et le diamètre
   suivent la géométrie des deux images 1152 × 768 : centre à 50 % / 49 %.
   Le dessin est agrandi de 20 % dans la scène pour réduire ses marges vides ;
   le diamètre de l'assiette vaut alors ≈ 60 % de la largeur de la scène. */
.nutrition-scene {
  position: relative; width: 100%; max-width: 560px; aspect-ratio: 3 / 2;
  margin: 4px auto 0; overflow: hidden;
}
.nutrition-assiette {
  position: absolute; left: -10%; top: -10%; display: block;
  width: 120%; max-width: none; height: auto; pointer-events: none;
}
/* ── La scène kcal — P01 et P24 (R13, 22/09) ─────────────────────────────────
   Née sur l'accueil le 20/09, reprise telle quelle par le détail de la journée
   à la demande de Luca : ces règles ne dépendent d'aucun écran. L'hôte ne
   choisit que ce qui entoure la scène (le pied de l'accueil, l'écart et les
   tuiles de P24), jamais ses proportions, ses couleurs ou sa typographie. */
.nutrition-scene.cadran-kcal { display: block; }
.cadran-kcal .jr-anneau {
  position: absolute; left: 50%; top: 49%; width: 66%;
  transform: translate(-50%, -50%); margin: 0;
}
.cadran-kcal .jr-anneau-piste { stroke: #E8D4BC; stroke-width: 8; }
.cadran-kcal .jr-anneau-arc { stroke: #C66334; stroke-width: 8; }
.cadran-kcal .jr-anneau-centre { padding: 12%; }
.cadran-kcal .today-kcal { color: #47260F; font-size: clamp(32.5px, 10.83cqi, 59.5px); white-space: nowrap; }
.cadran-kcal .today-goal-line { flex-wrap: wrap; gap: 4px; }
.cadran-kcal .today-target { color: #795436; font-size: clamp(16.5px, 5.37cqi, 24.5px); font-weight: 600; }
.cadran-kcal .info-dot { color: #795436; border-color: #BA9B7A; width: 17px; height: 17px; min-height: 17px; font-size: 15px; }
.cadran-kcal .today-track { display: none; }
#accueil-jour { gap: 0; }
#accueil-jour .today-pied {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
  gap: 9px; margin-top: 2px; align-items: center;
}
#accueil-jour .today-eaten, .nutrition-repas {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 6px;
  margin: 0; min-width: 0; color: var(--espresso); font-size: 18px;
  grid-column: auto; line-height: 1.25;
}
#accueil-jour .today-eaten b, .nutrition-repas b { display: block; color: var(--espresso); font-size: clamp(18px, 5.49cqi, 25px); font-weight: 750; }
#accueil-jour .today-eaten small, .nutrition-repas small { display: block; margin-top: 2px; font-size: clamp(15px, 4.5cqi, 21px); color: var(--nutrition-secondaire); }
#accueil-jour .eaten-flame .icon { width: 25px; height: 30px; }
.nutrition-repas { padding-left: 8px; gap: 5px; border-left: 1px solid var(--sand); }
.nutrition-repas > .icon { width: 24px; height: 27px; flex: none; color: var(--nutrition-lipides); }
.nutrition-ajout {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 6px 13px; border: 0; border-radius: 999px;
  color: #FFF8ED; background: linear-gradient(145deg, #CF7848, #BC5228);
  font: 700 clamp(20px, 5.1cqi, 26px)/1.1 var(--ecriture);
  cursor: pointer;
}
.nutrition-ajout .pastille-plus {
  display: inline-block; width: auto; height: auto; margin: 0; padding: 0;
  background: none; border: 0; border-radius: 0; color: inherit;
  font: 300 34.5px/.8 var(--ecriture);
}
#accueil-macros .nutrition-scene { grid-column: 1 / -1; }
#accueil-macros .macro-donut {
  position: absolute; left: 50%; top: 49%; width: 75%; height: auto;
  transform: translate(-50%, -50%);
}
#accueil-macros .macro-secteur { stroke: #FFF2DD; stroke-width: 1.4; stroke-linejoin: round; }
#accueil-macros .mdonut-part { transition: none; }
#accueil-macros .mdonut-piste, #accueil-macros .mdonut-part { stroke-width: 3.5; }
#accueil-macros .mdonut-piste { stroke: #E8D4BC; }
#accueil-macros { gap: 6px; }
#accueil-macros .macro-legende { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; }
#accueil-macros .macro-legende li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 4px; row-gap: 2px;
  padding: 0 10px; align-content: start;
}
#accueil-macros .macro-legende li:first-child { padding-left: 0; }
#accueil-macros .macro-legende li:last-child { padding-right: 0; }
#accueil-macros .macro-legende li + li { border-left: 1px solid var(--sand); }
#accueil-macros .mleg-point { width: 24px; height: 26px; grid-row: 1; align-self: center; }
#accueil-macros .mleg-point .icon { width: 100%; height: 100%; }
#accueil-macros .mleg-nom { font: 700 clamp(18px, 4.8cqi, 25px)/1.1 var(--ecriture); }
#accueil-macros .mleg-val { grid-column: 1 / -1; margin: 0; font-size: clamp(15px, 4.2cqi, 21px); white-space: normal; color: var(--nutrition-secondaire); }
#accueil-macros .mleg-val b { font-size: clamp(22.5px, 6.25cqi, 28px); }
.mleg-barre { grid-column: 1 / -1; height: 7px; margin-top: 5px; background: var(--sand); border-radius: 999px; overflow: hidden; }
.mleg-barre i { display: block; height: 100%; border-radius: inherit; transition: width .45s ease; }
#accueil-macros .macro-note { margin-top: 3px; font-size: 15px; color: var(--nutrition-secondaire); }
@container (max-width: 310px) {
  #accueil-jour .today-pied { gap: 7px; }
  #accueil-jour .eaten-flame, .nutrition-repas > .icon { display: none; }
  .nutrition-repas { padding-left: 7px; gap: 4px; }
  .nutrition-ajout { padding: 6px 10px; gap: 5px; }
  #accueil-macros .macro-legende li { padding: 0 7px; }
  #accueil-macros .macro-legende li { grid-template-columns: minmax(0, 1fr); }
  #accueil-macros .mleg-point { width: 19px; height: 23px; }
  #accueil-macros .mleg-nom { grid-column: 1; grid-row: 2; }
  #accueil-macros .mleg-val { grid-column: 1 / -1; }
  #accueil-macros .mleg-point { grid-row: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .cadran-kcal .jr-anneau-arc, .encadre-jour .mdonut-part, .mleg-barre i { transition: none; }
}

/* ── Compteur de calories / journée — P24 de la refonte, 15/09 ─────────────────
   Tout est scopé sur `#screen-today` : l'accueil (P01, livré) partage les corps
   et les classes de la carte du jour, et ne doit pas bouger d'un pixel.
   Refait le jour même sur le retour de Luca — « ça ne ressemble pas du tout au
   mockup » : la tête était une carte, les tuiles et l'objectif prenaient la
   moitié de l'écran, et l'eau avait perdu sa gourde. */
#screen-today .jr-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
#screen-today .jr-date { display: flex; align-items: center; gap: 6px; min-width: 0; }
#screen-today .jr-date .day-pill { min-height: 44px; padding: 0 10px 0 14px; font-weight: 500; font-size: 22px; }
#screen-today .jr-fleches {
  display: flex; align-items: center; border: 1px solid var(--line); border-radius: 999px; background: var(--card);
}
#screen-today .jr-fleches .icon-btn { width: 40px; height: 44px; min-height: 44px; border: none; background: none; font-size: 26px; }
#screen-today .jr-fleches .icon-btn:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-radius: 999px; cursor: default;
}
#screen-today .jr-titres { margin: 2px 0 10px; }
#screen-today .jr-titre { margin: 0; font-family: var(--ecriture); font-weight: 700; font-size: clamp(32.5px, 9.3vw, 41px); line-height: 1.1; }
#screen-today .jr-sous { margin: 2px 0 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--muted); font-size: 22.5px; }
#screen-today .today-colonnes > * + * { margin-top: 12px; }
/* Pendant qu'une journée jamais lue arrive, l'ancienne s'estompe sous le nouveau
   titre : on voit que le geste a pris, et qu'on ne lit pas encore ce jour-là. */
#screen-today .jr-tete, #screen-today .jr-duo, #screen-today .meals-stack { transition: opacity .15s ease; }
#screen-today.jr-chargement .jr-tete, #screen-today.jr-chargement .jr-duo, #screen-today.jr-chargement .meals-stack { opacity: .45; }

/* La tête (R13, 22/09) : la scène kcal de l'accueil dans son cadre crème,
   l'écart sous l'assiette, puis les trois tuiles des macros. La scène elle-même
   n'a aucune règle ici — voir « La scène kcal », plus haut. */
#screen-today .jr-tete { display: grid; gap: 10px; }
#screen-today .jr-tete > .cadre-kcal { margin: 0; }
#screen-today .jr-tete .macro-bloc { justify-items: stretch; }
#screen-today .today-eaten { grid-column: 1 / -1; justify-content: center; margin: 2px 0 0; font-size: 21px; line-height: 1.3; text-align: center; }

/* Trois tuiles sous la scène (R13) : le nom, les grammes, la barre. Serrées à
   droite de l'ancien anneau, elles tombaient sous 60 px et coupaient
   « Protéines » comme « 51 / 120 g » ; sous l'assiette elles ont la largeur. */
#screen-today .jr-macros { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
#screen-today .jr-macro {
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "nom" "val" "barre";
  column-gap: 6px; row-gap: 5px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); min-width: 0;
}
#screen-today .jr-macro-nom { grid-area: nom; font-size: 20px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#screen-today .jr-macro-point { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; vertical-align: 1px; }
#screen-today .jr-macro-barre { grid-area: barre; display: block; height: 5px; border-radius: 999px; background: var(--line); overflow: hidden; }
#screen-today .jr-macro-barre > i { display: block; height: 100%; border-radius: 999px; }
#screen-today .jr-macro-val { grid-area: val; font-size: 19.5px; color: var(--muted); white-space: nowrap; }
#screen-today .jr-macro-val b { color: var(--espresso); }
/* Sous 360 px, une tuile par ligne : le nom et les grammes côte à côte. */
@media (max-width: 359px) {
  #screen-today .jr-macros { grid-template-columns: minmax(0, 1fr); }
  #screen-today .jr-macro { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "nom val" "barre barre"; }
}

/* L'objectif, compact ; l'eau et sa gourde à côté. */
#screen-today .jr-duo { display: grid; grid-template-columns: minmax(0, 40fr) minmax(0, 60fr); gap: 10px; align-items: start; }
@media (max-width: 379px) { #screen-today .jr-duo { grid-template-columns: minmax(0, 1fr); } }
#screen-today .jr-duo > .card { margin: 0; padding: 12px; border: 1px solid var(--line); box-shadow: none; }
#screen-today .jr-objectif {
  position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; text-align: left;
  font: inherit; color: var(--espresso); cursor: pointer; padding-right: 28px; min-height: 44px;
}
#screen-today .jr-objectif .jr-chevron { position: absolute; right: 8px; top: 50%; margin-top: -9px; width: 18px; height: 18px; color: var(--muted); transform: rotate(-90deg); }
#screen-today .jr-pastille { display: grid; place-items: center; width: 30px; height: 30px; border-radius: 50%; color: var(--terra); background: color-mix(in srgb, var(--terra) 12%, var(--card)); }
#screen-today .jr-pastille .icon { width: 16px; height: 16px; }
#screen-today .jr-duo-texte { display: grid; gap: 1px; min-width: 0; }
#screen-today .jr-duo-texte small { color: var(--muted); font-size: 19.5px; line-height: 1.25; }
#screen-today .jr-duo-texte b { font-size: 24px; }

#screen-today .jr-eau { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; align-items: start; }
#screen-today .jr-eau-texte { display: grid; gap: 2px; min-width: 0; }
#screen-today .jr-eau-tete { margin: 0; display: flex; align-items: center; gap: 5px; font-size: 19.5px; color: var(--muted); }
#screen-today .jr-eau-valeur { margin: 0; font-size: 21px; color: var(--muted); }
#screen-today .jr-eau-valeur b { font-size: 25px; color: var(--espresso); }
#screen-today .jr-eau-note { margin: 0; font-size: 19px; line-height: 1.3; color: var(--muted); }
#screen-today .jr-eau-gestes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
#screen-today .jr-eau-boire {
  min-height: 44px; padding: 0 12px; border-radius: 999px; border: none; cursor: pointer;
  background: color-mix(in srgb, var(--bleu) 14%, var(--card)); color: var(--bleu);
  font: inherit; font-size: 22.5px; font-weight: 700;
}
#screen-today .jr-eau-retirer {
  min-height: 44px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--card);
  color: var(--muted); font: inherit; font-size: 21px; cursor: pointer;
}
#screen-today .jr-eau-fixer { min-height: 44px; font-size: 20px; white-space: nowrap; padding: 0; }
#screen-today .jr-gourde { grid-column: 2; grid-row: 1; height: 150px; }

/* Le ruban de la semaine. */
#screen-today .jr-semaine { display: grid; grid-template-columns: 28px minmax(0, 1fr) 28px; align-items: center; gap: 2px; }
#screen-today .jr-semaine-fleche { width: 28px; height: 44px; min-height: 44px; border: none; background: none; font-size: 26.5px; color: var(--muted); padding: 0; }
#screen-today .jr-semaine-fleche:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-radius: 999px; cursor: default;
}
#screen-today .jr-semaine-jours { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 5px; }
#screen-today .jr-jour {
  display: grid; justify-items: center; gap: 0; min-height: 48px; padding: 5px 0;
  border: none; border-radius: 12px; font: inherit; cursor: pointer; color: var(--espresso);
  background: color-mix(in srgb, var(--sand) 32%, var(--cream));
}
/* Le jour de la semaine fait partie du libellé du bouton : `--muted` y rendait
   3,03:1 sur le sable (revue du 21/09). */
#screen-today .jr-jour small { font-size: 19px; color: var(--bouton-second-texte); }
#screen-today .jr-jour b { font-size: 24px; font-weight: 600; }
#screen-today .jr-jour.on { background: var(--bouton-fond); color: var(--bouton-texte); }
/* ⛔ Et pas `opacity: .9` : une encre translucide n'est plus mesurable comme un
   couple de couleurs, alors que c'est la date du jour choisi. Un mélange à
   88 % rendait encore 4,23:1 en sombre : la même encre que le chiffre, c'est
   la taille qui hiérarchise. */
#screen-today .jr-jour.on small { color: var(--bouton-texte); }
/* Un jour à venir : il porte un chiffre **et** son jour de semaine, donc deux
   libellés à lire. `opacity: .45` les délavait avec leur pastille (R05, revue
   de 03b) ; le couple inactif garde la lecture et dit l'indisponibilité. */
#screen-today .jr-jour:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  cursor: default;
}
#screen-today .jr-jour:disabled small { color: var(--bouton-inactif-texte); }
#screen-today .jr-jour:focus-visible, #screen-today .jr-objectif:focus-visible, #screen-today .jr-eau-boire:focus-visible,
#screen-today .meal-add:focus-visible, #screen-today .meal-plier:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--terra) 55%, transparent); outline-offset: 2px;
}

#screen-today .jr-actions { display: flex; flex-wrap: wrap; gap: 8px; }
#screen-today .jr-actions:not(:has(> :not([hidden]))) { display: none; }
#screen-today .jr-actions .chip { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; }
#screen-today .jr-actions .chip .icon { width: 15px; height: 15px; }

/* Les repas du mockup : une carte fine, des lignes denses. */
#screen-today .meals-stack { gap: 10px; }
#screen-today .meal-card { padding: 8px 12px 10px; border: 1px solid var(--line); box-shadow: none; }
#screen-today .meal-head { gap: 8px; }
#screen-today .meal-icon { width: 28px; height: 28px; background: none; }
#screen-today .meal-icon .icon { width: 22px; height: 22px; }
#screen-today .meal-name { flex: 1; min-width: 0; margin: 0; font-size: 25px; font-weight: 700; line-height: 1.2; }
#screen-today .meal-kcal { color: var(--espresso); font-size: 22.5px; font-weight: 700; white-space: nowrap; }
#screen-today .meal-plier { width: 40px; height: 44px; min-height: 44px; border: none; background: none; color: var(--terra); }
#screen-today .meal-plier .icon { width: 18px; height: 18px; transition: transform .2s ease; }
#screen-today .meal-card.replie .meal-plier .icon { transform: rotate(-90deg); }
#screen-today .meal-lignes { border: 1px solid var(--line); border-radius: 12px; padding: 0 0 0 8px; }
#screen-today .meal-lignes .diary-entry { padding: 6px 0; gap: 10px; }
#screen-today .meal-lignes .diary-entry + .diary-entry { padding-top: 6px; }
#screen-today .meal-lignes .diary-thumb { width: 44px; height: 44px; border-radius: 10px; }
#screen-today .meal-lignes .diary-label { font-size: 22.5px; }
#screen-today .meal-lignes .diary-kcal { color: var(--muted); font-weight: 400; font-size: 22px; white-space: nowrap; }
#screen-today .meal-vide { margin: 0; padding: 12px 0; color: var(--muted); font-size: 21px; }
#screen-today .meal-add {
  display: flex; align-items: center; justify-content: center; gap: 6px; width: auto; height: auto;
  min-height: 44px; margin: 8px auto 0; padding: 0 18px; border: none; border-radius: 999px;
  background: color-mix(in srgb, var(--terra) 11%, var(--card)); color: var(--cinnamon);
  font: inherit; font-size: 22px; font-weight: 500; cursor: pointer;
}
#screen-today .meal-add .icon { width: 15px; height: 15px; }
#screen-today .entry-menu { width: 36px; height: 44px; min-height: 44px; border: none; background: none; }
/* Sous 370 px, le total passe sous le nom (320 px : 37 px de débordement) ;
   à 390 il tient à droite, comme le mockup. */
#screen-today .meal-name { font-size: 24.5px; }
#screen-today .meal-kcal { font-size: 21px; }
@media (max-width: 369px) {
  #screen-today .meal-head { display: grid; grid-template-columns: 28px minmax(0, 1fr) 40px; grid-template-areas: "icone nom plier" "icone kcal plier"; column-gap: 8px; }
  #screen-today .meal-icon { grid-area: icone; }
  #screen-today .meal-name { grid-area: nom; }
  #screen-today .meal-kcal { grid-area: kcal; font-size: 21px; font-weight: 600; color: var(--muted); }
  #screen-today .meal-plier { grid-area: plier; }
}

.entry-menu { width: 26px; height: 26px; min-height: 26px; border: none; background: none; }
.entry-menu .icon { width: 16px; height: 16px; }

/* Chaque pixel repris sur la vignette, les écarts et le menu revient au
   libellé : les noms CIQUAL sont longs par construction (« Pain complet ou
   intégral (à la farine T150) ») et c'est la seule colonne qui se coupe. */
.diary-entry {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 0 0;
}
.diary-entry + .diary-entry { border-top: 1px solid var(--line); padding-top: 10px; }

.diary-thumb {
  width: 40px; height: 40px; flex: none; border-radius: 12px; overflow: hidden;
  background: var(--cream); display: flex; align-items: center; justify-content: center;
}
.diary-thumb img { width: 100%; height: 100%; object-fit: cover; }
/* Monochrome, et c'est une règle : une couleur par famille se lirait comme un
   jugement sur ce qui est mangé, ce que le § 10 refuse jusque dans une pastille
   de 40 px. Le dessin distingue, la teinte ne classe pas. */
.diary-thumb-empty { color: var(--sand); }
.diary-thumb-empty .icon { width: 22px; height: 22px; }
.diary-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* Deux lignes, pas une. Les libellés CIQUAL sont longs par construction, et la
   coupe sur une ligne tombait au quinzième caractère : « Pain complet ou i… »
   n'identifie plus rien. Tant qu'aucune vignette ne porte la reconnaissance,
   c'est le nom qui la porte seul. */
.diary-label {
  font-size: 22px; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.diary-detail { color: var(--muted); font-size: 19px; }
/* En italique, et sur sa propre ligne : la mention doit se distinguer du poids
   qui la précède — l'un est ce qu'on a pesé, l'autre est ce qu'on a supposé. */
.diary-estime { font-style: italic; display: block; }
.diary-kcal { font-size: 21px; font-weight: 600; color: var(--cinnamon); white-space: nowrap; }

/* ── Objectif ────────────────────────────────────────────────────────────── */

.danger-link { display: block; margin: 0 auto 8px; color: var(--danger); }

/* ── Définir un objectif — P25 de la refonte, 15/09 ─────────────────────────

   La maquette : ‹ à gauche et « Étape 1 sur 9 » à droite, une frise de points
   reliés, un grand titre serif avec la marmite souriante qui déborde à droite,
   des cartes à pictogramme teinté et bouton radio, un encart sauge, et un
   « Suivant → » plein. Toutes les règles sont scopées sur `#screen-goal`.

   ⚠️ **Le décor cède au titre, jamais l'inverse** — même arbitrage que `.ip-head`
   (P14) : la marmite est posée en absolu dans l'angle, le titre lui réserve sa
   largeur, et sous 360 px elle rétrécit. Sur les étapes chiffrées elle part :
   un champ et « Suivant » ne se cherchent pas sous le clavier. */
#screen-goal .goal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 14px;
}
#screen-goal .goal-compteur { margin: 0; color: var(--muted); font-size: 22.5px; }

/* Les points et leur fil : le fil est le fond de la liste, pas un élément de
   plus, et les points franchis le recouvrent de terre cuite. */
#screen-goal .goal-frise {
  list-style: none; margin: 0 0 22px; padding: 0 4px;
  display: flex; justify-content: space-between; align-items: center;
  background: linear-gradient(var(--line), var(--line)) center / calc(100% - 8px) 2px no-repeat;
}
#screen-goal .goal-frise li {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--line); box-shadow: 0 0 0 3px var(--cream);
}
#screen-goal .goal-frise li.fait { background: var(--terra); }
#screen-goal .goal-frise li.courant {
  width: 22px; height: 22px; background: var(--terra);
  box-shadow: 0 0 0 3px var(--cream), 0 2px 6px color-mix(in srgb, var(--terra) 35%, transparent);
}

#screen-goal .goal-corps { position: relative; }
/* Mesuré le 15/09 : « Quel est ton objectif ? » fait 247 px en 28 px. La
   marmite déborde de 16 px dans la gouttière, comme sur la maquette, pour que
   le titre tienne sur une ligne à 390 px ; les paliers plus bas suivent la
   même mesure (230 px en 26, 212 px en 24). */
#screen-goal .goal-decor {
  --decor-w: 118px;
  position: absolute; top: -18px; right: -16px; width: var(--decor-w); height: calc(var(--decor-w) * .82);
  pointer-events: none;
}
#screen-goal .goal-decor img { position: absolute; display: block; }
#screen-goal .goal-decor-marmite { right: 6px; bottom: 0; width: 86%; }
#screen-goal .goal-decor-tomate { left: -4px; bottom: -4px; width: 30%; }
#screen-goal .goal-decor-basilic { right: 10px; top: 0; width: 32%; transform: rotate(18deg); }
/* ⚠️ **Sur toutes les étapes** — demandé par Luca le 15/09 : le même
   traitement d'une question à l'autre. Le titre lui réserve sa largeur. */
#screen-goal .goal-decor { display: block; }
#screen-goal .wizard-q, #screen-goal .wizard-hint { padding-right: 96px; }
#screen-goal .wizard-hint { margin-bottom: 26px; }

#screen-goal .wizard-q {
  margin: 0 0 8px; font-family: var(--ecriture); font-weight: 400;
  font-size: 31px; line-height: 1.12;
}
#screen-goal .wizard-hint { color: var(--muted); font-size: 22.5px; line-height: 1.4; margin: 0 0 20px; }

/* ⚠️ La règle large borne `.wizard-choices` à 660 px ; ici l'encart et « Suivant »
   prennent la colonne, et des cartes plus étroites qu'eux se lisaient décalées. */
#screen-goal .wizard-choices { display: grid; gap: 12px; max-width: none; }
#screen-goal .wizard-choices button {
  display: flex; align-items: center; gap: 14px; width: 100%; min-height: 64px;
  text-align: left; cursor: pointer; color: inherit;
  border: 1.5px solid var(--line); background: var(--card);
  border-radius: 16px; padding: 12px 16px;
}
#screen-goal .wizard-choices button.on {
  border-color: var(--terra);
  background: color-mix(in srgb, var(--terra) 9%, var(--card));
}
#screen-goal .goal-choix-texte { flex: 1; min-width: 0; }
#screen-goal .goal-choix-texte strong {
  display: block; font-family: var(--ecriture); font-weight: 400; font-size: 26px; line-height: 1.2;
}
#screen-goal .goal-choix-texte small {
  display: block; margin-top: 3px; color: var(--muted); font-size: 21px; line-height: 1.35;
}
#screen-goal .goal-picto {
  flex: none; width: 52px; height: 52px; border-radius: 13px;
  display: grid; place-items: center;
}
#screen-goal .goal-picto .icon { width: 32px; height: 32px; }
#screen-goal .goal-picto-terra { background: color-mix(in srgb, var(--terra) 14%, var(--card)); color: var(--danger); }
#screen-goal .goal-picto-sauge { background: color-mix(in srgb, var(--sage) 18%, var(--card)); color: #3F5A45; }
#screen-goal .goal-picto-ambre { background: color-mix(in srgb, var(--amber) 18%, var(--card)); color: #8A6212; }
#screen-goal .goal-picto-sable { background: color-mix(in srgb, var(--sand) 35%, var(--card)); color: var(--muted); }
#screen-goal .goal-picto-rose { background: color-mix(in srgb, var(--coeur) 12%, var(--card)); color: var(--coeur); }
/* Le bouton radio : un anneau vide, rempli d'un point quand la carte est
   choisie. `aria-pressed` le dit sans la couleur. */
#screen-goal .goal-radio {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid var(--sand); background: var(--card);
}
#screen-goal .wizard-choices button.on .goal-radio {
  border-color: var(--terra);
  background: radial-gradient(circle, var(--terra) 0 5px, var(--card) 6px);
}

#screen-goal .goal-facultatif {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0 0; padding: 12px 16px; border-radius: 14px;
  background: color-mix(in srgb, var(--sage) 14%, var(--card));
  color: color-mix(in srgb, var(--sage) 55%, var(--espresso));
  font-size: 21px; line-height: 1.45;
}
#screen-goal .goal-facultatif .icon { flex: none; width: 26px; height: 26px; color: var(--sage); }

/* Le chiffre vit dans une carte, comme un choix : même bord, même rayon, le
   pictogramme à gauche. Le nombre en gros, l'unité à côté — on tape un chiffre,
   pas un formulaire. */
#screen-goal .goal-saisie {
  display: flex; align-items: center; gap: 14px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 16px;
  padding: 14px 16px;
}
#screen-goal .goal-saisie .wizard-field { flex: 1; margin-top: 0; }
#screen-goal .goal-saisie:focus-within { border-color: var(--terra); }
/* Le résultat : quatre cartes, aux teintes des macros du journal (`MACROS`). */
#screen-goal .goal-cibles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
#screen-goal .goal-cible {
  display: grid; grid-template-columns: auto 1fr; grid-template-areas: "picto nom" "picto valeur";
  column-gap: 10px; row-gap: 4px; align-items: center; min-width: 0;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 16px; padding: 12px;
  font-size: 20px; color: var(--muted);
}
#screen-goal .goal-cible .goal-picto { grid-area: picto; width: 42px; height: 42px; }
#screen-goal .goal-cible .goal-picto .icon { width: 24px; height: 24px; }
#screen-goal .goal-cible > span:not(.goal-picto) { grid-area: nom; }
#screen-goal .goal-cible input {
  grid-area: valeur; width: 100%; min-width: 0; border: none; border-bottom: 2px solid var(--line);
  background: none; color: var(--espresso); font-family: var(--ecriture); font-size: 29px; padding: 0 0 2px;
}
#screen-goal .goal-cible input:focus-visible { outline: none; border-bottom-color: var(--terra); }
#screen-goal .goal-cible:focus-within { border-color: var(--terra); }
@media (max-width: 359px) {
  #screen-goal .goal-cibles { grid-template-columns: 1fr; }
}
/* Le nombre en gros, l'unité à côté : on tape un chiffre, pas un formulaire. */
#screen-goal .wizard-field { display: flex; align-items: baseline; justify-content: center; gap: 10px; margin-top: 12px; }
#screen-goal .wizard-field input {
  width: 4.5ch; border: none; border-bottom: 2px solid var(--line);
  background: none; color: inherit; text-align: center;
  font-size: 47.5px; font-family: var(--ecriture); padding: 4px 0;
}
#screen-goal .wizard-field input:focus-visible { outline: none; border-bottom-color: var(--terra); }
#screen-goal .wizard-field span { color: var(--muted); font-size: 24.5px; }

/* Les deux molettes du délai : la forme de la feuille de quantité, posée sur la
   page. La relecture au-dessus dit ce que le serveur recevra (« 3 mois, soit
   environ 13 semaines »), parce que c'est en semaines qu'il calcule. */
#screen-goal .goal-delai-lu {
  margin: 0 0 14px; text-align: center; font-size: 24px; color: var(--espresso);
}
#screen-goal .goal-roues { max-width: 320px; margin: 0 auto; }

#screen-goal .wizard-next {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; margin-top: 22px; min-height: 54px; font-size: 24.5px;
}
#screen-goal .wizard-next .icon { width: 22px; height: 22px; }
#screen-goal .wizard-next.goal-sans-fleche .icon { display: none; }
#screen-goal .wizard-next:disabled { cursor: default; }
#screen-goal .pace-note { margin-bottom: 16px; }
#screen-goal .goal-plus-tard { display: block; margin: 8px auto 0; min-height: 44px; font-size: 24px; }
#screen-goal #goal-clear { margin-top: 4px; min-height: 44px; }

@media (max-width: 389px) {
  #screen-goal .goal-decor { --decor-w: 104px; }
  #screen-goal .wizard-q { font-size: 30px; }
  #screen-goal .wizard-q, #screen-goal .wizard-hint { padding-right: 84px; }
}
@media (max-width: 359px) {
  /* 320 px : 280 de contenu, « Quel est ton objectif ? » fait 194 px en 22. */
  #screen-goal .goal-decor { --decor-w: 80px; right: -12px; }
  #screen-goal .wizard-q { font-size: 27.5px; }
  #screen-goal .wizard-q, #screen-goal .wizard-hint { padding-right: 62px; }
  #screen-goal .goal-picto { width: 42px; height: 42px; }
  #screen-goal .wizard-choices button { gap: 10px; padding: 10px 12px; }
}

.pace-note {
  margin: 0; padding: 12px 14px; border-radius: 12px;
  background: var(--cream); border: 1px solid var(--line);
  font-size: 20px; line-height: 1.5;
}
.pace-note.warn { background: #FFF3E2; border-color: #F0DCBE; color: #7A5320; }

/* ── Feuille d'ajout au journal ──────────────────────────────────────────── */

#meal-sheet input, #meal-sheet select, #weightedit-sheet input {
  width: 100%; border: 1px solid var(--line); border-radius: 12px;
  padding: 11px 13px; background: var(--cream); color: inherit;
  font-size: 24px; min-height: 46px; margin-bottom: 8px;
}
.meal-free-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.meal-free-row input { margin-bottom: 10px; }
/* Les quatre libellés de la grille (C14 saisie libre, C19 création) sont sur
   une même ligne : `.champ-libelle:first-child` ôte sa marge haute au premier
   seul, et « kcal » flottait 16 px au-dessus de « Protéines ». Même marge pour
   les quatre, et alignés sur leur bas — un libellé qui passe à deux lignes
   reste posé sur son champ. */
.meal-free-row > .champ-libelle { margin: 8px 0 4px; align-self: end; }
#meal-sheet .btn-primary, #meal-sheet .btn-ghost { width: 100%; }
#meal-sheet .btn-ghost { margin-top: 8px; }

.meal-picker { max-height: 46vh; overflow-y: auto; display: grid; gap: 4px; }
.meal-pick {
  border: none; background: none; color: inherit; cursor: pointer;
  text-align: left; padding: 11px 12px; border-radius: 12px; font-size: 22px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.meal-pick:hover { background: var(--cream); }

/* La proposition de découpage — § A3. Teintée sans être alarmante : ce n'est
   pas une erreur à corriger, c'est un choix à faire, et « garder une fiche »
   est une réponse aussi valable que l'autre. */
.split-card { border: 1px solid var(--sand); }
.split-list {
  list-style: none; margin: 0 0 14px; padding: 0;
  display: grid; gap: 2px; max-height: 34vh; overflow-y: auto;
}
.split-list li {
  padding: 8px 0 8px 16px; font-size: 22px; position: relative;
  border-top: 1px solid var(--line);
}
.split-list li:first-child { border-top: none; }
.split-list li::before {
  content: ""; position: absolute; left: 2px; top: 15px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--sand);
}
/* Le même sujet, vu depuis un import en masse : le lot n'a pas d'écran de
   relecture, donc le compte doit se lire dans la liste. */
.batch-compilations {
  margin: 0 0 10px; padding: 9px 12px; border-radius: 12px;
  background: var(--cream); border: 1px solid var(--sand);
  color: var(--cinnamon); font-size: 19.5px;
}
.batch-multi {
  display: inline-block; margin-left: 8px; padding: 1px 8px;
  border-radius: 999px; background: var(--sand); color: var(--espresso);
  font-size: 17px; font-weight: 600; white-space: nowrap;
}

.split-actions { display: grid; gap: 8px; }
.split-actions .btn-primary, .split-actions .btn-ghost { width: 100%; }

/* Chercher un aliment, puis dire combien — § 13.

   Les largeurs sont préfixées par `#meal-sheet` et non écrites en classe seule :
   `#meal-sheet input` étend déjà tous les champs de la feuille à 100 %, et un
   `.qty-row input` à une classe contre un identifiant perdrait sans qu'on voie
   pourquoi. Même leçon que l'anneau du jour passé à 96 px. */
/* ── Écran « Rechercher » — § 13 ────────────────────────────────────────────

   Un écran de détail, donc rien en `position: fixed` ici : la barre de frappe
   défile avec le reste. C'était tentant de la coller en haut, et c'est le
   piège du § 2 des conventions — une couche fixe se calcule sur la fenêtre et
   ignore `--app-w`, il aurait fallu la recadrer ou la déclarer. Une barre qui
   défile ne coûte rien : on tape une fois, puis on lit. */

.fs-bar {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); padding: 4px 6px 4px 14px;
  margin-bottom: 14px;
}
.fs-bar-icon { width: 19px; height: 19px; flex: none; color: var(--muted); }
/* **16 px, et ce n'est pas négociable** (§ 1 des conventions) : en dessous,
   iOS agrandit la page à la mise au point et la laisse décalée quand le
   clavier se referme. Si la place manque, ce sont les rembourrages qui
   cèdent. `min-width: 0` parce qu'un champ en flex refuse de rétrécir sous sa
   taille intrinsèque, et la ligne pousserait les boutons hors du cadre. */
#fs-input {
  flex: 1; min-width: 0; border: none; background: none; color: inherit;
  font-family: var(--ecriture); font-size: 24px; padding: 10px 0; min-height: 44px;
}
#fs-input:focus { outline: none; }
/* Chrome et Safari posent leur propre croix dans un `type="search"` : deux
   croix côte à côte, dont une seule connaît nos états. */
#fs-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.fs-bar-btn {
  flex: none; width: 38px; height: 38px; min-height: 38px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%; background: none;
  color: var(--muted); cursor: pointer;
}
.fs-bar-btn .icon { width: 18px; height: 18px; }

.fs-kinds { margin-bottom: 18px; }

.fs-block { margin-bottom: 22px; }
.fs-block-title {
  font-family: var(--ecriture); font-size: 21px; font-weight: 600;
  margin: 0 0 10px; color: var(--espresso);
}
/* P27 : une rangée de cartes qui défile au doigt, et qui retient son geste —
   déclarée dans `RANGEES_LATERALES`. */
.fs-pills {
  display: flex; flex-wrap: nowrap; gap: 10px;
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
/* Une pastille de rappel n'est pas une puce de filtre : elle *ouvre* un
   aliment au lieu de restreindre une liste. Même forme, teinte plus sourde —
   `.chip.on` est réservé à ce qui filtre, et les confondre ferait lire les
   récents comme des filtres actifs. */
.fs-pill {
  display: inline-flex; align-items: center; gap: 7px;
  border: 1px solid var(--line); background: var(--card); color: inherit;
  border-radius: 999px; padding: 7px 14px 7px 8px; cursor: pointer;
  font: inherit; font-size: 20px; min-height: 38px; max-width: 100%;
}
.fs-pill .diary-thumb { width: 24px; height: 24px; border-radius: 50%; }
.fs-pill .diary-thumb-empty .icon { width: 14px; height: 14px; }
.fs-pill span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fs-card {
  display: flex; align-items: center; gap: 14px; width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); color: inherit; cursor: pointer;
  padding: 16px; text-align: left; margin-bottom: 10px;
}
.fs-card-icon { display: flex; color: var(--terra); flex: none; }
.fs-card-icon .icon { width: 26px; height: 26px; }
.fs-card-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.fs-card-lines strong { font-size: 22.5px; }
.fs-card-lines small { color: var(--muted); font-size: 19px; }
.fs-card-go { color: var(--muted); font-size: 26.5px; flex: none; }

/* Le résultat de tête — la réponse la plus probable, sortie du lot pour
   qu'elle se prenne sans lire les suivantes. Il n'apparaît que sur « Tous » :
   dans une liste déjà filtrée par nature, distinguer le premier de la liste
   revient à l'afficher deux fois. */
.fs-top {
  display: flex; align-items: center; gap: 14px; width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); color: inherit; cursor: pointer;
  padding: 14px; text-align: left; margin-bottom: 20px; box-shadow: var(--shadow);
}
.fs-top .diary-thumb { width: 72px; height: 72px; border-radius: 16px; }
.fs-top .diary-thumb-empty .icon { width: 34px; height: 34px; }
.fs-top-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.fs-top-name { font-family: var(--ecriture); font-size: 27px; line-height: 1.15; }
.fs-top-note { color: var(--muted); font-size: 19.5px; }
.fs-top-side { flex: none; text-align: right; display: flex; flex-direction: column; gap: 2px; }
.fs-top-kcal { font-size: 26px; font-weight: 600; color: var(--terra); }
.fs-top-per { color: var(--muted); font-size: 18px; }

/* L'étiquette de nature. Sur `.recall`, elle se pose entre le nom et la
   valeur ; elle ne prend la place de rien puisque `.recall-text` est le seul
   élément élastique de la rangée. */
.fs-tag {
  flex: none; border-radius: 999px; padding: 3px 9px; font-size: 17px;
  background: var(--cream); color: var(--muted); border: 1px solid var(--line);
}
.fs-tag-produit { color: var(--sage); }
.fs-tag-aliment { color: var(--cinnamon); }

/* **Deux lignes, pas une.** Les libellés CIQUAL sont longs par construction, et
   `.recall-name` coupe sur une ligne : la rangée sortait « Farine de … » et
   « Pomme Gol… », qui n'identifient plus rien. Exactement le défaut déjà corrigé
   sur `.diary-label`, revenu par une autre liste — l'étiquette de nature ayant
   pris en plus sa part de largeur.

   Le nom gagne de la hauteur plutôt que de la largeur : c'est la seule des deux
   dimensions dont on dispose, à 390 px avec une valeur et une étiquette sur la
   même ligne. Scopé à cet écran — les rappels du scan et de l'assiette tiennent
   sur une ligne et n'ont pas à changer de forme. */
#fs-results .recall-name {
  white-space: normal; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.fs-section { margin-bottom: 20px; }

/* La même section, posée sous la bibliothèque — § 13. Deux corrections, et la
   seconde est la seule qui se voit vraiment :

   1. un filet et de l'air, parce que la section suit une grille de vignettes et
      répond à une **autre** question que la frappe : sans séparation elle se lit
      comme la fin de la liste de recettes ;
   2. **pas de défilement interne.** `.recall-list` est plafonnée à 38vh sur
      l'écran de recherche, où elle occupe la page entière. Ici la page défile
      déjà : un second défilement imbriqué au milieu piège le doigt, et les
      dernières lignes deviennent inatteignables sans viser la bonne zone. */
#library-foods { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }
#library-foods .recall-list { max-height: none; overflow-y: visible; }
.fs-section-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-bottom: 4px;
}
.fs-section-head h2 {
  font-family: var(--ecriture); font-size: 21px; font-weight: 600; margin: 0;
}
/* « Tout voir » bascule sur la puce de nature correspondante plutôt que
   d'allonger la section : c'est la même liste, vue sans ses voisines, et le
   filtre reste visible en haut — sans quoi on ne saurait plus pourquoi les
   produits ont disparu. */
.fs-section-all {
  border: none; background: none; padding: 0; cursor: pointer;
  color: var(--terra); font: inherit; font-size: 20px; font-weight: 600;
}
/* L'attribution ODbL — elle suit la donnée partout où elle s'affiche, et une
   liste de résultats en affiche autant qu'une fiche. Discrète mais présente :
   la licence demande la mention, pas la mise en avant. */
.fs-section-source {
  color: var(--muted); font-size: 18px; margin: 6px 0 0; padding: 0 4px;
}

/* L'état vide n'est plus un cul-de-sac : c'est le moment exact où quelqu'un
   abandonne la saisie, donc il porte les deux gestes qui la sauvent. Colonne et
   non ligne : le bouton principal doit être atteignable au pouce sans viser. */
.fs-empty { display: flex; flex-direction: column; gap: 12px; text-align: center; }
/* Le même bloc, mais suivi de résultats : il se resserre pour laisser voir la
   liste sous lui, et se lit comme un avertissement plutôt qu'un aboutissement. */
.fs-approchant { padding: 16px; margin-bottom: 16px; }
/* Les mêmes portes, mais **en pied d'une liste qui a répondu** : elles doivent
   rester atteignables sans jamais concurrencer les résultats. D'où le trait de
   séparation et le bouton de scan rendu à sa forme discrète — au-dessus, la
   liste est la réponse ; ici on ouvre seulement une issue à qui n'y a pas trouvé
   son produit. */
.fs-sorties {
  margin-top: 20px; padding-top: 16px;
  border-top: 1px solid var(--line);
}
.fs-sorties .fs-empty-lead { color: var(--muted); font-size: 20px; }
.fs-sorties .btn-primary { background: transparent; color: var(--terra); border: 1px solid var(--line); }
.fs-empty-lead { margin: 0; }
.fs-empty .btn-primary { margin: 0; min-height: 48px; }
.fs-empty .link-btn { padding: 0; }

/* ── P27 — la recherche d'aliments du mockup, 15/09 ──────────────────────────

   ⚠️ **Toutes les règles P27 sont scopées sur `#screen-foodsearch`** : les
   lignes de résultat (`listeAliments`) sont aussi peintes sous la bibliothèque,
   qui n'a pas à changer de forme — § *une règle qui ne gagne que par sa place
   se perd*. */
#screen-foodsearch .fs-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 10px;
}
/* « Plusieurs » : la sélection multiple, en pilule comme « Conseils photo » de
   P26, et pressée quand le mode est actif. */
#screen-foodsearch .fs-multi {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  padding: 8px 16px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso);
  font: inherit; font-size: 22.5px; cursor: pointer;
}
#screen-foodsearch .fs-multi .icon { width: 18px; height: 18px; color: var(--terra); }
/* R05, revue du 21/09 : un état retenu est un bouton plein. `--cinnamon`
   s'éclaircit en sombre pour se lire *sur* le chocolat — sous une encre claire
   il va donc dans le mauvais sens (2,19:1 mesuré). */
#screen-foodsearch .fs-multi[aria-pressed="true"] {
  background: var(--bouton-fond); border-color: var(--bouton-fond); color: var(--bouton-texte);
}
#screen-foodsearch .fs-multi[aria-pressed="true"] .icon { color: var(--bouton-texte); }

/* Le titre et la marmite — la même mesure que P25 et P26 : le titre prend la
   ligne, le décor se pose à côté de la phrase. */
#screen-foodsearch .fs-titre { position: relative; margin-bottom: 14px; }
#screen-foodsearch .fs-heading {
  margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400; font-size: 30.5px; line-height: 1.12;
}
#screen-foodsearch .fs-sous {
  margin: 0; padding-right: 108px; min-height: 42px;
  color: var(--muted); font-size: 22.5px; line-height: 1.4;
}
#screen-foodsearch .fs-decor {
  --decor-w: 110px;
  position: absolute; top: 22px; right: -16px; width: var(--decor-w); height: calc(var(--decor-w) * .82);
  pointer-events: none;
}
#screen-foodsearch .fs-decor img { position: absolute; display: block; }
#screen-foodsearch .fs-decor-marmite { right: 6px; bottom: 0; width: 86%; }
#screen-foodsearch .fs-decor-tomate { left: -4px; bottom: -4px; width: 30%; }
#screen-foodsearch .fs-decor-basilic { right: 10px; top: 0; width: 32%; transform: rotate(18deg); }
@media (max-width: 389px) {
  #screen-foodsearch .fs-heading { font-size: 29.5px; }
  #screen-foodsearch .fs-decor { --decor-w: 96px; }
  #screen-foodsearch .fs-sous { padding-right: 88px; }
}
@media (max-width: 359px) {
  #screen-foodsearch .fs-heading { font-size: 27.5px; }
  #screen-foodsearch .fs-decor { --decor-w: 76px; top: 20px; }
  #screen-foodsearch .fs-sous { padding-right: 66px; font-size: 21px; }
}

/* La barre en carte arrondie, et la photo carrée à côté d'elle. */
#screen-foodsearch .fs-recherche { display: flex; align-items: stretch; gap: 10px; margin-bottom: 14px; }
#screen-foodsearch .fs-bar {
  flex: 1; min-width: 0; margin: 0; border-radius: 16px; padding: 3px 6px 3px 14px;
  box-shadow: 0 1px 2px rgb(46 36 29 / 5%);
}
#screen-foodsearch .fs-bar:focus-within { border-color: var(--terra); }
#screen-foodsearch .fs-photo {
  flex: none; width: 52px; min-height: 52px; border-radius: 16px; padding: 0;
  display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--cinnamon);
}
#screen-foodsearch .fs-photo .icon { width: 24px; height: 24px; }

/* Les puces de nature, avec leur pictogramme ; la choisie en terre cuite. Elles
   défilent sur une ligne plutôt que de passer à la suivante. */
/* ⚠️ Mesuré au banc : quatre puces à pictogramme demandent 425 px pour 350 —
   « Recettes » passait à la ligne. Un choix exclusif ne défile pas (voir
   `.meal-groups`) : quatre colonnes égales, et le pictogramme ne se montre que
   là où il tient. */
#screen-foodsearch .fs-kinds {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  grid-auto-flow: column; gap: 6px; margin-bottom: 18px; overflow: visible;
}
#screen-foodsearch .fs-kinds .chip {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px;
  padding: 8px 6px; border-radius: 12px; font-size: 22px; white-space: nowrap; min-width: 0;
}
#screen-foodsearch .fs-kinds .chip[hidden] { display: none; }
@media (max-width: 429px) { #screen-foodsearch .fs-kinds .chip .icon { display: none; } }
@media (max-width: 359px) { #screen-foodsearch .fs-kinds .chip { font-size: 20px; padding: 8px 2px; } }
#screen-foodsearch .fs-kinds .chip .icon { width: 17px; height: 17px; color: var(--cinnamon); }
#screen-foodsearch .fs-kinds .chip.on { background: var(--bouton-fond); border-color: var(--bouton-fond); color: var(--bouton-texte); }
#screen-foodsearch .fs-kinds .chip.on .icon { color: #fff; }

/* Récents et fréquents : un titre serif avec son pictogramme, puis une rangée
   de cartes illustrées qui défile au doigt — cinq cartes, la sixième coupée au
   bord dit qu'il y a une suite. */
#screen-foodsearch .fs-block { margin-bottom: 20px; }
#screen-foodsearch .fs-block-title {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--ecriture); font-weight: 400; font-size: 27px; margin: 0 0 10px;
}
#screen-foodsearch .fs-block-title .icon { width: 22px; height: 22px; color: var(--cinnamon); }
#screen-foodsearch .fs-pills {
  margin: 0 -20px; padding: 2px 20px 6px;
  /* L'aimantation se cale sur le bord du conteneur, pas sur son rembourrage :
     sans ceci la première carte collait au bord de l'écran. */
  scroll-padding-inline: 20px;
  scroll-snap-type: x proximity;
}
#screen-foodsearch .fs-pills::-webkit-scrollbar { display: none; }
#screen-foodsearch .fs-pill {
  flex: none; width: 96px; min-height: 0; max-width: none;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 6px 6px 8px; border-radius: 14px; text-align: center; scroll-snap-align: start;
}
#screen-foodsearch .fs-pill .diary-thumb {
  width: 100%; height: 72px; border-radius: 10px;
  background: color-mix(in srgb, var(--terra) 7%, var(--card));
}
#screen-foodsearch .fs-pill .diary-thumb img { object-fit: contain; padding: 6px; }
#screen-foodsearch .fs-pill .diary-thumb-empty .icon { width: 30px; height: 30px; }
#screen-foodsearch .fs-pill .fs-pill-nom {
  font-size: 19.5px; line-height: 1.25; white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
#screen-foodsearch .fs-repos-vide { color: var(--muted); font-size: 22px; margin: 4px 0 18px; }
@media (hover: hover) and (pointer: fine) {
  #screen-foodsearch .fs-pill:hover { border-color: var(--terra); }
}

/* Les deux replis, côte à côte ; empilés sous 360 px. */
#screen-foodsearch .fs-cartes { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
#screen-foodsearch .fs-cartes .fs-card[hidden] + .fs-card { grid-column: 1 / -1; }
#screen-foodsearch .fs-card {
  margin: 0; padding: 12px; gap: 10px; align-items: flex-start;
  background: color-mix(in srgb, var(--terra) 6%, var(--card));
  border-color: color-mix(in srgb, var(--terra) 20%, var(--line));
}
#screen-foodsearch .fs-card-icon {
  width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;
  background: var(--card);
}
#screen-foodsearch .fs-card-icon .icon { width: 20px; height: 20px; }
#screen-foodsearch .fs-card-lines strong { color: var(--cinnamon); font-size: 22.5px; }
#screen-foodsearch .fs-card-lines small { font-size: 19.5px; line-height: 1.3; }
#screen-foodsearch .fs-card-go { align-self: center; }
@media (max-width: 359px) {
  #screen-foodsearch .fs-cartes { grid-template-columns: 1fr; }
}

/* Les résultats : le premier en carte, les sections en listes-cartes à
   vignettes, kcal à droite et chevron — la ligne ouvre la quantité. */
#screen-foodsearch .fs-top { border-radius: 16px; }
#screen-foodsearch .fs-section-head h2 { font-family: var(--ecriture); font-weight: 400; font-size: 26.5px; }
#screen-foodsearch #fs-results .recall-list {
  max-height: none; overflow: visible;
  border: 1px solid var(--line); border-radius: 16px; background: var(--card);
  padding: 0 12px;
}
#screen-foodsearch #fs-results .recall { gap: 12px; padding: 10px 0; }
#screen-foodsearch #fs-results .recall .diary-thumb { width: 44px; height: 44px; border-radius: 10px; flex: none; }
#screen-foodsearch #fs-results .recall .diary-thumb img { object-fit: contain; }
#screen-foodsearch #fs-results .recall::after {
  content: "›"; flex: none; color: var(--muted); font-size: 26.5px; line-height: 1; margin-left: -2px;
}
#screen-foodsearch #fs-results .recall b { text-align: right; white-space: nowrap; }
/* La section dit déjà « Aliments » ou « Produits » : l'étiquette répétée sur
   chaque ligne mangeait la place du nom, coupé à quatre lettres à 390 px. */
#screen-foodsearch #fs-results .fs-tag { display: none; }

/* Le dernier recours de la cascade des portions. Discret par défaut — c'est un
   manque de la fiche, pas une étape de la saisie : la feuille sert d'abord à dire
   une quantité. Il ne se déploie que si on le demande. */
.food-portion { margin: -6px 0 14px; }
.food-portion .link-btn { padding: 0; font-size: 20px; }
.food-portion-form .hint { margin: 2px 0 8px; }
.qty-row { display: flex; align-items: center; gap: 8px; }
#meal-sheet .qty-row input,
#weightedit-sheet .qty-row input { width: 6.5rem; margin-bottom: 0; text-align: center; }
/* ⛔ **Plus de `width: 100%` sur le primaire** : « Enregistrer » a reçu son
   « Annuler », et une largeur pleine sur le premier renvoie le second à la
   ligne suivante — le pied commun (`flex: 1 1 auto`) n'a plus rien à partager.
   Même défaut que celui corrigé sur `#qs-sheet`. */
/* Le champ et son erreur suivent le retrait du titre (14 px) ; le pied porte
   déjà le sien. */
#weightedit-sheet .weightedit-row, #weightedit-sheet .form-error { margin-inline: 14px; }
#weightedit-sheet .weightedit-row { margin-top: 4px; }
/* L'aperçu à droite du champ : c'est le chiffre qu'on vient corriger, il se lit
   sur la même ligne que la masse plutôt qu'en dessous. */
#weightedit-apercu { color: var(--cinnamon); font-size: 22px; }
.emenu-lead { margin: 14px 0 8px; }
/* Les quatre repas suivent le retrait du titre et de l'amorce : trois bords
   différents dans une feuille de quatre lignes se voient tout de suite. */
#emenu-sheet .meal-groups { padding-inline: 14px; }
.qty-unit { font-size: 22.5px; }
.qty-grams { margin-left: auto; color: var(--muted); font-size: 19.5px; }

/* ── Les molettes de quantité ────────────────────────────────────────────────

   Elles remplacent un champ où l'on tapait un décimal, et où « 0,5 » devenait
   « 05 » sans le moindre signe (§ « la virgule décimale » des tests). Ce n'est
   donc pas de la décoration, et **la refonte du 13/08 ne devait pas ramener un
   champ de saisie** : elle change la forme, pas le principe.

   `scroll-snap-type: y mandatory` fait tout le travail d'aimantation, sans une
   ligne de JavaScript : le navigateur cale la molette sur un cran, et on se
   contente de lire lequel. Un glissement au doigt donne donc le même résultat
   qu'un appui, ce qui n'aurait pas été vrai avec une position calculée à la main.

   Le rembourrage haut et bas vaut exactement la moitié de la hauteur visible
   moins un cran : sans lui, le premier et le dernier cran ne peuvent jamais
   atteindre le centre, donc « 0 » et la dernière valeur seraient inatteignables.
   D'où le couple `--roue-h` / `.roue-pad`, qui ne doit **jamais** se dénouer. */
.roues {
  display: grid;
  /* Une seule colonne par défaut : la quantité. L'unité se choisit sur des puces
     au-dessus, sauf dans la feuille de relecture (`.roues-2`). */
  grid-template-columns: 1fr;
  gap: 10px;
}
/* Deux colonnes — quantité, unité — dans la feuille de relecture. Elles sont
   **égales** : il y en avait trois (`1fr 1fr 1.6fr`), et c'est la colonne des
   fractions qui a disparu en fusionnant avec celle des entiers. Sur 390 px, trois
   colonnes ne montraient rien de lisible. */
/* La colonne d'unité est **un peu plus large** : elle porte à nouveau la masse
   (« portion · 30 g »), qui est la question qu'on se pose en choisissant entre
   « portion » et « tasse ». Un chiffre tient partout, ce libellé non. */
.roues-2 { grid-template-columns: 1fr 1.3fr; }
/* Un cran plus petit dans la colonne d'unité, pour que « c. à soupe · 15 g » tienne
   sans se tronquer — et pas en dessous de 16 px, le plancher du § 1. */
.roue-unite .cran { font-size: 24px; }
.roue {
  /* Cinq crans de 44 px. Il y en avait trois : on lisait trois valeurs et on
     devinait le reste, ce qui était la deuxième critique de la maquette. La place
     vient de la colonne de fractions supprimée — rien n'a été pris ailleurs. */
  --roue-h: 220px;
  height: var(--roue-h);
  overflow-y: auto;
  scroll-snap-type: y mandatory;
  /* Aimantation nette au doigt sur iOS, où le défilement par inertie continue
     sinon bien après le geste. */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  background: var(--cream);
  border-radius: 18px;
  border: 1px solid var(--line);
  position: relative;
}
.roue::-webkit-scrollbar { display: none; }
/* (220 − 44) / 2. Lié à `--roue-h` par le calcul et non recopié : les deux
   valeurs se sont déjà retrouvées à devoir changer ensemble. */
.roue-pad { height: calc((var(--roue-h) - 44px) / 2); }
.cran {
  display: block; width: 100%; height: 44px; line-height: 44px;
  scroll-snap-align: center;
  border: none; background: none; padding: 0;
  /* 16 px est le plancher du § 1 des conventions : en dessous, iOS agrandit la
     page à la mise au point et la laisse décalée. Ici on est à 19, et le cran
     retenu monte à 22 — un sélecteur se lit d'un coup d'œil. */
  font: inherit; font-size: 26px; color: var(--muted);
  text-align: center; cursor: pointer;
  /* ⛔ **`font-size` n'est pas dans la liste, et ne doit pas y revenir.** Il y
     était, et c'était la moitié des saccades de la molette : une transition sur
     `font-size` relayoute le cran **à chaque image** pendant 120 ms, et un
     glissement un peu vif en franchit vingt — donc vingt relayouts qui se
     chevauchent, sur le fil qui doit faire défiler. La taille change toujours,
     mais d'un coup : un relayout par cran franchi au lieu de sept.
     `color` et `background-color` ne peignent que, elles peuvent s'animer. */
  transition: color .12s, background-color .12s;
}
/* Le cran retenu porte une **pastille**, et c'est ce qui a remplacé la bande
   centrale décorative posée en `::before` sur `.roues`. Deux raisons : la bande
   traversait toutes les colonnes d'un seul trait, donc elle soulignait autant les
   crans voisins que le bon ; et sa teinte annoncée était `var(--ink)`, un jeton
   **qui n'existe pas dans la palette** — le cran retenu n'a donc jamais eu sa
   couleur, il n'était que plus gros. `color-mix` sur `--terra` suit les deux
   thèmes tout seul, l'accent étant redéfini en sombre. */
.cran[aria-selected="true"] {
  color: var(--terra); font-size: 27.5px; font-weight: 600;
  background: color-mix(in srgb, var(--terra) 12%, transparent);
  border-radius: 12px;
}
/* La relecture de ce qui vient d'être choisi, sortie de la grille des molettes :
   elle y était posée en `grid-column: 1 / -1`, ce qui la liait à un nombre de
   colonnes dont elle n'a que faire. */
.roue-lecture {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px;
  /* Elle ouvre la saisie exacte : c'est là qu'on appuie pour corriger un chiffre,
     et une valeur encadrée a tout d'une case de saisie. Le bouton nommé juste en
     dessous reste le contrôle accessible ; ceci est le raccourci. */
  cursor: pointer;
}
.roue-lecture b { font-size: 26.5px; }
/* Quand la relecture est seule à porter le résultat — la feuille de relecture n'a
   ni total ni macros en dessous — elle devient l'encadré central de la maquette
   plutôt qu'une ligne de texte au fil de l'eau. */
.roue-lecture-seule {
  justify-content: center;
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px; margin-bottom: 14px;
  background: var(--cream);
}
.roue-lecture-seule b { color: var(--terra); font-size: 27px; }
/* L'identité de l'ingrédient en tête de feuille : vignette, nom, nature. La
   vignette est monochrome comme partout — une couleur par famille se lirait comme
   un jugement, ce que le § 10 interdit jusque dans une pastille. */
.qs-identite {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 12px;
}
.qs-vignette { flex: none; }
.qs-vignette .diary-thumb { width: 40px; height: 40px; }
.qs-lignes { min-width: 0; }
.qs-lignes .sheet-title { margin: 0; }
.qs-nature { display: block; color: var(--muted); font-size: 19.5px; }

/* **L'action principale prend toute la largeur, le recours passe dessous.**
   `.btn-primary` n'est pas large par défaut, et `width: 100%` n'était posé que sur
   `#meal-sheet` : ici « Valider » et « Je ne sais pas » se retrouvaient côte à côte
   sur la même ligne, à se disputer le regard comme deux options de même rang. Or
   elles ne le sont pas — l'une valide, l'autre renonce à répondre.
   Les 44 px sur le lien sont la cible tactile minimale (§ 11) : `.link-btn` pose
   `min-height: 0`, ce qui convient dans un paragraphe et pas sous un bouton. */
/* `#conv-sheet` a rejoint le pied commun le 20/09 : « Annuler » et « Ajouter
   à ma liste » s'y partagent la ligne, et un `width: 100%` sur le second les
   aurait empilés. ⚠️ **`#qs-sheet .btn-primary { width: 100% }` a été retiré
   pour la même raison** : « Valider » y a reçu son « Annuler », et une largeur
   pleine sur le premier renvoyait le second à la ligne suivante. Le pied commun
   pose déjà les 44 px ; ce qui reste ici est le lien de recours, qui garde sa
   ligne entière et l'écart qui le détache des boutons de rang. */
#qs-sheet .link-btn,
#conv-sheet .link-btn {
  display: block; width: 100%;
  margin-top: 8px; min-height: 44px;
}

/* ── C23 dans le carnet — DA01, 21/09, étendu en 03d-2 ───────────────────────
   ⛔ **Le moteur ne bouge pas.** `FRACTIONS`, `cransQuantite`, `peindreRoue`,
   `poserMolettes`, l'aimantation et le couple `--roue-h` / `.roue-pad` sont
   ceux du 13/08 : ce qui suit est une peau.

   ⚠️ **Le porteur est passé de `#qs-sheet` à `.carnet` — 03d-2.** C14 partage
   exactement ce moteur (`food-roues`, `recipe-roues`) et sa feuille est entrée
   au carnet : laisser la peau sur le seul identifiant de C23 aurait mis des
   molettes crème bordées de gris au milieu d'un papier ivoire, dans la feuille
   la plus ouverte de l'application. ⛔ **Et `goal-roue-*` (P25) ne bouge
   toujours pas** : son écran n'est pas une feuille du carnet, donc aucun de ces
   sélecteurs ne l'atteint — c'est pour ça que rien ici n'est écrit sur `.roues`
   ou `.cran` nus. Le rendu de C23 est **inchangé**, `#qs-sheet` portant déjà
   `.carnet` depuis les pilotes. */
/* Les deux molettes vivent dans **un** cadre ivoire, avec leurs étiquettes en
   première rangée de la grille. */
.carnet .roues { gap: 4px 10px; padding: 10px 14px 6px; }
/* ⚠️ **L'étiquette d'unité disparaît avec sa molette.** `poserMolettesUnite`
   masque la colonne d'unité quand l'aliment n'en propose qu'une et rend la
   grille à une colonne (C14) ; les deux étiquettes se retrouvaient alors
   empilées au-dessus des seules quantités. C23 déclare `.roues-2` en dur et
   n'est pas concernée. */
.carnet .roues:not(.roues-2) .carnet-etiquette + .carnet-etiquette { display: none; }
.carnet .roue { background: none; border: none; border-radius: 14px; }
/* Le cran retenu est la valeur qu'on vient lire : bande sauge, encre du carnet,
   et le degré « nombre » de l'échelle. Les voisins restent lisibles, plus
   discrets — l'encre douce, pas le gris des écrans (3,84:1 sur cet ivoire). */
.carnet .cran { color: var(--carnet-encre-douce); }
.carnet .cran[aria-selected="true"] {
  background: var(--carnet-sauge); color: var(--carnet-encre);
  border: 1px solid var(--carnet-sauge-filet); border-radius: 12px;
  font-size: 34px; font-weight: var(--g-titre);
}
/* L'unité garde un cran plus petit : « c. à soupe · 15 g » doit tenir. */
.carnet .roue-unite .cran[aria-selected="true"] { font-size: 26px; }
/* La relecture — c'est elle qui ouvre la saisie exacte — devient une ligne
   calme sous le cadre, et non un second encadré qui concurrencerait la molette. */
.carnet .roue-lecture-seule {
  margin-top: 12px; background: none; border: 1px dashed var(--carnet-filet);
  color: var(--carnet-encre);
}
/* ⚠️ **`--cinnamon` et non `--bouton-fond` — corrigé le 22/09, mesuré.**
   `--bouton-fond` est le **remplissage** d'un bouton (règle de couleur du lot
   03a) ; employé comme encre sur le papier sombre il rend **3,09:1**, sous le
   seuil d'un libellé comme sous celui d'un objet. `--cinnamon` est la terre
   cuite *d'encre* de l'application — celle de `.food-total b` depuis le
   13/08 — et rend 5,43:1 en clair, 7,06:1 en sombre. ⛔ **En clair les deux
   jetons valent le même #A94A2A** : le rendu de C23 ne change qu'en thème
   sombre, et seulement pour s'y lire. Le banc composants ne l'avait pas vu :
   il mesure les **commandes**, et un nombre relu n'en est pas une. */
.carnet .roue-lecture-seule b { color: var(--cinnamon); }
/* La ligne de relecture de C14 : même encre, et le nombre à la teinte d'action. */
.carnet .roue-lecture { color: var(--carnet-encre); }
.carnet .roue-lecture b { color: var(--cinnamon); font-weight: var(--g-titre); }
.carnet .qty-unit { color: var(--carnet-encre); font-size: var(--t-unite); }
.carnet .qty-grams { color: var(--carnet-encre-douce); font-size: var(--t-appoint); }
#qs-sheet .qs-nature { color: var(--carnet-encre-douce); }
/* La vignette de l'aliment est la seule illustration de cette feuille : elle
   vient du serveur (famille ou dessin d'ingrédient), pas du décor. */
#qs-sheet .qs-vignette .diary-thumb { width: 44px; height: 44px; }

/* Le total est ce qu'on vient chercher : il porte la taille et la teinte du
   chiffre, les macros restent en appui derrière. */
/* ⚠️ **La hauteur est réservée, pleine ou vide.** `renderFoodTotal` vide cette
   ligne quand la quantité est nulle : elle apparaissait donc en passant de 0 à 1,
   la feuille grandissait d'autant, et comme elle est ancrée en bas, **tout le
   contenu sautait vers le haut** au premier tour de molette. Signalé le 13/08.
   Réserver la place rend l'apparition sans effet sur la mise en page. */
.food-total {
  margin: 12px 0 10px; color: var(--muted); font-size: 20px;
  /* 33 px et non 30 : la ligne remplie mesure 32,5 px, le `<b>` des kcal étant en
     serif 21 px. Mesuré, pas estimé — 30 px laissaient 3 px de saut résiduel, ce
     qui suffit à se voir sur une feuille ancrée en bas. */
  min-height: 33px;
}
.food-total b {
  font-family: var(--ecriture); font-size: 27px; color: var(--cinnamon);
  margin-right: 3px;
}
#meal-sheet .link-btn { display: block; margin: 12px auto 0; }

/* ── Scanner mon assiette (§ 8) — P26, refonte du 15/09 ─────────────────── */

/* ⚠️ **Toutes les règles P26 sont scopées sur `#screen-plate`** — § *une règle
   qui ne gagne que par sa place se perd*. `.plate-screen` reste le nom que la
   règle de recadrage des couches fixes connaît, en tête de la feuille. */
.plate-screen {
  /* Largeur et centrage : recadrage des couches fixes, en haut de la feuille. */
  /* M01 : sous la bande « Pas de connexion » (z 70), jamais dessous. */
  position: fixed; top: var(--hors-reseau-h, 0px); bottom: 0; z-index: 45; overflow-y: auto;
  background: var(--cream);
  padding: 14px 20px calc(24px + env(safe-area-inset-bottom));
}
/* ⚠️ **Au-delà de 820 px, la colonne se décalait à droite** — relevé le 22/09,
   très visible sur l'écran caméra (05C). Deux causes. (1) La règle commune des
   couches fixes centre sur la colonne **à droite du rail** (`--rail-w`) ; or
   cet écran masque la barre d'onglets, donc le rail : il se centre sur la
   fenêtre. (2) La gouttière commune des paliers larges, `(100 % - --lecture-w)
   / 2`, prend ses 100 % sur le bloc conteneur — la **fenêtre** pour une couche
   fixe, pas l'écran : elle rognait la colonne de lecture (424 px au lieu de 700
   à 1 280 px). Ici, la largeur de l'écran est écrite en toutes lettres. Les
   autres couches fixes gardent la règle commune. */
#screen-plate { left: 50%; width: min(var(--app-w), 100%); }
@media (min-width: 820px) {
  #screen-plate {
    padding-inline: max(20px, calc((min(var(--app-w), 100vw) - var(--lecture-w)) / 2));
  }
}
#screen-plate .plate-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 10px;
}
/* « Conseils photo » : une pilule qui replie et déplie les quatre tuiles. Un
   bouton qui ne change rien est pire qu'un bouton absent (§ *où c'est rangé*). */
#screen-plate .plate-conseils {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px;
  padding: 8px 16px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso);
  font: inherit; font-size: 22.5px; cursor: pointer;
}
#screen-plate .plate-conseils .icon { width: 20px; height: 20px; color: var(--terra); }
#screen-plate .plate-conseils[aria-expanded="true"] {
  border-color: color-mix(in srgb, var(--terra) 40%, var(--line));
  background: color-mix(in srgb, var(--terra) 9%, var(--card));
}

/* Le titre et sa phrase, la marmite à droite — le même décor que P25, avec la
   même mesure : le titre réserve sa largeur au dessin. */
#screen-plate .plate-titre { position: relative; margin-bottom: 14px; }
/* ⚠️ Mesuré au banc : « Photographie ton repas » fait 290 px en 28 px et passait
   sur deux lignes à côté de la marmite. Le titre prend donc toute la ligne, en
   27 px, et le décor se pose à côté de la phrase — comme sur la maquette, où la
   marmite descend sous le haut du titre. */
#screen-plate .plate-heading {
  margin: 0 0 6px;
  font-family: var(--ecriture); font-weight: 400; font-size: 30.5px; line-height: 1.12;
}
#screen-plate .plate-sous {
  margin: 0; padding-right: 108px;
  color: var(--muted); font-size: 22.5px; line-height: 1.4;
}
#screen-plate .plate-decor {
  --decor-w: 118px;
  position: absolute; top: 26px; right: -16px; width: var(--decor-w); height: calc(var(--decor-w) * .82);
  pointer-events: none;
}
#screen-plate .plate-decor img { position: absolute; display: block; }
#screen-plate .plate-decor-marmite { right: 6px; bottom: 0; width: 86%; }
#screen-plate .plate-decor-tomate { left: -4px; bottom: -4px; width: 30%; }
#screen-plate .plate-decor-basilic { right: 10px; top: 0; width: 32%; transform: rotate(18deg); }
/* Sur le résultat, la place du décor est celle de la photo : le titre reprend
   toute la ligne. */
#screen-plate.resultat .plate-decor, #screen-plate.lecture .plate-decor { display: none; }
#screen-plate.resultat .plate-sous { padding-right: 0; }
#screen-plate .plate-sous { min-height: 64px; }
#screen-plate.resultat .plate-sous, #screen-plate.lecture .plate-sous { min-height: 0; padding-right: 0; }
@media (max-width: 389px) {
  #screen-plate .plate-heading { font-size: 29.5px; }
  #screen-plate .plate-decor { --decor-w: 104px; top: 24px; }
  #screen-plate .plate-sous { padding-right: 92px; }
}
@media (max-width: 359px) {
  #screen-plate .plate-heading { font-size: 27.5px; }
  #screen-plate .plate-decor { --decor-w: 80px; top: 22px; }
  #screen-plate .plate-sous { padding-right: 70px; font-size: 21px; }
}

/* Les quatre conseils : un pictogramme dans un rond pâle, le titre, la phrase.
   Quatre colonnes, qui passent à deux sous 360 px — c'est la même grille. */
/* ⚠️ Retour de Luca du 15/09 : « les boutons prennent énormément de place ».
   Pictogramme de 40 px et une phrase courte : les conseils se lisent en un coup
   d'œil, ils ne repoussent pas le cadre sous le pli. */
#screen-plate .plate-tips {
  list-style: none; margin: 0 0 12px; padding: 0;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px;
  text-align: center;
}
#screen-plate .plate-tips li { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; }
#screen-plate .plate-tips b { font-size: 19.5px; font-weight: 600; line-height: 1.2; }
#screen-plate .plate-tips small { color: var(--muted); font-size: 18px; line-height: 1.25; }
#screen-plate .plate-tips .plate-tip-icone { width: 40px; height: 40px; }
#screen-plate .plate-tips .plate-tip-icone .icon { width: 21px; height: 21px; }
#screen-plate .plate-tip-icone {
  flex: none; width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--terra) 20%, var(--cream)); color: var(--cinnamon);
}
#screen-plate .plate-tip-icone .icon { width: 26px; height: 26px; }
@media (max-width: 359px) {
  #screen-plate .plate-tips { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── L'écran de capture — R11, 22/09 ; plein cadre, 05C ────────────────────────

   Retour de Luca sur iPhone du 22/09 (L02/L03) : « le cadre est joli mais
   beaucoup trop petit ». Le flux prenait la moitié de la hauteur : en-tête,
   titre, barre et « Saisir un code-barres à la main » mangeaient le reste.

   → À l'étape caméra, **le cadre est l'écran** : `.plate-capture` prend la
   hauteur visible (rembourrages et zones sûres déduits) et le cadre la remplit.
   Retour et « Conseils photo » flottent sur son bord haut, la barre (galerie,
   déclencheur, recherche) sur son bord bas, sur un dégradé. Les quatre se
   posent dans **la même case de grille** que le cadre : aucun calcul de
   hauteur, et une carte ajoutée au-dessus — le suivi refusé — prend sa rangée
   sans pousser la barre sous le pli. Le titre reste le titre de la page pour
   les lecteurs d'écran ; à l'œil, la pastille d'état du cadre en tient lieu.
   Conseils, diamètre, astuce, code à la main et « Déjà mangé » suivent **sous**
   le cadre, au défilement ; « Conseils photo » y conduit.
   Plancher de 440 px : sur un écran très bas (paysage), la page défile plutôt
   que d'écraser le cadre. Hors étape caméra, la hauteur redevient naturelle. */
#screen-plate.camera .plate-capture {
  --barre-h: 118px;
  display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr);
  height: 100%; min-height: 440px;
}
/* ⚠️ Le rembourrage de `.plate-screen` perdait déjà contre `.screen:not(.pane)`
   (28 px en haut, la réserve de la barre d'onglets en bas) — une barre que cet
   écran masque. À l'étape caméra, l'identifiant le reprend : le cadre touche
   presque les bords, sous la barre d'état et au-dessus de l'indicateur
   d'accueil (`env(safe-area-inset-*)`). Les autres étapes gardent le
   rembourrage commun. */
#screen-plate.camera {
  padding: calc(8px + env(safe-area-inset-top)) 8px calc(8px + env(safe-area-inset-bottom));
}
/* Au-delà de 820 px, la gouttière large (voir `#screen-plate` plus haut) :
   le cadre garde la colonne de lecture, les conseils dessous aussi. */
@media (min-width: 820px) {
  #screen-plate.camera {
    padding-inline: max(20px, calc((min(var(--app-w), 100vw) - var(--lecture-w)) / 2));
  }
}
#screen-plate.camera .plate-refus { grid-row: 1; margin: 0 0 8px; }
#screen-plate.camera .plate-head,
#screen-plate.camera .plate-view,
#screen-plate.camera .plate-barre { grid-row: 2; grid-column: 1; }
#screen-plate.camera .plate-titre {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
/* Retour et « Conseils photo » posés sur le flux : fond sombre opaque à 70 %,
   texte ivoire — lisibles sur n'importe quelle image comme sur le cadre pâle
   de l'attente. La bande elle-même laisse passer les touches. */
#screen-plate.camera .plate-head {
  align-self: start; position: relative; z-index: 2;
  margin: 0; padding: 10px; pointer-events: none;
}
#screen-plate.camera .plate-head > * { pointer-events: auto; }
#screen-plate.camera #plate-close,
#screen-plate.camera .plate-conseils {
  min-height: 44px; border: 1px solid rgb(255 248 239 / 30%);
  background: rgb(33 25 20 / 70%); color: #fff8ef;
}
#screen-plate.camera #plate-close { width: 44px; height: 44px; }
#screen-plate.camera .plate-conseils { padding: 6px 14px; font-size: 21px; }
#screen-plate.camera .plate-conseils .icon { color: #ffc9a3; }
#screen-plate.camera .plate-conseils[aria-expanded="true"] {
  border-color: rgb(255 201 163 / 60%); background: rgb(33 25 20 / 78%);
}

/* Le cadre : le flux vivant, puis la photo prise. Hors caméra (attente, refus,
   pas de caméra), un fond pâle au bord pointillé et l'appareil dessiné ; flux
   ou photo, un fond noir sans bord. */
#screen-plate .plate-view {
  position: relative; display: block; width: 100%; padding: 0; overflow: hidden;
  border-radius: 22px; aspect-ratio: 4 / 3; margin: 0 0 12px;
  border: 2px dashed color-mix(in srgb, var(--terra) 45%, var(--line));
  background: color-mix(in srgb, var(--terra) 6%, var(--card));
  color: var(--cinnamon);
}
#screen-plate.camera .plate-view { align-self: stretch; min-height: 0; aspect-ratio: auto; margin: 0; }
#screen-plate .plate-view.vivante, #screen-plate .plate-view.pleine { border: none; background: #1c1612; }
#screen-plate .plate-view video,
#screen-plate .plate-view img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
#screen-plate .plate-view video { opacity: 0; transition: opacity .2s ease; }
#screen-plate .plate-view.vivante video { opacity: 1; }
#screen-plate .plate-cadre-invite {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
}
#screen-plate .plate-cadre-invite .icon { width: 48px; height: 48px; opacity: .75; }
#screen-plate .plate-cadre-invite[hidden],
#screen-plate .plate-view.vivante .plate-cadre-invite,
#screen-plate .plate-view.pleine .plate-cadre-invite { display: none; }

/* Les quatre angles de visée, seulement sur le flux vivant. À l'étape caméra,
   ils bornent le **centre** du cadre : c'est la zone que le lecteur des
   iPhone décode (`ZONE_LECTURE`, 70 % × 50 % de l'image, 05C) — viser là,
   c'est viser ce qui se lit. */
#screen-plate .plate-visee { position: absolute; inset: 18px 18px 72px; pointer-events: none; display: none; }
#screen-plate.camera .plate-visee { inset: 25% 12%; }
#screen-plate .plate-view.vivante:not(.pleine) .plate-visee { display: block; }
#screen-plate .plate-visee i {
  position: absolute; width: 34px; height: 34px;
  border: 3px solid rgb(255 255 255 / 88%); border-radius: 4px;
}
#screen-plate .plate-visee i:nth-child(1) { top: 0; left: 0; border-right: 0; border-bottom: 0; border-top-left-radius: 14px; }
#screen-plate .plate-visee i:nth-child(2) { top: 0; right: 0; border-left: 0; border-bottom: 0; border-top-right-radius: 14px; }
#screen-plate .plate-visee i:nth-child(3) { bottom: 0; left: 0; border-right: 0; border-top: 0; border-bottom-left-radius: 14px; }
#screen-plate .plate-visee i:nth-child(4) { bottom: 0; right: 0; border-left: 0; border-top: 0; border-bottom-right-radius: 14px; }

/* Ce que fait la caméra, en bas du cadre : une pastille sombre lisible sur
   n'importe quelle image (texte clair sur fond opaque, jamais sur la vidéo). */
#screen-plate .plate-etat {
  position: absolute; left: 12px; right: 12px; bottom: 12px; margin: 0 auto; width: fit-content; max-width: calc(100% - 24px);
  padding: 7px 14px; border-radius: 16px;
  background: rgb(33 25 20 / 78%); color: #fff8ef;
  font-size: 19px; line-height: 1.3; text-align: center;
}
#screen-plate .plate-view:not(.vivante):not(.pleine) .plate-etat { background: var(--card); color: var(--espresso); border: 1px solid var(--line); }
/* La barre flotte sur le bas du cadre : état et avis se posent au-dessus. */
#screen-plate.camera .plate-etat,
#screen-plate.camera .plate-avis { bottom: calc(var(--barre-h) + 6px); }
/* Ce qui demande un geste : une carte posée sur le bas du cadre. */
#screen-plate .plate-avis {
  position: absolute; left: 12px; right: 12px; bottom: 12px;
  padding: 12px 14px; border-radius: 18px;
  background: var(--card); color: var(--espresso); border: 1px solid var(--line);
  box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
  font-size: 20px; line-height: 1.35;
}
#screen-plate .plate-avis p { margin: 0 0 8px; }
#screen-plate .plate-avis-actions { display: flex; flex-wrap: wrap; gap: 8px; }
#screen-plate .plate-avis-actions button { flex: 1 1 auto; min-height: 44px; margin: 0; padding: 0 14px; font-size: 20px; }

/* Pendant la lecture, la photo se voile : un calque de `background` sur une
   couche immobile, pas un flou (§ 6). */
#screen-plate.lecture .plate-view::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(transparent 40%, rgb(46 36 29 / 45%));
}

/* La barre de la maquette : Galerie, déclencheur rond, recherche. Trois
   colonnes, le déclencheur toujours centré ; les deux côtés se partagent le
   reste et leurs libellés passent sur deux lignes plutôt que de déborder. */
#screen-plate .plate-barre {
  flex: none;
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: 10px; padding: 12px 0 2px;
}
/* Sur le flux : un dégradé sombre porte la barre, les coins suivent le cadre.
   « Galerie » passe en ivoire ; la pilule de recherche garde sa surface. */
#screen-plate.camera .plate-barre {
  align-self: end; position: relative; z-index: 2; box-sizing: border-box;
  min-height: var(--barre-h); padding: 26px 12px 10px; border-radius: 0 0 22px 22px;
  background: linear-gradient(rgb(20 15 12 / 0%), rgb(20 15 12 / 72%) 45%);
}
#screen-plate.camera .plate-pellicule { color: #fff8ef; }
#screen-plate .plate-pellicule {
  justify-self: start;
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px;
  min-width: 64px; min-height: 44px; padding: 2px 4px; border: none; background: none;
  color: var(--espresso); font: inherit; font-size: 19px; cursor: pointer;
}
#screen-plate .plate-pellicule-icone {
  width: 50px; height: 50px; border-radius: 14px; display: grid; place-items: center;
  border: 1px solid var(--line); background: var(--card); color: var(--cinnamon);
}
#screen-plate .plate-pellicule-icone .icon { width: 24px; height: 24px; }
/* Le déclencheur : un disque terre cuite dans son anneau, 72 px. */
#screen-plate .plate-shoot {
  width: 74px; height: 74px; padding: 5px; border-radius: 50%;
  border: 3px solid var(--terra); background: var(--cream); cursor: pointer;
}
#screen-plate .plate-shoot-disque { display: block; width: 100%; height: 100%; border-radius: 50%; background: var(--terra); }
#screen-plate .plate-shoot:active .plate-shoot-disque { transform: scale(.94); }
#screen-plate .plate-rechercher {
  justify-self: end;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 48px; max-width: 100%; padding: 6px 11px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--terra) 35%, var(--line));
  background: color-mix(in srgb, var(--terra) 10%, var(--card));
  color: var(--espresso); font: inherit; font-size: 19px; line-height: 1.15; text-align: left; cursor: pointer;
}
#screen-plate .plate-rechercher .icon { width: 20px; height: 20px; flex: none; color: var(--terra); }
/* Filet de sécurité du texte agrandi : un mot trop long se coupe plutôt que de
   faire défiler l'écran en largeur. */
#screen-plate .plate-rechercher span { min-width: 0; overflow-wrap: anywhere; }
/* 320 px : 93 px par côté. La loupe cède sa place au libellé, le déclencheur
   perd 8 px — mesuré au banc, « Rechercher » débordait de 4 px. */
@media (max-width: 359px) {
  #screen-plate .plate-shoot { width: 66px; height: 66px; }
  #screen-plate .plate-rechercher { padding: 6px 10px; font-size: 18.5px; }
  #screen-plate .plate-rechercher .icon { display: none; }
}
#screen-plate .plate-pellicule:focus-visible, #screen-plate .plate-shoot:focus-visible,
#screen-plate .plate-rechercher:focus-visible { outline: 3px solid var(--terra); outline-offset: 3px; }

#screen-plate .plate-code {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 44px; margin: 8px 0 10px; padding: 0;
  border: none; background: none; color: var(--cinnamon);
  font: inherit; font-size: 22px; cursor: pointer;
}
#screen-plate .plate-code .icon { width: 18px; height: 18px; }
#screen-plate > .form-error { margin: 10px 0 0; }

/* Le suivi refusé : une carte qui dit ce qui va se passer, avant de payer une
   lecture pour rien. */
#screen-plate .plate-refus {
  margin: 0 0 12px; padding: 12px 14px; border-radius: 14px;
  background: color-mix(in srgb, var(--danger) 8%, var(--card));
  border: 1px solid color-mix(in srgb, var(--danger) 30%, var(--line));
  font-size: 21px; line-height: 1.45;
}
#screen-plate .plate-refus p { margin: 0 0 4px; }

/* L'impasse : ce que le modèle n'a pas su lire, et la porte du dessous. Encadrée
   parce qu'elle porte deux choses qui vont ensemble — la raison et le geste — et
   qu'elle apparaît entre le déclencheur et « Déjà mangé », où rien ne les relierait. */
#screen-plate .plate-impasse {
  margin: 0 0 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  text-align: center;
}
#screen-plate .plate-impasse .form-error { margin: 0 0 6px; }

/* Le diamètre d'assiette : une carte, le pictogramme à gauche, le champ à
   droite — la forme des cartes de mesure de P25. ⚠️ Le champ garde ses 16 px
   (§ 1) : ce sont le libellé et les gouttières qui cèdent, jamais le texte. */
#screen-plate .plate-scale {
  display: flex; align-items: center; gap: 12px; margin: 0 0 12px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 16px;
  padding: 12px 14px;
}
#screen-plate .plate-scale:focus-within { border-color: var(--terra); }
#screen-plate .plate-scale .plate-tip-icone { width: 44px; height: 44px; }
#screen-plate .plate-scale .plate-tip-icone .icon { width: 22px; height: 22px; }
#screen-plate .plate-scale-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#screen-plate .plate-scale label { font-size: 22.5px; font-weight: 600; }
#screen-plate .plate-scale small { color: var(--muted); font-size: 19px; line-height: 1.35; }
#screen-plate .plate-scale .scan-qty { flex: none; }
#screen-plate .plate-scale input {
  margin-bottom: 0; width: 4.4rem; font-size: 24px;
  background: var(--cream); color: var(--espresso); border: 1px solid var(--line); border-radius: 10px;
}
@media (max-width: 359px) {
  #screen-plate .plate-scale { flex-wrap: wrap; }
  #screen-plate .plate-scale .scan-qty { margin-left: 56px; }
}

/* L'astuce, comme sur la maquette : la marmite, un titre serif, une phrase. */
#screen-plate .plate-astuce {
  display: flex; align-items: center; gap: 12px; margin: 0 0 6px;
  padding: 12px 14px; border-radius: 16px;
  border: 1px solid var(--line); background: var(--card);
}
#screen-plate .plate-astuce img { flex: none; width: 64px; height: 64px; object-fit: contain; }
#screen-plate .plate-astuce b {
  display: block; font-family: var(--ecriture); font-weight: 400; font-size: 25px; margin-bottom: 2px;
}
#screen-plate .plate-astuce p { margin: 0; color: var(--muted); font-size: 21px; line-height: 1.4; }
/* ── Rappeler ce qui a déjà été enregistré ───────────────────────────────────

   Le repas photographié (§ 14) et le produit scanné (§ 13 bis) posent la même
   question — « c'est encore la même chose qu'hier » — et méritent donc le même
   objet : une liste sous le geste principal, jamais au-dessus. Rappeler est un
   raccourci ; scanner reste le geste. */
.recall-block { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 14px; }
.recall-block h2 {
  font-size: 21px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 8px;
}
/* Bornée : la feuille de scan s'ouvre déjà avec un aperçu caméra, et une liste
   qui pousse le tout hors de l'écran sur un petit téléphone ne rappelle rien. */
.recall-list { max-height: 38vh; overflow-y: auto; }
/* En **tête** d'une feuille, et non en pied : le filet et la marge du dessus ne
   séparent alors plus rien — ils décollent le bloc du titre. On les déplace en
   dessous, où ils font ce qu'on leur demande : séparer les propositions des
   quatre sources qui suivent. */
#meal-recurring-block {
  margin-top: 0; margin-bottom: 6px;
  border-top: none; border-bottom: 1px solid var(--line);
  padding-top: 0; padding-bottom: 8px;
}
.recall {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 12px 0; border: none; border-top: 1px solid var(--line);
  background: none; color: inherit; text-align: left; font: inherit;
  min-height: 52px; cursor: pointer;
}
.recall:first-child { border-top: none; }
.recall-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.recall-name { font-size: 22.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recall-note { color: var(--muted); font-size: 18px; }
.recall b { font-size: 21px; font-weight: 600; color: var(--cinnamon); }

/* L'attente : la marmite souriante qui se balance doucement — un `transform`,
   jamais une propriété de mise en page (§ 6), et l'échappatoire plus bas. */
#screen-plate .plate-loading { text-align: center; padding: 6px 0 30px; }
#screen-plate .plate-loading-marmite {
  width: 96px; max-width: 40%; display: block; margin: 0 auto;
  animation: plate-mijote 2.4s ease-in-out infinite;
}
@keyframes plate-mijote {
  0%, 100% { transform: translateY(0) rotate(-1.5deg); }
  50% { transform: translateY(-6px) rotate(1.5deg); }
}
@media (prefers-reduced-motion: reduce) { #screen-plate .plate-loading-marmite { animation: none; } }
#screen-plate .plate-loading p { font-family: var(--ecriture); font-size: 27px; margin: 18px 0 4px; }
#screen-plate .plate-loading small { color: var(--muted); }

/* Le résultat : la photo, puis la carte du total en serif. */
#screen-plate .plate-total { text-align: center; display: grid; gap: 4px; border-radius: 16px; }
#screen-plate .plate-label { margin: 0; color: var(--muted); font-size: 20px; }
#screen-plate .plate-total strong { font-family: var(--ecriture); font-size: 34.5px; font-weight: 400; }
#screen-plate .plate-total span { color: var(--muted); font-size: 20px; }
#screen-plate .plate-label.warn { color: var(--danger); font-weight: 600; }

/* ⚠️ Mesuré au banc à 390 px : cinq colonnes sur une rangée laissaient 120 px
   au nom, et « Saumon, cru, élevage — pavé standard… » tenait sur six lignes.
   Le nom et sa base prennent la première rangée, la masse et les kcal la
   seconde, alignées à droite sous la poubelle. */
#screen-plate .plate-item {
  display: grid; align-items: center; column-gap: 10px; row-gap: 6px;
  grid-template-columns: 28px minmax(0, 1fr) auto 44px;
  grid-template-areas: "vignette texte texte retirer" ". saisie kcal .";
  padding: 12px 0; border-top: 1px solid var(--line);
}
#screen-plate .plate-item > .diary-thumb { grid-area: vignette; align-self: start; margin-top: 2px; }
#screen-plate .plate-item > .plate-item-text { grid-area: texte; }
#screen-plate .plate-item > .plate-amount { grid-area: saisie; justify-self: end; }
#screen-plate .plate-item > .plate-kcal { grid-area: kcal; }
#screen-plate .plate-item > .shop-del { grid-area: retirer; align-self: start; }
#screen-plate .plate-item:first-child { border-top: none; }
/* La vignette d'une ligne d'assiette — mêmes dimensions que celle d'un
   ingrédient (`.ing .diary-thumb`), parce que c'est le même objet vu sur un
   autre écran et que deux tailles se remarqueraient en passant de l'un à
   l'autre. `flex: none` : elle ne se comprime pas quand le libellé est long. */
#screen-plate .plate-item .diary-thumb { width: 28px; height: 28px; border-radius: 9px; flex: none; }
#screen-plate .plate-item .diary-thumb-empty .icon { width: 17px; height: 17px; }
#screen-plate .plate-item-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* Le nom est un bouton, mais il ne doit pas en avoir l'air : c'est d'abord un
   libellé, et une ligne de résultat couverte de boutons se lit mal. Le crayon
   discret suffit à dire qu'on peut y toucher. */
#screen-plate .plate-item-name {
  display: flex; align-items: center; gap: 6px;
  border: none; background: none; padding: 0; margin: 0;
  color: inherit; font: inherit; font-size: 22.5px; text-align: left;
  cursor: pointer;
}
#screen-plate .plate-item-pencil { display: flex; color: var(--sand); flex: none; }
#screen-plate .plate-item-pencil .icon { width: 13px; height: 13px; }
#screen-plate .plate-item-name:hover .plate-item-pencil { color: var(--terra); }
#screen-plate .plate-item-basis { color: var(--muted); font-size: 18px; }
#screen-plate .plate-candidates { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
#screen-plate .plate-candidates .chip { padding: 8px 12px; font-size: 20px; min-height: 44px; }
/* Le champ portait un nombre nu : l'unité ne vivait que dans l'`aria-label`,
   donc nulle part pour qui regarde l'écran. « 80 » ne dit pas s'il faut penser
   en grammes ou en millilitres, et une boisson se saisissait en grammes. */
#screen-plate .plate-amount { display: flex; align-items: center; gap: 5px; flex: none; }
#screen-plate .plate-item input {
  width: 5rem; text-align: center; border: 1px solid var(--line);
  border-radius: 10px; padding: 8px 6px; background: var(--card); color: inherit;
  font-size: 24px; min-height: 42px;
}
#screen-plate .plate-unit { color: var(--muted); font-size: 19.5px; width: 1.6rem; }
/* Bascule g ↔ ml : soulignée en pointillé, le signe conventionnel d'« il y a
   quelque chose à faire ici », et dotée d'une cible tactile décente. */
#screen-plate .plate-unit.switchable {
  cursor: pointer; color: var(--terra);
  border-bottom: 1px dotted currentColor;
  padding: 6px 2px; margin: -6px 0;
}
#screen-plate .plate-item .plate-kcal { font-size: 21px; font-weight: 600; color: var(--cinnamon); width: 4.6rem; text-align: right; }
#screen-plate .plate-item .shop-del { flex: none; }

/* Au-delà du palier du § 10, l'écran garde une largeur de lecture — il est
   inscrit au registre des écrans qui ne s'étirent pas : une caméra de 940 px
   de large n'aide personne à cadrer une assiette. */

/* ── Mode cuisine (§ 4) — P05, refonte du 10/09 ──────────────────────────── */

/* Plein écran, par-dessus tout, barre d'onglets comprise : on cuisine avec le
   téléphone posé à 60 cm, pas en naviguant. */
/* ⚠️ **Trois déclarations sur quatre étaient perdantes, et rien ne le disait.**
   `.screen:not(.pane)` vaut (0,2,0) et `.cook-screen` (0,1,0) : le mode cuisine
   héritait donc, **depuis toujours**, du rembourrage des écrans ordinaires
   (`padding-bottom: var(--tabbar-h) + 32px`) et de leur `overflow-y: auto`.
   Mesuré au banc à 390 × 844 : le pied s'arrêtait à **744 px pour 844**, c'est-à-dire
   100 px réservés à une barre d'onglets que ce mode **masque** ; et l'écran
   entier pouvait défiler, ce qui est exactement ce que sa disposition en trois
   bandes existe pour empêcher.

   C'est le § *une règle qui ne gagne que par sa place se perd*, à ceci près
   qu'ici elle ne perdait pas par sa place mais par sa **spécificité** — et le
   remède est le même : la règle se scope sur son contexte, donc elle gagne où
   qu'on la déplace. ⛔ Et surtout pas `!important`, qui gagne aussi et gagne
   contre tout, y compris contre la règle qu'on écrira demain. */
.cook-screen {
  /* Largeur et centrage : recadrage des couches fixes, en haut de la feuille.
     ⚠️ **`position: fixed` reste ici**, et pas dans la règle scopée ci-dessous :
     c'est ce sélecteur-là que la règle de recadrage nomme, et le garde-fou des
     couches fixes compare les sélecteurs mot pour mot. Une couche qui se
     déclarerait fixe sous un autre nom serait une couche de plus à recadrer. */
  /* M01 : la bande hors connexion (z 70) couvrait la moitié de « Fermer ».
     L'écran commence sous elle ; `--hors-reseau-h` vaut 0 en ligne. */
  position: fixed; top: var(--hors-reseau-h, 0px); bottom: 0; z-index: 45;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--cream);
}
/* Les deux seules propriétés que `.screen:not(.pane)` redéclare, et donc les
   deux seules qui aient besoin de gagner par spécificité. */
.screen.cook-screen:not(.pane) {
  padding: calc(14px + env(safe-area-inset-top)) 20px calc(14px + env(safe-area-inset-bottom));
  overflow: hidden;
}
/* ⚠️ **Et le rythme vertical des écrans n'est pas celui d'un mode.**
   `.screen > * + *` pose 24 px entre les blocs — juste sur une page qui défile,
   ruineux ici : cinq bandes en portaient 96 px, sur l'écran dont chaque pixel
   est un pixel d'instruction. Le `gap` de 12 px suffit et se lit au même
   endroit que la disposition. */
/* ⚠️ **Écrite `.cook-screen > * + *`, elle perdait** — même spécificité que
   `.screen > * + *`, qui vit mille deux cents lignes plus bas et gagne donc par
   l'ordre du fichier. C'est le § *une règle qui ne gagne que par sa place se
   perd*, et il s'est refermé à l'écriture de sa propre correction : mesuré, les
   quatre bandes gardaient leurs 24 px, soit **96 px** pris à l'instruction. */
.cook-screen.screen > * + * { margin-top: 0; }

.cook-head { display: flex; align-items: center; gap: 8px; flex: none; }
/* ⚠️ **Un seul objet, pas une flèche et un mot.** Deux cibles pour un geste,
   c'est deux définitions du même retour — et c'est celle qu'on emprunte le
   moins qui garderait l'ancien comportement. Le libellé fait donc partie du
   bouton. */
.cook-quitter {
  display: inline-flex; align-items: center; gap: 8px; margin-right: auto;
  min-height: 44px; padding: 8px 14px 8px 12px;
  border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--espresso);
  font-size: 21px; font-weight: 600; white-space: nowrap; cursor: pointer;
}
.cook-quitter .icon { width: 17px; height: 17px; color: var(--muted); }
/* ⛔ **Ce qui cède est la queue de la phrase, jamais un mot coupé** —
   l'arbitrage des temps de P04 transposé à un libellé. Mesuré : l'entête demande
   362 px pour 350 à 390 px de large, et 362 pour 280 à 320. Retirer la croix
   n'en rendait que 25, donc elle reste : c'est « le mode cuisine » qui s'en va,
   et « Quitter » est un libellé entier, pas un mot coupé. La phrase complète
   vit dans `aria-label`, à toutes les largeurs.

   ⚠️ **Et la classe est posée par la mesure, pas par un palier.** Un
   `@media (max-width: …)` aurait été un nombre à défendre, puis à réajuster le
   jour où « Ingrédients » devient un mot plus long. */
.cook-head.serre .cook-quitter-suite { display: none; }

/* Icône dessus, mot dessous : c'est la forme de la maquette, et elle tient dans
   44 px de cible tout en nommant ce qu'elle fait. */
.cook-outil {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  flex: none; min-width: 52px; min-height: 46px; padding: 4px 6px;
  border: none; background: none; color: var(--muted);
  font-size: 16.5px; font-weight: 600; cursor: pointer;
}
.cook-outil .icon { width: 21px; height: 21px; }
.cook-outil.on { color: var(--terra); }

/* ── Le rappel du plat ────────────────────────────────────────────────────── */
.cook-rappel { display: flex; align-items: center; gap: 12px; flex: none; }
/* ⚠️ **La vignette est un `.diary-thumb`**, celui du journal et des cartes du
   planning : c'est le corps qui porte la cascade photo → dessin → casserole et
   qui **se dimensionne**. On l'agrandit ici de 40 à 52 px — un rappel de plat se
   regarde de plus loin qu'une ligne de journal — sans rien réécrire d'autre. */
.cook-vignette { display: flex; flex: none; }
.cook-vignette .diary-thumb { width: 52px; height: 52px; }
.cook-rappel-texte { min-width: 0; }
/* Deux lignes au plus : un titre long est un titre, pas la moitié de l'écran —
   et ce qu'on lit ici est l'étape, pas le nom du plat. */
.cook-recette {
  margin: 0; font-family: var(--ecriture); font-size: 25px; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.cook-meta { margin: 3px 0 0; color: var(--muted); font-size: 19.5px; }

.cook-progression { display: flex; align-items: center; gap: 12px; flex: none; }
.cook-bar { flex: 1; height: 6px; border-radius: 999px; background: var(--line); }
.cook-bar > div { height: 100%; border-radius: 999px; background: var(--terra); transition: width .3s ease; }
.cook-count { margin: 0; color: var(--muted); font-size: 19.5px; white-space: nowrap; }

.cook-timers { display: flex; flex-wrap: wrap; gap: 8px; flex: none; }
.cook-timer {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 999px;
  padding: 7px 12px; font-size: 21px; font-weight: 600; cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.cook-timer .icon { width: 15px; height: 15px; color: var(--muted); }
.cook-timer small { font-weight: 400; color: var(--muted); }
/* Un minuteur fini ne disparaît pas tout seul : on a les mains occupées, et
   rater la fin d'une cuisson coûte plus cher qu'une pastille de trop. */
.cook-timer.done {
  background: var(--terra); border-color: var(--terra); color: #FFFFFF;
  animation: timer-done 1s ease-in-out infinite;
}
.cook-timer.done .icon, .cook-timer.done small { color: #FFFFFF; }
@keyframes timer-done { 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .cook-timer.done { animation: none; } }

/* ── L'étape, et rien qu'elle ─────────────────────────────────────────────── */

/* ⛔ **Le corps ne centre plus son contenu.** Un conteneur qui défile et qui
   centre **coupe le haut** de ce qui dépasse : la première ligne d'une longue
   instruction partait au-dessus du bord, hors d'atteinte du défilement. C'est le
   § 2 dans sa version la plus discrète — la ligne est peinte, elle est seulement
   inatteignable — et il ne se voit que sur les instructions longues, c'est-à-dire
   celles qu'on a le plus besoin de lire. */
/* ⛔ **C'est la carte qui défile, pas la bande qui la porte** — et ce n'est pas
   un rangement. Un fond se peint **derrière** le contenu : les quatre dégradés
   de l'indice de suite, posés d'abord sur `.cook-body`, passaient sous une carte
   **opaque** qui occupe toute la largeur. Ils étaient là, ils étaient servis, et
   ils ne pouvaient rien montrer — le § *un garde-fou qui ne garde plus rien*,
   obtenu en recopiant un mécanisme juste au mauvais étage.

   La bande ne fait donc plus que donner une hauteur ; c'est la carte qui la
   remplit **au plus**, et qui défile en dedans. Une étape courte reste courte. */
/* ⚠️ **`flex-start`, sinon la carte s'étire.** Un enfant flex s'étend par défaut
   à la hauteur de sa bande : une étape de trois lignes rendait une carte de
   500 px avec 300 de blanc dedans, ce qui se lit comme un contenu manquant. Elle
   fait donc la hauteur de son texte, et `max-height: 100%` la borne. */
.cook-body { flex: 1; min-height: 0; display: flex; align-items: flex-start; }
.cook-etape {
  background: var(--card); border-radius: var(--radius);
  padding: 18px; box-shadow: var(--shadow);
  width: 100%; max-height: 100%; overflow-y: auto; overscroll-behavior: contain;
}
/* ⚠️ **Et une instruction qui continue le dit** — le § *une feuille qui défile
   le dit*, à un conteneur de plus. iOS ne montre sa barre qu'*une fois qu'on a
   défilé : à l'ouverture, une étape entière et une étape coupée se ressemblent
   trait pour trait, et le geste qui les distingue est justement celui qu'on ne
   fait pas si on croit tout voir. Ici on cuisine les mains occupées, donc ce
   qu'on perdrait est la moitié d'une consigne.

   Deux couvercles couleur `--card` ancrés au **contenu** et deux ombres ancrées
   au **conteneur** : en haut de course le couvercle masque l'ombre, dès qu'on
   défile il s'en va et l'ombre paraît. ⚠️ **Rien n'est mesuré** — une étape qui
   tient garde ses couvercles en place et n'affiche aucune ombre, donc l'indice
   ne peut ni mentir ni se désynchroniser d'un contenu qui change. */
.cook-etape {
  background-image:
    linear-gradient(to bottom, var(--card) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--card) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0));
  background-position: top, bottom, top, bottom;
  background-size: 100% 20px, 100% 20px, 100% 11px, 100% 11px;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.cook-etape-tete { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.cook-puce {
  width: 38px; height: 38px; flex: none; border-radius: 50%;
  background: var(--terra); color: #FFFFFF;
  display: grid; place-items: center;
  font-family: var(--ecriture); font-size: 26px; font-weight: 700;
}
.cook-title { font-family: var(--ecriture); font-size: 27.5px; margin: 0; flex: 1; line-height: 1.2; }
/* La durée de l'étape se lit, elle ne se règle pas : c'est ce que la recette
   dit. Le geste, lui, est le bouton du minuteur en dessous. */
.cook-duree {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 12px; border-radius: 999px;
  /* ⚠️ **`--espresso`, et c'est une mesure qui l'a décidé.** `--cinnamon` sur
     `--sand` rend 2,99 de contraste en thème sombre — les deux tokens y bougent
     dans le même sens, et une pastille qui se lit le jour disparaît la nuit. Le
     token de texte, lui, suit son fond : 9,5 en clair, 6,3 en sombre. */
  background: var(--sand); color: var(--espresso);
  font-size: 20px; font-weight: 600; white-space: nowrap;
}
/* Lisible à 60 cm : c'est la contrainte du § 4, pas une préférence. */
.cook-text { font-size: 26px; line-height: 1.55; margin: 0; }
.cook-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }

/* ⚠️ **En colonne, et c'est la mesure qui l'a décidé.** Côte à côte, le bouton
   recevait 207 px pour un libellé qui en demande 230 : « Démarrer le minuteur »
   passait sur deux lignes et le bloc montait à 102 px. La maquette l'empile
   déjà — la durée au-dessus, la commande sous elle, pleine largeur —, et c'est
   la seule disposition qui tienne le libellé entier à 320 px. */
.cook-minuteur {
  display: flex; flex-direction: column; align-items: stretch; gap: 10px;
  margin-top: 16px; padding: 14px; border-radius: 14px;
  background: var(--cream); border: 1px solid var(--line);
}
.cook-minuteur-duree {
  font-family: var(--ecriture); font-size: 32.5px; line-height: 1; text-align: center;
  font-variant-numeric: tabular-nums;
}
.cook-lancer { gap: 9px; }
.cook-lancer .icon { width: 18px; height: 18px; }

/* ── Le bloc qui décompte — 12/09 ────────────────────────────────────────────

   ⚠️ **Ce qui change d'aspect est le nombre, pas le cadre.** Un fond coloré sur
   tout le bloc ferait crier une cuisson en cours pendant qu'on lit l'étape
   suivante — c'est un état, pas une alerte. Le décompte passe en `--terra` et
   grandit ; le reste ne bouge pas.

   ⛔ **Et la commande s'efface quand elle n'est plus le geste.** « Arrêter »
   pleine couleur au-dessus de « Suivante » donnerait deux boutons pleins sur
   le même écran, dont le plus visible est celui qu'on ne veut pas presser. */
.cook-minuteur.tourne .cook-minuteur-duree,
.cook-minuteur.fini .cook-minuteur-duree { color: var(--terra); font-size: 43px; }
.cook-minuteur.tourne .cook-lancer, .cook-minuteur.fini .cook-lancer {
  background: transparent; color: var(--muted); border: 1.5px solid var(--line);
}
.cook-minuteur.tourne .cook-lancer .icon, .cook-minuteur.fini .cook-lancer .icon {
  color: var(--muted);
}
/* Un minuteur fini ne disparaît pas tout seul — même règle que la pastille :
   on a les mains occupées, et rater la fin d'une cuisson coûte plus cher qu'un
   bloc de trop. */
.cook-minuteur.fini .cook-minuteur-duree { animation: timer-done 1s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .cook-minuteur.fini .cook-minuteur-duree { animation: none; }
}

/* ── Le pied : avancer, ou aller à une étape ──────────────────────────────── */
.cook-foot { display: flex; flex-direction: column; gap: 10px; flex: none; }
.cook-nav { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.cook-nav .btn-primary, .cook-nav .btn-ghost { min-height: 52px; font-size: 24px; padding-inline: 12px; }
.cook-nav .btn-ghost { gap: 6px; }
/* Le chevron du sprite descend ; ici il désigne l'arrière. */
.cook-nav .btn-ghost .icon { width: 17px; height: 17px; transform: rotate(90deg); }
.cook-nav .btn-ghost:disabled { background: none; }

/* ⚠️ **Centrée, mais pas par `justify-content: center`** — demandé le 11/09.
   Sur une rangée qui défile, centrer rogne des **deux** côtés dès qu'elle
   déborde : la pastille 1 partirait à gauche, hors d'atteinte du défilement.
   C'est le piège que P04 a nommé sur ses temps. Des marges automatiques sur la
   première et la dernière pastille centrent tant que tout tient, et valent zéro
   dès que ça déborde — le début reste alors là où on le cherche. */
.cook-etapes {
  display: flex; gap: 14px; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; padding-block: 3px;
}
.cook-etapes::-webkit-scrollbar { display: none; }
.cook-etape-lien:first-child { margin-left: auto; }
.cook-etape-lien:last-child { margin-right: auto; }
/* ⚠️ **`min-height`, sinon la pastille est un ovale.** `button { min-height:
   44px }` bat `height: 38px` : elles faisaient 38 × 44, vu sur la capture. Le
   dessin reste à 38 et la cible à 44, par la zone tactile commune plus haut. */
.cook-etape-lien {
  position: relative;
  width: 38px; height: 38px; min-height: 38px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--line); background: var(--card); color: var(--muted);
  font-size: 22.5px; font-weight: 700; cursor: pointer;
}
/* Ce qui est fait s'éteint, ce qu'on lit s'allume : deux états, et le troisième
   — ce qui reste — est l'absence des deux. */
.cook-etape-lien.faite { color: var(--sage); border-color: var(--sage); }
.cook-etape-lien.on {
  background: var(--bouton-fond); border-color: var(--bouton-fond); color: var(--bouton-texte);
}

/* ⚠️ **La barre d'onglets absente rend la hauteur qu'elle prenait.** `.sheet`
   se pose au-dessus d'elle — `bottom: var(--tabbar-h) + 12px` —, et le mode
   cuisine la masque : la feuille des ingrédients flottait donc 60 px au-dessus
   du bord, et perdait d'autant sur son plafond, c'est-à-dire trois lignes de
   liste. C'est le § 10 pris à l'envers : ce qui se dégage d'une voisine se
   remet en place quand elle n'est plus là. */
body:has(#screen-cook:not([hidden])) #cooking-sheet {
  bottom: calc(12px + env(safe-area-inset-bottom));
  max-height: min(88vh, calc(100dvh - 24px - env(safe-area-inset-bottom) - var(--hors-reseau-h, 0px)));
}

.cooking-ing {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 4px; border-top: 1px solid var(--line); cursor: pointer;
}
.cooking-ing:first-child { border-top: none; }
.cooking-ing.done { opacity: .5; }
.cooking-ing.done .cooking-label { text-decoration: line-through; }
.cooking-label { flex: 1; font-size: 23px; }
.cooking-qty { font-weight: 600; color: var(--cinnamon); white-space: nowrap; }
/* ⚠️ **Plus petite qu'ailleurs, et c'est la ligne qui l'impose.** Quatre
   colonnes — la case de 44 px, le dessin, le nom, la quantité — demandent
   324 px à 320 d'écran avec la vignette de 40 des autres listes. Ce qui cède
   est le dessin, jamais le nom ni la quantité : il redit ce que le libellé
   nomme déjà. */
.cooking-ing .diary-thumb { width: 32px; height: 32px; border-radius: 9px; }
.cooking-ing .diary-thumb-empty .icon { width: 18px; height: 18px; }

/* ── Ce que l'étape demande, sous l'étape — 12/09 ─────────────────────────────

   ⚠️ **Un filet au-dessus, pas une carte dans la carte.** Le bloc vit déjà dans
   `.cook-etape` : lui donner un fond et une ombre ferait deux cadres imbriqués
   à trois pixels l'un de l'autre, et la carte de l'étape cesserait de se lire
   comme un tout. Ce qu'on sépare est une **suite** de l'instruction, pas un
   objet à côté d'elle. */
.cook-ing-etape { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.cook-ing-titre {
  margin: 0 0 4px; font-size: 19.5px; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
}
/* Lisible à 60 cm comme l'instruction au-dessus : c'est la contrainte du § 4.
   ⚠️ Et la ligne garde ses 44 px de cible — on coche les doigts gras. */
.cook-ing-etape .cooking-label { font-size: 24.5px; }
.cook-ing-etape .cooking-qty { font-size: 24px; }

/* ── Panneau de filtres ──────────────────────────────────────────────────── */

/* Trois bandes empilées : l'en-tête et les actions restent, le milieu défile.
   La feuille entière défilait, en-tête et actions compris — « Voir les N
   résultats » se trouvait donc 158 px sous le bord visible à l'ouverture, sans
   rien pour dire qu'il existait. Le bouton qui valide un panneau ne doit jamais
   demander qu'on aille le chercher. */
.filters-sheet {
  display: flex; flex-direction: column;
  max-height: min(82vh, calc(100dvh - var(--tabbar-h) - 48px - var(--hors-reseau-h, 0px)));
  overflow: hidden; padding: 16px;
}
.filters-sheet .sheet-head { flex: none; }
.filters-scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  /* Le défilement s'arrête dans la feuille : sans ça, arriver en bas fait
     repartir la page dessous, et la feuille a l'air de glisser toute seule. */
  overscroll-behavior: contain;
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.sheet-head .sheet-title { margin: 0; }
.sheet-head .icon-btn { width: 36px; height: 36px; min-height: 36px; }
.sheet-head .icon-btn .icon { width: 16px; height: 16px; }

.filter-block { padding: 16px 4px; border-bottom: 1px solid var(--line); }
.filter-block:last-of-type { border-bottom: none; }
.filter-block h3 {
  font-family: var(--ecriture); font-size: 21px; font-weight: 600;
  margin: 0 0 10px; display: flex; align-items: center; gap: 8px;
}
.filter-value { margin: -4px 0 10px; color: var(--cinnamon); font-weight: 600; font-size: 21px; }

/* ── Les blocs du panneau sont ceux des réglages — C16, 20/09 ──────────────
   Même dessin qu'une feuille de réglages (`.settings-block` +
   `.settings-sous-titre`), à deux retraits près : la feuille porte déjà 16 px,
   et les 14 px du bloc les décaleraient du titre. Le trait de séparation vient
   de `.filter-block`, qui sert encore au filtre de « ce que j'ai cuisiné ». */
.filters-sheet .settings-block { padding: 14px 4px; border-bottom: 1px solid var(--line); }
.filters-sheet .settings-block:last-of-type { border-bottom: none; }
.filters-sheet .settings-sous-titre { margin: 0 0 10px; }
/* La ligne de la bascule s'aligne sur le bloc, pas sur le retrait des feuilles
   de réglages. */
.filters-sheet .reglage-ligne { padding: 2px 0; }
.filters-sheet .reglage-ligne small { color: var(--muted); }

/* (La seconde règle `.info-dot` vivait ici, muette et gagnante — voir la règle
   unique en tête de feuille, avec ce que cette copie a coûté.) */

/* Curseur à deux poignées : deux `input[range]` superposés. Le second ne
   capte le pointeur que sur sa poignée, sinon il masquerait le premier sur
   toute la largeur et la borne basse deviendrait inatteignable. */
/* ⚠️ **La plage choisie se colore — Luca, 21/09 (« jauge couleur »).** Le rail
   entier était sable, poignées comprises : rien ne disait où l'on avait réglé.
   Le rail est dessiné par la boîte (`::before` le fond, `::after` la plage en
   terre cuite, entre `--lo` et `--hi` que `renderFilters` pose en fractions) et
   les pistes natives sont transparentes — deux `input` superposés ont deux
   pistes, et une piste opaque cacherait la couleur. Les 11 px des bords sont
   le demi-rayon d'une poignée : le centre d'une poignée ne va jamais au bord. */
.range-pair { position: relative; height: 34px; --lo: 0; --hi: 1; }
.range-pair::before, .range-pair::after {
  content: ""; position: absolute; top: 15px; height: 4px; border-radius: 999px;
  pointer-events: none;
}
.range-pair::before { left: 0; right: 0; background: var(--line); }
/* Victoria, 21/09 : « du vert à gauche au rouge graduel ». ⚠️ Le dégradé
   couvre **tout le rail** et la plage choisie en est découpée (`clip-path`) :
   étiré sur la plage seule, il ferait rouge un « jusqu'à 300 kcal ». Ici une
   couleur dit toujours la même quantité de calories. */
.range-pair::after {
  left: 11px; right: 11px;
  background: linear-gradient(90deg, var(--sage), var(--amber) 50%, var(--danger));
  clip-path: inset(0 calc((1 - var(--hi)) * 100%) 0 calc(var(--lo) * 100%) round 999px);
}
.range-pair input {
  position: absolute; inset: 0; width: 100%; margin: 0; z-index: 1;
  -webkit-appearance: none; appearance: none; background: none; pointer-events: none;
}
.range-pair input::-webkit-slider-runnable-track { height: 4px; background: transparent; border-radius: 999px; }
.range-pair input::-moz-range-track { height: 4px; background: transparent; border-radius: 999px; }
.range-pair input::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 22px; height: 22px; border-radius: 50%; margin-top: -9px;
  background: var(--card); border: 2px solid var(--terra); cursor: pointer;
}
.range-pair input::-moz-range-thumb {
  pointer-events: auto; width: 22px; height: 22px; border-radius: 50%;
  background: var(--card); border: 2px solid var(--terra); cursor: pointer;
}

/* ⚠️ **`.dish-grid` est partie avec la section « Type de plat », le 10/09.**
   Elle doublait les étiquettes — `dish` filtre le même champ qu'un tag — et ne
   portait que six des quatorze valeurs du groupe « Type ». */

/* `align-items: center` posait la case à mi-hauteur d'un libellé sur deux
   lignes, donc en travers du texte dès qu'il repliait. Alignée en haut, elle
   se range en face du titre, qui est ce qu'elle commande. */
/* La pastille du bouton « Filtrer » : le même dessin que celle du bouton
   « Filtrer » de la recherche communautaire, sur un rond déjà `relative`. */
.filtres-nombre {
  position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px; background: var(--coeur); color: #FFFFFF; font-size: 16.5px; font-weight: 700;
  display: grid; place-items: center;
}

.switch-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; cursor: pointer; }
.switch-row > span { min-width: 0; }
.switch-row strong { display: block; font-size: 22px; }
.switch-row small { display: block; color: var(--muted); font-size: 19px; line-height: 1.4; }
.switch-row input { width: 46px; height: 27px; flex: none; accent-color: var(--terra); margin-top: 2px; }

/* ⚠️ **Deux boutons côte à côte, comme le pied commun des feuilles** — C16,
   20/09. `auto-fit` laisse le bouton seul du signalement (`#report-sheet`)
   occuper toute la largeur, comme avant : la grille sert les deux pieds. */
.filters-actions {
  flex: none; display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 10px;
  padding-top: 14px; border-top: 1px solid var(--line);
  background: var(--card);
}
.filters-actions .btn-primary, .filters-actions .btn-ghost { width: 100%; min-height: 50px; }

/* ── Vignette de recette ─────────────────────────────────────────────────── */

.card-fav {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 32px; height: 32px; min-height: 32px; border-radius: 50%;
  /* Le cœur est terre cuite sur une pastille blanche, et s'inverse allumé —
     12/09 : la pastille sombre translucide le rendait gris sur toute photo. */
  border: none; background: rgba(255, 255, 255, .92); color: var(--coeur);
  display: grid; place-items: center; cursor: pointer; padding: 0;
}
.card-fav .icon { width: 16px; height: 16px; }
.card-fav.on { background: var(--coeur); color: #FFFFFF; }
/* Le marque-page d'une publication d'autrui — P33 : sous le cœur, même dessin. */
.card-save {
  position: absolute; top: 46px; right: 8px; z-index: 2;
  width: 32px; height: 32px; min-height: 32px; border-radius: 50%;
  border: none; background: rgba(255, 255, 255, .92); color: var(--coeur);
  display: grid; place-items: center; cursor: pointer; padding: 0;
}
.card-save .icon { width: 15px; height: 15px; }
.card-save.on { background: var(--coeur); color: #FFFFFF; }
.card-avis {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 2px;
  font-size: 18px; color: var(--cinnamon); font-weight: 600;
}
.card-avis .icon { width: 13px; height: 13px; color: #C08A2E; }

/* ── Le ⋮ de la carte, au bout de sa ligne du bas — corrigé le 10/09 ─────────
   **Il vivait dans le coin supérieur gauche de la photo**, en miroir du cœur,
   et la raison écrite alors était juste sur le fond : le bas de la carte porte
   un texte de longueur inconnue — le temps et les calories, la raison du
   classement, la provenance —, donc un bouton posé là l'aurait recouvert.

   ⚠️ **Ce qui manquait n'était pas la place, c'était de la réserver.** Les deux
   maquettes du 10/09 le posent en bas à droite, et c'est là qu'on le cherche :
   un coin d'image porte ce qui parle de l'image, une fin de ligne porte ce qui
   parle de la fiche. Les deux lignes du pied lui rendent donc 38 px à droite —
   ⛔ toutes les deux, sinon la provenance passe dessous sur les seules fiches
   qui en portent une.

   ⚠️ **Et il perd sa pastille sombre.** Elle existait pour tenir sur une photo ;
   sur le papier de la carte, elle serait une tache — le cœur, lui, reste sur
   l'image et la garde. */
.card-menu {
  position: absolute; right: 4px; bottom: 5px; z-index: 2;
  width: 32px; height: 32px; min-height: 32px; border-radius: 50%;
  border: none; background: none; color: var(--muted);
  display: grid; place-items: center; cursor: pointer; padding: 0;
}
.card-menu .icon { width: 16px; height: 16px; }

/* ── Scanner un produit ──────────────────────────────────────────────────── */

.scan-sheet input, .scan-sheet select {
  width: 100%; border: 1px solid var(--line); border-radius: 12px;
  padding: 11px 13px; background: var(--cream); color: inherit;
  font-size: 24px; min-height: 46px; margin-bottom: 8px;
}
.scan-sheet .btn-primary, .scan-sheet .btn-ghost { width: 100%; min-height: 48px; }
/* Dans une feuille, l'espace vertical est compté : le bloc de rappel s'y serre
   plus que sur l'écran de scan d'assiette, qui a toute la page. */
.scan-sheet .recall-block { margin-top: 12px; padding-top: 10px; }
/* ── C19, 20/09 — la feuille à bandes ────────────────────────────────────────
   Le titre porte l'icône du code-barres à sa gauche, au retrait du socle
   (14 px) ; `.sheet-head` écarte ses enfants aux deux bords, ici ils se
   suivent. */
.scan-head { justify-content: flex-start; padding: 6px 14px 4px; }
.scan-head .icon { width: 22px; height: 22px; color: var(--cinnamon); }
.scan-head .sheet-title { padding: 0; }
/* ⚠️ **« Déjà scannés » défile avec la bande du milieu.** `.recall-list` se
   borne à 38 vh parce que, dans une feuille qui défilait en entier, une longue
   liste emportait la caméra hors de vue ; ici la caméra est épinglée et c'est
   la bande qui défile — le plafond ne ferait plus qu'un second défileur dans
   le premier. La règle générale reste pour les autres listes de rappel. */
#scan-recent .recall-list { max-height: none; overflow-y: visible; }

/* Le résultat d'une estimation. **`min-height` et non un bloc qui apparaît**
   (§ 5, troisième corollaire) : les deux lignes sont vides tant qu'aucun poids
   n'est saisi, et sans réserve elles pousseraient le bouton de validation vers
   le bas au premier chiffre tapé — sous le doigt qui vient de le viser.

   La réserve est **mesurée, pas estimée**, et il a fallu deux relevés : 62 px
   paraissaient suffire, le bloc vide en fait 77,9 et le bloc rempli 94,8 — puis
   111,6 dès qu'un exemple porte un nom long (« Cookie Chocolat saveur noisette
   Sans sucres »). Une réserve fixe ne pouvait donc pas suivre : **c'est la
   justification qu'il faut borner**, pas la réserve qu'il faut agrandir. Trois
   lignes exactement, et la hauteur cesse de dépendre du contenu.

   Ce qui est tronqué est la fin des exemples, jamais le début : la fourchette
   et l'effectif de la cohorte — ce qui porte la justification — sont en tête. */
.estimate-out {
  display: flex; flex-direction: column; gap: 4px;
  min-height: 103px; margin: 4px 0 12px;
  padding: 12px 14px; border-radius: 12px;
  background: var(--cream); border: 1px solid var(--line);
}
.estimate-kcal { font-size: 26.5px; font-weight: 600; font-family: var(--ecriture); }
/* Le gris de `--muted` : la justification se lit après le chiffre, jamais à sa
   place. C'est elle qui empêche l'estimation de passer pour une mesure. */
.estimate-why {
  color: var(--muted); font-size: 19px; line-height: 1.35;
  /* Trois lignes, ni plus ni moins : `min-height` tient le bloc vide, le clamp
     tient le bloc bavard. Les deux valeurs sont le même calcul — 3 × 1,35 ×
     12,5 px — et doivent le rester. */
  min-height: 50.6px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}

.scan-view {
  position: relative; border-radius: 16px; overflow: hidden;
  background: #000; aspect-ratio: 4 / 3; margin-bottom: 12px;
}
.scan-view video { width: 100%; height: 100%; object-fit: cover; }

/* Le cadre de visée : quatre angles plutôt qu'un rectangle plein, pour ne pas
   masquer ce qu'on vise. */
.scan-frame {
  position: absolute; inset: 18% 12%;
  border: 2px solid rgba(255, 247, 241, .9); border-radius: 10px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .35);
  clip-path: polygon(0 0, 26% 0, 26% 4%, 4% 4%, 4% 26%, 0 26%, 0 74%, 4% 74%,
    4% 96%, 26% 96%, 26% 100%, 0 100%, 100% 100%, 74% 100%, 74% 96%, 96% 96%,
    96% 74%, 100% 74%, 100% 26%, 96% 26%, 96% 4%, 74% 4%, 74% 0, 100% 0);
}
/* La vignette entre dans la carte : c'est le même objet qu'on vient de toucher
   dans la liste, et le revoir confirme qu'on a tapé sur la bonne ligne. */
.product-card {
  background: var(--cream); border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 16px; margin-bottom: 12px;
  display: flex; align-items: center; gap: 12px;
}
.product-lines { flex: 1; min-width: 0; display: grid; gap: 2px; }
.product-thumb { flex: none; display: flex; }
.product-thumb .diary-thumb { width: 48px; height: 48px; }
.product-thumb .diary-thumb-empty .icon { width: 26px; height: 26px; }
.product-card strong { font-size: 24px; }
.product-card span { color: var(--muted); font-size: 20px; }
.product-card small { color: var(--cinnamon); font-size: 20px; font-weight: 600; }

.scan-qty { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.scan-qty input { width: 6rem; margin-bottom: 0; text-align: center; }
.scan-qty span { color: var(--muted); }
.scan-actions { display: grid; gap: 8px; }
.btn-scan { background: var(--sand); }
.btn-scan .icon { width: 20px; height: 20px; }

/* ── Liste de courses ────────────────────────────────────────────────────── */

/* ⚠️ **`minmax(0, 1fr)` et non la colonne implicite.** Signalé le 22/08 sur une
   vraie liste : le bandeau des recettes **débordait de l'écran** — « Pâtes
   crémeuses à l'ail et au parmesan » demandait 588 px pour les 350 disponibles,
   et l'on faisait défiler la page horizontalement pour atteindre le pas à pas.

   La colonne implicite d'une grille se dimensionne sur le `min-content` de ses
   items, et **une colonne est partagée** : il suffit d'un titre long pour que
   toutes les lignes s'élargissent. `min-width: 0` sur le titre ne suffisait pas
   — il était déjà là, et bien appliqué : ce n'est pas le titre qui refusait de
   rétrécir, c'est la **piste** qui refusait de descendre sous son min-content.

   ⛔ **Et ce n'est pas la vignette qui l'a créé** : sans elle la ligne demandait
   encore 534 px. Elle a coûté 54 px sur un débordement de 184 — le défaut
   attendait un titre assez long, et la bibliothèque en a. */
.list-recipes {
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 8px; margin-bottom: 16px;
}
/* **Deux rangs, aux deux emplacements.** La version sur un rang laissait
   **86 px au titre** des 350 : « Pâtes crémeuses à l'ail et au parmesan » se
   lisait « Pâtes cr… ». Le pas à pas prend 130 px et ne peut pas maigrir sans
   passer sous les 44 px de cible, la vignette 44, la croix 34 — il n'y a pas de
   largeur à reprendre. Le titre prend donc le premier rang, le pas à pas et la
   croix descendent au second.

   ⚠️ **Et c'est la même règle pour le bandeau et pour l'en-tête de groupe.**
   Elle vivait sous `.shop-group-recette` : le bandeau, resté sur un rang,
   tronquait exactement ce que l'en-tête savait déjà afficher. Un composant
   partagé qui se met en page différemment selon l'endroit est deux composants
   qui s'ignorent — la puce de pliage, elle, n'existe qu'à un seul des deux, et
   sa colonne se ferme toute seule quand elle manque. */
.list-recipe {
  display: grid; align-items: center;
  grid-template-columns: auto 1fr auto;
  grid-template-areas: "vignette titre puce" "vignette portions retrait";
  column-gap: 10px; row-gap: 6px;
  background: var(--card); border-radius: 16px; padding: 10px 12px;
  box-shadow: var(--shadow);
}
/* L'ordre que `listRecipeRow` produit : vignette, titre, pas à pas, croix — et
   la puce de pliage en cinquième, ajoutée par `groupBlock` au seul en-tête. */
.list-recipe > :nth-child(1) { grid-area: vignette; align-self: start; }
.list-recipe > :nth-child(2) { grid-area: titre; }
.list-recipe > :nth-child(3) { grid-area: portions; justify-self: start; }
/* La croix à l'autre bout de son rang, loin du « − » du pas à pas : deux cibles
   voisines dont l'une retire une portion et l'autre la recette entière est un
   voisinage qu'on paie une fois. */
.list-recipe > :nth-child(4) { grid-area: retrait; justify-self: end; }
.list-recipe > :nth-child(5) { grid-area: puce; }
/* Deux lignes plutôt qu'une coupe : tronquer un nom de recette fait perdre
   exactement ce qu'on cherchait — c'est lui qui identifie la ligne. */
.list-recipe-title {
  min-width: 0; font-size: 22px; font-weight: 600;
  white-space: normal; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.25;
}
.list-recipe-del { width: 34px; height: 34px; min-height: 34px; font-size: 25px; }
.list-recipe-del .icon { width: 15px; height: 15px; }

.stepper-sm button { width: 32px; height: 32px; min-height: 32px; font-size: 24.5px; border-radius: 10px; }
.stepper-sm output { min-width: 22px; font-size: 22.5px; }
/* ⛔ **Pas `opacity: .35` — revue de 03b.** Elle délavait le « − » *et* son
   fond : le signe tombait à 1,9:1 sur la carte, sur le bouton qui dit qu'on ne
   peut pas descendre plus bas. Le couple inactif est celui de tout le fichier
   (R05) : franchement plus pâle que l'actif, et lisible. */
.stepper-sm button:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--line); cursor: default;
}

/* ── P13, la liste de courses — 13/09 ──────────────────────────────────────── */
.sl-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 16px; }
.sl-titres { flex: 1; min-width: 0; }
/* ⚠️ **Le titre tient sur une ligne, et c'est mesuré.** « Liste de courses » en
   serif 30 px demande 231 px ; avec « Partager » (118) et le ⋮ (40) il en
   faudrait 409 pour les 350 d'un iPhone 14 — le titre passait donc à deux
   lignes et la maquette n'en a qu'une. `clamp` plutôt qu'une requête de média :
   la coupe ne tombe pas à une largeur d'appareil mais là où le titre cesse
   d'entrer. */
.sl-titre {
  margin: 0 0 4px; font-family: var(--ecriture); font-weight: 400;
  font-size: clamp(27.5px, 7.23vw, 32.5px); line-height: 1.1;
}
.sl-intro { margin: 0; color: var(--muted); font-size: 22.5px; }
/* « Partager » se lit comme une commande, pas comme un titre : le trait, la
   terre cuite, et le mot à côté du dessin. */
.sl-partage {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  min-height: 44px; padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--terra) 35%, var(--line));
  border-radius: 999px; background: var(--card); color: var(--terra);
  font: inherit; font-size: 22px; font-weight: 600; cursor: pointer;
}
.sl-partage .icon { width: 17px; height: 17px; }
.sl-partage { font-size: 21px; }
/* Trois axes : la grille du composant commun en porte deux, celle-ci en compte
   trois — c'est la seule différence. */
.sl-axes { grid-template-columns: repeat(3, 1fr); }
.sl-axes .chip {
  border: none; background: none; color: var(--bouton-second-texte);
  border-radius: 10px; padding: 9px 8px; min-height: 40px; font-size: 21px;
  white-space: nowrap;
}
.sl-axes .chip.on { background: var(--bouton-fond); color: var(--bouton-texte); font-weight: 600; }
.sl-resume { margin: -6px 0 14px; color: var(--muted); font-size: 21px; }

/* ── Les deux tuiles — P13, 13/09 ──────────────────────────────────────────── */
.sl-tuiles { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px; }
.sl-tuile {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 10px; border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.sl-tuile:disabled { cursor: default; }
.sl-tuile-icone { width: 34px; height: 34px; flex: none; border-radius: 10px; display: grid; place-items: center; }
.sl-tuile-icone .icon { width: 19px; height: 19px; }
.sl-tuile-frigo .sl-tuile-icone { background: color-mix(in srgb, var(--sage) 18%, var(--card)); color: var(--sage); }
.sl-tuile-recettes .sl-tuile-icone { background: color-mix(in srgb, var(--terra) 14%, var(--card)); color: var(--terra); }
.sl-tuile-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sl-tuile-texte b { font-size: 21px; line-height: 1.2; }
.sl-tuile-texte small { font-size: 18px; color: var(--muted); line-height: 1.25; }
.sl-tuile-chevron { width: 15px; height: 15px; flex: none; color: var(--muted); transform: rotate(-90deg); }
/* Sous 360 px les deux tuiles s'empilent : à 320, deux colonnes leur laissent
   136 px, où « 3 recettes » passe sur deux lignes et l'icône prend le tiers. */
@media (max-width: 359px) { .sl-tuiles { grid-template-columns: 1fr; } }

/* ── La barre d'ajout — P13, 13/09 ─────────────────────────────────────────── */
.sl-barre { display: flex; gap: 8px; margin-bottom: 16px; }
.sl-barre-champ {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px;
  min-height: 52px; padding: 12px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  color: var(--muted); font: inherit; font-size: 24px; text-align: left; cursor: pointer;
}
.sl-barre-champ .icon { width: 20px; height: 20px; flex: none; color: var(--terra); }
.sl-barre-scan {
  flex: none; width: 52px; min-height: 52px; padding: 0;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  color: var(--terra); cursor: pointer; display: grid; place-items: center;
}
.sl-barre-scan .icon { width: 22px; height: 22px; }

/* Dans la feuille : plus de carte ni d'ombre, c'est la feuille qui les porte. */
.add-item {
  background: none; padding: 0;
  margin-bottom: 0; display: grid; gap: 10px;
  /* ⚠️ **La boîte de suggestions est en `absolute`** : sans ancêtre positionné,
     elle se calerait sur le premier qu'elle trouve en remontant — ou sur le
     volet, donc n'importe où. Peinte, trouvable par un test de présence, et à
     côté du champ : le § 2 dans sa version la plus discrète. */
  position: relative;
}
.add-item input, .add-item select {
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px;
  background: var(--cream); color: inherit; font-size: 24px; min-height: 44px;
  min-width: 0;
}
.add-item input:focus, .add-item select:focus {
  outline: none; border-color: var(--terra);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 18%, transparent);
}
/* Quantité et unité restent étroites : ce sont « 500 » et « g », pas des
   phrases. La famille prend la place qui reste. */
/* ⚠️ **Trois champs, deux rangs.** Le formulaire vit dans une feuille depuis
   P13 : il a toute la largeur, et la famille n'a plus à se battre pour sa
   place — le § du 14/08 mesurait 174 px pour « Famille — devinée », qui ne
   tenaient pas sur la même ligne que la quantité et l'unité à 390 px. */
.add-detail { display: grid; grid-template-columns: 4.5rem 1fr; gap: 8px; align-items: center; }
.add-detail > select { grid-column: 1 / -1; }

.shop-group { margin-bottom: 22px; }
/* Un groupe replié ne garde pas la marge d'un groupe ouvert : dix familles
   repliées laissaient 220 px de blanc, et l'écran avait l'air vide alors qu'il
   était rangé. */
.shop-group-plie { margin-bottom: 6px; }

/* ⛔ **Plus de capitales forcées.** `text-transform: uppercase` tenait sur
   « CRÉMERIE » ; sur l'axe des familles les noms font jusqu'à vingt-huit
   caractères — « ÉPICES, HERBES & CONDIMENTS » avec un interlettrage de .06em
   demande 268 px pour les 350 disponibles, et une capitale accentuée perd son
   accent dans plusieurs polices système. Le nom s'écrit comme il est stocké. */
.shop-group .block-title { margin-bottom: 8px; }
/* La carte du groupe garde sa place quand elle est repliée : c'est la même
   commande, et elle ne doit pas sauter sous le doigt. */
.shop-group-plie .shop-group-btn { margin-bottom: 0; }
/* L'en-tête d'un groupe est une **carte**, comme la maquette : le dessin du
   rayon, son nom en serif, le compte en pastille et la puce de pliage. */
.shop-group-btn {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); cursor: pointer;
  font: inherit; color: inherit; text-align: left;
}
.shop-group-icone {
  width: 34px; height: 34px; flex: none; border-radius: 10px;
  display: grid; place-items: center;
  background: var(--cream); color: var(--terra);
}
.shop-group-icone .icon { width: 19px; height: 19px; }
.shop-group-nom { font-family: var(--ecriture); font-weight: 400; font-size: 26px; }
/* Le compte, en retrait du nom : il informe, il ne titre pas. */
/* Le compte, en pastille : il informe, il ne titre pas. */
.shop-group-count {
  font-size: 19px; font-variant-numeric: tabular-nums;
  color: var(--muted); background: var(--cream);
  border-radius: 999px; padding: 2px 9px; margin-left: 2px;
}
/* `margin-left: auto` pousse la puce au bord : c'est la cible qu'on vise pour
   replier, et elle doit être là où le pouce la cherche. */
.shop-caret {
  margin-left: auto; flex: none; font-size: 22.5px; line-height: 1;
  color: var(--muted);
  /* Une rotation, jamais un second glyphe : `transform` se compose sans
     recalculer la disposition, là où changer le texte de la puce redemande une
     mesure — le § *ce qu'on anime ne relayoute pas*. */
  transition: transform .18s ease;
}
.shop-group-plie .shop-caret { transform: rotate(-90deg); }

/* En-tête d'un groupe de l'axe « par recette » : la ligne de recette, et la
   puce de pliage au bout. La ligne prend toute la place restante — c'est elle
   qui porte le titre, donc c'est elle qui doit céder, jamais la puce. */
.shop-group-recette { margin-bottom: 8px; }

/* Ce qui appartient en propre à l'en-tête de groupe : rien de la mise en page,
   qui est désormais celle du composant (voir `.list-recipe`). */
/* Seule, la puce n'a pas de nom à pousser : `margin-left` n'a plus d'objet et
   la largeur se ferme sur son contenu. */
.shop-group-btn-seul { width: auto; flex: none; gap: 5px; padding: 6px 4px; }
.shop-group-btn-seul .shop-caret { margin-left: 0; }

/* Une recette dont tout est coché. La phrase remplace un blanc, qui se lirait
   comme un chargement qui n'a pas fini. */
.shop-group-vide {
  font-size: 20px; color: var(--muted);
  padding: 4px 12px 10px; margin: 0;
}

/* La couverture de la recette versée. Plus petite que la vignette d'un article
   — 34 px contre 40 — et rectangulaire : c'est un plat, pas un ingrédient, et
   la différence de forme évite de lire un titre de groupe comme une ligne. */
.list-recipe .diary-thumb { width: 44px; height: 34px; border-radius: 8px; }
/* La mention d'une ligne partagée passe à la ligne : collée derrière le nom,
   elle se lisait comme la suite du libellé (« farine aussi pour une autre
   recette »). */
.shop-partage { flex-basis: 100%; font-style: italic; }

/* ⚠️ **Les écarts sont serrés parce que c'est le nom qui manque de place.**
   Mesuré à 390 px : case 44 + vignette 40 + quantité + ⋯ laissaient **129 px**
   au libellé, où « Lait demi-écrémé » passe à la ligne. Huit pixels repris sur
   les gouttières et le rembourrage lui en rendent douze, sans toucher à la
   cible de la case — c'est le § *ce sont le rembourrage et les colonnes
   voisines qui cèdent, jamais le texte*. */
.shop-item {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; background: var(--card);
  border-radius: 14px; margin-bottom: 6px;
}
/* La vignette de la ligne de courses : le même objet que dans le journal, à la
   même taille. Ce qui change est le coin — une liste qu'on parcourt en marchant
   gagne à ce que ses vignettes se distinguent des cases à cocher, qui sont les
   seules autres boîtes de la ligne. */
.shop-item .diary-thumb { width: 40px; height: 40px; border-radius: 10px; }
/* **La cible fait 44 px, le dessin en fait 26.** Et il fallait les séparer :
   `height: 26px` était écrasé par le plancher de 44 px que le § 11 pose sur tout
   bouton, si bien que la case se peignait en **26 × 44** — un rectangle vertical
   arrondi, mesuré, qui ne se lit plus comme une case à cocher.

   ⛔ **Et surtout pas rabaisser la cible.** C'est la correction qui vient
   d'abord, et elle échange un défaut d'aspect contre un défaut d'usage : on
   coche cette case en marchant, d'une main, sur la plus petite cible de
   l'écran. Le bouton reste donc grand et transparent, et c'est un
   pseudo-élément qui porte le carré — les deux vivent dans la même cellule de
   grille, donc restent centrés l'un sur l'autre sans calcul. */
.shop-check {
  width: 44px; height: 44px; flex: none; cursor: pointer; padding: 0;
  border: none; background: none; color: transparent;
  display: grid; place-items: center;
}
.shop-check::before {
  content: ""; grid-area: 1 / 1;
  width: 26px; height: 26px; border-radius: 8px;
  border: 1.5px solid var(--line); background: var(--cream);
}
.shop-check .icon { grid-area: 1 / 1; width: 16px; height: 16px; }
/* ⚠️ **Deux sélecteurs, une seule déclaration de ce qu'est une case cochée.**
   La liste de courses porte l'état sur la **ligne** — c'est elle qui se barre ;
   la feuille du mode cuisine le porte sur la **case**. Une seconde règle écrite
   à côté aurait divergé, et c'est exactement ce qui s'était produit : la
   feuille posait son fond en style à la main, sous le `::before`, donc rien ne
   se cochait à l'écran. */
.shop-item.done .shop-check, .shop-check.on { color: #FFFFFF; }
.shop-item.done .shop-check::before, .shop-check.on::before {
  background: var(--sage); border-color: var(--sage);
}

.shop-text { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 7px; }
/* La quantité ferme la ligne et s'aligne en colonne : les nombres se lisent
   d'un balayage, et le nom garde le début de la rangée. */
.shop-qty { flex: none; color: var(--muted); font-size: 21px; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Le ⋯ d'une ligne : discret, et à la cible commune de 44 px par son `::after`. */
.shop-more {
  flex: none; width: 26px; height: 30px; padding: 0;
  border: none; background: none; color: var(--muted); cursor: pointer;
  display: grid; place-items: center;
}
.shop-more .icon { width: 18px; height: 18px; }
/* ⚠️ **Sous 360 px, ce sont encore les voisines qui cèdent.** Mesuré sur un
   iPhone SE de 1ʳᵉ génération : le libellé tombait à **75 px**. La vignette et
   les gouttières rendent 18 px ; la case garde ses 44, et le texte se replie
   plutôt que de se couper — § *ni le nom ni le chiffre ne se tronquent*. */
@media (max-width: 359px) {
  .shop-item { gap: 6px; padding: 9px 8px; }
  .shop-item .diary-thumb { width: 34px; height: 34px; border-radius: 9px; }
}
/* Le nom porte la reconnaissance : c'est lui qu'on cherche dans la liste, donc
   lui qui garde le contraste plein. */
.shop-name { font-size: 23px; }
/* La quantité passe devant et s'efface d'un ton. `tabular-nums` est ce qui rend
   l'alignement en colonne réel : sans lui, le « 1 » d'une police proportionnelle
   est deux fois plus étroit que le « 0 », et « 140 g » et « 100 g » ne finissent
   pas à la même abscisse. */
.shop-qty {
  font-size: 22.5px; font-weight: 500; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.shop-note { font-size: 19px; color: var(--muted); flex-basis: 100%; }

/* Coché ne veut pas dire disparu : on revient souvent sur ses pas dans un
   magasin, et une ligne qui s'efface emporte l'information avec elle. */
.shop-item.done { opacity: .55; }
.shop-item.done .shop-name, .shop-item.done .shop-qty { text-decoration: line-through; }
/* ⛔ **La vignette ne se barre pas, elle se décolore.** Un trait en travers
   d'une image de 40 px la rend illisible sans rien annoncer, alors que c'est
   elle qui sert à retrouver la ligne quand on revient sur ses pas. */
.shop-item.done .diary-thumb { filter: grayscale(1); }

.shop-del {
  width: 32px; height: 32px; flex: none; border: none; background: none;
  color: var(--muted); cursor: pointer; padding: 0;
}
.shop-del .icon { width: 17px; height: 17px; }

/* En-tête d'onglet en trois parts. Les deux flancs sont des colonnes `1fr` :
   ils prennent la même largeur quoi qu'ils contiennent, donc le titre tombe au
   centre de l'écran et non au centre de ce qui reste. Un titre décalé de la
   largeur d'un bouton se voit immédiatement. */
.pane-head {
  display: grid;
  /* ⚠️ **`auto` au milieu laissait le titre recouvrir les boutons.** Mesuré le
     22/08 : « Liste de courses » demande 188 px, les deux flancs en réservent
     160 des 350, et le titre débordait de **9 px à 390 px** — davantage à 320,
     où l'on lisait « Liste de cou⋯s ». La colonne du milieu prenait sa largeur
     de contenu (`max-content`) et les flancs, posés à `min-width: 0`, cédaient
     en laissant leurs boutons ressortir **par la gauche**, donc sur le titre.

     `minmax(0, auto)` autorise le titre à se replier ; `min-content` sur les
     flancs leur interdit de passer sous leurs boutons. Un titre long tient
     alors sur deux lignes au lieu d'en recouvrir un. */
  grid-template-columns: 1fr minmax(0, auto) 1fr;
  align-items: start; gap: 10px; margin-bottom: 22px;
}
/* ⛔ **Et les deux flancs gardent la même largeur minimale.** Premier essai :
   `min-content` sur les flancs — l'aplomb revenait, mais le flanc **gauche**,
   vide, tombait à 0 pendant que le droit tenait ses 76 px, et le titre se
   retrouvait **45 px à gauche du centre**. C'est le défaut que le commentaire
   ci-dessus interdit, repris par l'autre bout : « un titre décalé de la largeur
   d'un bouton se voit immédiatement », et 45 px en font davantage. Une valeur
   partagée est ce qui garde les deux colonnes égales quoi qu'elles portent. */
.pane-head > .head-side { min-width: 76px; }

/* ⚠️ **Et il fallait aussi que le titre maigrisse.** Mesuré : « Liste de
   courses » demande 188 px à 30 px, et la colonne du milieu n'en a que 178 à
   390 px — 108 à 320. Le titre ne peut donc **pas** tenir sur une ligne à 30 px
   avec deux boutons en regard, à aucune largeur de téléphone : ou il maigrit,
   ou il se replie, ou il recouvre. À 22 px il fait 138 px et tient d'une ligne
   à 390, de deux à 320, toujours centré et jamais au-dessus d'un bouton.

   `clamp` et non une requête de média : la coupure ne tombe pas à une largeur
   d'appareil mais là où le titre cesse d'entrer, ce qui dépend du titre.
   ⛔ Le `.greeting` de l'accueil garde ses 30 px — il occupe toute la largeur et
   n'a aucun bouton à côté de lui, donc rien de ce paragraphe ne le concerne. */
.pane-head .page-title {
  font-size: clamp(27.5px, 6.53vw, 32.5px);
  line-height: 1.12; text-wrap: balance;
}
.head-title { text-align: center; min-width: 0; }
.head-side { display: flex; align-items: center; gap: 8px; min-width: 0; }
.head-right { justify-content: flex-end; }

/* Même traitement pour les écrans de détail : retour à gauche, titre centré.
   ⚠️ **La règle vit plus bas, et une seule fois** — voir `.detail-head` dans la
   section des écrans de détail. Elle était déclarée ici *et* là-bas, avec deux
   dispositions différentes ; les deux ont la même spécificité, donc c'est
   l'ordre du fichier qui tranchait et celle d'en bas gagnait en silence. Le
   `align-items: start` écrit ici n'a jamais rien aligné. */

/* La série de jours saisis. Discrète à dessein : elle compte le fait de tenir
   son journal, pas la conformité à une cible — et elle ne doit pas prendre le
   pas sur ce qu'on est venu lire. */
.streak {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 20px; font-weight: 600; color: var(--terra);
  background: color-mix(in srgb, var(--terra) 12%, transparent);
  border-radius: 999px; padding: 5px 11px; white-space: nowrap;
}
.streak .icon { width: 15px; height: 15px; }

/* L'avatar qui a remplacé l'onglet Profil : initiale par défaut, photo dès
   qu'il y en a une. */
.avatar-btn {
  width: 42px; height: 42px; min-height: 42px; flex: none;
  border: 1px solid var(--line); border-radius: 50%; cursor: pointer;
  background: var(--card); color: var(--cinnamon);
  font-size: 24px; font-weight: 600; padding: 0; overflow: hidden;
  display: grid; place-items: center;
}
.avatar-btn img { width: 100%; height: 100%; object-fit: cover; }

/* ── La carte d'identité du profil — § 16 ────────────────────────────────────

   Le portrait est plus grand qu'en en-tête (56 contre 42) : c'est l'écran où
   l'on vient voir son compte, et une vignette de barre d'onglets y ferait figure
   de miniature égarée. Il n'est pas cliquable — on est déjà sur le profil.

   ⚠️ **`flex: none` sur le portrait, et `min-width: 0` sur le texte.** Sans le
   premier, une adresse longue comprime le rond en ovale ; sans le second, elle
   déborde de la carte au lieu de se couper — les deux se voient sur
   `luca.corona1405@gmail.com`, qui fait 25 caractères. */
.identity-card { display: flex; align-items: center; gap: 14px; }

/* ── L'écran « Importer » — § 10 ─────────────────────────────────────────────

   Les portes réutilisent `.sheet-item`, qui est le dessin d'une entrée de menu :
   même hauteur, même icône, même paire titre/détail. Les rhabiller aurait donné
   deux apparences au même geste selon la porte par laquelle on l'atteint. */
/* ── Les fournisseurs reliés, dans la feuille de réglages — § 15 bis ───────── */

.settings-block { padding: 4px 14px 10px; }
.settings-sous-titre { padding: 0; margin: 10px 0 6px; color: var(--muted); font-size: 19.5px; }

.identite-ligne {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 9px 0; border-top: 1px solid var(--line);
}
.identite-texte { display: flex; flex-direction: column; min-width: 0; }
.identite-texte strong { font-size: 22.5px; font-weight: 600; }
.identite-texte small {
  color: var(--muted); font-size: 19px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Un bouton grisé garde son libellé lisible : il dit ce qu'on ne peut pas
   faire, et son `title` dit pourquoi. ⛔ Donc par une encre, pas par `opacity`. */
.identite-ligne .link-btn:disabled { cursor: not-allowed; }

/* ── La feuille « demander » — celle qui remplace prompt() et confirm() ────── */

/* ⚠️ **Un seul endroit pour le champ du carnet** — DA01, 21/09. `#ask-champ`
   est un identifiant : une règle de classe posée à côté ne l'aurait jamais
   emporté, et on se serait retrouvé avec un champ crème dans une feuille
   ivoire. Les deux sélecteurs partagent donc la même déclaration, et
   `.carnet-champ` reste la classe que les autres menus emploieront (03d).
   Le plancher de 16 px du § 1 n'est pas touché : `--t-champ` vaut 24 px. */
#ask-champ, .carnet .carnet-champ {
  display: block; width: auto; border: 1px solid var(--carnet-filet); border-radius: 16px;
  padding: 12px 14px; background: var(--carnet-carte); color: var(--carnet-encre);
  font-size: var(--t-champ); min-height: 48px;
  margin: 10px 2px 4px;
}

/* Une option se touche : c'est une entrée de menu (`.sheet-item`), et elle en
   garde le dessin — socle des feuilles, 20/09. `.ask-option` ne porte plus que
   ce qui lui est propre : la cible tactile du § 11, que `test_demander` garde. */
/* ⚠️ **Le retrait vient de l'entrée, pas de la liste.** `.sheet-item` porte
   déjà ses 14 px : les cumuler posait le libellé d'une option 14 px plus à
   droite que le titre de la question, et le survol s'arrêtait avant le bord
   comme aucune autre entrée de feuille. */
.ask-liste { display: grid; gap: 2px; margin: 6px 0 4px; max-height: 46vh; overflow-y: auto; }
.ask-option { min-height: 44px; font-size: 22.5px; }
/* Dans le carnet, une option est une carte comme les autres — mais **sans
   chevron ni illustration** : elle choisit, elle n'ouvre pas une étape de plus,
   et le cadre interdit le chevron trompeur sur une sélection. L'état courant
   prend la sauge, avec la coche que pose `_askOuvrir`. */
.carnet .ask-liste { gap: 8px; margin: 4px 0; }
.carnet .ask-option {
  min-height: 56px; padding: 12px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 18px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font-size: var(--t-carte); font-weight: var(--g-accent);
}
.carnet .ask-option:hover:not(:disabled) { background: var(--carnet-carte); border-color: var(--carnet-filet-vif); }
/* Le texte et le champ suivent le retrait du titre (14 px), comme les feuilles
   de réglages : quatre bords différents dans une feuille de trois lignes.
   Les menus du socle (C02, C03) ont la même amorce sous leur titre. */
:is(#ask-sheet, #pick-sheet, #cmenu-sheet, #rmenu-sheet, #prefs-sheet, #emenu-sheet) > .hint { margin-inline: 14px; }
/* Le pied vient du socle (`.sheet-actions`). Le bouton qui détruit porte la
   couleur de ce qu'il fait — et il reste à droite, là où l'œil finit : on lit
   la question avant l'action. */
.ask-actions .btn-primary.danger { background: var(--danger); border-color: var(--danger); }

/* ── Créer une recette — § 15 ────────────────────────────────────────────────

   ⚠️ **Les champs héritent du plancher de 16 px** posé au § 1 : iOS agrandit la
   page dès qu'on met au point un champ dont le texte est plus petit, puis la
   laisse zoomée *et décalée* quand le clavier se referme. On ne redéclare donc
   aucune taille ici — la contrainte se tient en ne l'écrasant pas. */
#create-recipe-form { display: block; }

.field-label {
  display: block; font-size: 19.5px; color: var(--muted);
  margin: 10px 0 4px; font-weight: 600;
}

/* ── Nouvelle recette — P16 de la refonte, 13/09 ─────────────────────────── */

.nr-head { position: relative; }
/* Le statut, en haut à droite comme la maquette — une étiquette, pas un bouton. */
.nr-statut {
  position: absolute; right: 0; top: 4px; z-index: 1;
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
  border-radius: 999px; background: var(--card); border: 1px solid var(--line);
  font-size: 19.5px; color: var(--muted);
}
.nr-statut .icon { width: 16px; height: 16px; }
.nr-decor { margin-top: 40px; }
.nr-decor-cuillere { right: 10px; bottom: 4px; width: 80px; transform: rotate(-30deg); }

.nr-section { margin: 20px 0 2px; font-family: var(--ecriture); font-weight: 400; font-size: 28px; line-height: 1.2; }
.nr-section-sous { margin: 0 0 10px; color: var(--muted); font-size: 21px; }
h2.nr-section + .nr-champ { margin-top: 10px; }

/* La photo : la grande zone pointillée à gauche, les portes à droite. Sous
   360 px les portes passent dessous plutôt que d'écraser la zone. */
.nr-photo { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 10px; }
.nr-photo-zone {
  min-height: 150px; padding: 0; border: 1.5px dashed var(--sand); border-radius: 18px;
  background: var(--card); color: inherit; font: inherit; cursor: pointer; overflow: hidden;
}
.nr-photo-vide { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 14px 8px; text-align: center; }
.nr-photo-rond {
  display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; margin-bottom: 6px;
  background: color-mix(in srgb, var(--terra) 12%, var(--card)); color: var(--terra);
}
.nr-photo-rond .icon { width: 26px; height: 26px; }
.nr-photo-vide strong { font-size: 22.5px; font-weight: 600; }
.nr-photo-vide small { color: var(--muted); font-size: 19px; line-height: 1.3; }
.nr-photo-apercu { display: block; width: 100%; height: 100%; }
.nr-photo-apercu img { display: block; width: 100%; height: 100%; min-height: 150px; object-fit: cover; }
.nr-photo-portes { display: grid; gap: 8px; align-content: start; }
.nr-porte {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 8px 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  color: inherit; font: inherit; font-size: 21px; line-height: 1.2; text-align: left; cursor: pointer;
}
.nr-porte .icon { flex: none; width: 22px; height: 22px; color: var(--terra); }
@media (max-width: 359px) { .nr-photo { grid-template-columns: 1fr; } }

/* Les champs de la maquette : une icône, le champ, la flèche du menu. */
.nr-champ {
  position: relative; display: flex; align-items: center; gap: 8px; min-height: 52px;
  padding: 0 10px; background: var(--card); border: 1px solid var(--line); border-radius: 14px;
}
.nr-champ:focus-within { border-color: var(--terra); }
.nr-champ > .icon { flex: none; width: 20px; height: 20px; color: var(--terra); pointer-events: none; }
/* ⚠️ **16 px, le plancher du § 1** — les champs n'en déclarent pas moins.
   ⛔ **Et pas les radios** : mesuré au banc, `input` tout court donnait aux deux
   boutons de confidentialité 50 px de haut et toute la largeur — deux grands
   ronds posés sur leur propre texte. */
#create-recipe-form input:not([type="radio"]), #create-recipe-form select {
  flex: 1; min-width: 0; width: 100%; min-height: 50px; padding: 0 4px;
  border: none; background: none; color: inherit; font: inherit; font-size: 24px; outline: none;
}
#create-recipe-form select {
  appearance: none; -webkit-appearance: none; padding-right: 14px; cursor: pointer;
  text-overflow: ellipsis;
}
.nr-grille .nr-champ::after {
  content: ""; position: absolute; right: 11px; top: 50%; width: 7px; height: 7px;
  border-right: 1.8px solid var(--muted); border-bottom: 1.8px solid var(--muted);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.nr-grille { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 10px; }
/* Les libellés du menu tiennent sur une ligne à 390 ; sous 375 px, une colonne. */
@media (max-width: 374px) { .nr-grille { grid-template-columns: 1fr; } }

/* La confidentialité : deux cartes-radios, la choisie en terre cuite. */
.nr-choix { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 10px; }
.nr-option {
  position: relative; display: flex; align-items: center; gap: 8px; min-height: 64px; padding: 10px 34px 10px 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px; cursor: pointer;
}
.nr-option input { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; margin: 0; accent-color: var(--terra); }
.nr-option > .icon { flex: none; width: 22px; height: 22px; color: var(--muted); }
.nr-option strong { display: block; font-size: 22.5px; font-weight: 600; white-space: nowrap; }
.nr-option small { color: var(--muted); font-size: 19px; }
.nr-option:has(input:checked) { border-color: var(--terra); background: color-mix(in srgb, var(--terra) 8%, var(--card)); }
.nr-option:has(input:checked) > .icon, .nr-option:has(input:checked) strong { color: var(--cinnamon); }
.nr-option:has(input:focus-visible) { outline: 2px solid var(--terra); outline-offset: 2px; }
@media (max-width: 359px) { .nr-choix { grid-template-columns: 1fr; } }
.nr-note { margin: 8px 0 0; }

.nr-continuer {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 54px; margin-top: 22px; font-size: 24px;
}
.nr-fleche { width: 20px; height: 20px; transform: rotate(-90deg); }
#create-recipe-error { margin-top: 14px; }

/* ── L'écran de bienvenue — le premier d'un compte neuf ───────────────────── */

.welcome-lead {
  color: var(--muted); font-size: 24px; line-height: 1.55;
  margin: 4px 0 22px; max-width: 34ch;
}

/* Mêmes cartes que l'écran « Importer » : un chemin se dessine pareil partout,
   sinon le même geste paraît différent selon la porte par laquelle on l'atteint. */
.welcome-portes { display: grid; gap: 10px; }
.welcome-portes .sheet-item {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 14px;
}

/* ── La dernière question — § 15 ter ──────────────────────────────────────────
   Elle réemploie les cartes des portes : c'est une liste de choix, comme celle
   d'au-dessus, et lui inventer une forme à elle ferait croire qu'on change
   d'écran. Le choix retenu se marque, il ne valide pas tout seul — on peut se
   tromper de ligne au pouce, et « Autrement » ouvre un champ. */
.welcome-q {
  font-family: var(--ecriture); font-size: 27.5px; line-height: 1.3;
  margin: 4px 0 8px;
}
.welcome-portes .sheet-item.on {
  border-color: var(--terra);
  box-shadow: inset 0 0 0 1px var(--terra);
}
.welcome-autre { display: block; margin-top: 14px; }
.welcome-autre span { display: block; font-size: 21px; margin-bottom: 6px; }
.welcome-autre small { color: var(--muted); font-weight: 400; }
/* 16 px minimum — § 1, verrou n° 4 : en dessous, iOS agrandit la page à la mise
   au point et la laisse décalée quand le clavier se referme. */
.welcome-autre input {
  width: 100%; font-size: 24px;
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--card); color: inherit;
}
#screen-welcome .btn-primary { margin-top: 18px; }

/* ── Réglages — C05, 17/09 — et ce que la P30 du 16/09 avait de listes ──────
   ⚠️ Scopé sur `#screen-reglages` (la page) — § *une règle qui ne gagne que
   par sa place se perd*. Les listes de réglages ont quitté le profil ; les
   règles suivent. */
#screen-reglages .profil-rubrique {
  margin: 28px 0 10px; font-family: var(--ecriture); font-weight: 700;
  font-size: 27.5px; color: var(--espresso);
}
#screen-reglages .profil-liste {
  list-style: none; margin: 0; padding: 0 16px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
}
#screen-reglages .profil-liste > li + li { border-top: 1px solid var(--line); }
#screen-reglages .profil-rubrique + .profil-liste { margin-top: 0; }
#screen-reglages .profil-ligne {
  display: flex; align-items: center; gap: 14px; width: 100%;
  min-height: 56px; padding: 10px 0; border: none; background: none;
  font: inherit; color: var(--espresso); text-align: left; text-decoration: none;
  cursor: pointer;
}
#screen-reglages .profil-ligne > .icon { flex: none; width: 24px; height: 24px; color: var(--espresso); }
#screen-reglages .profil-ligne > .icone-sauge { color: var(--sage); }
#screen-reglages .profil-ligne > span:not(.pastille) {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
#screen-reglages .profil-ligne strong { font-size: 24px; font-weight: 500; }
#screen-reglages .profil-ligne small { font-size: 20px; color: var(--muted); line-height: 1.35; }
#screen-reglages .profil-ligne > i { flex: none; font-style: normal; font-size: 27.5px; color: var(--muted); }
#screen-reglages .profil-ligne .bar { margin: 6px 0 0; }
#screen-reglages .profil-ligne .corrections-lead.warn { color: var(--cinnamon); font-weight: 600; }
/* R04, 21/09 — la panne d'une ligne se dit en couleur d'alerte, et se reprend
   sous la ligne, alignée sur son texte (24 px d'icône + 14 px d'écart). */
#screen-reglages .profil-ligne small.warn { color: var(--cinnamon); font-weight: 600; }
#screen-reglages .profil-reessayer { margin: 0 0 12px 38px; min-height: 44px; }
#screen-reglages .profil-bascule .interrupteur { margin: 0 2px 0 0; }
#screen-reglages .profil-apparence { flex-wrap: wrap; cursor: default; }
#screen-reglages .profil-apparence .theme-picker { flex: 1 1 100%; flex-wrap: wrap; }
#screen-reglages .profil-apparence .theme-picker button { min-width: 0; }
#screen-reglages .profil-apparence .theme-picker button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; font-size: 21px;
}

/* ── Comptes bloqués — P35, 18/09 ─────────────────────────────────────────────
   ⚠️ Scopé sur `#screen-bloques` — § *une règle qui ne gagne que par sa place
   se perd*. La maquette : une intro, une carte d'aide rosée, un titre compté,
   une carte de lignes (initiale · nom · @pseudo · date · Débloquer en pastille),
   un état vide avec la marmite. Ce qu'elle montrait en plus — un ⋮ sans geste,
   « Comptes suggérés », des portraits — n'est pas là, et c'est la fiche. */
#screen-bloques .bloques-intro {
  margin: -6px 0 18px; color: var(--muted); font-size: 22.5px; line-height: 1.45;
}
#screen-bloques .bloques-aide {
  display: flex; align-items: center; gap: 14px; padding: 14px 16px;
  border-radius: 18px;
  background: color-mix(in srgb, var(--terra) 9%, var(--card));
  border: 1px solid color-mix(in srgb, var(--terra) 22%, var(--line));
}
#screen-bloques .bloques-aide-icone {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center; color: var(--cinnamon);
  background: color-mix(in srgb, var(--terra) 16%, var(--card));
}
#screen-bloques .bloques-aide-icone .icon { width: 24px; height: 24px; }
#screen-bloques .bloques-aide-texte { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
#screen-bloques .bloques-aide-texte strong { font-size: 24px; font-weight: 600; color: var(--espresso); }
#screen-bloques .bloques-aide-texte small { font-size: 21px; color: var(--muted); line-height: 1.4; }

#screen-bloques .bloques-titre {
  margin: 26px 0 10px; font-family: var(--ecriture); font-weight: 700;
  font-size: 27.5px; color: var(--espresso);
}
#screen-bloques .bloques-carte {
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  padding: 0 16px;
}
#screen-bloques .bloque-ligne {
  display: flex; align-items: center; gap: 10px; padding: 12px 0;
}
#screen-bloques .bloque-ligne + .bloque-ligne { border-top: 1px solid var(--line); }
#screen-bloques .bloque-portrait {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso);
  font-size: 26px; font-weight: 600;
}
#screen-bloques .bloque-texte {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px;
}
#screen-bloques .bloque-texte strong { font-size: 24px; font-weight: 600; color: var(--espresso); }
#screen-bloques .bloque-texte small {
  font-size: 20px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠️ **La date se replie, elle ne se coupe pas** — mesuré au banc à 390 px :
   « Bloqué le 18 septembre 2026 » sortait « Bloqué le 18… » derrière la
   pastille. Un pseudo tronqué reste un pseudo ; une date tronquée n'est plus
   une date. */
#screen-bloques .bloque-texte .bloque-depuis { white-space: normal; line-height: 1.3; }
/* La pastille « Débloquer » : 44 px de haut (AA), en terre cuite sur rosé —
   c'est une commande, pas une étiquette. Le libellé disparaît sous 360 px et
   l'icône reste, avec l'`aria-label` qui nomme le compte. */
#screen-bloques .bloque-debloquer {
  flex: none; display: inline-flex; align-items: center; gap: 5px;
  min-height: 44px; padding: 0 12px; border-radius: 999px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--terra) 22%, var(--line));
  background: color-mix(in srgb, var(--terra) 10%, var(--card));
  color: var(--cinnamon); font: inherit; font-size: 22px; font-weight: 600;
}
#screen-bloques .bloque-debloquer .icon { width: 18px; height: 18px; }
#screen-bloques .bloque-debloquer:disabled { cursor: progress; }
#screen-bloques .bloque-ligne-envoi .bloque-texte { opacity: .55; }
@media (max-width: 359px) {
  #screen-bloques .bloque-debloquer { padding: 0 12px; }
  #screen-bloques .bloque-debloquer span { display: none; }
}

#screen-bloques .bloques-attente { color: var(--muted); padding: 24px 0; text-align: center; }
#screen-bloques .bloques-vide {
  text-align: center; padding: 26px 16px 8px; color: var(--muted); font-size: 22.5px; line-height: 1.45;
}
#screen-bloques .bloques-vide-marmite { width: 150px; height: auto; display: block; margin: 0 auto 6px; }
#screen-bloques .bloques-vide-titre {
  margin: 0 0 8px; font-family: var(--ecriture); font-weight: 700;
  font-size: 29px; color: var(--espresso);
}
#screen-bloques .bloques-vide p { margin: 0; }
#screen-bloques .bloques-erreur {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 24px 16px; text-align: center; color: var(--muted);
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
}
#screen-bloques .bloques-erreur p { margin: 0; }
#screen-reglages .profil-apparence .theme-picker .icon { width: 17px; height: 17px; }
/* Retour du 16/09 : les vidéos prennent la forme des autres rubriques — titre
   serif dehors, lignes dans la même carte — au lieu de l'ancienne `.card`. */
#screen-reglages .profil-videos, #screen-reglages .profil-signalements { margin-top: 0; }
#screen-reglages .profil-rubrique-tete {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
#screen-reglages .profil-rubrique-tete .link-btn { min-height: 44px; font-size: 22.5px; }
#screen-reglages .profil-videos .video-row { border-bottom: none; padding: 12px 0; }
#screen-reglages .profil-videos .video-thumb { width: 56px; height: 42px; }
#screen-reglages .profil-videos .video-row { grid-template-columns: 56px 1fr; gap: 14px; }
/* Tes signalements — retour du 16/09 : la forme des autres lignes, texte sur
   deux lignes au plus, statut en pastille discrète sous le texte. */
#screen-reglages .profil-signalement { cursor: default; }
#screen-reglages .profil-signalement strong {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; font-size: 23px; line-height: 1.35;
}
#screen-reglages .profil-signalement small { display: flex; align-items: center; gap: 8px; margin-top: 3px; }
#screen-reglages .profil-statut {
  font-style: normal; font-size: 19px; font-weight: 600; line-height: 1;
  padding: 4px 9px; border-radius: 999px;
  background: var(--tuile-sable); color: var(--tuile-sable-i);
}
#screen-reglages .profil-statut.statut-vu { background: var(--tuile-rose); color: var(--tuile-rose-i); }
#screen-reglages .profil-statut.statut-traite { background: var(--tuile-sauge); color: var(--tuile-sauge-i); }
#screen-reglages .profil-source { margin: 22px 2px 0; font-size: 19.5px; color: var(--muted); line-height: 1.45; }

/* La déconnexion ferme le profil : en bas, là où on la cherche (signalé par Luca
   le 15/09), à gauche comme sur la maquette, le mot de la maison à droite. */
#screen-reglages .profil-pied {
  display: flex; align-items: center; gap: 12px; margin: 22px 0 28px;
}
#screen-reglages .profil-deconnexion {
  flex: 0 1 auto; min-width: 0; display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 52px; margin: 0; padding: 0 14px; white-space: nowrap;
  color: var(--deconnexion-texte); background: var(--deconnexion-fond);
  border-color: transparent; font-weight: 600;
}
#screen-reglages .profil-deconnexion .icon { width: 21px; height: 21px; }
#screen-reglages .profil-merci {
  flex: 1 1 0; min-width: 0; margin: 0; text-align: center; transform: rotate(-7deg);
  font-family: var(--ecriture); font-size: clamp(20px, 6.4vw, 25px); line-height: 1.05;
  color: var(--coeur);
}
#screen-reglages .profil-merci .icon { display: block; margin: 2px auto 0; width: 22px; height: 22px; }
@media (max-width: 359px) {
      #screen-reglages .profil-merci { font-size: 26px; white-space: normal; }
  #screen-reglages .profil-deconnexion { padding: 0 12px; gap: 7px; }
  #screen-reglages .profil-apparence .theme-picker { gap: 6px; }
  #screen-reglages .profil-apparence .theme-picker button { padding: 8px 4px; font-size: 19.5px; gap: 4px; }
}
/* ⚠️ **Un champ ne descend jamais sous 16 px** (§ 1). */
#profil-sheet textarea {
  width: 100%; font: inherit; font-size: 24px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--cream); color: var(--espresso);
  resize: vertical;
}
#profil-sheet #pp-bio-champ label { display: block; margin: 8px 0 6px; }
/* ── Les feuilles du compte — P30, 16/09 ─────────────────────────────────────
   ⚠️ **Tout champ à 16 px** (§ 1) ; commandes à 44 px. */
#profil-sheet .compte-portrait { display: flex; align-items: center; gap: 16px; }
#profil-sheet .compte-portrait .identity-portrait { width: 72px; height: 72px; font-size: 31px; }
#profil-sheet .compte-portrait-actions { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
#profil-sheet .compte-fichier { position: relative; display: inline-flex; align-items: center; min-height: 44px; cursor: pointer; }
#profil-sheet .compte-fichier input {
  position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;
}
#profil-sheet .compte-fichier:focus-within { outline: 2px solid var(--terra); outline-offset: 2px; }
#profil-sheet .compte-ligne, #profil-sheet .compte-actions {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
#profil-sheet .compte-ligne > span { display: flex; flex-direction: column; min-width: 0; }
#profil-sheet .compte-ligne small { color: var(--muted); overflow-wrap: anywhere; }
#profil-sheet .compte-ligne .link-btn { min-height: 44px; }
#profil-sheet .compte-actions > * { flex: 1; }
#profil-sheet .compte-champ, #securite-sheet .compte-champ {
  display: flex; flex-direction: column; gap: 6px; margin-top: 12px; font-size: 21px; color: var(--muted);
}
#profil-sheet .compte-champ input, #securite-sheet .compte-champ input {
  font: inherit; font-size: 24px; min-height: 48px; padding: 10px 14px; color: var(--espresso);
  border: 1px solid var(--line); border-radius: 14px; background: var(--cream);
}
#profil-sheet .compte-formulaire .hint, #securite-sheet .compte-formulaire .hint { margin: 10px 0 0; }
#profil-sheet .compte-formulaire .compte-actions { margin-top: 12px; }
#profil-sheet .compte-etat { margin: 6px 0 0; min-height: 1.3em; }
#profil-sheet .compte-etat.warn { color: var(--danger); }
#profil-sheet .reglage-ligne select { font-size: 24px; min-height: 44px; }
#securite-sheet .compte-formulaire .btn-primary { width: 100%; margin-top: 14px; }
#securite-sheet .compte-large { width: 100%; min-height: 48px; margin-top: 10px; }
#donnees-sheet .donnees-liste { margin: 4px 0 14px; }
#donnees-sheet .donnees-liste li { font-size: 22.5px; }
/* Un seul retrait pour tout ce que ces feuilles portent — vu au banc : titres
   de section, langue, pseudo et liste des données tombaient sur quatre bords
   différents. Celui du titre de la feuille (14 px) fait foi. */
:is(#profil-sheet, #securite-sheet, #donnees-sheet) > :is(.hint, .stats, .sheet-item, .form-error) {
  margin-inline: 14px; width: auto;
}
:is(#profil-sheet, #securite-sheet, #donnees-sheet) .settings-block { padding: 2px 14px 12px; }
:is(#profil-sheet, #securite-sheet, #donnees-sheet) .settings-block + .settings-block {
  border-top: 1px solid var(--line); padding-top: 6px;
}
:is(#profil-sheet, #securite-sheet, #donnees-sheet) .settings-sous-titre {
  margin: 12px 0 4px; padding: 0; font-family: var(--ecriture); font-size: 19px;
  font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
:is(#profil-sheet, #securite-sheet, #donnees-sheet) .settings-block :is(.reglage-ligne, .sheet-item) { padding-inline: 0; }
:is(#profil-sheet, #securite-sheet, #donnees-sheet) .reglage-ligne small { color: var(--muted); }
#donnees-sheet > .sheet-item { padding-inline: 0; }
#securite-sheet .identite-ligne .link-btn { min-height: 44px; }
/* Le pied de ces feuilles (retrait de 14 px, bouton seul en pleine largeur) est
   celui du socle `.sheet-actions` depuis le 20/09 : plus de surcharge ici. */
#donnees-sheet .donnees-liste { margin-bottom: 8px; }

/* Les deux étapes de la suppression écrivent sur plusieurs lignes. */
#ask-texte { white-space: pre-line; }
#notif-sheet .notif-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* ── P29 — la bienvenue de la maquette, 15/09 ────────────────────────────────
   ⚠️ Toutes les règles nouvelles sont scopées sur `#screen-welcome`. */

/* La scène : bord à bord, sous la zone sûre, les pièces posées en pourcentages
   d'une boîte à hauteur fixe — un seul nombre (`--scene-h`) règle tout. Aucune
   pièce n'est une information : `aria-hidden` sur la scène entière. */
#screen-welcome { position: relative; }
#screen-welcome .welcome-scene {
  --scene-h: 320px;
  position: relative; height: var(--scene-h); overflow: hidden;
  margin: -28px -20px 0;
  background:
    radial-gradient(120% 70% at 50% 100%, var(--cream) 58%, transparent 59%) bottom / 100% 60px no-repeat,
    radial-gradient(90% 80% at 70% 20%, #FFF8EF 0%, #F6E3CF 60%, #EBCBAE 100%);
}
#screen-welcome .welcome-scene img { position: absolute; display: block; pointer-events: none; }
#screen-welcome .ws-logo { left: 18px; top: calc(20px + env(safe-area-inset-top, 0px)); width: 30%; max-width: 160px; z-index: 3; }
/* ⚠️ Mesuré au banc : les dessins n'ont pas les mêmes proportions (la cuillère
   fait 319 × 1400) — dimensionnés tous en largeur, la cuillère montait à 377 px
   et passait devant la marmite. Chaque pièce se règle sur la dimension qui la
   borne, et la marmite reste au centre, la plus grande. */
#screen-welcome .ws-marmite { left: 50%; bottom: 34px; width: 58%; max-width: 310px; transform: translateX(-50%); z-index: 2; }
#screen-welcome .ws-couvercle { left: 50%; bottom: calc(34px + min(58vw, 310px) * .56); width: 40%; max-width: 215px; transform: translateX(-2%) rotate(-24deg); transform-origin: left bottom; z-index: 2; }
#screen-welcome .ws-vapeur { left: 50%; bottom: calc(40px + min(58vw, 310px) * .5); height: 44%; transform: translateX(-80%); opacity: .6; z-index: 1; }
#screen-welcome .ws-basilic { left: 3%; bottom: 40px; width: 20%; max-width: 104px; z-index: 3; }
#screen-welcome .ws-tomate-1 { left: 0; bottom: 26px; width: 15%; max-width: 80px; z-index: 4; }
#screen-welcome .ws-tomate-2 { left: 13%; bottom: 18px; width: 12%; max-width: 62px; z-index: 4; }
#screen-welcome .ws-ail { right: 15%; bottom: 26px; width: 12%; max-width: 62px; z-index: 3; }
#screen-welcome .ws-cuillere { right: 6%; bottom: 20px; height: 46%; transform: rotate(52deg); transform-origin: 50% 90%; z-index: 1; }
@media (max-width: 359px) { #screen-welcome .welcome-scene { --scene-h: 260px; } }

/* « Passer » : une pilule sur la scène, en haut à droite, comme sur la maquette. */
#screen-welcome .welcome-passer {
  position: absolute; top: calc(18px + env(safe-area-inset-top, 0px)); right: 16px; z-index: 2;
  min-height: 44px; padding: 8px 18px; border-radius: 999px; border: none;
  /* La pilule vit sur la scène, qui reste claire en sombre : ses couleurs sont
     fixes, sinon le texte du thème sombre disparaît sur son fond (vu au banc). */
  background: rgb(255 252 246 / 90%); color: #2E241D; font: inherit; font-size: 22.5px; cursor: pointer;
  box-shadow: 0 1px 4px rgb(46 36 29 / 10%);
}

#screen-welcome .welcome-titre {
  margin: 4px 0 6px; text-align: center;
  font-family: var(--ecriture); font-weight: 400; font-size: 41px; line-height: 1.1;
}
#screen-welcome .welcome-sous { margin: 0 0 6px; text-align: center; font-size: 25px; color: var(--espresso); }
#screen-welcome [data-welcome-step="portes"] .welcome-lead { margin: 0 auto 18px; text-align: center; max-width: 38ch; font-size: 23px; }

/* Les quatre cartes : deux colonnes, pastel, pictogramme teinté au-dessus. La
   choisie est bordée de terre cuite ; `aria-pressed` le dit sans la couleur. */
:is(#screen-welcome, #auth-welcome) .welcome-cartes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
:is(#screen-welcome, #auth-welcome) .welcome-carte {
  display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0;
  padding: 14px 10px; border-radius: 18px; text-align: center; cursor: pointer;
  border: 1.5px solid transparent; color: var(--espresso); font: inherit;
}
:is(#screen-welcome, #auth-welcome) .welcome-carte-terra { background: color-mix(in srgb, var(--terra) 8%, var(--card)); }
:is(#screen-welcome, #auth-welcome) .welcome-carte-sauge { background: color-mix(in srgb, var(--sage) 10%, var(--card)); }
:is(#screen-welcome, #auth-welcome) .welcome-carte-icone { display: grid; place-items: center; width: 44px; height: 44px; }
:is(#screen-welcome, #auth-welcome) .welcome-carte-terra .welcome-carte-icone { color: var(--danger); }
:is(#screen-welcome, #auth-welcome) .welcome-carte-sauge .welcome-carte-icone { color: color-mix(in srgb, var(--sage) 70%, var(--espresso)); }
:is(#screen-welcome, #auth-welcome) .welcome-carte-icone .icon { width: 34px; height: 34px; }
:is(#screen-welcome, #auth-welcome) .welcome-carte-icone img { width: 44px; height: 44px; object-fit: contain; }
:is(#screen-welcome, #auth-welcome) .welcome-carte strong { font-family: var(--ecriture); font-weight: 400; font-size: 25px; line-height: 1.2; }
:is(#screen-welcome, #auth-welcome) .welcome-carte small { color: var(--muted); font-size: 20px; line-height: 1.35; }
#screen-welcome .welcome-carte[aria-pressed="true"] {
  border-color: var(--terra); box-shadow: 0 2px 10px color-mix(in srgb, var(--terra) 18%, transparent);
}
@media (max-width: 339px) { :is(#screen-welcome, #auth-welcome) .welcome-cartes { grid-template-columns: minmax(0, 1fr); } }

/* Le choix du suivi : une carte, l'interrupteur à droite, non coché par défaut. */
#screen-welcome .welcome-suivi {
  display: flex; align-items: center; gap: 12px; margin-top: 12px;
  padding: 12px 14px; border-radius: 16px; background: var(--card); border: 1px solid var(--line); cursor: pointer;
}
#screen-welcome .welcome-suivi-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#screen-welcome .welcome-suivi-texte strong { font-size: 23px; font-weight: 600; }
#screen-welcome .welcome-suivi-texte small { color: var(--muted); font-size: 20px; }
#screen-welcome .welcome-commencer {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 54px; font-size: 24.5px;
}
#screen-welcome .welcome-commencer .icon { width: 22px; height: 22px; }
#screen-welcome .welcome-compte { display: block; min-height: 44px; margin: 8px auto 0; padding: 0 16px; color: var(--cinnamon); font-size: 23px; font-weight: 600; }

/* Les cartes de présentation (sur le welcome de P38) ne sont pas des
   commandes — même forme, sans curseur ni état choisi. */
:is(#screen-welcome, #auth-welcome) div.welcome-carte { cursor: default; }

#welcome-source-skip { display: block; margin: 14px auto 0; }

/* ── Le champ d'import — § 10 quater ────────────────────────────────────────

   ⚠️ **Le champ prend toute la largeur, et le bouton passe dessous.** Mesuré au
   banc à 390 px : côte à côte, il ne restait **180 px** au champ — trois mots
   par ligne dès qu'on y colle une recette, et une adresse coupée au vingtième
   caractère. C'est déjà la forme de l'import en masse, deux cartes plus bas, et
   rien ne justifiait que les deux diffèrent.

   ⛔ **Et la règle se scope sur `.card`, pas sur l'ordre du fichier.**
   `.card textarea` existe **plus bas** avec la même spécificité : elle gagnait
   donc au départage, et le champ héritait de `resize: vertical`, d'un fond
   `--cream` et d'une marge basse que personne n'avait demandés. Mesuré au banc,
   pas relu — `getComputedStyle` disait `vertical` alors que la feuille servie
   portait bien `none`. Une règle qui ne gagne que par sa **place** se perd au
   premier déplacement, et rien ne le dit. */
.import-row { display: flex; flex-wrap: wrap; gap: 10px; }
.import-row input, .card .import-row textarea {
  flex: 1 1 100%; min-width: 0;
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--card); color: inherit;
}
/* Le champ grandit avec ce qu'on y colle, **jusqu'à un plafond** — au-delà il
   défile de lui-même. Sans plafond, une recette de quarante lignes pousserait
   le bouton « Importer » hors de l'écran : le § 5, obtenu par un champ de
   saisie. ⛔ `resize: none` parce qu'un champ qui grandit tout seul n'a personne
   à qui offrir ce geste, et que la poignée native se pose par-dessus le coin
   arrondi. */
.card .import-row textarea {
  resize: none; overflow-y: auto; margin-bottom: 0;
  line-height: 1.45; max-height: 220px;
}
.import-row .btn-primary { flex: 1 1 100%; }

.import-note { margin-top: 12px; }

/* ── Importer — P14 de la refonte, 13/09 ─────────────────────────────────

   La maquette : un grand titre serif avec un décor culinaire à droite, le
   champ en tête, une rangée de plateformes *dites*, des tuiles pour les autres
   portes, une carte d'idées sur fond sauge, et des accès en pied. Ce qu'elle
   dessinait en « Derniers imports » n'existe pas côté serveur — voir la fiche.

   ⚠️ **Le décor cède la place au texte, jamais l'inverse.** Il est en
   `flex: none` avec une largeur fixe : c'est le titre qui passe sur deux
   lignes à 390 px (« Importer une / recette »), et sous 360 px c'est le
   décor qui rétrécit. Un titre coupé à un mot par ligne serait le § 10 —
   un écran étiré — obtenu en sens inverse. */
.ip-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "retour decor" "texte decor";
  column-gap: 12px; margin-bottom: 14px;
}
.ip-head > .icon-btn { grid-area: retour; align-self: start; }
.ip-head-texte { grid-area: texte; min-width: 0; padding-top: 6px; }
.ip-titre {
  margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400;
  font-size: clamp(30px, 8.48vw, 34.5px); line-height: 1.1;
}
.ip-intro { margin: 0; color: var(--muted); font-size: 22.5px; line-height: 1.35; }
.ip-decor { grid-area: decor; position: relative; width: 136px; height: 118px; align-self: end; }
.ip-decor img { position: absolute; display: block; pointer-events: none; }
.ip-decor-marmite { right: 0; bottom: 0; width: 122px; }
.ip-decor-aliment { width: 36px; }
.ip-decor-tomate { left: 0; bottom: 0; }
.ip-decor-ail { left: 24px; bottom: -6px; width: 30px; }
.ip-decor-basilic { right: -2px; top: 4px; width: 40px; transform: rotate(16deg); }
@media (max-width: 359px) {
  .ip-decor { width: 100px; height: 88px; }
  .ip-decor-marmite { width: 90px; }
  .ip-decor-aliment { width: 26px; }
  .ip-decor-ail { width: 22px; left: 18px; }
  .ip-decor-basilic { width: 30px; }
}

/* Le champ : l'icône de lien à gauche et la croix à droite vivent **dans** le
   cadre du champ, donc c'est le champ qui leur fait de la place. ⚠️ La règle
   qui décide de ce champ reste `.card .import-row textarea` (voir plus haut) :
   celle-ci ne touche qu'au rembourrage, et gagne par spécificité (0,3,1), pas
   par sa place dans le fichier. */
/* « Analyser » à côté du champ, comme la maquette : le champ prend le reste et
   le bouton garde sa largeur, aligné en haut pour qu'un collage de quarante
   lignes grandisse *sous* lui. ⚠️ **Sous 375 px le bouton passe dessous** :
   122 px de bouton dans 292 px de carte laissaient 106 px de texte au champ,
   soit un libellé de substitution sur deux lignes dans un champ d'une. */
.ip-saisie .import-row { flex-wrap: nowrap; align-items: flex-start; }
.ip-champ { position: relative; flex: 1 1 auto; min-width: 0; }
.ip-saisie .import-row .ip-analyser {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  min-height: 49px; padding: 0 12px; white-space: nowrap;
}
.ip-analyser .icon { width: 18px; height: 18px; }
@media (max-width: 374px) {
  .ip-saisie .import-row { flex-wrap: wrap; }
  .ip-saisie .import-row .ip-analyser { flex: 1 1 100%; justify-content: center; }
}
.card .import-row .ip-champ textarea { display: block; width: 100%; padding-left: 40px; padding-right: 10px; }
/* La place de la croix ne se réserve que quand la croix est là : à 320 px, le
   champ n'a que 150 px de texte avec les deux rembourrages, et le libellé de
   substitution y passait sur deux lignes. */
.card .import-row .ip-champ:has(.ip-effacer:not([hidden])) textarea { padding-right: 44px; }
.ip-champ-icone {
  position: absolute; left: 12px; top: 13px; width: 22px; height: 22px;
  color: var(--muted); pointer-events: none;
}
.ip-effacer {
  position: absolute; right: 2px; top: 2px; width: 44px; height: 44px;
  display: grid; place-items: center; border: none; background: none;
  color: var(--muted); cursor: pointer; border-radius: 12px;
}
.ip-effacer .icon { width: 20px; height: 20px; }
.ip-effacer:hover { color: var(--espresso); }

/* Les plateformes, **dites** : une liste, pas des boutons. Cinq colonnes égales
   sur 322 px de carte à 390 : « Instagram » en 12,5 px y tient sans se couper. */
.ip-sources {
  list-style: none; margin: 14px auto 0; padding: 0; max-width: 480px;
  display: flex; justify-content: space-between; gap: 2px;
}
/* ⚠️ **Six sources depuis Pinterest (13/09), et chacune garde son mot entier.**
   Des colonnes égales coupaient « Instagram » ; `nowrap` à 12,5 px débordait de
   la carte de 20 px à 390. Chaque mot prend sa largeur à 11,5 px — 295 px pour
   310 —, et **sous 375 px la rangée devient deux rangs de trois** plutôt que de
   rogner un nom. */
.ip-sources li {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font-size: 17px; color: var(--muted); text-align: center; white-space: nowrap;
}
.ip-sources .icon { width: 24px; height: 24px; color: var(--espresso); }
@media (max-width: 374px) {
  .ip-sources { display: grid; grid-template-columns: repeat(3, 1fr); row-gap: 12px; }
  .ip-sources li { font-size: 19px; }
}
.ip-saisie .import-note { margin-top: 12px; margin-bottom: 0; }

.ip-separateur {
  display: flex; align-items: center; gap: 12px; margin: 18px 0 12px;
  color: var(--muted); font-size: 21px;
}
.ip-separateur::before, .ip-separateur::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

/* Les trois cartes de la maquette, sur une rangée. 100 px par carte à 390 :
   le titre passe sur deux lignes, c'est ce que la maquette dessine aussi. */
.import-portes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.ip-tuile {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 6px; padding: 16px 8px 14px; min-height: 44px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  color: inherit; font: inherit; cursor: pointer;
}
.ip-tuile:hover { background: var(--cream); }
.ip-tuile-icone { display: flex; color: var(--terra); }
.ip-tuile-icone .icon { width: 30px; height: 30px; }
.ip-tuile strong { font-size: 22.5px; font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.ip-tuile small { color: var(--muted); font-size: 19px; line-height: 1.3; }
@media (max-width: 359px) {
  .import-portes { gap: 6px; }
  .ip-tuile { padding: 12px 4px 10px; }
  .ip-tuile strong { font-size: 20px; }
  .ip-tuile small { font-size: 17px; }
}

.ip-ligne {
  display: flex; align-items: center; gap: 12px; width: 100%; margin-top: 10px;
  padding: 12px 14px; min-height: 56px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ip-ligne:hover { background: var(--cream); }
.ip-ligne-icone { flex: none; display: flex; color: var(--terra); }
.ip-ligne-icone .icon { width: 26px; height: 26px; }
.ip-ligne-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ip-ligne-texte strong { font-size: 23px; font-weight: 600; }
.ip-ligne-texte small { color: var(--muted); font-size: 19.5px; line-height: 1.3; }
.ip-ligne-chevron { flex: none; width: 18px; height: 18px; color: var(--muted); transform: rotate(-90deg); }
.ip-acces .ip-ligne { margin-top: 0; }
.ip-acces { display: grid; gap: 10px; margin-top: 10px; }

/* Les derniers imports : vignette avec le logo de sa plateforme, titre, date,
   et « Recette créée » en sauge — la ligne de la maquette. */
.ip-recents { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.ip-recent {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px 10px 10px; min-height: 64px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ip-recent:hover { background: var(--cream); }
.ip-recent-vignette { position: relative; flex: none; width: 54px; height: 54px; }
/* ⚠️ **Le cadre dimensionne ses enfants, pas une classe posée sur l'image** :
   `replierSiCassee` remplace une photo morte par un dessin neuf, qui n'emporte
   pas les classes de l'image qu'il remplace — mesuré au banc, un blanc de poulet
   de 400 px posé en travers de la liste. */
.ip-recent-img, .ip-recent-vignette > img, .ip-recent-vignette > div {
  display: block; width: 54px; height: 54px; border-radius: 12px; object-fit: cover;
  background: var(--cream);
}
.ip-recent-vignette .thumb-fallback { display: grid; place-items: center; color: var(--sand); }
.ip-recent-vignette .thumb-fallback .icon { width: 26px; height: 26px; }
.ip-recent-badge {
  position: absolute; right: -4px; bottom: -4px; width: 22px; height: 22px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--card); color: var(--espresso); border: 1px solid var(--line);
}
.ip-recent-badge .icon { width: 13px; height: 13px; }
.ip-recent-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ip-recent-texte strong {
  font-size: 22.5px; font-weight: 600; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ip-recent-texte small { color: var(--muted); font-size: 19px; }
.ip-recent-etat {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  font-size: 19px; color: var(--sage); white-space: nowrap;
}
.ip-recent-etat .icon {
  width: 18px; height: 18px; padding: 3px; border-radius: 50%;
  background: var(--sage); color: #fff;
}
/* L'état reste sur la ligne du titre, comme la maquette, tant qu'il reste au
   titre de quoi tenir sur deux lignes ; sous 375 px il passe sous la date. */
@media (max-width: 374px) {
  .ip-recent { flex-wrap: wrap; }
  .ip-recent-etat { order: 3; width: 100%; padding-left: 66px; margin-top: -4px; }
}
.ip-recents-vide { margin: 0; }
.ip-voir-tout { margin-top: 10px; }

/* La carte d'idées : un fond sauge pâle, dérivé du jeton pour suivre le
   sombre — un hex posé ici serait une seconde définition de la sauge. */
.ip-idees {
  display: flex; gap: 14px; align-items: flex-start; margin-top: 16px;
  padding: 16px; border-radius: 18px;
  background: color-mix(in srgb, var(--sage) 16%, var(--card));
}
.ip-idees-icone { flex: none; width: 30px; height: 30px; color: var(--sage); margin-top: 2px; }
.ip-idees b { display: block; font-size: 23px; margin-bottom: 4px; }
.ip-idees p { margin: 0; color: var(--muted); font-size: 20px; line-height: 1.4; }

.identity-portrait {
  flex: none; width: 56px; height: 56px; border-radius: 50%;
  overflow: hidden; display: grid; place-items: center;
  background: var(--sand); color: var(--espresso);
  font-size: 27.5px; font-weight: 600; line-height: 1;
}
.identity-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }

.identity-text { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.identity-text b { font-size: 24.5px; font-weight: 600; }
.identity-text small {
  color: var(--muted); font-size: 19.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Retour d'un écran de détail ─────────────────────────────────────────── */

/* Les onglets glissent nativement dans le carrousel ; seuls les écrans de
   détail, qui vivent hors de lui, gardent une transition. */
@keyframes slide-from-right { from { opacity: .4; transform: translateX(-22px); } }

.screen.slide-right { animation: slide-from-right .24s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .screen.slide-right { animation: none; }
}

/* ── Navigation ──────────────────────────────────────────────────────────── */

.tabbar {
  /* Bord bas seulement : la largeur et le centrage viennent du recadrage des
     couches fixes, en haut de cette feuille. */
  position: fixed; bottom: 0; z-index: 40;
  height: var(--tabbar-h);
  /* Quatre onglets plus le ➕ : cinq cellules, et le ➕ occupe la troisième —
     donc exactement le milieu. C'est ce qui a fait sortir Profil de la barre :
     à six cellules, aucun réglage de largeur ne peut centrer un bouton placé
     en troisième position sur deux groupes de 2 et 3. */
  display: grid; grid-template-columns: repeat(5, 1fr);
  align-items: center;
  /* ⛔ **Pas de `backdrop-filter` ici, et c'est la règle la plus chère du
     fichier.** La barre est *fixe*, *pleine largeur* et présente sur **tous**
     les écrans : un flou d'arrière-plan oblige le navigateur à re-flouter la
     bande qu'elle couvre **à chaque image de chaque défilement**, sur toute
     l'application. Le défilement vertical quitte alors le fil compositeur — le
     même mode d'échec qu'un `touchmove` non passif (voir en tête d'`app.js`),
     obtenu par une seule ligne de décoration.
     Le fond est donc **opaque**. Une barre translucide sans flou serait pire
     encore : on verrait le contenu glisser dessous en clair. */
  background: var(--barre);
  /* Pas de filet : le fichier « footer Navbar » n'en a pas — la bande se
     distingue du fond des pages par sa seule couleur, et c'est le couvercle
     de la cocotte qui marque son bord. */
  padding-bottom: env(safe-area-inset-bottom);
}
.tab {
  border: none; background: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  /* Rembourrage dissymétrique : centrés dans la barre, les onglets se lisaient
     « un rien trop haut » (Luca, 12/09) — ils descendent de 5 px. */
  font-size: 16px; color: var(--bouton-second-texte); padding: 12px 2px 2px;
  overflow: hidden; white-space: nowrap;
  /* ⛔ Pas de surlignage natif au toucher : iOS et Android peignent un
     rectangle gris sur la boîte du bouton pendant le tap — sur la cocotte,
     dont la boîte est transparente, c'est un « vieux carré » qui apparaît une
     fraction de seconde (signalé le 12/09). Le retour du geste est le
     `:active` ci-dessous, sur le dessin lui-même. */
  -webkit-tap-highlight-color: transparent;
}
.tab .icon { width: 23px; height: 23px; opacity: .62; }
.tab.active { color: var(--cinnamon); font-weight: 600; }
.tab.active .icon { opacity: 1; color: var(--cinnamon); }
/* ⚠️ **« Communaut » — mesuré au banc, revue de 03b.** Cinq cellules de 61 px à
   320 px : le plus long des cinq libellés demande 67 px à 16 px de Caveat, et
   `overflow: hidden` le coupait **sans ellipse**, donc sans rien dire. Le
   libellé d'un onglet ne s'abrège pas : c'est le nom de la destination. Il
   passe donc à 13,5 px sous 360 px — la même exception que les vingt autres
   règles `max-width: 359px` de ce fichier —, ce qui ramène le besoin à 57 px.
   ⛔ Pas de `letter-spacing` négatif : sur une manuscrite, les lettres sont
   dessinées liées, et les resserrer les fait se chevaucher. */
@media (max-width: 359px) {
  .tab { font-size: 13.5px; padding-left: 0; padding-right: 0; }
  .tab .icon { width: 21px; height: 21px; }
}

/* ── Le ➕ est une cocotte — 12/09 ────────────────────────────────────────
   Fichier « footer Navbar » : plus de disque terra, un dessin. Il n'a ni fond
   ni ombre — le dessin porte son propre contour, découpé avec lui — et il
   **dépasse de la barre** par le couvercle, comme sur le fichier. Trois choses
   que le disque n'avait pas à savoir :
   - `.tab` coupe ce qui dépasse (`overflow: hidden`) : le couvercle serait
     rasé net au bord de la barre, sans un mot. Le ➕ rouvre le débordement.
   - la géométrie se dérive du fichier, à l'échelle 1320 → 390 (× 3,385) : la
     découpe fait 255 × 212 → 75 × 63 px, et sa tête est 89 px au-dessus de
     la bande → 26 px. Un test relit le fichier livré.
   - le bouton reste un `.tab` de la grille (troisième cellule de cinq), donc
     `tabOrder()` et le carrousel ne changent pas. */
.tab-add {
  justify-self: center; align-self: start;
  width: 75px; height: 63px; margin-top: -26px; padding: 0;
  overflow: visible;
  transition: transform .18s ease;
}
.tab-add-cocotte { width: 75px; height: 63px; display: block; }
.tab-add:hover { transform: translateY(-2px) scale(1.04); }
.tab-add:active { transform: scale(.94); }

/* Les deux pièces qui n'existent que dans le rail (§ « tablette et bureau ») :
   la marque en tête de colonne, et le mot que le ➕ n'a pas la place de porter
   sur une barre de cinq cellules. Elles vivent dans le HTML de la barre plutôt
   que dans un second balisage — un rail écrit à part serait une seconde
   navigation à tenir, et c'est `tabOrder()` qui ordonne le carrousel. */
.tab-add-mot, .rail-marque { display: none; }
.rail-marque { align-items: center; padding: 4px 10px 16px; }
.rail-marque img { width: 132px; height: auto; }

/* ── Feuille du bouton central ───────────────────────────────────────────── */

.sheet-backdrop {
  position: fixed; inset: 0; z-index: 50;
  /* Même famille que la barre d'onglets : un `backdrop-filter` couvrant tout
     l'écran reste à re-composer à chaque image tant qu'une couche bouge
     au-dessus — et les molettes défilent précisément au-dessus. Deux pixels de
     flou ne se voient pas derrière un voile à 45 % ; ils se sentent. */
  background: rgba(30, 24, 20, .45);
}
/* ⚠️ **Une feuille est bornée en hauteur.** Elle est ancrée en bas et grandit vers
   le haut : sans plafond, elle sort de l'écran par le haut dès que son contenu
   dépasse, et son titre part avec. Constaté le 13/08 — la feuille de quantité est
   passée à 702 px en gagnant des molettes plus hautes, pour 752 px disponibles à
   844 pt de haut. Elle tenait de justesse sur un grand téléphone et débordait de
   127 px sur un iPhone SE.

   Le plafond n'est donc pas une précaution : c'est ce qui rend la hauteur du
   contenu **sans conséquence**. Ce qui dépasse défile dans `.sheet-scroll`, et
   `.sheet-actions` garde l'action principale dans le cadre (§ 5 des conventions —
   c'est le même motif que `.filters-sheet`). */
.sheet {
  position: fixed; z-index: 51;
  display: flex; flex-direction: column;
  /* `--hors-reseau-h` : la bande du haut prend sa part du plafond (M01). */
  max-height: min(88vh, calc(100dvh - var(--tabbar-h) - 24px - var(--hors-reseau-h, 0px)));
  /* ⚠️ **Le centre de la fenêtre n'est pas celui de la colonne — M02, 21/09.**
     `left: 50%` a suffi tant qu'il n'y avait que la colonne ; avec le rail de
     252 px à gauche (≥ 1 180 px), la feuille se posait 126 px à gauche du
     contenu qu'elle commente — mesuré à 1 280 px : la zone de contenu va de 252
     à 1 280, son centre est donc à **766** px, et la feuille de 440 px se
     posait à 420–860 au lieu de 546–986. ⚠️ Les deux bornes de la première
     version de ce commentaire (« la colonne va de 504 à 1 268 ») étaient
     fausses : elles décrivaient une colonne centrée sur la fenêtre entière, ce
     que la règle voisine ne fait précisément plus. C'est la formule du § 2, celle de la barre
     d'onglets et de la bande hors connexion, et `--rail-w` vaut zéro sur
     téléphone : la règle reste unique. ⛔ **Sans la largeur de la règle
     commune** — une feuille est bornée à 440 px, pas à la colonne entière.
     Le voile, lui, garde la fenêtre : il neutralise aussi le rail. */
  left: calc(50% + var(--rail-w) / 2); transform: translateX(-50%);
  bottom: calc(var(--tabbar-h) + 12px);
  width: min(440px, calc(100% - 28px));
  background: var(--card); border-radius: var(--radius);
  box-shadow: 0 18px 50px rgba(30, 24, 20, .28);
  padding: 8px;
  /* ⚠️ **Le plafond découpait au lieu de faire défiler**, et c'est la moitié du
     § 5 qui manquait. `overflow: hidden` rendait la hauteur du contenu sans
     conséquence *pour la mise en page* — la feuille ne sortait plus de l'écran —
     mais ce qui dépassait **disparaissait sans un signe** : le menu du ➕ a
     gagné « Importer en masse » le 15/08, et « Scanner un repas » a cessé
     d'exister sur un petit téléphone. Rien à l'écran ne dit qu'il y a une suite,
     puisqu'il n'y a pas de barre de défilement à voir.

     Le défilement est donc le **défaut**, et non plus une option qu'une feuille
     doit penser à demander : une feuille de menu n'a aucune raison de déclarer
     trois bandes pour six boutons, et c'est justement celle qui oublie. */
  overflow-y: auto;
  /* Arriver au bout ne fait pas repartir la page dessous — même raison que
     `.sheet-scroll`, qui garde la sienne pour les feuilles à bandes. */
  overscroll-behavior: contain;
  animation: sheet-in .22s ease;
}
/* Une feuille qui gère elle-même ses bandes reprend la main : son plafond est
   déjà réparti entre `.sheet-fixed`, `.sheet-scroll` et `.sheet-actions`, et la
   laisser défiler **en plus** ferait sortir l'en-tête et le bouton de validation
   par les deux bouts — le défaut exact que ces bandes évitent.

   Le `:has()` traite le cas dynamique : `#meal-sheet` est un simple menu à
   l'étape « source » et une feuille à bandes à l'étape « quantité », selon
   celle des deux qui est démasquée. Une classe posée en JavaScript ferait la
   même chose, avec un endroit de plus où l'oublier. */
.sheet.sheet-tall,
.sheet.filters-sheet,
.sheet.report-sheet,
.sheet:has(> .sheet-tall:not([hidden])) {
  overflow: hidden;
}

/* ⚠️ **Et `overflow-y: auto` ne suffisait pas — c'est la seconde moitié, et sans
   elle la première ne fait rien.** `.sheet` est un conteneur flex en colonne :
   ses enfants ont `flex-shrink: 1` par défaut, donc sous le plafond ils se
   **compriment** au lieu de déborder. Il n'y a alors rien à faire défiler, et la
   feuille écrase son contenu en silence — mesuré le 16/08 : le menu du ➕
   demandait 632 px pour 476 disponibles sur un iPhone SE de 1ʳᵉ génération, et
   `scrollHeight` valait exactement `clientHeight`.

   C'est le pendant exact du `min-height: 0` de `.sheet-tall`, noté juste en
   dessous : là-bas il **autorise** un enfant à devenir plus petit que son
   contenu, ici on le lui **interdit**. Les deux réglages disent la même chose —
   un enfant flex ne se dimensionne pas tout seul, il faut trancher.

   ⛔ **Les deux listes d'exception doivent rester identiques**, et un test le
   vérifie : une feuille qui reprend la main sur le défilement (au-dessus) est
   exactement celle dont les bandes doivent pouvoir se comprimre (ici). Les
   laisser diverger rendrait une feuille qui ne défile ni ne comprime. */
.sheet:not(.sheet-tall):not(.filters-sheet):not(.report-sheet) > *:not(.sheet-tall) {
  flex: none;
}
@keyframes sheet-in {
  from { opacity: 0; transform: translateX(-50%) translateY(14px); }
}
/* La même entrée pour une feuille ancrée au centre (§ « tablette et bureau »).
   ⛔ **Et déclarée ici, pas dans le palier qui s'en sert** : une règle imbriquée
   dans un `@media` porte deux niveaux d'accolades, ce que les tests qui lisent
   la feuille de style découpent à un seul — le garde-fou des dispositions
   déclarées deux fois a signalé deux classes parfaitement saines. Une animation
   n'a de toute façon aucune raison d'être conditionnelle : c'est son emploi qui
   l'est. */
@keyframes sheet-in-centre {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 12px)); }
}
/* Une étape haute de feuille : la bande centrale défile, l'action reste dans le
   cadre. Même motif que `.filters-sheet`, et pour la même raison — sinon un
   contenu long emporte le titre hors de l'écran par le haut et le bouton de
   validation par le bas. `min-height: 0` est ce qui autorise un enfant flex à
   devenir plus petit que son contenu : sans lui, `overflow-y: auto` n'a jamais
   d'occasion de s'appliquer et la feuille repousse son plafond. */
.sheet-tall { display: flex; flex-direction: column; min-height: 0; }
/* Épinglé au-dessus de la bande qui défile : ce qui dit **à quoi** la quantité
   s'applique ne doit pas pouvoir sortir du cadre. */
.sheet-fixed { flex: none; }

/* La valeur exacte, second recours. Discrète par construction : la molette est le
   geste par défaut, et un champ mis en avant réinviterait à taper — ce qui est
   exactement ce que les molettes ont supprimé. */
.valeur-exacte { margin: 2px 0 10px; }
.valeur-exacte-form { display: flex; align-items: center; gap: 8px; }
.valeur-exacte-form input {
  flex: 1; min-width: 0;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: var(--espresso);
  padding: 9px 12px; min-height: 44px;
  /* 16 px, jamais moins : sous ce plancher iOS agrandit la page à la mise au point
     du champ et la laisse décalée (§ 1 des conventions). C'est la cause la plus
     fréquente des « fenêtres décentrées », et un champ ouvert exprès y expose. */
  font: inherit; font-size: 24px;
}
.valeur-exacte-form .link-btn { flex: none; min-height: 44px; }

/* ── Le champ prend la place du nombre — 05/09 ───────────────────────────────
   La ligne de lecture **est** l'endroit où l'on corrige : le formulaire s'y
   déplie à la place de la valeur, et le « ✓ » vient contre lui. ⚠️ **`flex: 1`
   sur le formulaire** : sans lui il ne prend que sa largeur de contenu et le
   champ se retrouve étroit au milieu d'une ligne large — le défaut qu'on vient
   de corriger, obtenu autrement. */
.roue-lecture .valeur-exacte-form { flex: 1; min-width: 0; }
/* Un bouton d'icône contre le champ : carré, même hauteur, et sa cible fait les
   44 px du § 1 par sa taille même. ⛔ **Pas un `.link-btn`** — « Appliquer » en
   toutes lettres reprenait la moitié de la ligne, et c'est ce que le geste vient
   de remplacer. */
.btn-check {
  flex: none; width: 44px; height: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--terra); border-radius: 12px;
  background: var(--terra); color: #FFFFFF; cursor: pointer;
}
.btn-check .icon { width: 20px; height: 20px; }
/* ⛔ **« Appliquer » n'est pas « Je ne sais pas » — signalé le 05/09.**
   `#qs-sheet .link-btn` pose `width: 100%` pour les liens **du pied** de la
   feuille, qui sont des actions de page ; il attrapait aussi celui-ci, à
   l'intérieur d'une ligne. Mesuré au banc : le bouton prenait 312 px des 346,
   et le champ tombait à **26** — moins que son propre rembourrage. La valeur
   s'écrivait *(`value` valait bien « 237 »)* et n'avait nulle part où
   s'afficher : le § *la couche est peinte, elle est seulement inatteignable*,
   appliqué à un texte.

   ⚠️ **L'identifiant bat deux classes**, toujours : c'est le piège déjà écrit
   trois fois ici *(`.pane-head.today-head`, l'anneau du journal, le « i » de la
   carte du jour)*. Deux règles qui nomment le même composant se départagent par
   la spécificité, jamais par l'intention — d'où un sélecteur qui porte lui aussi
   l'identifiant de la feuille. */
#qs-sheet .valeur-exacte-form .link-btn,
#conv-sheet .valeur-exacte-form .link-btn,
#meal-sheet .valeur-exacte-form .link-btn {
  display: inline-flex; align-items: center;
  width: auto; margin: 0;
}
.sheet-scroll {
  flex: 1; min-height: 0; overflow-y: auto;
  /* Le défilement s'arrête dans la feuille : arriver en bas ne doit pas faire
     repartir la page dessous, qui donnerait l'impression d'une feuille qui glisse. */
  overscroll-behavior: contain;
}
/* ── Le pied d'une feuille — socle des menus, 20/09 ─────────────────────────
   Une disposition commune : les boutons se partagent la largeur à parts égales
   (Annuler/Fermer en `btn-ghost` à gauche, l'action nommée en `btn-primary` à
   droite ; un seul bouton prend toute la largeur). Ce qui n'est pas un bouton
   de rang — un `.link-btn` secondaire (« Je ne sais pas », « Voir la fiche »),
   un champ, une phrase — prend sa propre ligne, sous ou au-dessus. Même retrait
   que le titre (14 px). */
.sheet-actions { flex: none; display: flex; flex-wrap: wrap; gap: 10px; padding: 10px 14px 6px; }
.sheet-actions > * { flex: 1 1 100%; min-width: 0; }
/* ⚠️ **44 px, y compris seul sur sa ligne.** `.btn-primary` n'a pas de
   plancher à lui : à deux, l'étirement du pied le rattrape ; seul, il
   retombait à 41 px — sous la cible tactile du § 11, sur le bouton qui
   valide (#corr-save, #recipe-add, les pieds à bouton unique des réglages). */
/* ⚠️ **`1 1 auto`, pas `1 1 0`.** À parts strictement égales, chaque bouton
   d'un pied à deux vaut 154 px à 390 px — dont ~114 px de texte : « Enregistrer
   mon avis personnel » passait à trois lignes, et « Voir 186 recettes » gardait
   sa moitié de ligne pendant que son voisin débordait. La base `auto` part de
   la largeur du libellé, puis la place restante se partage : les pieds à deux
   boutons courts restent équilibrés, un libellé long prend ce qu'il lui faut. */
.sheet-actions > .btn-primary, .sheet-actions > .btn-ghost { flex: 1 1 auto; min-height: 44px; }
/* Un lien du pied garde la cible tactile du § 11 : `.link-btn` pose
   `min-height: 0`, ce qui convient dans un paragraphe et pas sous un bouton. */
.sheet-actions > .link-btn { min-height: 44px; }
/* « Voir la recette » est le secondaire de C13 : sur sa ligne, au-dessus des
   deux boutons de rang — à trois par ligne, il se coupait en deux à 390 px. */
#plan-sheet .sheet-actions > #plan-voir { flex-basis: 100%; }
/* ⚠️ **Le pied de la feuille d'ajout vit dans le formulaire**, et non contre le
   bord de la feuille : le retrait de 14 px l'aurait rendu plus étroit que les
   champs juste au-dessus, qui n'en ont pas (`.add-item { padding: 0 }`). Le
   bouton d'envoi doit rester **dans** `<form>` pour que la touche Entrée du
   clavier mobile continue d'ajouter l'article. */
#ai-sheet .add-item > .sheet-actions { padding-inline: 0; }

/* ⚠️ **Une feuille défile, mais rien ne le disait.** iOS ne montre sa barre
   qu'*une fois qu'on a défilé* : à l'ouverture, une feuille pleine et une feuille
   coupée se ressemblent trait pour trait. C'est la moitié qui restait du § 5 —
   le contenu ne disparaît plus en silence, mais on ne sait toujours pas qu'il y
   a une suite tant qu'on n'a pas tenté le geste.

   Une ombre douce apparaît donc au bord par lequel il reste quelque chose.

   ⛔ **Et surtout pas une couche, ni une mesure en JavaScript.** Les deux
   corrections évidentes tombent chacune dans un piège déjà documenté :

   - un élément posé par-dessus le contenu est une couche qui recouvre pendant
     que ça glisse — le mode d'échec du § 6, celui du `backdrop-filter` ;
   - poser un drapeau depuis un écouteur `scroll` demande de lire `scrollTop`,
     `scrollHeight` et `clientHeight` **à chaque image du geste**, soit la
     disposition forcée qu'on venait de retirer du `touchstart`.

   Ici il n'y a **rien sur le chemin** : ce sont quatre fonds sur l'élément qui
   défile déjà. Deux couvercles couleur `--card` sont ancrés au **contenu**
   (`local`) et glissent avec lui ; deux ombres sont ancrées au **conteneur**
   (`scroll`) et ne bougent pas. En haut de course, le couvercle du haut est
   pile sur l'ombre du haut et la masque ; dès qu'on défile, il s'en va et
   l'ombre paraît.

   ⚠️ **Ce qui le rend juste est qu'il ne mesure rien.** Une feuille qui ne
   déborde pas garde ses deux couvercles en place : elle n'affiche aucune ombre
   sans qu'un test de hauteur ait eu lieu, donc l'indice ne peut pas mentir ni
   se désynchroniser d'un contenu qui change. C'est le seul point du dépôt où
   « ne pas savoir » et « la bonne réponse » coïncident gratuitement. */
.sheet,
.sheet-scroll,
.filters-scroll,
.ask-liste {
  background-image:
    linear-gradient(to bottom, var(--card) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--card) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0));
  /* ⚠️ Le palier à 55 % n'est pas un réglage esthétique : 55 % de 20 px = 11 px,
     la hauteur exacte de l'ombre. Un couvercle qui fond dès son bord la laisse
     transparaître, et la feuille annonce une suite en haut de course alors
     qu'elle est à son début. Mesuré : 244,4 contre 248 sur une liste courte. */
  background-position: top, bottom, top, bottom;
  background-size: 100% 20px, 100% 20px, 100% 11px, 100% 11px;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll;
  background-color: var(--card);
}

.sheet-item {
  display: flex; align-items: center; gap: 14px; width: 100%;
  border: none; background: none; cursor: pointer; text-align: left;
  padding: 13px 14px; border-radius: 16px; color: inherit;
}
/* ⚠️ **Une entrée de feuille peut être un lien.** `.sheet-item` a été écrite
   pour des `<button>` ; un `<a>` y arrive souligné et, sans `align-items`, avec
   une puce de ligne. La règle vaut pour les deux, sinon la seule entrée qui
   ouvre un document se distinguerait de ses voisines sans que ça veuille dire
   quoi que ce soit. */
a.sheet-item { text-decoration: none; }
.sheet-item:hover:not(:disabled) { background: var(--cream); }
.sheet-item:disabled { cursor: not-allowed; }
/* ⚠️ **`--danger` est un rouge de remplissage, pas un rouge d'écriture.** Sur
   la carte sombre d'une feuille, #A6412F rendait 2,57:1 — et c'est le libellé
   des huit entrées « Supprimer… » du menu contextuel (revue du 21/09). */
.sheet-item.danger strong { color: var(--danger-texte); }
.sheet-icon { display: flex; color: var(--cinnamon); }
.sheet-icon .icon { width: 22px; height: 22px; }
.sheet-item strong { display: block; font-size: 22.5px; }
/* La précision d'une entrée fait partie de son nom accessible : `--muted`
   rendait 3,90:1 sur la carte claire (mesuré au banc du 21/09, une fois les
   enfants colorés pris en compte). Même valeur que `--muted` en sombre. */
.sheet-item small { color: var(--bouton-second-texte); font-size: 19px; }
.sheet-title { font-size: 24px; margin: 6px 0 4px; padding: 0 14px; }
/* ⚠️ **Un seul titre de feuille, serif — socle des menus, 20/09.** `h2` et `h3`
   se rendent pareil : la balise dit le rang, la classe dit le dessin. Les
   sous-titres de bloc (`.settings-sous-titre`) portent aussi la classe et
   gardent leur rendu discret — d'où l'exclusion, dans la même règle. */
.sheet-title:not(.settings-sous-titre) {
  font-family: var(--ecriture); font-weight: 400; font-size: 25px; line-height: 1.25;
  color: var(--espresso);
}
/* Le titre reçoit le focus à l'ouverture (`tabindex="-1"`) pour être lu ; il
   n'a pas besoin d'un cadre pour ça. Le clavier le retrouve sur les entrées. */
/* Un sous-titre de bloc qui porte aussi `.sheet-title` (#c30-sheet,
   #notif-sheet) : l'exclusion ci-dessus lui rend le sans-serif, pas sa taille
   — `.sheet-title { font-size: 16px }` est déclarée plus bas que
   `.settings-sous-titre`, à spécificité égale, et la battait. */
.sheet-title.settings-sous-titre { font-size: 19.5px; }
.sheet-title:focus { outline: none; }
/* Le choix courant d'un menu (tri, collection qui contient déjà la recette) :
   fond crème, titre terre cuite, et une coche à droite si l'auteur la pose. */
.sheet .sheet-item.on { background: var(--cream); }
.sheet .sheet-item.on strong { color: var(--cinnamon); }
.sheet-coche { display: flex; flex: none; margin-left: auto; color: var(--cinnamon); }
.sheet-coche .icon { width: 20px; height: 20px; }
/* La liste des collections défile avec la feuille : plus de second défileur à
   plafond propre, qui coupait la liste à 46 vh sous une feuille déjà bornée. */
.pick-row.on .sheet-icon { color: var(--sage); }
/* Chargement, liste vide ou échec de la requête : une phrase à la place des
   lignes, au retrait du titre. */
.pick-message { margin: 8px 14px 10px; }

/* ── Le carnet — socle graphique des menus C01–C30 (DA01, 21/09) ─────────────

   **Ce que Luca demande n'est pas une police sur l'ancien menu** : du papier
   ivoire, des cartes distinctes, des objets dessinés en couleur, de la sauge
   pour ce qui est retenu et deux brins de verdure dans les marges. Le cadre
   (`design/nouveau-visuel/CADRE_MENUS.md`) en tire cinq familles pour trente
   feuilles ; ce bloc est leur vocabulaire commun, et il tient en une classe
   posée sur la feuille — `class="sheet carnet"` — plus quelques modificateurs.

   ⛔ **Opt-in, et non un nouveau `.sheet`.** Les trente menus ne passent pas le
   même jour : une refonte de `.sheet` lui-même aurait repeint les trente-neuf
   feuilles du fichier, dont celles que ce lot n'a ni relues ni photographiées.
   Le socle du 20/09 — plafond, défilement, bandes, pied, pile — n'est pas
   touché : il est **réutilisé**, et seule la peau change.

   Mode d'emploi pour la déclinaison (03d) :

   | Famille du cadre | Classe de feuille | Pièces à employer |
   |---|---|---|
   | A — choisir une action | `.carnet` | `.carnet-tete`, `.carnet-cartes`, `.sheet-item.carte-action` + `.carnet-illu` + `.carte-chevron`, `.carnet-separateur` |
   | B — régler ou filtrer | `.carnet` | `.carnet-tete`, `.carnet-cartes`, `.reglage-ligne.carte-reglage` (+ `.on`), `.carnet-note` |
   | C — saisir ou doser | `.carnet` | `.carnet-contexte`, `.carnet-cadre`, `.carnet-etiquette`, pied commun |
   | D — expliquer ou confirmer | `.carnet.carnet-gauche` | `.carnet-tete`, `.carnet-texte`, `.carnet-cadre`, `.btn-primary.danger` |
   | E — scanner (C19) | ⛔ pas de papier par-dessus la caméra ; seules les commandes reprennent les boutons du carnet |

   Les jetons `--carnet-*` sont définis dans les **trois** blocs de palette. Ne
   pas écrire une couleur en dur ici : une feuille qui invente son ivoire ne
   suivra pas le thème sombre, et c'est exactement ce que le cadre refuse. */

.sheet.carnet {
  /* Les épaules arrondies de la référence : plus rondes en haut qu'en bas.
     ⚠️ La feuille redevient un dialogue centré au-dessus de 1 180 px (§ tablette
     et bureau) — les quatre coins s'égalisent alors, plus bas. */
  border-radius: 30px 30px 24px 24px;
  border: 1px solid var(--carnet-filet);
  padding: 22px 16px 14px;
  color: var(--carnet-encre);
  box-shadow: var(--carnet-ombre);
  /* ⚠️ **Le dégradé du papier passe *après* les quatre fonds du § 5.** Ceux-ci
     sont l'indicateur de défilement : deux couvercles ancrés au contenu, deux
     ombres ancrées au conteneur. Les remplacer aurait rendu une feuille qui
     défile sans le dire ; les couvercles reprennent donc la couleur du papier,
     et la variation vers `--carnet-papier-bas` est la cinquième couche, celle
     que les quatre premières recouvrent aux extrémités. */
  background-image:
    linear-gradient(to bottom, var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--carnet-papier-bas) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(145deg, var(--carnet-papier), var(--carnet-papier) 58%, var(--carnet-papier-bas));
  background-position: top, bottom, top, bottom, top;
  background-size: 100% 20px, 100% 20px, 100% 11px, 100% 11px, 100% 100%;
  background-repeat: no-repeat;
  background-attachment: local, local, scroll, scroll, local;
  background-color: var(--carnet-papier);
}
/* Une feuille à bandes (`.sheet-tall`) porte ses fonds sur `.sheet-scroll` :
   c'est lui qui défile, donc c'est lui qui doit dire qu'il reste quelque chose. */
/* ⚠️ **`.filters-scroll` est la même bande sous un autre nom** (C16) : le
   panneau de filtres a son propre triplet depuis le 20/09, que quatre tests
   figent. Il rejoint la liste plutôt que d'être renommé — 03d-1. */
.carnet > .sheet-scroll, .carnet .ask-liste, .carnet > .filters-scroll {
  background-image:
    linear-gradient(to bottom, var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0));
  background-color: transparent;
}

/* ── Tête de feuille ───────────────────────────────────────────────────────
   Titre centré pour un menu court ; `.carnet-gauche` le range à gauche pour un
   formulaire dense ou une explication longue, comme le demande le cadre. Le
   contexte réel (aliment, recette, date, portée) vient **dessous**, dans une
   `.carnet-contexte` — jamais dans le décor. */
.carnet-tete { position: relative; text-align: center; padding: 0 12px; margin: 0 0 18px; }
.carnet .carnet-tete .sheet-title {
  margin: 0; padding: 0;
  font-size: var(--t-titre); font-weight: var(--g-titre); line-height: var(--lh-titre);
  color: var(--carnet-encre);
}
.carnet-sous {
  margin: 8px 0 0; padding: 0;
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
  color: var(--carnet-encre-douce);
}
.carnet-gauche .carnet-tete { text-align: left; padding: 0; margin-bottom: 14px; }
/* Le petit cœur dessiné de la référence. ⛔ **C01 seulement** : le cadre le dit,
   les autres familles n'ont droit qu'à une branche ou à leur pictogramme. */
.carnet-coeur { display: block; width: 29px; height: 30px; margin: 9px auto 0; color: var(--bouton-fond); }
/* Deux brins au maximum par feuille courte, dans les marges, jamais derrière
   une phrase, un champ ou une molette (cadre § « pied et décor »). Ils sont
   `aria-hidden` dans le balisage et ne prennent ni le doigt ni la tabulation. */
.carnet-brin { position: absolute; width: 34px; height: 53px; pointer-events: none; }
.carnet-brin.gauche { left: -2px; bottom: -10px; transform: rotate(-18deg); }
.carnet-brin.droite { right: -2px; bottom: -10px; transform: scaleX(-1) rotate(-18deg); }

/* ── Carte d'action (famille A) ────────────────────────────────────────────
   Illustration à gauche, titre et courte explication au centre, chevron à
   droite **seulement si l'action ouvre une autre étape**. Toute la carte est
   cliquable : c'est toujours un `.sheet-item`, donc le focus d'ouverture, la
   pile et les tests du socle le trouvent. */
.carnet-cartes { display: grid; gap: 11px; }
/* ⚠️ **Et le même écart sans conteneur.** Le menu du ➕ ne peut pas ouvrir de
   bloc avant ses entrées — six tests le coupent à la première balise fermante,
   et c'est une contrainte du fichier, pas une préférence : l'espacement des
   cartes posées à même la feuille se règle donc à l'adjacence. Les feuilles qui
   peuvent grouper emploient `.carnet-cartes`, qui donne exactement le même
   écart ; les deux ne se cumulent jamais sur la même carte. */
.carnet > .sheet-item.carte-action + .sheet-item.carte-action { margin-top: 11px; }
.carnet .sheet-item.carte-action {
  gap: 12px; min-height: 86px; padding: 12px;
  border: 1px solid var(--carnet-filet); border-radius: 20px;
  background: var(--carnet-carte);
  box-shadow: 0 3px 9px rgba(81, 51, 41, .04);
}
/* ⚠️ Déclaré après `.sheet-item:hover` : à spécificité égale, c'est l'ordre qui
   tranche, et le survol crème de l'ancien menu effaçait la carte ivoire. */
.carnet .sheet-item.carte-action:hover:not(:disabled) {
  background: var(--carnet-carte); border-color: var(--carnet-filet-vif);
}
.carnet .sheet-item.carte-action strong {
  font-size: var(--t-carte); font-weight: var(--g-titre); line-height: 1.06;
  color: var(--carnet-encre);
}
.carnet .sheet-item.carte-action small {
  display: block; margin-top: 4px;
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
  color: var(--carnet-encre-douce);
}
.carnet .sheet-item.danger strong { color: var(--danger-texte); }
/* L'illustration : 47 px de dessin, pas une icône de 22 px agrandie. C'est la
   distinction du cadre — les grandes cartes ont des pictogrammes illustrés, les
   petites commandes gardent les SVG fonctionnels. */
/* ⚠️ **Scopée à `.carnet`, sinon `.reglage-ligne span` la bat** : une ligne de
   réglage met ses `span` en colonne, et l'illustration s'y étirait. */
.carnet .carnet-illu { flex: none; display: flex; width: 47px; height: 51px; }
/* Plus petite sur une carte de réglage : le geste est la bascule, le dessin
   accompagne (34 × 40 sur la planche). */
.carnet .carte-reglage .carnet-illu { width: 34px; height: 40px; }
.carnet-illu svg { width: 100%; height: 100%; filter: drop-shadow(0 3px 2px rgba(103, 63, 38, .10)); }
/* Le chevron reste une icône fonctionnelle, à l'encre douce du carnet. */
.carte-chevron { flex: none; width: 11px; height: 17px; color: var(--carnet-encre-douce); }
/* Un intertitre de groupe (« Avec ton suivi alimentaire ») : deux filets sauge
   et une mention. Il remplace un `.sheet-title` de rubrique dans une feuille
   courte, où un second titre se lirait comme une seconde feuille. */
.carnet-separateur {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 3px 11px; color: var(--carnet-encre-douce);
  font-size: var(--t-appoint); line-height: 1.2;
}
.carnet-separateur::before, .carnet-separateur::after {
  content: ""; flex: 1; height: 1px; background: var(--carnet-sauge-filet);
}

/* ── Carte de réglage (famille B) ──────────────────────────────────────────
   La ligne de réglage devient une carte, et la sélection prend la sauge — coche
   ou bascule, jamais un chevron trompeur. `.on` est posé par le code qui peint
   la ligne : `:has()` aurait suffi à Chromium, pas aux webviews plus anciennes
   qu'embarque la coque. */
.carnet .reglage-ligne.carte-reglage {
  gap: 10px; min-height: 73px; padding: 11px 12px;
  border: 1px solid var(--carnet-filet); border-radius: 20px;
  background: var(--carnet-carte);
}
.carnet .reglage-ligne.carte-reglage.on {
  background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
}
.carnet .reglage-ligne.carte-reglage strong {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
.carnet .reglage-ligne.carte-reglage small {
  font-size: var(--t-carte-sous); line-height: var(--lh-carte); color: var(--carnet-encre-douce);
}
/* ⚠️ **`.switch-row input` fixait 46 × 27**, et la bascule du socle dessine son
   bouton pour 48 × 28 : les genres sociaux de C08 sont des `.switch-row`, et
   leur rail rendait un curseur décentré d'un pixel. La taille revient à celle
   du rail, le geste ne change pas. — 03d-1 */
.carnet .switch-row input.bascule { width: 48px; height: 28px; accent-color: auto; }
/* La bascule commune garde son geste ; seules ses teintes suivent le papier. */
.carnet .carte-reglage .bascule { background: var(--carnet-filet); border: 1px solid var(--carnet-filet-vif); }
.carnet .carte-reglage .bascule:checked { background: var(--carnet-sauge-trait); border-color: var(--carnet-sauge-trait); }
/* ── L'état retenu, dans le carnet ─────────────────────────────────────────
   ⚠️ **Ces deux règles doivent vivre ici, et pas dans la section de C04.**
   `.sheet .sheet-item.on` (le choix courant d'un menu : crème + titre terre
   cuite) a exactement la même spécificité que `.carnet .sheet-item.on` — trois
   classes. À égalité, c'est l'**ordre** qui tranche, et le socle des feuilles
   est déclaré plus bas que la section « demander » : écrites là-bas, elles
   perdaient en silence, et l'option courante de C04 reprenait le crème des
   écrans au lieu de la sauge du cadre. Constaté sur la capture du 21/09. */
.carnet .sheet-item.on, .carnet .ask-option.on {
  background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
}
.carnet .ask-option.on strong, .carnet .sheet-item.on strong { color: var(--carnet-encre); }
.carnet .sheet-coche { color: var(--carnet-sauge-trait); }
.carnet-note {
  margin: 14px 5px 0; text-align: center;
  font-size: var(--t-appoint); line-height: var(--lh-carte); color: var(--carnet-encre-douce);
}

/* ── Contexte et cadres (familles C et D) ──────────────────────────────────
   « Ce sur quoi porte la feuille » : l'aliment, l'entrée du journal, la recette.
   Une carte calme, au-dessus de la zone où l'on agit. */
.carnet-contexte {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 16px; padding: 12px;
  border: 1px solid var(--carnet-filet); border-radius: 18px;
  background: var(--carnet-carte);
}
.carnet-contexte > span { min-width: 0; }
.carnet-contexte strong {
  display: block; font-size: var(--t-carte); font-weight: var(--g-titre);
  line-height: 1.08; color: var(--carnet-encre);
}
.carnet-contexte small { display: block; font-size: var(--t-carte-sous); color: var(--carnet-encre-douce); }
/* Un cadre ivoire pour ce qui se règle ou se lit : molettes, valeurs, blocs de
   texte. C'est le conteneur neutre de la famille C, et le « papier dans le
   papier » de la famille D. */
.carnet-cadre {
  border: 1px solid var(--carnet-filet); border-radius: 22px;
  background: var(--carnet-carte); padding: 12px 14px;
}
.carnet-etiquette {
  display: block; margin-bottom: 4px; text-align: center;
  font-size: var(--t-appoint); color: var(--carnet-encre-douce);
}
.carnet-texte {
  margin: 14px 2px; font-size: var(--t-corps); line-height: var(--lh-corps);
  color: var(--carnet-encre);
}

/* ── Pied du carnet ────────────────────────────────────────────────────────
   Le pied reste `.sheet-actions` — même répartition, mêmes 44 px minimum, même
   « un bouton seul prend la ligne ». Ce qui change est le dessin : des pilules
   de 48 px, un principal terre cuite et un secondaire calme. */
.carnet > .sheet-actions { padding: 18px 2px 2px; gap: 9px; }
.carnet .sheet-actions .btn-ghost,
.carnet .sheet-actions .btn-primary {
  min-height: 48px; border-radius: 999px;
  font-size: var(--t-corps); font-weight: var(--g-accent); line-height: 1.12;
}
.carnet .sheet-actions .btn-ghost { border: 1px solid var(--carnet-filet); color: var(--bouton-second-texte); }
/* Le geste qui détruit : fond rouge **mesuré** dans les deux thèmes, et son
   encre avec lui. ⛔ Pas de `--danger` nu, qui n'est pas redéfini en sombre. */
.carnet .sheet-actions .btn-primary.danger {
  background: var(--danger-fond); color: var(--danger-fond-texte); border-color: transparent;
}
.carnet .sheet-actions .link-btn { color: var(--carnet-encre-douce); font-size: var(--t-carte-sous); }

/* Ce qui arrive du socle sans classe propre — une amorce `.hint`, une erreur de
   formulaire — se lit sur le papier plutôt que sur la carte des écrans. */
/* ⚠️ **Et pas seulement en enfant direct** — 03d-1 : les amorces des quinze
   menus déclinés vivent aussi dans une rubrique ou une bande épinglée. */
.carnet .hint { color: var(--carnet-encre-douce); font-size: var(--t-carte-sous); }
.carnet .pick-message { color: var(--carnet-encre-douce); }

/* Une feuille courte sur un écran court : le décor cède avant le contenu. C'est
   la règle du cadre — « les contraintes de défilement priment sur une hauteur
   dessinée ». Sur un iPhone SE, le menu du ➕ perd son cœur et resserre ses
   cartes plutôt que de pousser son pied hors du cadre. */
@media (max-height: 620px) {
  .carnet-coeur, .carnet-brin { display: none; }
  .sheet.carnet { padding: 13px 14px 8px; }
  .carnet .sheet-item.carte-action { min-height: 66px; padding: 9px 10px; }
  .carnet-cartes { gap: 8px; }
  .carnet > .sheet-item.carte-action + .sheet-item.carte-action { margin-top: 8px; }
  .carnet .carnet-tete .sheet-title { font-size: var(--t-titre-sec); }
  .carnet-tete { margin-bottom: 10px; }
  .carnet-separateur { margin: 9px 3px 7px; }
  .carnet > .sheet-actions { padding-top: 10px; }
  /* ⚠️ **Le sous-texte reste** : il dit où mène l'entrée, et le cadre range les
     contraintes de défilement avant la hauteur dessinée. Sur un iPhone SE de
     1ʳᵉ génération, le ➕ à quatre entrées défile donc — mesuré 655 px pour
     474 disponibles, ramenés à 608 par ce palier — et son pied s'atteint au
     geste comme au clavier. L'entrée alimentaire unique du lot 05 rend le
     reste (une carte de moins, soit ~90 px). */
}
/* 320 px : les marges passent à 14 px (cadre), le pied s'empile plutôt que de
   couper les libellés en deux. */
@media (max-width: 360px) {
  .sheet.carnet { padding-left: 13px; padding-right: 13px; }
  .carnet .sheet-item.carte-action { gap: 9px; padding: 11px 9px; }
  .carnet-illu { width: 41px; height: 45px; }
}

/* ── Le carnet, ce que la déclinaison a demandé de plus — 03d-1, 21/09 ───────

   Quinze menus des familles **A** (choisir une action) et **B** (régler ou
   filtrer) après les quatre pilotes de 03c. ⛔ **Aucun jeton neuf** : tout ce
   qui suit se compose avec les `--carnet-*` du bloc ci-dessus, et aucune
   couleur n'est écrite en dur — une carte qui invente son ivoire ne suivrait
   pas le thème sombre.

   Ce que les pilotes n'avaient pas et que ces quinze réclament :

   | Pièce | Pour |
   |---|---|
   | `.carte-compacte` + `.carnet-pastille` | un choix immédiat : tri (C18), ordre (C09) |
   | `.carnet-vignette` | la couverture d'une collection (C02) |
   | `.carnet .chip` | les puces de critères (C10, C16, C28) |
   | rubriques (`.settings-block`, `.champ-libelle`) | les groupes de réglages (C16, C22, C30, C08) |
   | champs du carnet | emplacement, date, ingrédients (C22, C10) |
   | `.ecran-carnet` | les deux **pages** C05 et C08, qui restent des pages |
*/

/* ── Carte compacte : un choix immédiat ────────────────────────────────────
   ⛔ **Ni chevron ni dessin de 47 px.** Le cadre l'écrit pour C18 — « aucun
   chevron pour un choix immédiat » — et une illustration culinaire par critère
   de tri aurait demandé six dessins qui ne racontent rien (« par titre »).
   L'icône fonctionnelle du sprite suffit, posée dans un médaillon sauge, et la
   coche dit ce qui est retenu. La cible reste au-dessus des 44 px du § 1. */
.carnet .sheet-item.carte-compacte { min-height: 64px; padding: 10px 12px; gap: 11px; }
.carnet-pastille {
  flex: none; display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: 50%; background: var(--carnet-sauge); color: var(--carnet-sauge-trait);
}
.carnet-pastille .icon { width: 22px; height: 22px; }
/* ⚠️ **Sur une ligne retenue, le médaillon reprend la carte** : la sélection
   peint la ligne en sauge, et un rond sauge sur un fond sauge disparaît. */
.carnet .sheet-item.on .carnet-pastille,
.carnet .reglage-ligne.on .carnet-pastille { background: var(--carnet-carte); }
/* La corbeille et la croix d'une carte destructive gardent leur encre rouge :
   le dessin est explicite, la couleur le redit (cadre, § « carte d'action »). */
.carnet .sheet-item.danger .carnet-pastille {
  background: var(--danger-fond); color: var(--danger-fond-texte);
}

/* ── Vignette : la couverture d'une collection (C02) ───────────────────────
   Une photo servie quand elle existe, le dessin de la boîte à fiches sinon.
   ⛔ Jamais un carré vide : sur une carte de 86 px, il se lit comme une panne
   de chargement. */
.carnet-vignette {
  flex: none; display: grid; place-items: center; overflow: hidden;
  width: 52px; height: 52px; border-radius: 15px;
  border: 1px solid var(--carnet-filet); background: var(--carnet-sauge);
}
.carnet-vignette img { width: 100%; height: 100%; object-fit: cover; display: block; }
.carnet-vignette svg { width: 38px; height: 40px; }
/* Le repli commun de l'app (`replierSiCassee`) remplace l'image par un `div` :
   il prend toute la vignette plutôt que de laisser un coin de sauge nu. */
.carnet-vignette > div { display: grid; place-items: center; width: 100%; height: 100%; }
@media (max-width: 360px) { .carnet-vignette { width: 46px; height: 46px; } }

/* ── Les puces du carnet (C10, C16, C28) ───────────────────────────────────
   ⚠️ **Une puce retenue passe en sauge, pas en terre cuite plein.** Le socle
   des écrans fait de `.chip.on` un bouton plein (R05) ; dans le carnet, le
   cadre nomme la sélection : « fond sauge clair, coche/trait sauge, libellé
   cacao ». L'encre du carnet y rend 9,55:1 (mesure de 03c). La terre cuite
   reste au **pied**, pour l'action, et on ne la dépense pas sur dix puces.
   ⚠️ Et 44 px de cible au lieu des 38 de `.chip` : une puce neuve n'a pas de
   raison de reprendre une dette connue (§ 1). */
/* ⚠️ **Les rangées de puces deviennent des grilles explicites.** `.filters`
   n'a aucune règle de mise en page dans le fichier : ses puces sont des
   `inline-block` qui se replient sur la hauteur de ligne. À 44 px de cible et
   en Caveat, deux lignes de puces se touchaient. Un `flex` et un écart le
   disent, sans toucher aux rangées hors du carnet. */
.carnet .filters, .carnet .chips, .carnet .tag-puces {
  display: flex; flex-wrap: wrap; gap: 9px; overflow-x: visible;
}
.carnet .chip {
  min-height: 44px; padding: 9px 15px;
  border-color: var(--carnet-filet); background: var(--carnet-carte);
  color: var(--carnet-encre); font-size: var(--t-carte-sous); line-height: 1.15;
}
/* ⚠️ **`.meal-groups` est nommée dans le sélecteur, et c'est obligatoire.**
   `.meal-groups .chip[aria-pressed="true"]` (R05, 21/09) a exactement la même
   spécificité que `.carnet .chip[aria-pressed="true"]` et vit **plus bas** dans
   le fichier : à égalité c'est l'ordre qui tranche, et le repas retenu de C28
   restait un aplat terre cuite au milieu de cartes sauge. */
.carnet .chip.on, .carnet .chip[aria-pressed="true"],
.carnet .meal-groups .chip[aria-pressed="true"],
.carnet .ps-motifs .chip.on {
  background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
  color: var(--carnet-encre); font-weight: var(--g-accent);
}
/* La croix qui efface un critère actif (C10) reste une commande discrète. */
.carnet .chip .icon { color: var(--carnet-encre-douce); }
.carnet .chip.on .icon { color: var(--carnet-sauge-trait); }

/* ── Rubriques de la famille B ─────────────────────────────────────────────
   « Régler ou filtrer » = un titre court, puis ce qu'il groupe. ⛔ **Pas de
   carte dans une carte** : les feuilles de réglages du socle enveloppent leurs
   lignes dans un `.settings-block` à retrait ; ici ce sont les **lignes** qui
   sont des cartes (comme C07), et le bloc n'est plus qu'un groupe — son titre
   le nomme, son écart le sépare du suivant. */
.carnet .settings-block { padding: 0; border-bottom: none; }
.carnet .settings-block + .settings-block { margin-top: 20px; }
.carnet .settings-block > .settings-sous-titre,
.carnet .settings-block > .sheet-title.settings-sous-titre {
  display: flex; align-items: center; gap: 8px;
  margin: 0 4px 9px; padding: 0;
  font-size: var(--t-carte-sous); font-weight: var(--g-accent);
  line-height: var(--lh-carte); color: var(--carnet-encre);
}
/* Le pictogramme de métier d'une rubrique — 26 px, pas les 47 d'une carte : il
   nomme un groupe, il n'est pas la cible du geste (cadre, § « tête de feuille » :
   « au plus une petite branche ou leur pictogramme de métier »). */
.carnet-rubrique-illu { flex: none; width: 26px; height: 27px; }
/* Le libellé d'un groupe posé à même la feuille (C10) : c'est lui qui porte
   l'écart, faute de bloc au-dessus. */
.carnet .champ-libelle {
  margin: 20px 4px 9px;
  font-size: var(--t-carte-sous); font-weight: var(--g-accent);
  line-height: var(--lh-carte); color: var(--carnet-encre);
}
.carnet .champ-libelle:first-child { margin-top: 0; }
/* Une amorce de rubrique (« Ces réglages commandent… ») se lit sur le papier. */
.carnet .settings-block > .hint { margin: -2px 4px 9px; }

/* ── Champs du carnet dans une rubrique ────────────────────────────────────
   `.carnet-champ` du socle vaut pour un champ posé à même la feuille et porte
   sa propre marge ; ceux-ci sont dans un groupe qui l'a déjà donnée. Le
   plancher de 16 px du § 1 tient : `--t-champ` vaut 24 px.
   ⚠️ **Les deux champs de C10 sont visés par leur identifiant** : `#cf-sans`
   bat n'importe quelle classe, et une règle de classe posée à côté serait
   restée muette — le champ serait resté crème dans une feuille ivoire. */
.carnet .pd-champ { margin: 0; font-size: var(--t-appoint); color: var(--carnet-encre-douce); }
/* Un champ posé dans la bande épinglée d'une feuille à bandes (C21) : il suit
   le lien de retour, donc il reprend l'écart que son groupe ne lui donne pas. */
.carnet .sheet-fixed > .pd-champ { margin-top: 10px; }
/* Les alternatives de C21 : la même carte que partout, et la vignette réelle de
   l'article du frigo à gauche — c'est la donnée qui fait le dessin. */
.carnet .sub-choix {
  min-height: 74px; margin-bottom: 10px; padding: 11px 12px; gap: 12px;
  border: 1px solid var(--carnet-filet); border-radius: 20px; background: var(--carnet-carte);
}
.carnet .sub-choix strong { font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre); }
.carnet .sub-choix small { font-size: var(--t-carte-sous); color: var(--carnet-encre-douce); }
.carnet .sub-retour { color: var(--bouton-second-texte); font-size: var(--t-carte-sous); min-height: 52px; }
.carnet .empty { color: var(--carnet-encre-douce); }

/* ── C09 : les lignes de l'ordre, en cartes ────────────────────────────────
   Le rang dessiné est le « repère visuel » du cadre ; les deux flèches sont la
   commande existante, et elles gardent leurs 44 px. */
.carnet .order-row {
  padding: 9px 11px; margin-bottom: 10px; border: 1px solid var(--carnet-filet);
  border-radius: 20px; background: var(--carnet-carte); min-height: 66px;
}
.carnet .order-row:last-child { border-bottom: 1px solid var(--carnet-filet); margin-bottom: 0; }
.carnet .order-name {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
.carnet .order-rang { font-size: var(--t-carte-sous); font-weight: var(--g-titre); }
/* ⚠️ **Un `>` et non un espace, et ce n'est pas un détail de style.**
   `test_paquet_ecrans` retrouve la règle des flèches de l'ordre par une
   expression régulière non ancrée, pour vérifier ses 44 px de cible. Écrite
   avec un espace, celle-ci — déclarée **plus haut** dans le fichier — était
   trouvée à sa place, et le test rougissait sur une règle qui ne parle que de
   couleur. ⛔ **Et le sélecteur ne se recopie pas non plus dans ce
   commentaire** : le test lit le fichier brut, commentaires compris. Les 44 px
   vivent toujours dans la règle d'origine, plus bas. */
.carnet .order-row > .icon-btn { color: var(--carnet-encre-douce); }
.carnet #order-erreur { margin: 10px 4px 0; }
.carnet .pd-champ > select, .carnet .pd-champ > input,
.carnet #cf-ingredients, .carnet #cf-sans {
  display: block; width: 100%; box-sizing: border-box; margin-top: 6px;
  min-height: 48px; padding: 11px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-champ);
}
.carnet .reglage-ligne select {
  border-color: var(--carnet-filet); background: var(--carnet-carte);
  color: var(--carnet-encre); border-radius: 14px;
}
/* Une erreur reste dans la feuille, à l'encre du danger — jamais un toast qui
   part avant d'être lu (cadre, § « réglage et formulaire »). */
.carnet .form-error {
  margin: 12px 4px 0; color: var(--danger-texte);
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
}
/* Un lien discret dans le corps d'une feuille (« Retirer la date »,
   « Choisir un autre ingrédient ») : calme, mais pas sous le seuil. */
.carnet > .link-btn, .carnet .settings-block > .link-btn,
.carnet .sheet-fixed > .link-btn { color: var(--bouton-second-texte); font-size: var(--t-carte-sous); }

/* ── Le pied propre du panneau de filtres (C16) ────────────────────────────
   ⚠️ **`.filters-actions` n'est pas `.sheet-actions`**, et quatre tests le
   figent : la bande de pied du panneau porte son propre nom depuis le 20/09.
   Elle reçoit donc ici le même dessin que le pied du carnet, sans être
   renommée — un renommage aurait fait rougir les tests pour un habillage. */
/* ── Une feuille à bandes dans le carnet (03d-1) ───────────────────────────
   Six menus y sont passés parce qu'ils débordaient à 320 px. Deux réglages
   suivent la bande : la tête épinglée perd la marge qu'elle avait dans le flux
   (la bande la reprend), et la bande garde le retrait des cartes. */
.carnet > .sheet-fixed.carnet-tete { margin-bottom: 14px; }
.carnet > .sheet-scroll { padding: 1px 2px; margin: 0 -2px; }
/* ⚠️ **`.sheet-head` est un `flex` à deux parts** : le titre d'un côté, la
   croix de l'autre. C16 n'a plus de croix, et sa tête porte maintenant un
   sous-titre — resté en `flex`, il se serait posé **à côté** du titre. */
.carnet .sheet-head.carnet-tete { display: block; }
.carnet > .filters-actions { padding-top: 16px; border-top-color: var(--carnet-filet); background: transparent; }
.carnet .filters-actions .btn-ghost, .carnet .filters-actions .btn-primary {
  min-height: 48px; border-radius: 999px;
  font-size: var(--t-corps); font-weight: var(--g-accent); line-height: 1.12;
}
.carnet .filters-actions .btn-ghost { border: 1px solid var(--carnet-filet); color: var(--bouton-second-texte); }
/* La valeur courante d'un curseur : elle ressort, à l'encre de la sélection. */
.carnet .filter-value { color: var(--carnet-sauge-trait); font-size: var(--t-carte-sous); }
.carnet .range-pair::before { background: var(--carnet-filet); }
.carnet .range-pair input::-webkit-slider-thumb { background: var(--carnet-carte); border-color: var(--carnet-sauge-trait); }
.carnet .range-pair input::-moz-range-thumb { background: var(--carnet-carte); border-color: var(--carnet-sauge-trait); }
/* Le trait d'un bloc de filtre resté hors des rubriques du carnet. */
.carnet .filter-block { border-bottom-color: var(--carnet-filet); }
.carnet .cf-plus > summary { color: var(--carnet-encre); }

/* ── Les deux pages de la famille B — C05 et C08 ───────────────────────────
   ⛔ **Elles restent des pages.** Le cadre l'écrit deux fois — « garder la page
   Réglages », « garder la page Notifications », « un numéro Cxx ne transforme
   pas systématiquement son écran en petite fenêtre ». Le carnet ne leur pose
   donc aucune feuille par-dessus : il leur donne son papier, ses cartes, son
   encre et sa sauge. `.ecran-carnet` est l'opt-in, comme `.carnet` l'est pour
   une feuille, et il ne touche ni l'en-tête, ni la barre d'onglets, ni le logo.

   ⚠️ **Les sélecteurs portent l'identifiant de l'écran, et c'est obligatoire** :
   les règles existantes de ces deux pages sont scopées `#screen-reglages …` et
   `#screen-notifications …` (un identifiant), qu'aucune paire de classes ne
   peut battre. Une règle `.ecran-carnet .profil-liste` serait restée muette. */
.screen.ecran-carnet { background: var(--carnet-papier); color: var(--carnet-encre); }
.ecran-carnet .page-title { color: var(--carnet-encre); }
/* ⚠️ **Les puces d'une page du carnet suivent celles de ses menus.** Le socle
   des écrans fait de `.chip.on` un bouton plein (R05) ; sur le papier, la
   rangée de genres de C08 devenait la seule tache terre cuite d'un écran
   ivoire, juste au-dessus de cartes sauge qui disent « retenu ». Même encre,
   même fond, même mesure que dans les feuilles (9,55:1). */
.ecran-carnet .chip {
  min-height: 44px; padding: 9px 15px;
  border-color: var(--carnet-filet); background: var(--carnet-carte);
  color: var(--carnet-encre); font-size: var(--t-carte-sous);
}
.ecran-carnet .chip.on {
  background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
  color: var(--carnet-encre); font-weight: var(--g-accent);
}
.ecran-carnet .pane-head .icon-btn { color: var(--carnet-encre); }
/* La rubrique d'une page : le même titre court que dans une feuille. */
#screen-reglages.ecran-carnet .profil-rubrique,
#screen-notifications.ecran-carnet .profil-rubrique {
  margin: 24px 4px 10px; font-size: var(--t-titre-sec); color: var(--carnet-encre);
}
#screen-reglages.ecran-carnet .profil-rubrique:first-child { margin-top: 10px; }
/* Le groupe de lignes devient une carte ivoire à filet pêche, ses séparateurs
   sont souples, et chaque ligne garde ses 56 px de cible. */
#screen-reglages.ecran-carnet .profil-liste {
  padding: 2px 14px; border-radius: 22px;
  background: var(--carnet-carte); border-color: var(--carnet-filet);
}
#screen-reglages.ecran-carnet .profil-liste > li + li { border-top-color: var(--carnet-filet); }
#screen-reglages.ecran-carnet .profil-ligne { color: var(--carnet-encre); min-height: 62px; }
#screen-reglages.ecran-carnet .profil-ligne strong {
  font-size: var(--t-carte); font-weight: var(--g-accent); line-height: 1.1;
}
#screen-reglages.ecran-carnet .profil-ligne small {
  font-size: var(--t-carte-sous); color: var(--carnet-encre-douce); line-height: var(--lh-carte);
}
#screen-reglages.ecran-carnet .profil-ligne > i { color: var(--carnet-encre-douce); }
/* L'icône de métier d'une ligne : un médaillon sauge, comme la carte compacte
   d'une feuille. C'est la « touche dessinée » de la page, sans lui donner
   dix-sept illustrations de 47 px qui la rendraient illisible. */
#screen-reglages.ecran-carnet .profil-ligne > .icon {
  width: 38px; height: 38px; padding: 8px; box-sizing: border-box;
  border-radius: 50%; background: var(--carnet-sauge); color: var(--carnet-sauge-trait);
}
#screen-reglages.ecran-carnet .profil-ligne > .icone-sauge { color: var(--carnet-sauge-trait); }
#screen-reglages.ecran-carnet .profil-saisie input {
  border-color: var(--carnet-filet); background: var(--carnet-papier);
  color: var(--carnet-encre); border-radius: 14px; min-height: 44px;
}
#screen-reglages.ecran-carnet .profil-source,
#screen-reglages.ecran-carnet .profil-merci { color: var(--carnet-encre-douce); }
#screen-reglages.ecran-carnet .profil-liste .bascule { background: var(--carnet-filet); border: 1px solid var(--carnet-filet-vif); }
#screen-reglages.ecran-carnet .profil-liste .bascule:checked { background: var(--carnet-sauge-trait); border-color: var(--carnet-sauge-trait); }
/* ⚠️ **La pastille d'invitations garde sa couleur** : elle compte quelque
   chose qui attend, et ce n'est pas un état de sélection. */

/* Les notifications : la même carte ivoire, une par ligne, et les puces du
   carnet. ⛔ Le portrait, la vignette et le point des non-lues ne changent
   pas — ce sont les données de la ligne, pas son habillage. */
#screen-notifications.ecran-carnet .notif {
  border-radius: 20px; padding: 12px;
  background: var(--carnet-carte); border: 1px solid var(--carnet-filet);
}
#screen-notifications.ecran-carnet .notifs-liste { gap: 10px; }
#screen-notifications.ecran-carnet .notif-phrase { color: var(--carnet-encre); font-size: var(--t-carte-sous); }
#screen-notifications.ecran-carnet .notif-date { color: var(--carnet-encre-douce); font-size: var(--t-appoint); }
#screen-notifications.ecran-carnet .notif-portrait { border-color: var(--carnet-filet); }
#screen-notifications.ecran-carnet .empty, #screen-notifications.ecran-carnet .hint { color: var(--carnet-encre-douce); }
/* Une notification non lue prend la sauge de la sélection — et son point
   rouge reste, c'est lui qui la compte. ⛔ **Posée en JS (`.notif.neuve`), pas
   en `:has()`** : la coque embarque des webviews plus anciennes que Chromium
   de bureau, et un « non lu » qui ne s'y peindrait pas laisserait un point de
   8 px seul à le dire — c'est la règle que le socle a écrite pour C07. */
#screen-notifications.ecran-carnet .notif.neuve {
  background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
}

/* ── Le carnet, famille C « saisir ou doser » — 03d-2, 22/09 ─────────────────

   Cinq menus après les quinze des familles A et B : C12 (convives), C13
   (prévoir un plat), C14 (ajouter au journal), C20 (enlever du frigo) et C24
   (corriger les valeurs). ⛔ **Aucun jeton neuf** ; deux degrés de l'échelle
   typographique enfin employés — `--t-nombre` et `--t-unite`, écrits au § 1
   pour « le nombre qu'on vient lire » et son unité, et qui n'avaient encore
   aucun porteur.

   La grammaire de la famille est celle du cadre : **contexte** (ce sur quoi la
   feuille porte), **zone centrale claire** (champ, cadre, molette), **pied
   stable**. Les pièces existent déjà — `.carnet-contexte`, `.carnet-cadre`,
   `.carnet-etiquette`, le pied du socle ; ce qui suit les raccorde aux
   composants que ces cinq feuilles apportent avec elles.

   | Pièce | Pour |
   |---|---|
   | vignette de contexte | l'article du frigo (C20), le produit (C14) |
   | pas à pas du carnet | les convives (C12) |
   | `.recall` en cartes | ce qui revient et les recettes (C13, C14) |
   | champs et valeurs | la saisie libre (C14), les cinq corrections (C24) |
*/

/* ── La vignette d'une carte de contexte ───────────────────────────────────
   Une vraie donnée, pas un décor : la photo du produit, son dessin d'aliment,
   puis le pictogramme de famille — c'est `vignette()` qui descend les crans, et
   le carnet ne fait que lui donner la taille et la sauge du socle. */
.carnet .carnet-contexte .diary-thumb,
.carnet .carnet-contexte .product-thumb .diary-thumb {
  width: 44px; height: 44px; background: var(--carnet-sauge);
}
.carnet .carnet-contexte .diary-thumb-empty { color: var(--carnet-sauge-trait); }
/* ⚠️ **Deux contextes tiennent sur deux lignes**, et l'interligne serré de la
   carte (1,08, réglé pour un nom d'ingrédient court) les faisait se toucher :
   « Pâtes A — 4 portions · ≈ 900 g en tout » et « Yaourt grec nature · La
   Ferme ». ⛔ Scopé, pour ne pas décaler de 3 px les cartes d'une ligne des
   feuilles déjà photographiées. */
#meal-sheet .carnet-contexte strong,
#corr-sheet .carnet-contexte strong { line-height: var(--lh-carte); }
.carnet .carnet-contexte .diary-thumb-empty .icon { width: 24px; height: 24px; }
/* ⚠️ **`.product-card` est déclarée plus bas dans le fichier** (§ scanner) et
   pose son propre fond crème, son filet gris et son rayon de 14 px : à
   spécificité égale elle aurait battu `.carnet-contexte`, et la carte du
   produit serait restée grise au milieu du papier. D'où l'identifiant. */
#meal-sheet .product-card.carnet-contexte {
  background: var(--carnet-carte); border: 1px solid var(--carnet-filet);
  border-radius: 18px; padding: 12px; margin-bottom: 14px;
}
#meal-sheet .product-card strong {
  font-size: var(--t-carte); font-weight: var(--g-titre); color: var(--carnet-encre);
}
#meal-sheet .product-card span { color: var(--carnet-encre-douce); font-size: var(--t-carte-sous); }
/* L'énergie pour 100 g : la terre cuite d'encre, lisible dans les deux thèmes. */
#meal-sheet .product-card small { color: var(--cinnamon); font-size: var(--t-carte-sous); }

/* ── Un sous-titre posé à même la feuille ──────────────────────────────────
   « Pourquoi ? (facultatif) » de C20 n'a pas de `.settings-block` au-dessus :
   il nomme une rangée de puces, pas un groupe de cartes. Il reprend donc le
   dessin des libellés de rubrique du carnet, en portant sa propre marge. */
.carnet > .sheet-title.settings-sous-titre {
  margin: 18px 4px 9px; padding: 0;
  font-size: var(--t-carte-sous); font-weight: var(--g-accent);
  line-height: var(--lh-carte); color: var(--carnet-encre);
}

/* ── C12 — le nombre de convives ───────────────────────────────────────────
   Cadre : « placer l'aliment et son unité en contexte, puis une zone centrale
   claire dont la valeur sélectionnée ressort ». Ici la valeur est le nombre de
   couverts, et c'est **le** contenu de la feuille : il prend le degré
   `--t-nombre` et la terre cuite, les deux commandes restent des cibles de
   48 px. ⛔ Le pas à pas est celui du socle — même balisage, mêmes libellés. */
#conv-sheet .conv-cadre { padding: 10px 14px 14px; }
.carnet .conv-row { margin: 0; }
.carnet .conv-row .stepper { gap: 20px; }
.carnet .conv-row .stepper button {
  width: 48px; height: 48px; min-height: 48px; border-radius: 16px;
  border-color: var(--carnet-filet); background: var(--carnet-papier);
  color: var(--bouton-second-texte); font-size: var(--t-titre-sec); line-height: 1;
}
/* ⚠️ **Grisé, jamais transparent** (R05) : « un convive de moins » s'éteint à 1,
   et `.stepper button` n'était dans aucune des listes de l'état inactif du § 1
   — il ne changeait donc pas d'aspect du tout. Le couple inactif le dit. */
.carnet .conv-row .stepper button:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--carnet-filet); cursor: not-allowed;
}
.carnet .conv-row output {
  min-width: 72px; font-size: var(--t-nombre); font-weight: var(--g-titre);
  line-height: 1.05; color: var(--cinnamon);
}

/* ── C13 — prévoir un plat ─────────────────────────────────────────────────
   Le pictogramme de métier vit dans la tête, à gauche du titre ; l'amorce
   passe à la ligne. `flex-wrap` et non deux blocs : le titre peut faire deux
   lignes (« Gratin de courgettes au chèvre »), et l'amorce garde sa largeur. */
#plan-sheet .carnet-tete {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
}
#plan-sheet .carnet-tete .sheet-title { flex: 1; min-width: 0; }
#plan-sheet .plan-illu { width: 38px; height: 41px; }
#plan-sheet .carnet-tete > .hint { flex-basis: 100%; margin: 0; }
/* Le jour et le moment sur la même surface douce ; leurs deux libellés portent
   l'écart, donc le cadre n'ajoute qu'un peu de respiration en bas. */
.carnet .plan-quand { padding: 4px 12px 12px; }
.carnet .plan-quand > .champ-libelle { margin: 12px 2px 8px; }
.carnet .plan-quand > .champ-libelle:first-child { margin-top: 6px; }
#plan-sheet.carnet #plan-recherche {
  margin-top: 4px; min-height: 48px; padding: 11px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-champ);
}

/* ── Les lignes de rappel, en cartes (C13 et C14) ──────────────────────────
   `.recall` est une ligne à filet supérieur, dessinée pour une liste dense.
   Dans le carnet, une recette et un repas qui revient sont des **choses qu'on
   choisit** : ce sont donc des cartes, comme les autres entrées. ⛔ Le
   plafond de `.recall-list` n'est pas touché : c'est lui qui empêche la liste
   de pousser le pied hors du cadre. */
.carnet .recall {
  gap: 12px; min-height: 74px; margin-bottom: 9px; padding: 10px 12px;
  border: 1px solid var(--carnet-filet); border-top: 1px solid var(--carnet-filet);
  border-radius: 20px; background: var(--carnet-carte);
}
.carnet .recall:last-child { margin-bottom: 0; }
.carnet .recall .diary-thumb { width: 44px; height: 44px; background: var(--carnet-sauge); }
.carnet .recall .diary-thumb-empty { color: var(--carnet-sauge-trait); }
.carnet .recall-name {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
.carnet .recall-note { color: var(--carnet-encre-douce); font-size: var(--t-carte-sous); }
.carnet .recall b { color: var(--cinnamon); font-size: var(--t-carte-sous); }
/* « Ce qui revient » : un intertitre de groupe, comme `.carnet-separateur`,
   mais posé sur un `<h2>` qui existait déjà. Le filet du bas le sépare des
   quatre sources ; il reste au-dessus d'elles, où il est utile. */
.carnet .recall-block > h2 {
  margin: 0 3px 9px; text-transform: none; letter-spacing: 0;
  font-size: var(--t-appoint); font-weight: var(--g-accent);
  color: var(--carnet-encre-douce);
}
#meal-sheet.carnet #meal-recurring-block { border-bottom-color: var(--carnet-filet); padding-bottom: 10px; }
/* ⚠️ **Et l'écart sous « Ce qui revient » se déclare ici**, où l'identifiant du
   bloc bat la règle de groupe : les deux valeurs auraient divergé. */
#meal-sheet.carnet #meal-recurring-block + .carnet-cartes { margin-top: 10px; }

/* ── C14 — ajouter au journal ──────────────────────────────────────────────
   Cinq étapes dans une feuille, dont deux à bandes. Le pied et la bande
   défilante ne sont donc **pas** des enfants directs du carnet : les deux
   règles du socle (`.carnet > .sheet-actions`, `.carnet > .sheet-scroll`) ne
   les atteignent pas, et c'est ce qui laissait le pied de l'étape des sources
   collé à la dernière carte. */
#meal-sheet.carnet .sheet-actions { padding: 18px 2px 2px; gap: 9px; }
#meal-sheet.carnet .sheet-tall > .sheet-scroll {
  background-image:
    linear-gradient(to bottom, var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0));
  background-color: transparent;
  padding: 1px 2px; margin: 0 -2px;
}
#meal-sheet.carnet .sheet-fixed { padding-inline: 2px; }
/* La bascule de base de lecture et les deux recours sous les molettes. */
.carnet .base-bascule { margin-top: 14px; }
.carnet .food-total { color: var(--carnet-encre-douce); font-size: var(--t-carte-sous); }
.carnet .food-total b {
  font-family: inherit; font-size: var(--t-titre-sec);
  font-weight: var(--g-titre); color: var(--cinnamon);
}
.carnet .disclaimer { color: var(--carnet-encre-douce); font-size: var(--t-appoint); }
.carnet .food-portion .link-btn { color: var(--bouton-second-texte); font-size: var(--t-carte-sous); }
/* Le formulaire de saisie libre et la portion : des cadres ivoire, des champs
   à 48 px. ⚠️ Les quatre colonnes de macros restent une grille de quatre —
   `.meal-free-row` la déclare, et ses libellés gardent `align-self: end`. */
.carnet .meal-libre { padding: 4px 12px 12px; }
.carnet .meal-libre > .champ-libelle:first-child { margin-top: 8px; }
/* ⚠️ **L'identifiant de la feuille, et il est obligatoire.** `#meal-sheet
   input` habille depuis le 13/08 **tous** les champs de cette feuille — nom,
   quatre macros, portion, valeur exacte, quantité au frigo — en crème bordé de
   gris. Une règle en classes (`.carnet .meal-free-row input`) perd contre un
   identifiant : les quatre colonnes de macros restaient beiges dans un cadre
   ivoire, et on ne voyait plus leur filet. Constaté sur la capture, pas au
   test. ⛔ La largeur n'est pas redéclarée : le `width: 100%` de la règle
   d'origine sert au formulaire de valeur exacte, qui est un `flex`. */
#meal-sheet.carnet input, #meal-sheet.carnet select {
  min-height: 48px; padding: 10px 12px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-champ);
}
#meal-sheet.carnet .meal-free-row input { padding-inline: 8px; text-align: center; }
/* ⚠️ **Les quatre libellés de macros passent au degré « étiquette
   minuscule ».** Le cadre ivoire et les marges du carnet reprennent 44 px à la
   rangée — mesuré : 302 px pour 346 sur la base, donc 63 px par colonne au lieu
   de 82. « Protéines g » y passait à deux lignes et les quatre champs se
   décalaient en escalier (`align-self: end`). Le retrait du cadre est resserré,
   et `--t-micro` est **fait pour ça** : « étiquette minuscule » au § 1. ⛔ Le
   plancher de 16 px vise les **champs**, pas leur libellé — les quatre entrées
   restent à `--t-champ`. */
.carnet .meal-libre { padding-inline: 8px; }
#meal-sheet.carnet .meal-free-row > .champ-libelle {
  font-size: var(--t-micro); font-weight: var(--g-accent);
}
.carnet .fap-qte { color: var(--carnet-encre-douce); font-size: var(--t-appoint); }
/* La zone de correction d'une fiche : une question, pas une alerte. */
.carnet .correction { border-top-color: var(--carnet-filet); }
.carnet .correction-ask {
  background: var(--carnet-sauge); border: 1px solid var(--carnet-sauge-filet);
  border-radius: 16px; padding: 11px 12px;
}
.carnet .correction-lead { color: var(--carnet-encre); font-size: var(--t-carte-sous); }
.carnet .correction-ask .hint { color: var(--carnet-encre-douce); }
/* ⚠️ **Un `>` et non un espace, et ce n'est pas un détail de style.**
   `test_corrections_aliments` retrouve la règle des deux réponses par un
   découpage non ancré du fichier, pour vérifier qu'elles se partagent la ligne
   à parts égales. Écrite avec un espace, celle-ci — déclarée **plus haut** —
   était trouvée à sa place, et le test rougissait sur une règle qui ne parle
   que de couleur. ⛔ Et le sélecteur ne se recopie pas dans ce commentaire
   non plus : le test lit le fichier brut. Le partage de la ligne vit toujours
   dans la règle d'origine, plus bas. */
.carnet .correction-actions > .btn-ghost {
  border: 1px solid var(--carnet-filet); border-radius: 999px;
  background: var(--carnet-carte); color: var(--bouton-second-texte);
  font-size: var(--t-carte-sous);
}
.carnet #correction-open, .carnet #food-detail-open, .carnet #food-back {
  color: var(--bouton-second-texte); font-size: var(--t-carte-sous);
}

/* ── C20 — enlever un produit du frigo ─────────────────────────────────────
   Le champ du retrait dans son cadre, et sa question en étiquette : « combien
   tu en enlèves » est le libellé du champ, il reste un `<label>`. ⛔ Le cadre
   se masque avec le champ (`hidden` sur le `<label>` lui-même) sur un article
   non compté ou toujours disponible — la condition ne change pas. */
#ps-sheet .pd-champ.carnet-cadre {
  display: block; margin: 0 0 4px; padding: 12px 14px;
  font-size: var(--t-carte-sous); font-weight: var(--g-accent); color: var(--carnet-encre);
}
#ps-sheet .pd-champ.carnet-cadre > input {
  display: block; box-sizing: border-box; width: 100%; margin-top: 7px;
  min-height: 48px; padding: 10px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-papier); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-nombre); font-weight: var(--g-titre);
  text-align: center;
}
.carnet .ps-motifs { gap: 9px; margin: 0 0 4px; }

/* ── C24 — corriger les valeurs d'un produit ───────────────────────────────
   Cadre : « valeurs nutritionnelles bien alignées, unités visibles ». Deux
   colonnes : le nom de la valeur à gauche, le nombre et son unité à droite —
   les cinq champs tombent donc dans une seule colonne, ce qu'un libellé
   au-dessus de chaque champ ne permettait pas. */
#corr-sheet .corr-grid {
  grid-template-columns: 1fr auto; align-items: center; gap: 10px 12px;
  padding: 14px 12px;
}
#corr-sheet .corr-grid .edit-label {
  margin: 0; font-size: var(--t-carte-sous); line-height: var(--lh-carte);
  color: var(--carnet-encre);
}
.corr-valeur { display: flex; align-items: center; gap: 8px; }
#corr-sheet .corr-grid .edit-field {
  width: 88px; margin: 0; min-height: 48px; padding: 10px 8px; text-align: center;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-papier); color: var(--carnet-encre);
  font-size: var(--t-champ);
}
.corr-unite {
  min-width: 82px; color: var(--carnet-encre-douce);
  font-size: var(--t-appoint); line-height: var(--lh-carte);
}
/* ⚠️ **L'anneau de focus doit battre la bordure au repos, pas seulement
   l'ombrer.** `#corr-sheet .corr-grid .edit-field` pose `border-color` avec un
   identifiant : une règle de focus en classes perdait la bordure et ne laissait
   qu'un halo, donc le champ visé ne se distinguait plus de ses quatre voisins.
   Le trait sauge rend 5,47:1 sur le papier (mesuré au banc composants). */
.carnet .edit-field:focus,
#corr-sheet .corr-grid .edit-field:focus {
  border-color: var(--carnet-sauge-trait);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--carnet-sauge-trait) 30%, transparent);
}

/* ── C14 : la place que les cinq étapes doivent laisser au pied ────────────
   ⚠️ **Mesuré, pas estimé — 22/09.** L'étape des sources demandait 846 px pour
   741 à 390 × 844, et 775 pour 700 à 1 280 × 900 : quatre cartes de 86 px, deux
   rangées de puces de repas, « Ce qui revient » et une amorce. ⛔ Et le défaut
   **préexiste** : la même étape mesurait déjà 770 px pour 743 sur la base du
   22/09 à 09:00 — les cartes l'ont aggravé, elles ne l'ont pas créé. Elle ne
   peut pas passer en feuille à bandes : deux tests hors de ce lot la découpent
   au caractère près (`<div id="meal-step-source">` … `\n  </div>`), donc elle
   défile avec la feuille, ce qui est le **défaut** du socle et non un oubli.

   Ce palier reprend 78 px sans rien retirer : cartes au plancher bas du cadre
   (80 px, pour 80–96 demandés), marges de tête resserrées, « Ce qui revient »
   plus compact. Après : 768 px à 390 — un cran **sous** la base — et 697 pour
   700 à 1 280, donc le pied revient dans le cadre sur grand écran. Le reste du
   défilement n'apparaît que quand « Ce qui revient » propose quelque chose. */
#meal-sheet.carnet > .carnet-tete { margin-bottom: 12px; }
#meal-sheet.carnet > .carnet-tete > .hint { margin-bottom: 3px; }
#meal-sheet.carnet > .meal-groups { margin-bottom: 8px; }
#meal-sheet.carnet .carnet-cartes { gap: 9px; margin-top: 10px; }
#meal-sheet.carnet .sheet-item.carte-action { min-height: 80px; padding: 10px 12px; }
#meal-sheet.carnet .sheet-item.carte-action small { margin-top: 3px; }
/* ⚠️ **Le chevron au bord, et pas contre le texte.** `.sheet-item` est un
   `flex` sans part souple : les quatre libellés de C14 sont courts, donc le
   chevron se posait juste après « Depuis une recette » au lieu du bord droit —
   vu à 1 280 px, où la carte est large. Les menus déjà photographiés (03d-1)
   ont des libellés qui remplissent la carte, et la règle reste donc scopée :
   unifier `.carte-chevron` repeindrait des feuilles hors de ce lot. */
#meal-sheet.carnet .sheet-item.carte-action > span:not(.carnet-illu) { flex: 1; min-width: 0; }
#meal-sheet.carnet .sheet-actions { padding-top: 12px; }
#meal-sheet.carnet .recall { min-height: 66px; padding: 9px 11px; }
#meal-sheet.carnet .recall-block > h2 { margin-bottom: 6px; }

/* ── C14 après un scan : la molette d'abord — retour de Luca sur iPhone, 22/09 ──

   Mesuré à 390 × 844 sur la base (05C) : les quatre puces tenaient sur deux
   rangées (97 px), le pied épinglé faisait 305 px, et la bande de la molette
   n'avait plus que 123 px — la quantité, qu'on vient régler, était la partie
   la plus petite de la feuille.

   (1) **Les quatre puces sur une rangée.** Une grille de quatre colonnes à la
   largeur de leur libellé ; « Petit déjeuner » passe sur deux lignes dans sa
   puce plutôt que de pousser « Collation » sur la rangée suivante. Texte à
   19 px (18 sous 360 px) : ce sont des boutons, pas des champs — le plancher
   de 16 px des champs ne les concerne pas, la cible de 44 px si. */
#meal-sheet .meal-groups {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, auto);
  justify-content: stretch; gap: 6px;
}
#meal-sheet .meal-groups .chip {
  min-width: 0; min-height: 44px; padding: 5px 6px;
  white-space: normal; text-align: center; line-height: 1.05; font-size: 19px;
}
@media (max-width: 359px) {
  #meal-sheet .meal-groups { gap: 4px; }
  #meal-sheet .meal-groups .chip { padding-inline: 4px; font-size: 18px; }
}
/* (2) **Le pied se resserre sans rien retirer.** Liste et frigo sur une rangée
   (`.food-suites`), le nombre collé au bouton du frigo, « Choisir un autre
   aliment » sans sa marge de 12 px. Une suite masquée laisse toute la rangée
   à l'autre. ⚠️ Le champ reprend sa largeur : `#meal-sheet input` le mettait
   à 100 %, et c'est ce qui poussait le bouton hors de la feuille. */
#meal-sheet .food-suites { display: flex; gap: 8px; align-items: stretch; }
#meal-sheet .food-suites > * { flex: 1 1 0; min-width: 0; }
#meal-sheet .food-suites > [hidden] { display: none; }
#meal-sheet .food-suites .btn-ghost {
  margin: 0; min-height: 48px; padding: 4px 8px;
  white-space: normal; line-height: 1.1;
}
#meal-sheet .food-au-frigo { gap: 6px; }
#meal-sheet .food-au-frigo .btn-ghost { flex: 1 1 0; min-width: 0; width: auto; }
#meal-sheet.carnet .fap-qte input {
  width: 50px; min-height: 48px; margin: 0; padding: 6px 2px; text-align: center;
}
#meal-sheet.carnet .sheet-actions #food-back { margin-top: 0; min-height: 44px; }
/* ── La feuille tient dans l'écran — Luca, 23/09 ─────────────────────────────
   « Il faut toujours scroller vers le bas. » Ce qui est repris l'est là où
   aucune fonction ne disparaît : la bascule « Pour 100 g » (masquée, les valeurs
   restent dans la fiche complète et sur la carte), les deux liens sur une
   rangée, l'attribution au format d'une mention, et les marges. La molette,
   elle, prend ce qui reste — `ajusterLaMolette`. */
#meal-sheet .base-bascule[hidden] { display: none; }
#meal-sheet .correction, #meal-sheet #food-detail-open {
  display: inline-block; vertical-align: middle; width: auto;
}
#meal-sheet .correction { border-top: none; padding: 0; margin: 2px 0 0; }
#meal-sheet #food-detail-open { margin-left: 10px; }
#meal-sheet .link-btn { min-height: 30px; margin: 0 auto; font-size: 18px; }
#meal-sheet #food-attribution { font-size: 15px; margin: 2px 0 0; }
#meal-sheet .food-total { font-size: 18px; min-height: 26px; margin: 2px 0; }
#meal-sheet .food-total b { font-size: 22px; }
#meal-sheet .roues { gap: 4px; padding: 2px 14px 0; }
#meal-sheet .roues > .carnet-etiquette { display: none; }
/* Le repli quand la place manque ; `ajusterLaMolette` pose la hauteur exacte. */
#meal-sheet .roue { --roue-h: 132px; }
/* ⛔ **Un plancher pour la bande, sinon la molette disparaît.** Mesuré à
   320 × 568 : le pied épinglé ne lui laissait que 12 px. Sous 660 px de
   hauteur, le pied cesse d'être épinglé : tout défile ensemble. */
@media (max-height: 660px) {
  #meal-sheet .sheet-tall { display: block; overflow-y: auto; }
  #meal-sheet .sheet-tall > .sheet-scroll { overflow: visible; }
}

/* ── « Combien ? » encadré, comme le champ du frigo — Luca, 23/09 ────────────
   La valeur lue était un petit texte aligné à gauche : elle ne se lisait pas
   comme la réponse à la question, et rien n'indiquait qu'on pouvait la taper.
   Elle prend le libellé et le cadre d'un champ ; le reste de la feuille ne
   bouge pas. */
#meal-sheet .champ-combien {
  display: flex; align-items: center; gap: 10px; margin: 6px 0 2px;
}
#meal-sheet .champ-combien .champ-libelle { flex: none; margin: 0; }
#meal-sheet .lecture-cadre {
  flex: 1 1 auto; min-width: 0; min-height: 48px; padding: 4px 10px;
  /* Bloc et non `flex` : la valeur, l'unité et les grammes sont trois nœuds
     textuels qui se centrent à la ligne. En `flex`, ils restaient collés à
     gauche du cadre (mesuré : x = 121 pour un cadre de 249 px). */
  display: block; text-align: center; line-height: 40px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--card);
}
#meal-sheet .lecture-cadre > .lecture-affichee { display: inline; }
#meal-sheet .lecture-cadre .valeur-exacte-form { flex: 1 1 auto; min-width: 0; }
#meal-sheet .lecture-cadre #food-exact { width: 100%; min-width: 0; margin: 0; text-align: center; }

/* 320 px : la liste cède un peu de largeur au frigo, qui porte son nombre ;
   les deux libellés passent sur deux lignes à 18 px plutôt que « Ajouter /
   à mon / frigo » sur trois. */
@media (max-width: 359px) {
  #meal-sheet .food-suites .btn-ghost { font-size: 18px; padding-inline: 4px; }
  #meal-sheet .food-suites > .food-au-frigo { flex-grow: 1.35; gap: 4px; }
  #meal-sheet.carnet .fap-qte input { width: 42px; }
}
/* Écran court : la carte du produit reste épinglée (elle dit à quoi la
   quantité s'applique), mais se resserre — c'est la molette qui manque de
   place. L'identifiant est celui de la règle d'origine, plus haut. */
@media (max-height: 620px) {
  #meal-sheet .product-card.carnet-contexte { padding: 6px 10px; margin-bottom: 8px; }
  #meal-sheet .product-card .product-lines { line-height: 1.1; }
}

/* ── Écran court : le décor cède avant le contenu ──────────────────────────
   Le palier du socle (`max-height: 620px`) resserre déjà le papier, les cartes
   et le pied. ⚠️ **Il ne suffisait pas à deux feuilles de cette famille**,
   mesurées à 320 × 568 (cadre 474 px) :

   | feuille     | base 22/09 | carnet nu | après ce palier |
   |---|---|---|---|
   | plan-sheet  | 484/476 ✔  | 597/474 ✘ | voir le journal |
   | ps-sheet    | 392/392 ✔  | 508/474 ✘ | voir le journal |

   C13 est le cas dur : sept puces de jour et quatre de moment tiennent sur
   quatre rangées, et tout cela vit dans la bande **fixe**, qui ne défile pas
   par construction — c'est elle qui dit à quoi la recherche s'applique. Le
   cadre ivoire du couple jour/moment cède donc sa bordure et son retrait sur
   un écran court : la surface douce est un confort, le pied atteignable ne
   l'est pas. ⛔ Aucune commande ne descend sous 40 px de hauteur, et le § 1
   garde ses 44 px de **cible** par la zone étendue des puces. */
@media (max-height: 620px) {
  .carnet .carnet-contexte { padding: 8px 10px; margin-bottom: 10px; }
  .carnet .champ-libelle { margin: 8px 3px 4px; }
  .carnet .chip { min-height: 40px; padding: 6px 12px; }
  .carnet .plan-quand { padding: 0; border: none; background: none; }
  #plan-sheet .plan-illu { display: none; }
  #plan-sheet.carnet #plan-recherche { min-height: 44px; margin-top: 2px; }
  .carnet > .sheet-title.settings-sous-titre { margin: 10px 4px 6px; }
  #ps-sheet .pd-champ.carnet-cadre { padding: 9px 11px; }
  #ps-sheet .pd-champ.carnet-cadre > input {
    min-height: 44px; font-size: var(--t-titre-sec); margin-top: 5px;
  }
  .carnet .conv-row .stepper button { width: 44px; height: 44px; min-height: 44px; }
  #conv-sheet .conv-cadre { padding: 8px 12px 10px; }
  #corr-sheet .corr-grid { gap: 8px 10px; padding: 10px 10px; }
  #corr-sheet .corr-grid .edit-field { min-height: 44px; }
}

/* 360 px et moins : les deux colonnes de C24 se resserrent, et la mention
   d'unité passe sous le nombre plutôt que de tronquer « kcal / 100 g ». */
@media (max-width: 360px) {
  #corr-sheet .corr-grid { gap: 9px 8px; padding: 12px 9px; }
  #corr-sheet .corr-grid .edit-field { width: 76px; }
  .corr-unite { min-width: 0; }
  .carnet .conv-row .stepper { gap: 14px; }
}

/* ── Le carnet : la fin de la famille C, puis toute la famille D — 03d-3, 22/09

   Cinq menus pour finir la déclinaison : C29 (corriger la quantité d'une entrée
   du journal) clôt « saisir ou doser », puis C06, C15, C17 et C25 forment la
   famille D — « expliquer ou confirmer ». ⛔ **Aucun jeton neuf, aucune
   illustration neuve** : les quatre feuilles de la famille D réemploient
   carnet, balance, convives, boîte, marmite, crayon, étincelle, enveloppe,
   écran et assiette du sprite.

   La grammaire de la famille D est celle du cadre, et son pilote est C04
   (`ask-sheet`, livré en 03c) : titre **à gauche** (`carnet-gauche`), texte
   aéré (`.carnet-texte`), l'information prioritaire dans un `.carnet-cadre`,
   pied du socle, et **décor minimal** — « réduire à une ou aucune branche dans
   un formulaire dense, une erreur ou un consentement ». Aucune des quatre ne
   porte de brin : C06 demande un accord, C15 se lit les mains grasses, C17 est
   un formulaire d'erreur et C25 une explication chiffrée. */

/* ── C29 — corriger la quantité d'une entrée du journal ────────────────────
   Cadre : « quantité corrigée mise en avant, unité et contexte lisibles ». Le
   nombre prend donc le degré `--t-nombre` (43 px) dans le cadre ivoire de la
   famille, comme le retrait de C20, et l'aperçu passe **sous** lui : posé à
   droite du champ, il partageait la ligne avec une masse de 43 px et se lisait
   comme une seconde unité.

   ⚠️ **L'identifiant de la feuille est obligatoire ici.** `#weightedit-sheet
   input` habille ce champ depuis le 13/08 — crème, filet gris, 24 px — et bat
   n'importe quelle règle en classes : le champ serait resté beige au milieu du
   papier ivoire, comme les macros de C14 l'étaient avant 03d-2. */
#weightedit-sheet .weightedit-cadre { margin: 0; padding: 10px 14px 12px; }
#weightedit-sheet.carnet .weightedit-row {
  margin: 2px 0 0; justify-content: center; gap: 10px;
}
#weightedit-sheet.carnet input {
  /* 9rem : à 43 px, « 1 250 » tient sans rogner le premier chiffre (vu à 1 280). */
  width: 9rem; margin-bottom: 0; min-height: 56px; padding: 6px 8px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-papier); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-nombre); font-weight: var(--g-titre);
  text-align: center;
}
#weightedit-sheet.carnet .qty-unit {
  font-size: var(--t-unite); color: var(--carnet-encre-douce);
}
/* ⚠️ **`--cinnamon`, pas `--bouton-fond`** — le rappel de 03d-2 : sur le papier
   sombre du carnet, la terre cuite des commandes ne rend que 3,09:1, et un
   nombre relu n'est pas une commande. `--cinnamon` est la terre cuite d'encre,
   celle de `.food-total b` et de la lecture des molettes. */
#weightedit-sheet.carnet #weightedit-apercu {
  display: block; margin: 9px 0 0; text-align: center;
  font-size: var(--t-carte-sous); color: var(--cinnamon);
}
/* Le pied et l'erreur vivent dans le `<form>` : les deux règles du socle
   (`.carnet > .sheet-actions`, le retrait de 14 px du 20/09) ne les atteignent
   pas — même cas que les cinq étapes de C14. */
#weightedit-sheet.carnet .sheet-actions { padding: 18px 2px 2px; gap: 9px; }
#weightedit-sheet.carnet .form-error { margin: 12px 4px 0; }

/* ── C06 — le consentement au suivi santé ──────────────────────────────────
   Cadre : « consentement lisible, petites illustrations informatives ».
   Les trois catégories deviennent trois cartes du carnet — c'est ce que sont
   les entrées de la référence — et l'encart de confidentialité prend le
   `.carnet-cadre` de la famille D, parce que c'est l'information prioritaire
   de cette feuille : la seule sortie de données qu'elle déclare.

   ⚠️ **Les règles d'origine vivent 4 500 lignes plus bas** (`.consent-liste`,
   `.consent-encart`, le retrait de 14 px) : à spécificité égale elles
   l'emporteraient par l'ordre du fichier. Toutes celles-ci passent donc par
   `.carnet`, qui les bat d'une classe. */
.carnet .consent-liste { margin: 0 0 14px; }
.carnet .consent-liste li {
  align-items: center; gap: 12px; min-height: 80px; padding: 10px 12px;
  border: 1px solid var(--carnet-filet); border-radius: 20px;
  background: var(--carnet-carte);
}
.carnet .consent-liste li + li { margin-top: 10px; }
.carnet .consent-liste li > span:last-child { gap: 3px; }
.carnet .consent-liste strong {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
.carnet .consent-liste small {
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
  color: var(--carnet-encre-douce);
}
.carnet .consent-encart {
  align-items: flex-start; gap: 12px; margin: 0 0 4px; padding: 13px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 22px;
  background: var(--carnet-carte);
}
/* La boîte fermée — ce qui ne sort pas d'ici — au degré d'une illustration de
   rubrique : l'encart est un paragraphe, pas une carte d'action. */
.carnet .consent-encart .carnet-rubrique-illu { width: 30px; height: 31px; margin-top: 2px; }
.carnet .consent-encart p {
  font-size: var(--t-corps); line-height: var(--lh-corps); color: var(--carnet-encre);
}
/* La feuille du carnet porte déjà ses 16 px : le retrait de 14 px d'origine
   décalait l'amorce, l'encart et la phrase de retrait de ses cartes. */
.carnet .consent-amorce, .carnet .consent-retrait { margin-inline: 0; }
.carnet .consent-retrait { margin-top: 14px; margin-bottom: 0; }
/* Le lien de la politique garde son soulignement : c'est le seul lien du
   carnet à mener hors de l'application, et `--cinnamon` est la terre cuite
   d'encre déjà employée pour les liens de contenu (`.evidence a`, `.step-tags
   a`). ⛔ Pas `--bouton-fond` : 3,09:1 sur le papier sombre (rappel 03d-2). */
.carnet .consent-retrait a { color: var(--cinnamon); text-decoration: underline; }

/* ── C15 — les ingrédients pendant la cuisine ──────────────────────────────
   Cadre : « liste d'ingrédients lisible pendant la cuisine ; décor quasi
   absent, fermeture facile ». La feuille garde donc sa tête à deux pièces —
   titre et croix — au lieu du `display: block` que 03d-1 avait donné à la
   tête du panneau de filtres, qui n'a pas de croix.

   ⚠️ **Les lignes deviennent des cartes, et c'est une contrainte de lecture
   avant d'être un habillage** : on cherche un ingrédient dans une liste de
   quinze, l'écran posé à un mètre, les doigts gras. Un filet d'un pixel entre
   deux lignes ne sépare rien à cette distance. */
#cooking-sheet .sheet-head.carnet-tete { display: flex; }
#cooking-sheet .carnet-sous { margin: 0 0 12px; }
/* La croix reprend les 44 px du § 1 : elle en faisait 36 dans `.sheet-head`,
   et c'est le seul geste de sortie que la cuisine offre au doigt. */
.carnet .sheet-head .icon-btn {
  width: 44px; height: 44px; min-height: 44px;
  border: 1px solid var(--carnet-filet); background: var(--carnet-carte);
  color: var(--carnet-encre);
}
.carnet .sheet-head .icon-btn .icon { width: 19px; height: 19px; }
.carnet .cooking-ing {
  gap: 12px; min-height: 66px; margin-bottom: 9px; padding: 10px 12px;
  border: 1px solid var(--carnet-filet); border-top: 1px solid var(--carnet-filet);
  border-radius: 20px; background: var(--carnet-carte);
}
.carnet .cooking-ing:last-child { margin-bottom: 0; }
.carnet .cooking-label {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
.carnet .cooking-qty { font-size: var(--t-carte-sous); color: var(--cinnamon); }
/* ⛔ **Pas `opacity` sur une ligne cochée** — le § des couleurs 03a : ce qui
   est fait s'éteint par une **encre** et son trait barré, pas par une
   transparence qui emporte aussi le dessin et la case. Le fond sauge reprend
   le vocabulaire du carnet : ce qui est retenu porte la sauge.
   ⚠️ Scopé au carnet : le pied de l'étape (`.cook-ing-etape`, écran P05) garde
   sa ligne et son atténuation, il n'est pas dans ce lot. */
.carnet .cooking-ing.done {
  opacity: 1; background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet);
}
.carnet .cooking-ing.done .cooking-label { color: var(--carnet-encre-douce); }
.carnet .cooking-ing.done .cooking-qty { color: var(--carnet-encre-douce); }
.carnet .cooking-ing .diary-thumb { width: 40px; height: 40px; border-radius: 12px; background: var(--carnet-sauge); }
.carnet .cooking-ing .diary-thumb-empty { color: var(--carnet-sauge-trait); }
.carnet .cooking-ing .diary-thumb-empty .icon { width: 20px; height: 20px; }
/* La case à cocher du carnet : le filet du papier au repos, la sauge cochée —
   la même que les puces retenues et les bascules. */
/* ⚠️ **C'est `::before` qui dessine la case**, pas l'élément : une règle posée
   sur `.shop-check` aurait peint un fond *sous* le carré, exactement le défaut
   du 12/09 où rien ne se cochait. */
.carnet .cooking-ing .shop-check::before {
  border-color: var(--carnet-filet-vif); background: var(--carnet-papier);
}
.carnet .cooking-ing .shop-check.on::before {
  background: var(--carnet-sauge-trait); border-color: var(--carnet-sauge-trait);
}
/* ⛔ **Et la coche elle-même change d'encre.** `.shop-check.on` la peint en
   blanc depuis le 12/09 ; en thème sombre le trait sauge est **clair**
   (`#D4E2B5`), donc blanc sur clair = 1,37:1 — la coche disparaissait, et
   c'est exactement le « pas de texte blanc sur un fond clair » du cadre. Le
   papier du carnet suit le thème : 5,88:1 en clair, 11,28:1 en sombre. */
.carnet .cooking-ing .shop-check.on { color: var(--carnet-papier); }
/* La vignette d'une ligne cochée reprend le papier : sur la bande sauge, son
   propre fond sauge la faisait disparaître (vu sur la capture sombre). */
.carnet .cooking-ing.done .diary-thumb { background: var(--carnet-papier); }

/* ── C17 — signaler un problème ────────────────────────────────────────────
   Cadre : « formulaire de signalement sobre, champs identifiés et retour
   d'envoi ; aucun message amusant à la place de l'erreur ». La feuille garde
   ses trois bandes (`report-sheet` / `report-scroll` / `filters-actions`) et
   son contrat d'envoi ; ce sont ses surfaces qui passent au papier.

   ⚠️ **Les règles d'origine vivent 5 000 lignes plus bas** : à spécificité
   égale elles gagneraient par l'ordre du fichier. Le préfixe `.carnet` les bat
   d'une classe, et l'identifiant sert là où l'original en portait un. */
.carnet.report-sheet { padding: 22px 16px 14px; }
#report-sheet .sheet-head.carnet-tete { display: flex; align-items: flex-start; }
/* Le pictogramme de métier que le cadre autorise à la place d'un brin, dans le
   fil du titre : le drapeau du signalement. */
#report-sheet .sheet-title { display: flex; align-items: center; gap: 9px; min-width: 0; }
.carnet .report-scroll { padding: 1px 2px; margin: 0 -2px; }
.carnet.report-sheet .filters { margin: 0 0 4px; }
.carnet.report-sheet textarea {
  border: 1px solid var(--carnet-filet); border-radius: 18px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-champ); line-height: var(--lh-corps);
  padding: 12px 14px; min-height: 120px;
}
.carnet.report-sheet textarea:focus {
  border-color: var(--carnet-sauge-trait);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--carnet-sauge-trait) 30%, transparent);
}
/* « Joindre une capture » est une commande secondaire : la pilule calme du
   pied du carnet, pas un rectangle crème. */
.carnet .report-attach {
  min-height: 48px; border-radius: 999px;
  border: 1px solid var(--carnet-filet); background: var(--carnet-carte);
  color: var(--bouton-second-texte);
  font-size: var(--t-carte-sous); font-weight: var(--g-accent);
}
.carnet .report-thumb {
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-carte);
}
/* Ce qui part sans qu'on l'écrive — l'écran, la taille, le thème, la langue,
   le terminal : l'information prioritaire de la feuille, donc le cadre ivoire
   de la famille D et l'enveloppe qui dit ce qu'on y met. */
.carnet .report-joint {
  align-items: flex-start; gap: 11px; margin: 14px 0 0; padding: 12px 14px;
  border: 1px solid var(--carnet-filet); border-radius: 22px;
  background: var(--carnet-carte);
  color: var(--carnet-encre-douce);
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
}
.carnet .report-joint .carnet-rubrique-illu { width: 28px; height: 29px; margin-top: 1px; }

/* ── C25 — comprendre le calcul nutritionnel ───────────────────────────────
   Cadre : « explication du calcul en blocs de lecture ; distinguer valeur
   connue et estimation, sans faux graphique ». Les sept lignes d'un tableau
   serré deviennent donc des cartes — le libellé, sa masse, ses kcal, ce à quoi
   la ligne s'accroche et sa part réelle du total.

   ⛔ **Les trois registres de la lecture restent distincts**, et c'est tout
   l'objet de l'écran : une ligne qui ne compte pas (terre cuite), une ligne
   rattachée par ressemblance (ambre), une ligne juste (encre douce). Le carnet
   ne les fond pas dans une seule couleur de papier. */
#calc-sheet .sheet-fixed { padding-inline: 2px; }
#calc-sheet .carnet-tete { margin-bottom: 10px; }
#calc-sheet .sheet-title { display: flex; align-items: center; gap: 9px; min-width: 0; }
/* Le total, dans le cadre de la famille D : le grand nombre au degré
   `--t-titre-sec`, la bande de précision et la masse comptée en dessous. */
.carnet .calc-lead {
  margin: 0; padding: 12px 14px;
  color: var(--carnet-encre-douce);
  font-size: var(--t-carte-sous); line-height: var(--lh-carte);
}
/* ⛔ **Pas `display: block` sur le gras**, et c'est une leçon de capture : le
   total est suivi de « · bonne » dans la même phrase (`calc-lead` est écrite
   d'un seul `innerHTML`), et le passer en bloc laissait une ligne orpheline qui
   commence par un point médian. Il reste donc dans le fil, au degré du titre
   de section, et la bande de précision se lit à sa suite. */
.carnet .calc-lead b {
  font-size: var(--t-titre-sec); font-weight: var(--g-titre); color: var(--cinnamon);
}
.carnet #calc-note { padding-inline: 4px; margin-top: 12px; }
.carnet .calc-row {
  gap: 3px 12px; margin-bottom: 9px; padding: 11px 13px;
  border: 1px solid var(--carnet-filet); border-bottom: 1px solid var(--carnet-filet);
  border-radius: 20px; background: var(--carnet-carte);
}
.carnet .calc-row:last-child { margin-bottom: 0; }
.carnet .calc-row .cr-label {
  font-size: var(--t-carte); font-weight: var(--g-accent); color: var(--carnet-encre);
}
/* ⚠️ **`--cinnamon` et non `--bouton-fond`** (rappel 03d-2) : un nombre relu
   n'est pas une commande, et la terre cuite des commandes ne rend que 3,09:1
   sur le papier sombre. Les chiffres restent alignés en colonne. */
.carnet .calc-row .cr-kcal {
  font-size: var(--t-carte); font-weight: var(--g-titre); color: var(--cinnamon);
}
.carnet .calc-row .cr-food {
  font-size: var(--t-carte-sous); line-height: var(--lh-carte); color: var(--carnet-encre-douce);
}
.carnet .calc-row .cr-food b { color: var(--carnet-encre); font-weight: var(--g-accent); }
.carnet .cr-cohorte { font-size: var(--t-appoint); color: var(--carnet-encre-douce); }
/* ── Les deux registres d'incertitude, et ils doivent rester lisibles ──────
   « Rattachée par ressemblance » et « cette ligne vaudrait de X à Y » sont en
   ambre depuis le 17/08 : c'est la ligne qui compte peut-être le **mauvais**
   aliment, et le cadre demande de « distinguer valeur connue et estimation ».
   ⛔ Mais `--amber` nu ne rend que **2,21:1** sur la carte ivoire — défaut
   antérieur à ce lot, que le papier ne fait qu'exposer. L'ambre est donc mêlé
   à l'encre du carnet, qui suit le thème : 5,04:1 en clair, 8,97:1 en sombre.
   ⛔ Pas `--cinnamon` : ce serait la couleur du nombre juste à côté, et les
   deux registres se confondraient. */
.carnet .cr-cohorte.pese,
.carnet .calc-note-devine { color: color-mix(in srgb, var(--amber) 45%, var(--carnet-encre)); }
.carnet .calc-bar { height: 5px; margin-top: 8px; background: var(--carnet-filet); }
.carnet .calc-bar i { background: var(--cinnamon); }
/* Une ligne qui ne compte pas : son encre s'éteint, et la raison garde la terre
   cuite écrite du § des couleurs — jamais une transparence. */
.carnet .calc-row.off { background: var(--carnet-papier-bas); }
.carnet .calc-row.off .cr-kcal, .carnet .calc-row.off .cr-label { color: var(--carnet-encre-douce); }
.carnet .calc-note-off { color: var(--danger-texte); }

/* ── C19 — scanner un produit, famille E — 05C, 22/09 ──────────────────────
   Le dernier des trente menus à prendre le carnet. Cadre : « grand aperçu
   réel, commandes dessinées lisibles hors du flux » ; ⛔ **aucun papier par-
   dessus la caméra** — la vue garde toute la largeur de la feuille et son 4/3
   (un test le fige), seuls ses coins s'arrondissent comme le cadre de P26.
   Les étapes sont des bandes (`.sheet-tall`) et non des enfants directs du
   carnet : les règles du socle `.carnet > .sheet-actions`/`.sheet-scroll` ne
   les atteignent pas, d'où l'identifiant — même raison que C14.

   ⚠️ **Les commandes du 05A** (« +1 », « Reprendre la caméra », « Scanner à
   nouveau ») vivent hors du pied : elles prennent ici la pilule secondaire du
   carnet (filet pêche, encre des boutons secondaires, 44 px), sinon elles
   restaient les seules pièces grises de la feuille. */
#scan-sheet.carnet .sheet-tall > .sheet-scroll {
  background-image:
    linear-gradient(to bottom, var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to top,    var(--carnet-papier) 55%, rgba(0, 0, 0, 0)),
    linear-gradient(to bottom, rgba(30, 24, 20, .13), rgba(30, 24, 20, 0)),
    linear-gradient(to top,    rgba(30, 24, 20, .13), rgba(30, 24, 20, 0));
  background-color: transparent;
}
#scan-sheet.carnet .sheet-actions { padding: 14px 2px 2px; gap: 9px; }
/* La tête : le pictogramme du code-barres dans un médaillon sauge, le titre
   à l'encre du carnet — compacte, pour laisser la hauteur à la caméra. */
#scan-sheet.carnet .scan-head { gap: 10px; padding: 0 4px 10px; }
#scan-sheet.carnet .scan-head .icon {
  flex: none; width: 36px; height: 36px; padding: 7px; border-radius: 50%;
  background: var(--carnet-sauge); color: var(--carnet-sauge-trait);
}
#scan-sheet.carnet .scan-head .sheet-title {
  font-size: var(--t-titre-sec); font-weight: var(--g-titre); color: var(--carnet-encre);
}
/* ⛔ Le papier du carnet a 16 px de marge, la feuille d'origine 8 : la vue
   reprend ces 9 px de chaque côté pour garder exactement sa taille (346 px de
   large à 390, comme avant l'habillage). */
#scan-sheet.carnet .scan-view { border-radius: 22px; margin-inline: -9px; }
#scan-sheet.carnet .carnet-cartes { gap: 9px; margin-top: 10px; }

#scan-sheet.carnet .sheet-item.carte-action { min-height: 76px; padding: 10px 12px; }
#scan-sheet.carnet .sheet-item.carte-action > span:not(.carnet-illu) { flex: 1; min-width: 0; }
/* Les pilules du carnet hors du pied : « +1 », « Reprendre la caméra »,
   « Scanner à nouveau ». */
#scan-sheet.carnet .btn-ghost,
#scan-sheet.carnet #scan-rescan-manuel,
#scan-sheet.carnet #create-rescan {
  min-height: 48px; border-radius: 999px;
  border: 1px solid var(--carnet-filet); background: var(--carnet-carte);
  color: var(--bouton-second-texte);
  font-size: var(--t-corps); font-weight: var(--g-accent); line-height: 1.12;
}
#scan-sheet.carnet #scan-rescan-manuel,
#scan-sheet.carnet #create-rescan {
  display: flex; align-items: center; justify-content: center;
  width: 100%; margin: 8px 0 10px; padding: 6px 16px; text-decoration: none;
}
#scan-sheet.carnet .sd-plus { min-height: 44px; min-width: 58px; padding: 0 14px; }
/* « Rangé » : une carte de contexte de la famille C — ce qui vient d'entrer au
   frigo, et sa date à portée du pouce. */
#scan-sheet.carnet .scan-dernier {
  background: var(--carnet-carte); border: 1px solid var(--carnet-filet);
  border-radius: 18px; padding: 8px 10px 8px 14px;
}
#scan-sheet.carnet .sd-nom { color: var(--carnet-encre); font-weight: var(--g-accent); }
#scan-sheet.carnet .sd-date { color: var(--carnet-encre-douce); font-size: var(--t-appoint); }
/* Les champs : même peau que ceux de C14 (ivoire, filet pêche, 16 px et plus). */
#scan-sheet.carnet input {
  min-height: 48px; padding: 10px 12px;
  border: 1px solid var(--carnet-filet); border-radius: 16px;
  background: var(--carnet-carte); color: var(--carnet-encre);
  font: inherit; font-size: var(--t-champ);
}
#scan-sheet.carnet .meal-free-row input { padding-inline: 8px; text-align: center; }
#scan-sheet.carnet .sd-date input { min-height: 44px; margin: 0; }
#scan-sheet.carnet .estimate-out {
  background: var(--carnet-carte); border-color: var(--carnet-filet); border-radius: 18px;
}
#scan-sheet.carnet .estimate-why { color: var(--carnet-encre-douce); }
#scan-sheet.carnet .link-btn { color: var(--bouton-second-texte); }
@media (max-height: 620px) {
  #scan-sheet.carnet .scan-head { padding-bottom: 6px; }
  #scan-sheet.carnet .sheet-item.carte-action { min-height: 66px; padding: 8px 10px; }
}

/* ── Icônes ──────────────────────────────────────────────────────────────── */

/* Un seul jeu, monochrome, hérité de `currentColor` — donc il suit le thème et
   l'état actif sans exception.

   Le CSS ne fixe que la taille : `fill` et `stroke` sont portés par chaque
   tracé du sprite. Les lister ici en sélecteurs se désynchronise dès qu'on
   ajoute une icône, et l'oubli se voit en aplat noir. */
.icon {
  width: 20px; height: 20px; flex: none;
  vertical-align: -3px;
}
.icon.inline { width: 14px; height: 14px; vertical-align: -2px; }

/* ── En-têtes ────────────────────────────────────────────────────────────── */

/* Rythme vertical : c'est l'écran qui espace ses blocs, pas chaque
   composant qui déclare sa marge dans son coin. Sinon un composant qui
   n'en déclare pas — le formulaire d'import, la rangée de puces — vient
   coller son voisin, et le défaut ne se voit qu'à l'écran.
   `* + *` : jamais de marge sur le premier enfant, donc pas de blanc en
   haut de page. */
.screen > * + * { margin-top: 24px; }
/* L'enveloppe des cartes du journal (§ « tablette et bureau ») n'est pas un
   composant : elle n'existe que pour donner au multi-colonnes une hauteur
   libre. Le rythme vertical de ses cartes est donc celui de l'écran, recopié
   d'un cran plus bas — sans quoi elles se colleraient sur téléphone, où elle ne
   fait rien d'autre. */
.today-colonnes > * + * { margin-top: 24px; }

/* La carte du frigo sur l'écran du jour : une liste de trois lignes, le nom à
   gauche et l'échéance à droite — la même lecture que le frigo lui-même. */
.frigo-presses { list-style: none; margin: 12px 0 16px; padding: 0; display: grid; gap: 10px; }
.frigo-presses li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.frigo-presses span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.frigo-presses strong { flex: none; font-size: 19.5px; color: var(--muted); }
/* ⚠️ **Les mêmes variables que le frigo**, jamais une seconde palette : le même
   yaourt doit avoir la même couleur sur les deux écrans. */
.frigo-presses strong.warn { color: var(--terra); }
.frigo-presses strong.urgent,
.frigo-presses strong.danger { color: var(--danger, #b3261e); }
/* Même geste pour les deux colonnes de la fiche recette : elles n'existent que
   pour le grand écran, et le rythme vertical de leurs cartes descend d'un cran
   avec elles. */
.recipe-col > * + * { margin-top: 24px; }
/* ⚠️ **Ce sélecteur court sur deux lignes, et il ne parle pas de l'accueil.**
   `.screen > * +` et `.home-head` en sont les deux moitiés : il vise un en-tête
   **précédé d'un frère**, ce qui n'arrive sur aucun écran. Sa moitié utile est
   `.screen > .block + *`, qui règle le rythme entre blocs — ⛔ **et c'est
   pourquoi on n'y touche pas** : y écrire une marge d'en-tête la donnerait à
   tout élément suivant un `.block`. La mise en page des en-têtes à marque vit
   dans `.pane-head.tete-marque`, plus bas, où elle gagne par spécificité.
   ⚠️ Un commentaire posé **entre** les deux lignes ne les sépare pas : la
   déclaration suivante deviendrait le second membre de ce sélecteur. */
.screen > .block + *, .screen > * +
.home-head { margin-bottom: 0; }

/* ── Bonjour, et la question — P01 ───────────────────────────────────────────
   ⚠️ **Compacté par rapport à l'image livrée**, et c'est l'adaptation nommée par
   la revue : à 390 px logiques, la tête repoussait la première photo vers
   230 px. Le bonjour maigrit, le titre reste en serif — c'est lui qui donne le
   ton de l'écran — et l'ensemble rend une soixantaine de pixels aux recettes. */
/* ⚠️ **Mesuré, pas estimé.** À 390 px logiques, la première photo commençait à
   227 px — l'image livrée était à 230, et la revue demande de compacter cette
   tête. Chaque pixel repris ici est un pixel de recette gagné, et c'est le seul
   endroit où en reprendre sans toucher à un texte ni à une cible tactile. */
.accueil-tete { margin-bottom: 16px; }
/* ⚠️ **Et c'est le bloc suivant qui décide, pas cette marge-là.** Mesuré : la
   réduire de 24 à 16 n'a pas déplacé la photo d'un pixel — `.block` porte
   `margin-top: 30px`, et deux marges verticales adjacentes **fusionnent** à la
   plus grande. On reprend donc la place là où elle se prend vraiment, sur le
   premier bloc de l'écran, et sans toucher au rythme des autres. */
#resume-block { margin-top: 18px; }

/* ⚠️ **La cloche de l'accueil est nue** — c'est la maquette P01, et c'est aussi
   ce qui la distingue de celles des deux autres écrans, qui vivent dans un
   en-tête à trois parts où le cercle donne l'aplomb. Ici elle est seule en face
   de la marque : le cercle en faisait une commande de plus au moment où l'on
   veut ne voir que le nom.

   ⚠️ **Et elle garde ses 40 px de cible.** Ce qui disparaît est le fond et le
   filet, jamais la surface qu'on touche — le § 1 passe avant le dessin. */
.accueil-bonjour { margin: 0 0 2px; color: var(--muted); font-size: 22.5px; }
.accueil-titre {
  margin: 0; font-family: var(--ecriture); font-size: 32.5px; line-height: 1.15;
  letter-spacing: -.01em; color: var(--espresso);
}
/* ⚠️ **Sous 360 px, le titre cède avant les commandes.** « Qu'est-ce qu'on
   cuisine ? » demande deux lignes à 30 px sur un iPhone SE, et la troisième
   arrivait à 28 px de rembourrage près. C'est le § 1 : ce qui plie est le
   décor, jamais un champ ni une cible tactile. */
@media (max-width: 359px) { .accueil-titre { font-size: 30px; } }

/* ── Le bandeau d'accueil — 12/09 ────────────────────────────────────────────
   **L'image porte le logo, la vague et le cœur ; son pied est un aplat
   `--accueil-fond`, et c'est l'écran entier qui le prolonge.** Bord à bord,
   donc l'écran rend sa gouttière et ses enfants la reprennent — le geste de
   `#screen-collection`, pour la même raison.

   ⚠️ **Tout se cale sur la largeur, jamais sur une hauteur.** L'image est en
   `100% auto` : ses repères (le logo, le début de l'aplat) sont des fractions de
   sa largeur, et un `padding` en pourcentage se calcule justement sur la largeur
   du conteneur. Relevé sur le fichier (1320 × 528, 110 px de blanc retirés en
   haut le 12/09) : haut du logo à 3,6 %, cloche remontée vers 7 %,
   la vague et les feuilles finissent vers 45 % sur la moitié droite — le bonjour
   se pose à 42 % à gauche, et le titre, plus large, tombe dessous. */
#screen-home {
  --gouttiere: 20px;
  padding-top: 0; padding-inline: 0;
  /* ⚠️ **Le rebond découvre le fond de la *boîte*, pas celui du contenu** — vu
     sur iPhone le 12/09. En tirant l'écran, iOS translate le contenu et laisse
     voir ce que l'écran lui-même peint derrière : un aplat crème donnait une
     bande crème au-dessus du blanc du bandeau. Le fond est donc un dégradé à
     deux moitiés, peint sur la boîte de l'écran et qui ne défile pas : blanc
     en haut, où le rebond du haut le découvre, crème en bas, où c'est celui du
     bas. ⛔ **Et surtout pas `background-attachment: local`** : le fond part
     alors avec le contenu, la boîte ne peint plus rien, et le rebond montre le
     **noir** de la vue native — mesuré sur l'appareil, en haut comme en bas. */
  background: linear-gradient(#FFFFFF 50%, var(--accueil-fond) 50%);
}
/* ⚠️ **Le corps porte le crème, et une marge ne peut pas s'en échapper.**
   `flow-root` empêche la marge haute du premier bloc de traverser le corps :
   sinon elle s'ouvrirait *entre* le bandeau et le corps, sur le blanc de la
   boîte. Sa hauteur minimale garantit qu'il descend toujours sous la moitié
   blanche, même sur un compte vide et un grand écran. */
.accueil-corps {
  display: flow-root; margin-top: 0; padding-inline: var(--gouttiere);
  background: var(--accueil-fond); min-height: 50vh;
}
.accueil-corps > * + * { margin-top: 24px; }
.accueil-corps > .block + * { margin-bottom: 0; }
.accueil-bandeau {
  position: relative;
  padding-top: env(safe-area-inset-top);
  background:
    url("img/header-accueil-2.webp") 0 env(safe-area-inset-top) / 100% auto no-repeat,
    linear-gradient(#FFFFFF, #FFFFFF) 0 0 / 100% env(safe-area-inset-top) no-repeat,
    #F7ECDF;
}
/* ⚠️ **La cloche flotte sur le blanc du haut, à la hauteur du logo** : dans le
   flux, l'en-tête pousserait le bonjour d'une quarantaine de pixels qui ne
   tomberaient plus au même endroit de l'image selon la largeur. */
.accueil-bandeau > .pane-head.tete-marque {
  position: absolute; left: 0; right: 0; top: env(safe-area-inset-top);
  margin: 0; justify-content: flex-end;
  padding: max(4px, calc(7% - 20px)) var(--gouttiere) 0;
}
.accueil-bandeau > .accueil-tete {
  /* ⚠️ **Le bonjour est parti le 12/09** — la question se suffit. Le titre
     remonte donc à la place du bonjour, juste sous le cœur et ses feuilles
     (≈ 45 % de la largeur sur la moitié droite de l'image). */
  margin: 0; padding: 37.7% var(--gouttiere) 4px;
}
/* ⛔ **Le bandeau reste clair en thème sombre** — c'est une image — donc ses
   textes gardent des couleurs fixes, sinon le crème du thème s'écrirait sur du
   crème. */
.accueil-bandeau .accueil-titre { color: #2E241D; }
/* ── La fresque des quatre menus — 15/09 ────────────────────────────────────
   L’accueil garde son fichier et sa mise en page. Les trois autres découpes
   font également 1320 × 528 : même largeur de peinture, même origine et même
   échelle, y compris quand le profil borne sa colonne de lecture sur bureau.
   Le fond de la boîte et celui du corps suivent le montage de l’accueil pour
   que le rebond découvre du blanc en haut et le fond de page en bas. */
#screen-library, #screen-communaute, #screen-profile {
  --gouttiere: 20px;
  padding-top: 0; padding-inline: 0;
  background: linear-gradient(#FFFFFF 50%, var(--accueil-fond) 50%);
}
.menu-continu > .accueil-bandeau {
  --gouttiere: 20px;
  background:
    var(--menu-image) 0 env(safe-area-inset-top) / 100% auto no-repeat,
    linear-gradient(#FFFFFF, #FFFFFF) 0 0 / 100% env(safe-area-inset-top) no-repeat,
    #F7ECDF;
}
.bibliotheque-bandeau { --menu-image: url("img/header-mes-recettes.webp"); }
.communaute-bandeau { --menu-image: url("img/header-communaute.webp"); }
.profil-bandeau { --menu-image: url("img/header-profil.webp"); }
.menu-continu > .menu-corps {
  display: flow-root; margin-top: 0; padding-inline: var(--gouttiere);
  min-height: 50vh;
  /* En clair, le beige se prolonge exactement. En sombre, la zone d’espacement
     fait progressivement la jonction avec le fond sombre, sous le titre. */
  background:
    linear-gradient(#F7ECDF, var(--accueil-fond)) 0 0 / 100% 24px no-repeat,
    var(--accueil-fond);
}
.menu-corps > * { margin-top: 24px; }
.menu-corps .grille-tete { flex-wrap: wrap; }
.accueil-bandeau > .pane-head.profil-commandes { justify-content: space-between; }
.profil-commandes .icon-btn { color: #2E241D; background: transparent; }
.bibliotheque-bandeau .accueil-bonjour { color: #6B5E52; }
.accueil-bandeau .cloche { color: #2E241D; }

.greeting, .page-title { font-size: 32.5px; margin: 0 0 4px; letter-spacing: -.01em; }

/* ── La marque en tête d'accueil — planche du 03/09 ───────────────────────────
   Le serif de la maison, dans la couleur de la marque : c'est le mot du logo,
   pas une police d'emprunt. ⛔ Et pas l'image du verrou complet — voir le
   commentaire de l'en-tête dans `index.html` : le mot ne s'en découpe pas. */
.wordmark {
  margin: 0; font-family: var(--ecriture); font-size: 30.5px; line-height: 1;
  color: var(--terra); letter-spacing: .005em;
  /* La poêle du logo penche ; le mot du verrou aussi. Sans cette inclinaison il
     se lit comme un titre de section, pas comme une marque. */
  font-style: italic;
}

/* ── Le bonjour et la porte des calories ──────────────────────────────────────
   ⛔ **Empilés sur un téléphone, côte à côte dès qu'il y a la place.** Voir le
   § 10 : la planche est à 834 pt, et « Bonjour Victoria » plus « Ajouter des
   calories » demandent 420 px pour les 350 d'un iPhone 14. */
.bonjour { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.bonjour-texte { min-width: 0; }

/* La pastille reprend la forme des puces de filtre — bordure fine, plein
   arrondi — parce que c'est la même nature : un geste secondaire posé à côté
   d'un titre. ⚠️ **44 px de haut**, comme tout ce qui se touche (§ 1). */
.pastille-kcal {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 9px 8px 9px 16px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--terra);
  font-size: 21px; font-weight: 600; cursor: pointer;
  box-shadow: var(--shadow);
}
.pastille-kcal .icon { width: 17px; height: 17px; }
/* Le « + » est un rond plein : c'est lui qui dit que la pastille **ajoute**,
   là où la flamme ne dit que de quoi il s'agit. */
.pastille-plus {
  display: grid; place-items: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  background: color-mix(in srgb, var(--terra) 12%, var(--card));
  font-size: 24.5px; line-height: 1;
}

.greeting-sun { width: 26px; height: 26px; color: var(--amber); vertical-align: -4px; }
.subtitle { margin: 0; color: var(--muted); }

.block { margin-top: 30px; }
.block-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.block-title { font-size: 26.5px; margin: 0 0 14px; }

.btn-round {
  width: 44px; height: 44px; flex: none;
  border: none; border-radius: 50%;
  background: var(--bouton-fond); color: var(--bouton-texte);
  font-size: 29px; line-height: 1; cursor: pointer;
  box-shadow: 0 4px 12px rgba(48, 41, 35, .16);
  transition: transform .15s ease;
}
.btn-round:hover { transform: scale(1.06); }
.btn-round:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  cursor: progress; transform: none;
}

/* Bandeau discret d'imports en cours, sur l'accueil */
.running-imports { display: grid; gap: 10px; }
.running-card {
  background: var(--card); border-radius: 16px; padding: 13px 16px;
  box-shadow: var(--shadow); cursor: pointer; border: none;
  text-align: left; color: inherit; width: 100%;
}
.running-card b { font-size: 22px; }
.running-card small { color: var(--muted); font-size: 19px; display: block; }

/* ── Rails horizontaux ───────────────────────────────────────────────────── */
/* Le mockup fait défiler ces rangées latéralement plutôt que de les empiler :
   sur mobile ça garde plusieurs sections visibles d'un seul écran. */

.rail {
  display: flex; gap: 14px; overflow-x: auto; align-items: stretch;
  /* Arriver au bout d'une rangée ne doit pas faire tourner la page. */
  overscroll-behavior-x: contain;
  padding: 2px 2px 8px; margin: 0 -2px;
  scrollbar-width: none; scroll-snap-type: x proximity;
}
.rail::-webkit-scrollbar { height: 0; }
.rail > * { flex: 0 0 auto; scroll-snap-align: start; }
/* La largeur se pose sur **l'enveloppe**, pas sur la carte. Depuis que le cœur
   des favoris a imposé un conteneur, `.recipe-card` reçoit `width: 100%` d'une
   règle écrite plus bas dans ce fichier : à spécificité égale, c'est la
   dernière qui gagne, et la carte reprenait sa largeur intrinsèque — 207 px au
   lieu de 168, images comprises. Même piège que l'anneau du journal : deux
   règles qui nomment le même composant se départagent par l'ordre du fichier,
   pas par l'intention. */
.rail .recipe-card-wrap { width: 168px; }

/* ── L'invitation à découvrir — P01, refonte du 10/09 ────────────────────────
   ⚠️ **Moins haute que les cartes de recettes**, et c'est ce qui la range à sa
   place : elle annonce un ailleurs, elle n'est pas le sujet de l'écran. Le
   texte foncé et le lien suffisent — un grand bouton terre cuite ici
   concurrencerait le ➕, qui est le seul accent plein de la page.

   ⛔ **Fond sable, jamais une photo.** Un décor photographique derrière du
   texte est le mode d'échec du § 6 : ce qui se pose sur une image redevient
   opaque, et on aurait à repeindre la bande à chaque image de défilement. */
.card.invite-communaute {
  position: relative; overflow: hidden;
  display: flex; align-items: center;
  margin-top: 24px; padding: 18px 20px;
  background: var(--idee-fond);
  border: 1px solid color-mix(in srgb, var(--idee-fond) 80%, var(--espresso));
  box-shadow: none; color: var(--espresso);
}
.invite-texte { min-width: 0; }
.invite-titre { margin: 0 0 4px; font-family: var(--ecriture); font-size: 27px; line-height: 1.2; }
.invite-sous { margin: 0 0 10px; color: var(--idee-texte); font-size: 21px; }
/* ⚠️ **44 px de haut**, comme tout ce qui se touche : `.link-btn` porte
   `min-height: 0` par construction, et un lien sous cette barre est sous la
   contrainte AA. On le lui rend ici plutôt que d'élargir `.link-btn`, dont
   quarante autres emplois sont bien des liens dans du texte. */
.invite-lien {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0; font-size: 22.5px; font-weight: 600; color: var(--idee-lien);
  text-decoration: underline; text-underline-offset: 3px;
}
/* Le carnet est un décor : il ne prend pas de place dans le flux et il ne se
   lit pas. ⚠️ **Et il se retire sous 340 px**, où les 150 px qu'il occupe
   mordraient sur la phrase. */
.invite-dessin {
  position: absolute; right: 4px; bottom: 6px;
  width: 196px; height: auto; pointer-events: none;
}
/* Le texte lui laisse sa place : sans cette réserve, le lien passe sous le carnet.
   ⚠️ **Le carnet a grandi et quitté le bord (13/09, à la demande de Luca)** :
   rogné à droite, il se lisait comme coupé. Le lien peut donc se replier — et
   il se replie **à gauche** : centré, il se lisait comme deux boutons. */
/* ⚠️ **Le carnet a encore grandi (196 px, 13/09) et s'ancre en bas à droite.**
   Centré à cette taille, il demandait 200 px de réserve et le titre passait
   sur trois lignes. Posé dans le coin, il ne dispute la place qu'aux deux
   lignes du bas : le titre garde toute la largeur, et ce sont la phrase et le
   lien qui s'écartent de lui. */
.card.invite-communaute { padding-right: 20px; }
.invite-texte { flex: 1; }
.invite-sous, .invite-lien { max-width: calc(100% - 160px); }
.invite-lien { text-align: left; }
@media (max-width: 339px) {
  .invite-dessin { display: none; }
  .invite-sous, .invite-lien { max-width: none; }
}

/* ── Les deux lignes du bas — P01 § 5 ────────────────────────────────────────
   **Deux écrans entiers derrière deux lignes.** Le résumé du planning occupait
   sept colonnes et le rail des préparations une rangée de cartes ; ce que P01
   garde d'eux est leur porte. ⚠️ **Elles vivent sous le premier écran visible**,
   et c'est voulu : la page défile, et rien n'est réduit pour tenir en 844 px. */
.acces-lignes { display: flex; flex-direction: column; gap: 10px; }
.ligne-acces {
  display: flex; align-items: center; gap: 14px;
  width: 100%; min-height: 64px; padding: 12px 16px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: var(--espresso);
  font-size: 22.5px; text-align: left;
}
.ligne-acces:active { transform: scale(.99); }
@media (hover: hover) and (pointer: fine) {
  .ligne-acces:hover { border-color: color-mix(in srgb, var(--terra) 35%, var(--line)); }
}
.ligne-icone {
  display: grid; place-items: center; flex: none;
  width: 34px; height: 34px; border-radius: 11px; color: var(--cinnamon);
  background: color-mix(in srgb, currentColor 13%, var(--card));
}
.ligne-icone .icon { width: 19px; height: 19px; }
.ligne-texte { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.ligne-nom { font-weight: 600; }
.ligne-sous { color: var(--muted); font-size: 19.5px; }
/* Le chevron ferme la ligne : c'est lui qui dit qu'elle **ouvre** plutôt
   qu'elle ne bascule. `margin-left: auto` plutôt qu'un `justify-content` sur le
   parent, qui écarterait aussi l'icône de son texte. */
.ligne-chevron { margin-left: auto; flex: none; color: var(--muted); font-size: 27.5px; line-height: 1; }

/* ── La grille des derniers ajouts — P01 ─────────────────────────────────────
   ⛔ **Deux colonnes fixes, et non le `auto-fill` de `.recipe-grid`.** Celui-ci
   se remplit par largeur minimale : à 390 px il donne bien deux colonnes, mais
   à 360 il retombe à **une** — or l'accueil montre *deux* derniers ajouts, et
   une carte pleine largeur suivie d'une autre ne se lit plus comme une paire.
   ⚠️ La classe est posée à côté de `.recipe-grid` et non à sa place : la carte,
   ses écarts et ses replis restent le corps commun. */
/* ⚠️ **`.recipe-grid.grille-ajouts`, et c'est le § *une règle qui ne gagne que
   par sa place* pris une seconde fois dans la même journée.** Écrite
   `.grille-ajouts` seule, elle avait la **même** spécificité que `.recipe-grid`,
   déclarée sept cents lignes plus bas : à égalité, la dernière écrite gagne, et
   c'est l'`auto-fill` qui décidait. Mesuré au banc — 390 px donnait deux
   colonnes par accident, **360 px une seule** : une carte de 320 px suivie
   d'une autre, là où P01 demande une paire. Scopée sur les deux classes, elle
   gagne par spécificité, donc où qu'on la déplace. */
.recipe-grid.grille-ajouts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }

/* ── L'en-tête à marque — P01 et P02, refonte du 10/09 ───────────────────────
   ⛔ **Flex, et non la grille à trois colonnes de `.pane-head`.** Ces en-têtes
   n'ont que deux enfants — la marque et la cloche — et la grille les posait dans
   ses deux premières colonnes, donc **la cloche au milieu**.

   ⚠️ **Deux classes dans le sélecteur, et c'est une mesure qui l'a imposé.**
   Écrite `.home-head` seule elle avait la même spécificité que `.pane-head`, et
   surtout elle s'était glissée au milieu du sélecteur d'une règle qui court sur
   deux lignes : la déclaration devenait « un en-tête précédé d'un frère », ce
   qui n'existe pas. `getComputedStyle` rendait `grid` sur une feuille où `flex`
   était écrit, servi et lisible. */
.pane-head.tete-marque {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 6px;
}
/* ⚠️ **La cloche est nue sur ces deux écrans**, seule en face de la marque : le
   cercle en faisait une commande de plus au moment où l'on veut ne voir que le
   nom. Elle garde ses 40 px de cible — ce qui disparaît est le fond, jamais la
   surface qu'on touche (§ 1). */
.tete-marque .cloche { background: none; border-color: transparent; }

/* ── Le rail des collections — P02 ───────────────────────────────────────────
   ⚠️ **Des cartes larges et basses, pas les tuiles à photo du panneau.** Le
   panneau complet montre une couverture en 16/10 sur deux colonnes ; ici on
   longe une rangée, et ce qu'on lit est un **nom**. La vignette tient donc dans
   un carré de 44 px, et le titre a la place de vivre.

   ⛔ **Et la carte garde le corps commun de repli**, photo → dossier : 31 des 42
   couvertures du corpus répondaient 403 en août, et un carré cassé sur une
   rangée se voit plus qu'ailleurs. */
.rail-collections { gap: 10px; }
.collection-carte {
  display: flex; align-items: center; gap: 10px;
  /* ⚠️ **205 px, et c'est une mesure.** À 190, « Repas rapides » et « Batch
     cooking » demandaient 120 px pour les 110 qui restaient après la vignette et
     les rembourrages : deux noms sur quatre finissaient en « … ». Un nom de
     collection est ce qu'on vient lire sur cette carte — c'est la largeur qui
     cède, pas lui. */
  width: 205px; min-height: 68px; padding: 10px 12px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: var(--espresso);
  font-size: 22.5px; line-height: 1.3; text-align: left;
}
.collection-carte:active { transform: scale(.985); }
@media (hover: hover) and (pointer: fine) {
  .collection-carte:hover { border-color: color-mix(in srgb, var(--terra) 35%, var(--line)); }
}
.carte-vignette {
  display: grid; place-items: center; flex: none; overflow: hidden;
  width: 44px; height: 44px; border-radius: 12px;
  background: linear-gradient(135deg, #EFE3D8, #E3D3C4);
}
.carte-vignette img { width: 100%; height: 100%; object-fit: cover; display: block; }
.carte-texte { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.carte-nom {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠️ **La ligne du compte est réservée**, même sur la carte des favoris qui n'en
   a pas : sans elle, la première carte du rail serait plus basse que ses
   voisines, ce qui se lit comme un défaut d'affichage. */
.carte-compte { min-height: 1.25em; color: var(--muted); font-size: 19.5px; }

/* La carte des favoris porte le cœur plutôt qu'une photo — c'est un filtre, pas
   une collection, et rien ne l'illustre. */
.carte-coeur {
  background: color-mix(in srgb, var(--coeur) 12%, var(--card));
  color: var(--coeur);
}
.carte-coeur .icon { width: 22px; height: 22px; }
/* Allumée, elle dit qu'elle **filtre** — même marque que les puces, parce que
   c'en est une. */
.carte-favoris.on {
  border-color: var(--coeur);
  background: color-mix(in srgb, var(--coeur) 8%, var(--card));
}
.carte-favoris.on .carte-coeur { background: var(--coeur); color: #FFFFFF; }

/* ── Le titre de la grille et son tri — P02 ──────────────────────────────────
   ⚠️ **Sur la même ligne, parce qu'ils parlent de la même liste** : « toutes les
   recettes, dans cet ordre-ci ». Le tri vivait deux blocs plus haut, dans une
   rangée de puces sous la recherche. */
.grille-tete { align-items: center; margin-bottom: 14px; }
/* ⚠️ **Le titre ne se replie pas, le tri maigrit** — mesuré au banc. « Toutes
   les recettes » et « Trier par Derniers ajouts ⌄ » demandent 580 px pour les
   350 d'un iPhone 14 : le `space-between` compressait le titre, qui passait sur
   **deux lignes** au-dessus d'un bouton de 410 px. C'est l'ordre du § 1 —
   ce qui cède est le mot en trop, jamais le titre ni la cible. */
.grille-tete .block-title { margin: 0; white-space: nowrap; }
/* Le mot « Trier par » s'efface sur un téléphone : c'est la **valeur** qui
   informe, l'étiquette se déduit d'un bouton posé sous un titre de liste. Il
   revient dès qu'il y a la place, au premier palier du § 10. */
.tri-mot { color: var(--muted); font-weight: 400; }
@media (max-width: 819px) { .tri-mot { display: none; } }

/* Les deux commandes d'un en-tête de panneau, groupées à droite. */
.tete-actions { display: flex; align-items: center; gap: 10px; }

/* ── Une collection — P03, 10/09 ────────────────────────────────────────────
   **La couverture est `.hero`, celui de la fiche recette**, et il n'y avait rien
   à écrire : photo de fond, pastilles blanches translucides, voile pour que le
   blanc tienne sur n'importe quelle image. Ce qui change ici est qu'aucun texte
   ne se pose dessus — le titre vit dans la carte en dessous, qui remonte sur la
   photo. La hauteur est donc plus courte : une couverture ne doit pas repousser
   les recettes derrière un long en-tête. */
/* ⚠️ **`.hero.hero-collection`, et c'est une mesure qui l'a imposé** — la
   troisième fois de ce chantier. Écrite `.hero-collection` seule, elle avait la
   même spécificité que `.hero`, déclarée deux mille lignes **plus bas** : à
   égalité la dernière gagne, et la couverture gardait ses 250 px. Mesuré au
   banc : la première recette arrivait à 520 px, contre 371 sur la maquette.
   Deux classes, et elle gagne où qu'on la déplace. */
/* ── La couverture occupe la page — corrigé le 10/09 ─────────────────────────
   **Signalé le jour même : « le menu devrait prendre toute la place sur
   l'écran, la photo doit remonter tout en haut ».** Elle était encadrée — 28 px
   du haut, 20 px des bords, coins arrondis —, donc lue comme une illustration
   posée dans une carte plutôt que comme la page elle-même. C'est le § *une
   image qui porte une disposition doit être la page, pas une carte dedans*,
   écrit le 31/08 pour le frigo : « bord à bord, on voit un meuble ».

   ⛔ **Et la gouttière se rend aux enfants, elle ne s'annule pas.** L'écran
   perd son rembourrage, et ce qui n'est ni la photo ni le panneau le reprend —
   `--gouttiere` porte la **même** formule que `.screen`, y compris le `max()`
   du § 10. ⚠️ Elle est déclarée **sur l'écran** et jamais sur `:root` : le
   `100 %` d'un `max()` se résout chez celui qui l'emploie, et posé plus haut il
   se serait mesuré sur la fenêtre au lieu de la colonne. */
#screen-collection { --gouttiere: 20px; padding-top: 0; padding-inline: 0; }
#screen-collection > *:not(.hero):not(.collection-tete) {
  padding-inline: var(--gouttiere);
}
/* ⚠️ **Sous l'encoche, la photo ; devant elle, les boutons.** `viewport-fit=cover`
   est posé depuis toujours et rien ne réserve le haut — c'était sans
   conséquence tant qu'un rembourrage de 28 px poussait tout vers le bas. Une
   photo bord à bord passe **derrière** la barre d'état, ce qui est voulu ; le
   retour, le partage et le ⋮, non. Deuxième endroit de l'app à en avoir besoin,
   après l'écran d'attente. */
.hero.hero-collection {
  min-height: calc(190px + env(safe-area-inset-top));
  margin-bottom: 0; border-radius: 0;
}
.hero-collection .hero-back,
.hero-collection .hero-share,
.hero-collection .hero-menu { top: calc(14px + env(safe-area-inset-top)); }
/* ⚠️ **Le pas de 48 px, celui de la fiche recette**, et pas deux valeurs
   posées à l'œil : le ⋮ tient le bord, le partage se range à sa gauche. */
.hero-collection .hero-share { right: 62px; }
/* ⛔ **Le ⋮ revient au bord droit.** Sur une fiche recette il est le troisième
   d'une rangée — favori, collection, menu — et se range à 110 px du bord ; ici
   il est **seul**, et cette position le laissait au milieu de l'image. Mesuré au
   banc : c'est le même défaut que la cloche de l'accueil, une position héritée
   d'un contenu qui n'est plus là. */
.hero-collection .hero-menu { right: 14px; }
/* ⛔ **Et pas de voile.** `.hero-shade` existe pour qu'un texte blanc tienne sur
   n'importe quelle photo ; ici **rien ne se pose sur l'image** — le titre vit
   dans la carte en dessous, et les boutons portent leur propre pastille. Le
   voile n'assurait donc aucun contraste : il assombrissait le bas de la
   couverture pour rien, et sans photo il rendait un brun presque noir sous une
   carte ivoire. C'est le § *un voile n'est dense que s'il protège quelque
   chose*, écrit pour le frigo. */
.hero-collection .hero-shade { display: none; }

/* ⚠️ **La carte chevauche la photo**, comme le dessin : c'est ce qui les lie.
   ⛔ Et la marge négative se pose sur la carte, jamais sur la photo — un
   `margin-bottom` négatif sur `.hero` la ferait dépasser du conteneur qui la
   découpe, et le rayon du bas serait rogné. */
/* ⚠️ **Le panneau prend toute la largeur, comme la photo.** Il n'a donc plus
   que ses deux coins hauts — ce sont eux qui disent qu'il monte sur l'image —,
   ⛔ et plus d'ombre : une ombre n'a de sens que sur un bord libre, et il n'en
   reste aucun sur les côtés. Même geste que le frigo, où retirer bordure et
   coins arrondis est exactement ce qui fait passer d'une carte à une page. */
.card.collection-tete {
  position: relative; z-index: 1;
  margin-top: -28px; margin-bottom: 20px; padding: 18px 20px;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: none;
}
/* Le titre et le geste sur une ligne, le second aligné en haut : un nom long se
   replie sous lui-même plutôt que de pousser « Modifier » hors de la carte. */
.collection-tete-ligne {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
}
.collection-tete-ligne .accueil-titre { font-size: 30px; min-width: 0; }
/* ⚠️ **44 px de haut**, comme tout ce qui se touche : `.btn-mini` seul descend
   plus bas, et c'est le seul geste de propriétaire de cet écran. */
#collection-edit { flex: none; min-height: 44px; gap: 6px; }
#collection-edit .icon { width: 16px; height: 16px; }
.collection-tete .search-row { margin-top: 16px; margin-bottom: 0; }
/* Le compte de résultats prend la place du titre de grille : c'est lui qui dit
   ce que la liste montre, et il ne paraît que si un filtre restreint. */
/* ⚠️ **Le tri reste à droite même quand le compte se tait.** La ligne les pose
   aux deux bouts ; masqué, le compte laissait le bouton glisser à gauche —
   c'est-à-dire bouger d'un écran à l'autre selon qu'un filtre est posé. */
.collection-resultats { margin: 0; color: var(--muted); font-size: 21px; }
.grille-tete #collection-sort { margin-left: auto; }

/* ── L'identité d'une recette — P04, 10/09 ──────────────────────────────────
   **Le titre est descendu de la photo dans une carte qui la chevauche.** Il
   vivait dessus, en blanc sur un voile ; ici il se lit sur du papier, et le cœur
   est à côté de lui plutôt qu'en pastille dans un coin. Même geste que la
   couverture d'une collection, et la même marge négative. */
/* ── La couverture occupe la page — corrigé le 10/09, comme P03 ──────────────
   **Même correction, même jour, et c'est la preuve que le mécanisme était bon.**
   La photo d'une fiche était encadrée exactement comme celle d'une collection ;
   ce qui a servi là-bas se reprend ici sans une ligne de plus — l'écran perd son
   rembourrage, `--gouttiere` le rend à qui n'est ni la photo ni le panneau.

   ⚠️ **La liste des exceptions est plus longue que celle de P03, et c'est le
   § 10 qui l'impose** : au-delà de 1 180 px `#screen-recipe` devient une grille
   à deux colonnes, dont les enfants sont des `.recipe-col`. Ils reprennent donc
   la gouttière au même titre que les blocs du téléphone. */
#screen-recipe { --gouttiere: 20px; padding-top: 0; padding-inline: 0; }
#screen-recipe > *:not(.hero):not(.recipe-identite) {
  padding-inline: var(--gouttiere);
}
/* ⚠️ **Sous l'encoche, la photo ; devant elle, les boutons** — même arbitrage
   que P03, et les quatre pastilles du coin partagent la même descente. */
#hero {
  border-radius: 0;
  min-height: calc(250px + env(safe-area-inset-top));
}
/* ⛔ **Et pas de voile — même arbitrage que P03, trouvé à la capture.**
   `.hero-shade` existe pour qu'un texte blanc tienne sur n'importe quelle
   photo ; le titre est descendu dans la carte le 10/09, donc **rien ne se pose
   plus sur cette image** et les quatre pastilles portent la leur. Le voile
   n'assurait aucun contraste : il noircissait la moitié basse de la couverture
   pour rien, et sur une fiche sans photo il rendait un brun presque noir sous
   une carte ivoire. C'est le § *un voile n'est dense que s'il protège quelque
   chose*, écrit pour le frigo. */
#hero .hero-shade { display: none; }
#hero .hero-back, #hero .hero-fav,
#hero .hero-collect, #hero .hero-menu { top: calc(14px + env(safe-area-inset-top)); }
/* ⚠️ **Le panneau prend toute la largeur, comme la photo** : deux coins hauts,
   pas d'ombre — une ombre n'a de sens que sur un bord libre. */
.card.recipe-identite {
  position: relative; z-index: 1;
  margin-top: -28px; margin-bottom: 18px; padding: 18px 20px;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: none;
}
/* ⚠️ **Le cœur est resté dans le coin de la photo, demandé le 10/09.** Il avait
   suivi le titre dans cette carte ; c'est le coin supérieur droit qui a été
   retenu — c'est là qu'on le cherche. La carte ne porte donc que le titre.

   ⚠️ **Et le rangement est remonté avec lui, le soir même.** Les trois pastilles
   reprennent le pas de 48 px écrit plus bas — cœur, collection, ⋮ — et il n'y a
   aucun écart à réécrire ici : c'est tout l'intérêt de l'avoir posé une fois. */
.card.recipe-identite .accueil-titre { font-size: 30px; }

/* ── Les quatre temps, en colonnes ──────────────────────────────────────────
   ⚠️ **Ils tenaient sur une ligne à points médians.** En colonnes nommées, on
   lit « 5 min · Préparation » au lieu de deviner à quoi se rapporte un nombre.

   ── Et la rangée se déplie, elle ne défile plus — corrigé le 10/09 ──────────
   **Le rail partait vers la droite**, et la raison écrite alors — « à 320 px,
   quatre colonnes nommées ne tiennent pas » — décrivait le problème sans le
   résoudre : mesuré à 390 px, 458 px de contenu pour 310 visibles, donc
   « Portions » commençait hors du cadre et rien ne le disait. C'est le § 5 —
   *ce qui dépasse sans le dire n'existe pas* —, et un défilement horizontal
   dans une carte qui défile verticalement se dispute son geste avec elle.

   → **Ce qui tient reste en ligne, le reste se déplie dessous.** `overflow:
   hidden` et non `auto` : ce n'est pas une décoration, c'est ce qui garantit
   qu'aucun pixel ne parte hors du cadre pendant que le JavaScript mesure. */
/* ⚠️ **Centrée, et c'est le panneau qui le demande — 10/09.** Collée à gauche,
   la rangée laissait un vide à droite qui se lisait comme une quatrième colonne
   manquante ; le panneau étant bord à bord depuis le matin, ce vide occupait le
   quart de l'écran. ⛔ Le centrage ne peut se poser que **parce que rien ne
   déborde** : sur un contenu plus large que son cadre il rognerait des **deux**
   côtés, donc le début de la première colonne — c'est la mesure qui le rend
   sûr, pas la déclaration. */
.recipe-temps {
  display: flex; gap: 0; margin-top: 14px; overflow: hidden;
  justify-content: center;
}
/* ⛔ **Le chevron est une bande, et il est au milieu — demandé le 10/09.** Posé
   à droite de la rangée, il volait la place d'un bloc **et** se lisait comme la
   commande de la dernière colonne. Sur toute la largeur du panneau, il
   n'appartient plus à aucune : il ouvre le panneau entier, ce qu'il fait
   vraiment.

   ⚠️ **Et il fait 32 px de haut pour 44 de cible**, par le pseudo-élément
   commun : une bande pleine largeur de 44 px creuserait un pied plus épais que
   les lignes qu'il déplie. */
.recipe-temps-plus {
  position: relative; width: 100%;
  display: grid; place-items: center;
  height: 32px; min-height: 32px; margin-top: 6px; padding: 0;
  border: none; background: none; color: var(--muted); cursor: pointer;
}
.recipe-temps-plus .icon { width: 18px; height: 18px; transition: transform .18s ease; }
.recipe-temps-plus[aria-expanded="true"] .icon { transform: rotate(180deg); }
/* ⚠️ **La suite est une seconde rangée, pas une pile — 10/09.** Empilés, deux
   blocs de 90 px occupaient deux lignes pleines et se lisaient comme un
   formulaire ; côte à côte, ils continuent la rangée du dessus, ce qu'ils sont.
   ⛔ Et elle **se replie** (`wrap`) plutôt que de déborder : trois blocs
   dépliés sur un écran étroit passent à la ligne, ils ne partent pas hors du
   cadre. C'est le § 5, et c'est le défaut qu'on vient de retirer de la rangée
   du dessus. */
.recipe-temps-suite {
  display: flex; flex-wrap: wrap; justify-content: center; row-gap: 12px;
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
/* ⚠️ **Et les dessins cèdent des deux côtés à la fois.** La rangée du haut peut
   les avoir retirés faute de place ; les garder ici ferait lire deux styles de
   ligne dans le même panneau, à trois pixels l'un de l'autre. */
.recipe-temps-suite.sans-icone .recipe-temps-icone { display: none; }
.recipe-temps-suite.sans-icone .recipe-temps-bloc { gap: 0; }
/* ⚠️ **Serrés pour qu'il en tienne trois — demandé le 10/09.** À 14 px de
   rembourrage et 20 px d'icône, un bloc faisait 105 à 126 px : deux en ligne
   sur un téléphone de 390. Mesuré après resserrage — 85 à 105 px —, il en tient
   **trois** à 390 et à 360.

   ⛔ **Et le rembourrage est asymétrique**, ce qui n'est pas un détail : le
   filet vit à gauche du bloc, donc il lui faut de l'air à gauche, et seulement
   un peu à droite pour que le texte ne le touche pas. Un rembourrage égal payait
   deux fois le même écart. */
.recipe-temps-bloc {
  display: flex; align-items: center; gap: 6px; flex: none;
  padding: 0 6px 0 10px; border-left: 1px solid var(--line);
}
.recipe-temps-bloc:first-child { padding-left: 0; border-left: none; }
.recipe-temps-icone { color: var(--muted); display: grid; place-items: center; }
.recipe-temps-icone .icon { width: 18px; height: 18px; }
/* ⛔ **L'icône cède avant le troisième bloc, et jamais l'inverse.** À 320 px,
   trois blocs entiers font 294 pour 280 disponibles : il faut que quelque chose
   cède. Ce qui cède est le **dessin**, qui redit ce que le libellé nomme déjà —
   jamais le libellé, qui serait tronqué, ni le troisième bloc, qui est ce qu'on
   vient de demander. La classe est posée par la mesure, pas par un palier : un
   écran plus étroit ou un libellé plus long tomberont juste sans qu'on ait un
   nombre de plus à défendre. */
.recipe-temps.sans-icone .recipe-temps-icone { display: none; }
.recipe-temps.sans-icone .recipe-temps-bloc { gap: 0; }
.recipe-temps-texte { display: flex; flex-direction: column; line-height: 1.2; }
.recipe-temps-texte strong { font-size: 21px; white-space: nowrap; }
.recipe-temps-texte small { color: var(--muted); font-size: 16.5px; white-space: nowrap; }

/* ── Ce qu'on va faire du plat ──────────────────────────────────────────────
   **Un geste principal, trois secondaires.** Les quatre tuiles égales disaient
   « je le fais, je le prévois, je l'achète, je le note » ; on ouvre une fiche
   pour **cuisiner**, et les trois autres sont des rangements. */
.recipe-principal {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 54px; margin-bottom: 10px; font-size: 24px;
}
.recipe-principal .icon { width: 21px; height: 21px; }
/* ⚠️ **Trois colonnes, et une seule paraît en troisième place** : « Collection »
   sur ses propres fiches, « Enregistrer » sur une publication. Les deux
   s'excluent depuis toujours ; la grille en compte donc trois. */
/* ⚠️ **`auto-fit`, parce que la troisième place se masque.** « Collection »
   disparaît sur une fiche partagée — on ne range pas la fiche de quelqu'un
   d'autre —, et trois colonnes fixes auraient laissé un trou à sa place. */
.recipe-secondaires {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); gap: 8px;
}

/* Le rendement, à côté du titre des ingrédients : il dit ce que la recette
   **rend**, et il ne bouge pas quand on tourne le stepper de lecture. */
.ing-rendement { color: var(--muted); font-weight: 400; font-size: 21px; }

/* ── Accès rapides ───────────────────────────────────────────────────────── */

/* ⚠️ **`.shortcuts` seul depuis le 10/09, parce que la rangée n'est plus une
   carte.** Elle l'était — quatre cellules à filets dans un `.card` unique — et
   la règle s'écrivait `.card.shortcuts` pour battre le `padding: 20px` de
   `.card`, déclaré trois cents lignes plus bas. Les quatre tuiles portent
   maintenant chacune leur fond : la classe `card` a quitté le balisage, et une
   règle qui la nomme encore **ne s'applique plus du tout**.

   ⚠️ **Trouvé au banc, pas à la lecture** : `getComputedStyle` rendait
   `display: block` et `grid-template-columns: none` sur une feuille où la
   grille était pourtant écrite, servie et lisible. Les quatre tuiles
   s'empilaient en une colonne, chacune à sa largeur de contenu. C'est le
   § *une règle qui ne gagne que par sa place se perd*, dans sa version où ce
   n'est pas l'ordre mais le **sélecteur** qui ne désigne plus rien. */
/* ⛔ **Deux colonnes et deux rangées — refonte du 10/09, P01.** Les quatre
   cellules tenaient sur **une** ligne : à 390 px chacune faisait 87 px, et
   « Ma liste de courses » n'y tenait sur aucune des trois lignes qu'elle
   demandait. Le dessin les range deux par deux, avec l'icône à gauche et le
   texte à droite — ce que 168 px permettent et 87 non.

   ⛔ **Et quatre cartes distinctes, plutôt qu'une rangée à filets.** Une grille
   à deux rangées ne peut pas porter les filets d'une cellule à l'autre sans
   dessiner une croix au milieu, qui se lit comme un tableau. */
.shortcuts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px;
}
/* ⚠️ **Chaque tuile porte sa carte depuis le 10/09.** Elles partageaient une
   rangée unique séparée par des filets ; en deux rangées, les mêmes filets
   dessineraient une croix au milieu, qui se lit comme un tableau. */
.shortcut {
  display: flex; align-items: center; gap: 10px;
  /* ⚠️ **44 px de haut au minimum**, comme tout ce qui se touche (§ 1) — et la
     tuile en fait bien plus, ce qui est le but : c'est la cellule entière qui
     est la cible, pas son libellé. */
  min-height: 68px; padding: 12px 14px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: var(--espresso);
  font-size: 22.5px; line-height: 1.3; text-align: left;
}
.shortcut:active { transform: scale(.985); }
/* ⚠️ **Le survol ne se pose que là où il y en a un.** Un iPad en paysage et un
   portable font tous deux 1 194 px ; l'un se touche, l'autre se survole. */
@media (hover: hover) and (pointer: fine) {
  .shortcut:hover { border-color: color-mix(in srgb, var(--terra) 35%, var(--line)); }
}
/* Le texte prend ce qui reste, et se replie plutôt que de pousser l'icône. */
.sc-texte { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.sc-nom { font-weight: 600; }
/* ── La pastille d'une tuile — planche du 03/09 ───────────────────────────────
   Le dessin pose chaque pictogramme dans un carré arrondi teinté, et donne à
   chacun sa couleur : c'est ce qui rend les quatre reconnaissables avant même
   d'avoir lu leurs deux lignes de texte. ⚠️ **La teinte se dérive de la couleur
   du pictogramme** (`currentColor`), donc une cinquième tuile n'a qu'un ton à
   déclarer, pas deux valeurs à accorder. */
.sc-icon {
  display: grid; place-items: center;
  width: 34px; height: 34px; border-radius: 11px;
  color: var(--cinnamon);
  background: color-mix(in srgb, currentColor 13%, var(--card));
}
.sc-icon .icon { width: 19px; height: 19px; }
/* ── Les dessins des menus — 13/09 ────────────────────────────────────────────
   Un dessin en couleur porte déjà sa teinte : la pastille teintée derrière
   ferait un second fond pour le même objet. Elle s'efface donc, et le dessin
   prend la place — un peu plus grand qu'un pictogramme, qui se lit au trait. */
.sc-icon:has(> .dessin-menu), .ligne-icone:has(> .dessin-menu) {
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; flex: none; background: none; border-radius: 0;
}
/* ⚠️ **Mesuré en service à 390 px** : à 42 px de dessin, « Mes courses » passait
   sur deux lignes dans une tuile de 172. Ce sont le dessin et les marges qui
   cèdent, jamais le libellé. */
.shortcut:has(.dessin-menu) { gap: 8px; padding-left: 10px; padding-right: 10px; }
.sc-icon:has(> .dessin-menu) { width: 38px; height: 38px; }
.dessin-menu { width: 100%; height: 100%; object-fit: contain; display: block; }
/* ⛔ **Signalé le 13/09 sur un iPhone : le frigo n'était pas centré.** La
   pastille est une grille à une rangée `auto` : le `height: 100%` d'une image
   ne s'y résout pas, la rangée prend la hauteur propre du dessin, et un dessin
   en hauteur (frigo, poubelle) débordait de 14 px sous sa boîte — mesuré,
   52 px pour 38. Un dessin plus large que haut (le sac) tenait, d'où l'écart
   entre deux tuiles voisines. En flex, la hauteur de la boîte est définie et
   le pourcentage s'y résout. */
.sc-vert .sc-icon { color: var(--sage); }
.sc-terre .sc-icon { color: var(--terra); }
.sc-ambre .sc-icon { color: var(--amber); }
.sc-sauge .sc-icon { color: var(--cinnamon); }

/* ── Le compte d'un raccourci — maquette du 03/09 ─────────────────────────────
   ⚠️ **La ligne est réservée, pas conditionnelle.** Le compte de « que puis-je
   cuisiner » arrive après les trois autres — il coûte 584 ms sur la
   bibliothèque du foyer, mesuré le 04/09, et il est donc demandé une fois
   l'écran peint. Sans hauteur réservée, la rangée entière sauterait à son
   arrivée : c'est le § *une hauteur qui apparaît est une hauteur réservée*, la
   même règle que la ligne de kcal de la feuille de quantité.

   `min-height` et non `height` : « 3 aliments » tient sur une ligne, et rien
   n'interdit qu'un libellé futur en demande deux. */
/* ⚠️ **Le compte remplace le sous-texte, il ne s'ajoute pas — refonte du
   10/09.** Il vivait en troisième ligne, ce qui obligeait à lui réserver une
   hauteur : le quatrième nombre arrive une demi-seconde après les autres, et
   sans réserve toute la rangée sautait. En prenant la place d'un texte déjà
   écrit, il n'y a plus rien à réserver — la ligne existe dès la peinture, et
   c'est son **contenu** qui change. */
/* ⛔ **Et il se replie plutôt que de se couper.** Première version en
   `text-overflow: ellipsis` : mesuré à 390 px, « Ce qu'il te manque » ressortait
   « Ce qu'il te m… » — une tuile fait 169 px, dont 74 pour l'icône et les
   rembourrages. Un libellé tronqué est pire qu'un libellé sur deux lignes : il
   ne se lit pas, et rien ne dit ce qui manque. La fiche P01 demande des
   « libellés lisibles » ; c'est la hauteur qui cède, pas le texte (§ 1). */
.sc-compte {
  min-height: 1.25em; color: var(--muted); font-size: 19.5px; line-height: 1.25;
}
/* Une fois le nombre connu, il se distingue du texte qu'il remplace : sans ça
   on ne voit pas qu'une donnée est arrivée. */
.sc-compte[data-compte="oui"] { color: var(--cinnamon); font-weight: 600; }

/* ── Décaler un plat au doigt — 04/09 ─────────────────────────────────────────
   ⚠️ **`touch-action: none` sur la carte seulement**, jamais sur le conteneur :
   c'est ce qui laisse le navigateur nous donner les `pointermove` d'un doigt
   posé sur un plat, **sans** retirer le défilement au reste de l'écran. Le poser
   plus haut ferait un planning qu'on ne peut plus parcourir — le § *rien ne se
   met sur le chemin d'un défilement*.

   ⛔ **Et le fantôme ne prend aucun événement** : `elementFromPoint` ne verrait
   que lui, donc on ne saurait jamais au-dessus de quel jour on est. */
/* ⚠️ **`touch-action: none` sur la prise seulement**, jamais sur le conteneur :
   c'est ce qui laisse le navigateur nous donner les `pointermove` d'un doigt
   posé dessus **sans** retirer le défilement au reste de l'écran. Le poser plus
   haut ferait un écran qu'on ne peut plus parcourir — le § *rien ne se met sur
   le chemin d'un défilement*.

   ⚠️ **Deux prises, deux natures** : la carte d'un plat *(pas de poignée, la
   colonne fait 148 px)* et la poignée d'une rangée *(la rangée porte des champs
   de saisie, la saisir volerait le geste de qui corrige un nombre)*. */
.plan-repas, .rgrip { touch-action: none; }
/* ⛔ **Et le texte ne se sélectionne pas sous le doigt — signalé le 11/09.**
   L'appui long qui saisit un plat est aussi le geste qui sélectionne du texte :
   passé 350 ms, le téléphone posait ses poignées bleues, reprenait le geste
   pour lui, et le glissement s'annulait (`pointercancel`) sans rien déplacer.
   `touch-action` règle le défilement, pas la sélection — il faut les deux, et
   le menu contextuel d'un appui long (`touch-callout`) avec. */
.plan-repas, .rgrip {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
/* Pendant un glissement engagé, le doigt passe sur tout l'écran : sans cette
   règle, il étendrait une sélection à chaque titre qu'il survole. */
.en-glissement, .en-glissement * { -webkit-user-select: none; user-select: none; }

/* ⛔ **Le fantôme ne prend aucun événement** : `elementFromPoint` ne verrait que
   lui, donc on ne saurait jamais au-dessus de quoi on est. */
.glisse-fantome {
  position: fixed; z-index: 90; pointer-events: none;
  transform: translate(-50%, -50%); opacity: .92;
  box-shadow: var(--shadow); border-color: var(--terra);
}
/* Ce qu'on a saisi s'efface sur place, plutôt que de disparaître : sans lui, la
   liste se referme sous le doigt et on ne sait plus d'où l'on part. */
.glisse-source { opacity: .35; }
/* La cible se marque — sans quoi on lâche à l'aveugle. Un contour, pas un
   déplacement : rien ne doit bouger sous le doigt. */
.glisse-cible {
  outline: 2px dashed var(--terra); outline-offset: 4px; border-radius: 12px;
}

/* ── La feuille « prévoir un plat » — § 13, 04/09 ─────────────────────────────
   ⛔ **Les puces s'enroulent, elles ne défilent pas.** Les quatre moments font
   380 px pour 350 disponibles ; la rangée glissait, et comme « Dîner » est
   choisi par défaut elle s'ouvrait **déjà déroulée** — « Petit déjeuner » y
   apparaissait en « etit déjeuner ». Un mot tronqué en tête de rangée se lit
   comme un défaut d'affichage, pas comme une liste qu'on peut pousser. Le rail
   horizontal est le bon geste pour parcourir *beaucoup* de choses ; ici il y en
   a quatre. */
.plan-choix { flex-wrap: wrap; overflow-x: visible; }
/* ⚠️ **Quatre questions, et elles se nomment.** Deux rangées de puces nues
   au-dessus d'un champ de recherche ne disent pas ce qu'elles règlent : on lit
   « Déjeuner · Collation · Moi · Victoria » sans savoir laquelle décide de quoi.
   Elles portent `.champ-libelle` depuis le 20/09 — le libellé de champ de
   l'application, celui des filtres — au lieu d'un second dessin à elles. */
/* ⚠️ **Le retrait est porté par la bande, pas par chaque ligne.** Le titre
   avait ses 14 px et tout le reste de la bande fixe restait sur les 8 px de la
   feuille : l'amorce, les quatre libellés, les puces et le champ démarraient
   14 px à gauche de leur propre titre. Le retrait passe donc sur `.sheet-fixed`
   — un padding, pas une marge, pour que le `width: 100%` du champ reste juste.
   ⚠️ **Au carnet (03d-2), la feuille porte elle-même ses 16 px** : les 14 de
   plus auraient fait 30 px de marge à 390 px, là où le cadre en demande 18–20.
   La bande reprend donc 2 px, juste de quoi ne pas couper l'anneau de focus
   d'une puce contre le bord. Le titre vit maintenant dans sa `.carnet-tete`,
   qui rend déjà son propre retrait. */
#plan-sheet .sheet-fixed { padding-inline: 2px; }
#plan-recherche {
  width: 100%; font-size: 24px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--espresso);
}
/* La liste des recettes reprend la hauteur que la feuille lui laisse — c'est
   `.sheet-scroll` qui borne, pas elle : deux plafonds se contrediraient. */
#plan-recettes { max-height: none; }

/* ── Le planning — § 13, 04/09 ────────────────────────────────────────────────

   **Trois vues du même objet, et une seule feuille de style.** La liste et la
   grille partagent la carte d'un repas (`.plan-repas`) : deux dessins du même
   plat auraient fini par ne plus se ressembler.

   ⚠️ **Aucune hauteur fixe nulle part.** Trois recettes le même jour est un cas
   normal ; une cellule bornée les couperait sans un mot — c'est le piège du
   frigo, *une carte ne peut pas dépasser de sa travée*, évité en n'en posant
   aucune. */
.plan-observation {
  background: var(--sand); color: var(--espresso);
  border-radius: 14px; padding: 10px 12px; margin: 0 0 14px;
  /* La planche pose un pictogramme devant la phrase : `flex` l'aligne sur la
     première ligne plutôt que sur le bloc, sinon il flotte au milieu d'une
     observation qui passe sur deux lignes — le défaut du « i » du 16/08. */
  display: flex; align-items: flex-start; gap: 8px;
}
.note-icone { display: flex; flex: none; color: var(--cinnamon); margin-top: 1px; }
.note-icone .icon { width: 16px; height: 16px; }

/* ── Mon planning — P06, refonte du 11/09 ─────────────────────────────────── */

/* ⚠️ **L'écran espace ses bandes lui-même.** `.screen > * + *` pose 24 px entre
   blocs, juste sur une page de lecture ; ici six bandes de commandes se
   suivent — navigation, vues, périmètre, résumé — et 24 px entre chacune
   poussaient le premier jour sous le pli. L'identifiant gagne par spécificité,
   donc où qu'on déplace la règle. */
#screen-planning > * + * { margin-top: 12px; }
.plan-titre .accueil-titre { margin: 0; }
.plan-titre .hint { margin: 4px 0 0; }

.plan-nav { display: flex; align-items: center; gap: 8px; }
.plan-periode {
  margin: 0; min-width: 0; flex: 1; text-align: center;
  font-weight: 600; font-size: 24px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.plan-aujourdhui { flex: none; padding-inline: 14px; font-size: 21px; color: var(--espresso); }

/* Trois vues en contrôle segmenté, comme sur la maquette. ⚠️ Ce sont toujours
   des `.chip` — le test des vues lit le défaut sur leur classe —, et la rangée
   ne défile pas : trois choix tiennent. */
.filters.plan-vues {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3px;
  padding: 3px; border-radius: 14px; background: var(--line);
  overflow-x: visible;
}
.plan-vues .chip {
  border: none; background: transparent; color: var(--bouton-second-texte);
  border-radius: 11px; min-height: 40px; font-size: 22.5px;
}
.plan-vues .chip.on { background: var(--bouton-fond); color: var(--bouton-texte); }

/* ⚠️ **`.card.plan-resume`, pas `.plan-resume` seul** : `.card` porte son
   rembourrage et sa marge **plus bas** dans la feuille, et à égalité c'est
   l'ordre du fichier qui gagne. */
.card.plan-resume {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  padding: 12px 14px; margin-bottom: 0;
}
.plan-resume > .icon { width: 22px; height: 22px; color: var(--terra); flex: none; }
.plan-compte { flex: 1 1 auto; min-width: 0; font-weight: 600; font-size: 22.5px; }
/* Un vrai geste de navigation : 44 px de cible, pas un lien de phrase. */
/* ⚠️ **Sans rembourrage latéral** : à 360 px, les 12 px de `.link-btn` faisaient
   passer « Voir les courses » sous le compte, pour dix pixels. La cible reste de
   44 px de haut. */
.plan-courses {
  min-height: 44px; flex: none; margin-left: auto; padding-inline: 0;
  /* `--terra` est un fond d'accent : posé en texte il rend 4,28:1 sur la
     carte. `--cinnamon` est l'encre terre cuite, et c'est déjà celle de
     `.link-btn` (R05). */
  color: var(--cinnamon); font-weight: 600; font-size: 21px;
}

/* ── Une journée ───────────────────────────────────────────────────────────
   La date à gauche, les créneaux à droite — la maquette, et c'est ce qui fait
   lire une semaine d'un coup d'œil : sept dates alignées sur un bord. */
.plan-jour {
  /* ⚠️ **50 px, et c'est mesuré** : à 58 la vignette, le libellé et le × ne
     laissaient qu'environ 110 px au titre d'un plat, et « Porridge aux fruits
     rouges » s'arrêtait à « Porridge aux fruits… » à 390 px. */
  display: grid; grid-template-columns: 50px minmax(0, 1fr);
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
  margin-bottom: 12px;
}
.plan-jour-tete {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: 12px 2px; border-right: 1px solid var(--line); text-align: center;
}
.plan-jour-nom { font-size: 21px; color: var(--muted); text-transform: capitalize; }
.plan-jour-date { font-family: var(--ecriture); font-size: 30px; line-height: 1; color: var(--espresso); }
.plan-jour.aujourdhui .plan-jour-nom,
.plan-jour.aujourdhui .plan-jour-date { color: var(--terra); }
/* ⚠️ **Un jour passé s'estompe, il ne disparaît pas — demandé le 11/09.** Fond
   en retrait et contenu atténué : on lit encore ce qu'on a mangé lundi, et on
   voit d'un coup d'œil où commence ce qui reste à faire. ⛔ `opacity` sur le
   contenu et non sur le bloc : la bordure et le fond resteraient sinon aussi
   pâles que le texte, et la journée se lirait comme absente. */
.plan-jour.passe { background: var(--cream); }
.plan-jour.passe .plan-jour-tete,
.plan-jour.passe .plan-creneaux { opacity: .55; }
/* Le total du jour, sous la date : il appartient à la journée, pas à un plat. */
.plan-jour-kcal {
  margin-top: 6px; font-size: 18px; font-weight: 700; color: var(--cinnamon);
  line-height: 1.2;
}
.plan-jour-kcal small { display: block; font-weight: 400; color: var(--muted); }

.plan-creneaux { padding: 4px 8px; min-width: 0; }
/* ⚠️ **Le libellé à gauche tant que la place le permet, au-dessus sinon — et
   c'est la mise en page qui le mesure.** Un plat demande 180 px pour garder son
   titre lisible ; sous cette largeur, la case passe à la ligne sous son
   libellé. Aucun palier à défendre : un titre plus long ou un écran plus étroit
   tombent juste d'eux-mêmes. */
.plan-creneau {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px;
  padding: 6px 0;
}
.plan-creneau + .plan-creneau { border-top: 1px solid var(--line); }
.plan-moment {
  /* 56 px : « déjeuner » en fait 53. Chaque pixel pris ici est rendu au titre
     d'un plat, qui en a perdu 16 quand la croix a reçu sa place — 11/09. */
  flex: 0 0 56px; margin: 0;
  color: var(--muted); font-size: 19px; line-height: 1.25;
}
/* ⚠️ **200 px, et c'est une mesure.** À 180, 360 px gardait le libellé à côté
   et ne laissait que 99 px au titre : les huit plats du jeu d'essai étaient
   coupés. À 200, 390 px garde le libellé à côté (212 disponibles) et 360 le
   passe au-dessus, ce qui rend toute la largeur au plat. */
.plan-creneau-plats { flex: 1 1 200px; min-width: 0; display: grid; gap: 6px; }

.plan-repas {
  display: flex; align-items: center; gap: 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 6px 38px 6px 6px; width: 100%; text-align: left;
  cursor: pointer;
  /* ⛔ **Un `<button>` ne prend pas la couleur du texte, il prend celle du
     navigateur.** Sans cette ligne, le titre d'un plat restait noir sur une carte
     sombre — mesuré au banc en thème sombre, presque invisible. Le défaut datait
     d'avant la refonte ; le titre en gras l'a seulement rendu visible. */
  color: var(--espresso);
}
.plan-repas-wrap { position: relative; display: flex; }
.plan-repas-wrap .plan-repas { flex: 1; min-width: 0; }
/* ── Retirer un plat — repris le 11/09 ─────────────────────────────────────
   *« La croix marche mais est moche et mal placée. »* C'était un « × » de
   texte collé dans le coin, contre la fin du titre : il se lisait comme une
   lettre de trop, et le doigt le cherchait.

   → **Un bouton rond, centré sur le bord droit** — la place d'une action en
   bout de ligne, où la maquette pose sa poignée. ⚠️ **Toujours par-dessus la
   carte, jamais à côté** : posé en frère dans la rangée, il sortirait du cadre
   de la carte et se lirait comme un objet de plus. La carte lui réserve donc
   ses 38 px de droite — et le titre les reprend ailleurs : mesuré, il était
   tombé de 126 à 110 px à 390, et trois plats sur quatre étaient coupés.

   ⛔ **`min-height`, sinon c'est un ovale** — `button { min-height: 44px }`
   bat `height: 28px`, le piège des pastilles d'étape de P05. Le dessin reste à
   28, la cible à 44, par la zone tactile commune. */
.plan-retirer {
  position: absolute; top: 50%; right: 6px; z-index: 2;
  transform: translateY(-50%);
  width: 28px; height: 28px; min-height: 28px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--cream); color: var(--muted); cursor: pointer;
}
.plan-retirer .icon { width: 13px; height: 13px; }
.plan-retirer:hover, .plan-retirer:active { color: var(--danger); border-color: var(--danger); }
.plan-repas .diary-thumb { flex: none; width: 40px; height: 40px; }
.plan-repas-texte { flex: 1; min-width: 0; }
.plan-repas-titre {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-weight: 600; font-size: 21px; line-height: 1.25;
}
.plan-repas-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 10px;
  margin-top: 3px; color: var(--muted); font-size: 19px;
}
.plan-repas-meta > span { display: inline-flex; align-items: center; gap: 3px; white-space: nowrap; }
.plan-repas-meta .icon { width: 13px; height: 13px; }
/* ⚠️ **À côté du plat, et dans la couleur des calories du dépôt** — la même
   que la ligne de kcal de la fiche : un chiffre de nutrition se reconnaît
   d'un écran à l'autre. */
.plan-repas-kcal { color: var(--cinnamon); font-weight: 600; }
.plan-repas-qui { display: block; color: var(--muted); font-size: 18px; }
/* Ce qui ne me concerne pas reste **lisible**, et se distingue — c'est une
   participation, pas une visibilité : le masquer ferait deux tableaux. */
.plan-repas.pas-moi { opacity: .55; }

.plan-ajouter {
  display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px;
  color: var(--muted); background: none; border: 1.5px dashed var(--line);
  border-radius: 14px; padding: 8px 12px; cursor: pointer; font-size: 21px;
}
.plan-ajouter .icon { width: 16px; height: 16px; color: var(--terra); }

.plan-astuce { text-align: center; margin-bottom: 0; }
.plan-ajouter-repas { width: 100%; min-height: 52px; gap: 10px; font-size: 24px; }
.plan-ajouter-repas .icon { width: 20px; height: 20px; }

/* ── Confirmer un déplacement — signalé le 11/09 ──────────────────────────────
   Le plat, puis le trajet — « vendredi 11 · Dîner → mardi 8 · Déjeuner » —, puis
   deux boutons de même rang. Rien à régler : c'est une confirmation.
   Le pied vient du socle `.sheet-actions` depuis le 20/09. */
.deplacer-quoi { margin: 2px 14px 4px; font-weight: 700; font-size: 24px; }
.deplacer-trajet { margin: 0 14px 16px; color: var(--muted); font-size: 22px; }

/* ── La grille — sept colonnes qui défilent ───────────────────────────────────
   ⛔ **Elle glisse plutôt que de se comprimer.** Sept colonnes dans 350 pt font
   50 pt par jour et un titre à 5 pt ; on garde une largeur lisible et on défile,
   comme tous les rails de l'application. ⚠️ **`align-items: start`** : une
   colonne qui porte trois plats grandit, les autres ne la suivent pas. */
.plan-grille {
  display: grid; grid-auto-flow: column; grid-auto-columns: 148px;
  gap: 10px; align-items: start;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  /* ⚠️ **Arriver au bout de la grille ne doit pas emporter ce qu'il y a
     derrière.** Sans cette ligne, le geste **enchaîne** sur l'ancêtre qui
     défile aussi — le carrousel des onglets sur l'accueil, où pousser le
     dimanche changeait d'onglet. `.rail` et `.filters` la portent depuis
     toujours ; celle-ci ne l'avait pas, et c'est ce qui la distinguait d'elles
     sans qu'aucune ligne ne le dise. */
  overscroll-behavior-x: contain;
  padding-bottom: 6px;
}
/* ⚠️ **En grille, un jour est une colonne**, pas une ligne à deux zones : la
   date au-dessus, les créneaux dessous, le libellé au-dessus de ses plats. */
.plan-grille .plan-jour { margin-bottom: 0; display: block; }
.plan-grille .plan-jour-tete { border-right: none; border-bottom: 1px solid var(--line); padding: 8px 4px; }
.plan-grille .plan-creneaux { padding: 4px 6px; }
.plan-grille .plan-moment { flex-basis: 100%; }
.plan-grille .plan-creneau-plats { flex-basis: 100%; }
.plan-grille .plan-repas { padding: 6px; }
/* ⚠️ **En grille, la carte est verticale** : centré à droite, le bouton
   tomberait sur le titre. Il reprend le coin de la vignette, sur un fond de
   carte pour se lire sur n'importe quelle photo. */
.plan-grille .plan-retirer { top: 10px; right: 10px; transform: none; background: var(--card); }
.plan-grille .plan-ajouter span { display: none; }
.plan-grille .plan-ajouter { justify-content: center; }
.plan-grille .plan-repas { flex-direction: column; align-items: stretch; gap: 6px; }
/* En grille, la vignette prend la largeur de la colonne — c'est la seule vue
   où la carte est verticale. ⚠️ `.diary-thumb` porte déjà `object-fit: cover`
   sur son image : on change sa boîte, pas la façon dont la photo la remplit. */
.plan-grille .plan-repas .diary-thumb { width: 100%; height: 76px; }
.plan-grille .plan-moment { margin-top: 6px; }

/* ── Le calendrier de l'accueil — planche du 03/09 ────────────────────────────

   **Un tableau, pas une rangée de cartes.** Le dessin enferme les sept jours
   dans un cadre unique : une bande de quantièmes en haut, des filets entre les
   colonnes, l'observation en pied. C'est ce qui en fait **une** chose — une
   semaine — là où sept cartes posées côte à côte se lisent comme sept objets.

   ⛔ **Le cadre remplace donc la carte de chaque plat.** `.plan-repas` porte un
   fond, une bordure et un rayon sur l'écran du planning, où chaque plat est un
   objet qu'on saisit et qu'on déplace ; ici la **cellule** est le plat, et
   laisser les deux aurait donné un cadre plein de petits cadres. Ce qui reste
   partagé est le dessin — vignette, titre — et c'est ce que le § *un objet a un
   éditeur* protège. */
/* ⚠️ **`.card.plan-cadre`, et pas `.plan-cadre` seul.** `.card` porte
   `padding: 20px` et est déclarée **800 lignes plus bas** : à une classe contre
   une, c'est l'ordre du fichier qui tranche, et le rembourrage gagnait. Mesuré
   au banc à 834 pt : cadre 794, grille 752 — les quarante pixels manquants
   étaient exactement ce rembourrage, et les bandes ne rejoignaient donc aucun
   bord. C'est le piège déjà écrit trois fois ici (`.pane-head.today-head`,
   l'anneau du journal, le « i » de la carte du jour) : deux règles qui nomment
   le même composant se départagent par l'ordre, jamais par l'intention. */
.card.plan-cadre {
  /* `.card` donne le fond, le rayon et l'ombre ; le filet et la découpe sont à
     ce cadre-ci, qui contient une rangée qui glisse — et le rembourrage s'en
     va, parce que ce sont les bandes qui touchent les bords. */
  padding: 0; overflow: hidden; border: 1px solid var(--line);
}
/* ⚠️ **128 px et non 148** : sept colonnes valent 1 036 px sur l'écran du
   planning, où c'est le sujet ; en résumé d'accueil on en montre quatre d'un
   coup plutôt que trois, et le titre garde ses deux lignes. */
.plan-semaine {
  grid-auto-columns: 128px;
  /* ⛔ **Aucun écart, et c'est le filet qui sépare.** Un `gap` laisserait les
     traits flotter à 10 px de ce qu'ils joignent. */
  gap: 0; padding: 0;
  /* ⚠️ **Les colonnes s'étirent, à l'inverse de l'écran du planning.** Là-bas
     une colonne qui porte trois plats grandit seule, ce qui est juste : on y
     travaille jour par jour. Ici les filets doivent descendre d'un bord à
     l'autre, donc les sept colonnes font la même hauteur — sinon le trait
     s'arrête au milieu et se lit comme un défaut d'affichage. */
  align-items: stretch;
}
.plan-semaine .plan-jour {
  display: flex; flex-direction: column;
  padding: 0 8px 12px; margin-bottom: 0;
  border-left: 1px solid var(--line);
}
.plan-semaine .plan-jour:first-child { border-left: none; }

/* La bande des quantièmes : un fond légèrement en retrait et un filet sous
   elle. ⚠️ **Les sept en-têtes ont la même hauteur** — deux lignes, toujours —,
   c'est ce qui fait de leurs sept filets une seule ligne continue. */
.plan-semaine .plan-jour-tete {
  display: block; text-align: center;
  margin: 0 -8px 10px; padding: 10px 8px;
  background: var(--cream); border-bottom: 1px solid var(--line);
}
/* Jour et quantième l'un sous l'autre, comme sur la planche : « LUN » au-dessus
   de « 25 ». Le nom en capitales est ce qui les distingue d'un titre. */
.plan-semaine .plan-jour-nom {
  display: block; text-transform: uppercase; letter-spacing: .04em;
  font-size: 16.5px; font-weight: 700; color: var(--muted);
}
.plan-semaine .plan-jour-date { display: block; font-size: 24.5px; color: var(--espresso); }
/* Aujourd'hui se marque **des deux côtés** — le nom et le quantième —, sinon la
   colonne du jour se lit comme une colonne de plus. La planche teinte en plus sa
   bande : c'est ce qui la retrouve d'un coup d'œil dans sept colonnes grises. */
.plan-semaine .plan-jour.aujourdhui .plan-jour-nom,
.plan-semaine .plan-jour.aujourdhui .plan-jour-date { color: var(--terra); }
.plan-semaine .plan-jour.aujourdhui .plan-jour-tete {
  background: color-mix(in srgb, var(--terra) 9%, var(--card));
}
.plan-semaine .plan-moment {
  margin: 0 0 5px; text-align: center; font-size: 16.5px;
  /* Réservée même vide — voir `colonneDuJour` : c'est ce qui aligne le trait
     d'un jour sans plat sur les cartes de ses voisins. */
  min-height: 1.25em;
}
/* La cellule **est** le plat : ni fond, ni bordure, ni rayon — voir en tête. */
.plan-semaine .plan-repas {
  border: none; background: none; padding: 0; margin: 0; cursor: pointer;
  /* ⛔ **Et le doigt lui rend le défilement.** `.plan-repas` porte
     `touch-action: none` pour que l'écran du planning reçoive les `pointermove`
     d'un plat qu'on déplace ; sur ce résumé rien ne se déplace, et l'attribut
     n'y faisait qu'une chose : **retirer au navigateur le droit de faire glisser
     la semaine** dès que le doigt se posait sur une vignette, c'est-à-dire sur
     presque toute la surface. On ne pouvait pousser le calendrier qu'en visant
     les 8 px entre deux colonnes. */
  touch-action: auto;
}
.plan-semaine .plan-repas-titre {
  /* Deux lignes, toujours — c'est ce qui aligne les sept colonnes entre elles,
     et la même règle que le titre d'une carte de recette. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 19px; line-height: 1.3; text-align: center;
}
/* ⚠️ **Un jour vide reste une colonne.** La semaine se lit comme une semaine :
   sauter les jours sans plat ferait glisser « DIM » à la troisième place, et
   personne ne compterait. Le trait est muet à dessein — il n'y a rien à faire
   ici, la porte du planning est dans le titre du bloc. */
.plan-jour-rien {
  display: grid; place-items: center;
  border: 1px dashed var(--line); border-radius: 14px;
  min-height: 74px; color: var(--sand);
}

/* Le pied du cadre — l'observation, et la phrase du calendrier vide.
   ⛔ **Il rejoint le bord**, sans rayon ni marge : à l'intérieur d'un cadre, une
   pastille arrondie posée sur un fond se lit comme une carte de plus. */
.plan-cadre .plan-observation,
.plan-cadre-vide {
  margin: 0; padding: 12px 14px;
  border-top: 1px solid var(--line); border-radius: 0;
  background: var(--cream); color: var(--espresso);
}
.plan-cadre-vide { color: var(--muted); }

/* ── Les deux portes du pied — planche du 03/09 ───────────────────────────────
   Deux tuiles de même rang, empilées sur un téléphone et côte à côte dès qu'il
   y a la place. ⛔ **Pas de `grid-template-columns: 1fr 1fr` sur téléphone** :
   « Colle un lien vidéo TikTok, Instagram ou YouTube » sur 165 px fait cinq
   lignes, et le champ d'URL en fait une sixième. */
/* ── Les deux portes du pied — et la seule que la planche ne regroupe pas ─────
   **Les trois compteurs et les quatre accès rapides se sont groupés le 04/09 ;
   ces deux-là restent empilées sous 820 px, et c'est mesuré.** La tuile
   d'import ne porte pas qu'une copie : elle porte le **champ où l'on colle un
   lien**, et c'est aussi lui qui reçoit le partage du système (`traiterPartage`
   écrit dans `#import-url` et `#import-error`, sur l'accueil, après un correctif
   trouvé au banc — le message posé dans un écran masqué y faisait une boîte de
   0 × 0).

   Mesuré à 390 pt en demi-largeur : la tuile offre 122 px utiles, la pastille en
   prend 34, et il reste **76 px au champ** une fois le bouton rond posé. La
   planche s'en sort en n'ayant pas de champ du tout — elle dessine une porte à
   chevron vers l'écran « Importer ».

   ⛔ **Suivre le dessin coûterait donc trois choses**, pas une : le geste le plus
   court de l'application *(coller un lien sans changer d'écran)*, la
   réécriture de la cible de partage, et le test qui garde que les deux
   formulaires partagent un corps. C'est un arbitrage, pas une contrainte : le
   jour où l'on préfère le dessin, ce sont ces trois lignes qu'il faut reprendre
   ensemble. */

/* ⚠️ **L'import a perdu son aplat terre pleine largeur — planche du 03/09.**
   Il était l'objet le plus voyant de l'accueil, devant le total du jour et le ➕
   qui sont ce qu'on vient y faire. Deux tuiles neutres de même rang le remettent
   à sa place, et l'accent redevient une information plutôt qu'une décoration —
   le § *un signal qui ne distingue rien ne compte pas*, pris par l'autre bout :
   trois choses voyantes n'en distinguent aucune.

   ⚠️ **Cette note vivait sous une section « Import » dont plus aucune règle
   n'était atteinte — 07/09.** `.import-copy`, `.porte-import`, `.porte-icone`,
   `.porte-terre`, `.porte-sauge` et `.porte-chevron` dessinaient la tuile
   d'avant : six sélecteurs que ni `app.js` ni `index.html` ne nomment. La note,
   elle, décrit bien ce qu'on voit — mais ce qui le produit est `.portes`, ici.
   Un commentaire posé sur du code mort explique un écran que personne ne voit,
   et c'est le pire endroit où le chercher le jour où on veut le comprendre. */
.portes { display: grid; gap: 14px; }
.porte {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 14px; text-align: left; color: inherit;
  border: none; cursor: pointer; width: 100%; margin-bottom: 0;
  /* Les deux tuiles font la même hauteur — c'est la rangée qui l'impose —, et
     leur contenu se pose en haut : centré, la copie de la plus courte flottait
     au milieu d'un vide que l'autre avait rempli. */
  align-content: flex-start;
}
@media (min-width: 820px) {
  .portes { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
}

@media (min-width: 820px) {
  /* Les sept tiennent d'un coup : plus rien à faire glisser. C'est le § 10 —
     le grand écran élargit ce que le téléphone sait déjà montrer. */
  .plan-grille {
    grid-auto-flow: unset; grid-template-columns: repeat(7, minmax(0, 1fr));
    overflow-x: visible;
  }
}

/* ── Ce que j'ai cuisiné — P08, 11/09 ──────────────────────────────────────── */
.hc-tete { margin: 2px 0 14px; }
.hc-tete .hint { margin: 4px 0 0; }
/* ⚠️ **Une ligne, et c'est une mesure — signalé le 11/09 sur iPhone.** À
   13,5 px les quatre libellés demandent 284 px de texte : « Cette année »
   passait seule sur une seconde rangée. « Mois dernier » et 13 px font 254 px,
   et les puces se partagent la largeur : une ligne dès 375 px. ⛔ **En dessous,
   elles passent à la ligne, jamais hors du cadre** — un choix exclusif ne
   défile pas (voir `.meal-groups`) : une valeur retenue invisible est pire
   qu'un second rang. */
.hc-periodes {
  margin-bottom: 12px; gap: 6px;
  flex-wrap: wrap; overflow-x: visible;
}
.hc-periodes .chip { flex: 1 1 auto; padding-inline: 6px; font-size: 19.5px; text-align: center; }
.hc-filtres-ouvrir { position: relative; flex: none; }
/* Le compte des critères posés, sur le bouton : un filtre qu'on ne voit plus
   ne doit pas se lire comme un historique incomplet. */
.hc-filtres-ouvrir:not([data-actifs="0"])::after {
  content: attr(data-actifs);
  position: absolute; top: -4px; right: -4px;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--terra); color: #FFFFFF;
  font-size: 16.5px; font-weight: 600; line-height: 18px; text-align: center;
}
/* Dans la feuille, un choix exclusif enroule lui aussi. */
.hcf-choix { flex-wrap: wrap; overflow-x: visible; }
.hcf-veg { margin: 6px 0 14px; }
.hc-compte { margin: 0 0 4px; }
.hc-liste { display: flex; flex-direction: column; gap: 12px; transition: opacity .15s ease; }
/* Pendant qu'un filtre charge, la liste d'avant s'estompe : elle ne se lit plus
   comme le résultat du nouveau. `opacity` ne relayoute pas (§ 6). */
.hc-liste.en-chargement { opacity: .45; }
.hc-mois {
  margin: 14px 0 0; font-family: var(--ecriture); font-size: 26.5px; font-weight: 600;
  color: var(--espresso);
}
#hc-plus { width: 100%; min-height: 48px; margin-top: 16px; }
.hc-aucun p { margin: 0 0 12px; }
.hc-aucun .btn-ghost { width: 100%; min-height: 44px; }

/* ── La carte horizontale — `recipeCard(…, { horizontale })`, P08 ────────────
   ⚠️ **La vignette à gauche, le texte en colonne, et c'est tout ce qui change.**
   Le cœur passe au coin de la vignette, le ⋮ au coin du texte — la maquette
   les pose là, et au bas d'une carte horizontale le ⋮ recouvrirait la note. */
.recipe-card-wrap.horizontale .recipe-card { flex-direction: row; align-items: stretch; min-height: 124px; }
/* ⚠️ **`contain: size`, sinon c'est l'image qui fait la hauteur** — mesuré à
   834 px : un dessin carré de 256 px imposait 182 px à la carte, le texte en
   occupait 80. Sans taille propre, la vignette s'étire à la hauteur du texte. */
.recipe-card-wrap.horizontale .recipe-card > img,
.recipe-card-wrap.horizontale .recipe-card > .thumb-fallback {
  width: 34%; max-width: 160px; flex: none; aspect-ratio: auto; height: auto; min-height: 124px;
  contain: size;
}
/* ⛔ **Un rembourrage fixe, jamais en pourcentage** : `.recipe-card .thumb-illus`
   pose 13 %, qui se calcule sur la largeur de la **carte**. À 834 px, 91 px de
   chaque côté d'une vignette de 182 : le dessin n'avait plus aucune place, et
   la vignette était vide. */
.recipe-card-wrap.horizontale .recipe-card > .thumb-illus { padding: 16px; }
.recipe-card-wrap.horizontale .carte-texte {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px;
  padding: 11px 42px 12px 14px;
}
.recipe-card-wrap.horizontale .carte-texte b {
  padding: 0; height: auto; font-family: var(--ecriture); font-size: 24px; line-height: 1.25;
}
.recipe-card-wrap.horizontale .carte-texte small { padding: 0; margin-top: 0; }
/* ⚠️ **Sur `.carte-texte`, jamais `.recipe-card .card-source`** : un garde-fou
   cherche cette chaîne pour lire la règle commune, et en trouvait une qui la
   contenait plus haut — trouvé par la suite le jour même. */
.recipe-card-wrap.horizontale .carte-texte .card-source { padding: 0; }
.recipe-card-wrap.horizontale .card-menu { top: 6px; bottom: auto; right: 4px; }
.recipe-card-wrap.horizontale .card-fav { top: 6px; left: 6px; right: auto; }
/* Le chevron dit que la carte ouvre la fiche ; il ne se touche pas à part. */
.recipe-card-wrap.horizontale::after {
  content: "›"; position: absolute; right: 16px; top: 50%; transform: translateY(-50%);
  color: var(--muted); font-size: 27.5px; line-height: 1; pointer-events: none;
}
.carte-date { color: var(--muted); font-size: 19.5px; }
/* La note privée, telle qu'on l'a écrite — deux lignes au plus. */
.carte-note {
  align-self: flex-start; display: inline-flex; align-items: flex-start; gap: 6px;
  max-width: 100%; margin-top: 4px; padding: 5px 10px; border-radius: 10px;
  background: color-mix(in srgb, var(--sage, #7C8B63) 14%, transparent);
  color: var(--espresso); font-size: 19px; line-height: 1.35;
}
.carte-note .icon { width: 14px; height: 14px; flex: none; margin-top: 1px; color: var(--sage, #7C8B63); }
.carte-note span {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* ── La carte d'hydratation — § 13, 04/09 ─────────────────────────────────────
   Elle reprend la mise en page de la carte des calories — grand nombre, cible,
   barre — parce que c'est **la même chose** : un total du jour, avec ou sans
   repère. Deux mises en page pour deux compteurs auraient fini par ne plus se
   ressembler, et la seconde aurait pris du retard.

   ⛔ **Une seule jauge.** La planche en dessine deux pour le même fait — un
   anneau et dix verres — et elles se contredisaient déjà sur le dessin. */
/* ── La gourde qui se remplit — 13/09 ─────────────────────────────────────────
   **Elle remplace la barre** : même fait, même règle (aucune gourde sans
   objectif), un autre dessin, sous les nombres qu'elle illustre.

   ⚠️ **Toute la géométrie se dérive de la largeur de l'image**, et les fractions
   de `.gourde-interieur` sont mesurées sur `img/menus/gourde.webp` : le verre va
   de 3,1 % à 84,2 % en largeur, de 23,3 % à 95,4 % en hauteur, avec des coins de
   22 px sur 196. `tests/test_gourde.py` les recalcule sur le fichier, parce
   qu'une gourde redessinée ferait monter l'eau à côté du verre sans un mot.

   ⛔ **L'eau monte par `transform`, jamais par `height`.** Une hauteur animée
   relayoute la carte à chaque image (§ 6) ; une translation dans un cadre qui
   découpe se compose. Le niveau est une variable, `--niveau`, de 0 à 1. */
.eau-card { position: relative; }
/* ⚠️ **À côté des nombres, et petite** — signalé le 13/09 : posée dessous à
   292 px avec des étiquettes de 27 px, elle mangeait l'écran. La carte est une
   grille : les nombres à gauche, la gourde à droite sur toute leur hauteur, les
   gestes en dessous, sur toute la largeur.

   ⛔ **Une hauteur fixe, jamais celle de la carte** : calée sur la carte, elle
   rétrécissait dès que « Retirer un verre » disparaissait.

   ⚠️ **La dernière rangée est `1fr`**, et elle n'est pas décorative : la gourde
   est plus haute que les quatre lignes qu'elle longe, et sans rangée flexible
   la grille répartirait l'excédent **entre** ces lignes — mesuré, « 0 % de ton
   objectif » descendait de 40 px. */
#accueil-eau {
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto auto 1fr;
  column-gap: 8px; align-content: start;
}
#accueil-eau > * { grid-column: 1; }
#accueil-eau > .eau-gestes, #accueil-eau > #eau-fixer { grid-column: 1 / -1; }
#accueil-eau > #eau-fixer { justify-self: start; margin-top: 8px; }
#accueil-eau > .gourde { grid-column: 2; }
.gourde {
  grid-row: 1 / 6;
  position: relative;
  /* La carte garde sa hauteur ; les inscriptions suivent la taille du verre. */
  height: 240px; aspect-ratio: 196 / 480;
  pointer-events: none;
  isolation: isolate;
}
.gourde-verre { display: block; width: 100%; height: 100%; }
.gourde-interieur {
  position: absolute;
  left: 3.1%; width: 81.1%; top: 23.3%; height: 72.1%;
  border-radius: 13.8% / 6.4%;
  overflow: hidden;
}
/* ⚠️ **`multiply`, pour que le verre reste du verre.** Un bleu posé à plat
   effacerait les reflets peints dans l'image ; multiplié, il les teinte et les
   garde. Et la surface est une ellipse plus claire, pas une arête droite — le
   § *la surface est une ellipse, pas une ligne* de la marmite. */
.eau-card .gourde-eau {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bleu) 22%, white) 0,
    color-mix(in srgb, var(--bleu) 32%, white) 4%,
    color-mix(in srgb, var(--bleu) 55%, white) 100%);
  mix-blend-mode: multiply; opacity: .72;
  transform: translateY(calc((1 - var(--niveau, 0)) * 100%));
  transition: transform .6s cubic-bezier(.3, .8, .4, 1);
}
.gourde-eau::before {
  content: ""; position: absolute; left: 0; right: 0; top: -3px; height: 7px;
  border-radius: 50%; background: color-mix(in srgb, var(--bleu) 18%, white);
}
@media (prefers-reduced-motion: reduce) { .eau-card .gourde-eau { transition: none; } }

/* Inscriptions sur le verre — référence du 15/09 : heures, traits fins et
   encouragements bruns, sans bulles. Les sept repères suivent toujours l'eau,
   jamais l'heure courante.

   ⚠️ **La déclaration `@font-face` n'est plus ici — R12, 21/09.** Elle vivait
   dans cette section parce que la gourde était le seul endroit manuscrit de
   l'application ; c'est maintenant l'écriture de toute l'interface, et sa face
   est déclarée en tête de feuille avec son repli à l'échelle. Les tailles
   ci-dessous sont, elles, restées telles quelles : elles étaient déjà dessinées
   pour Caveat, la remise à l'échelle générale ne les concerne pas. */
.gourde-paliers {
  position: absolute;
  left: 3.1%; width: 81.1%;
  top: calc(23.3% + 12px); height: calc(72.1% - 26px);
  container-type: inline-size;
  color: #57341F;
}
.palier {
  position: absolute; left: 5%; width: 90%; height: auto; max-width: none;
  display: grid; grid-template-columns: 1.7em .85em minmax(0, 1fr);
  align-items: baseline; column-gap: .35em;
  top: calc((1 - var(--palier)) * 100%);
  transform: translateY(-50%);
  font-family: var(--ecriture);
  font-size: 9px; font-size: 11.4cqi; font-weight: 600; line-height: 1.05;
  opacity: .78;
  transition: opacity .4s ease;
}
.palier-heure { white-space: nowrap; font-variant-numeric: tabular-nums; }
.palier-trait { align-self: center; height: 1px; background: currentColor; border-radius: 1px; }
.palier-message { min-width: 0; }
.palier.atteint { opacity: 1; }
.gourde.reussie .palier { opacity: .18; }
/* La réussite est elle aussi imprimée sur le verre, sans nouvelle bulle. */
.gourde-bravo {
  position: absolute; left: 8%; width: 71%; top: 57%; margin: 0;
  transform: translateY(-50%) rotate(-5deg);
  color: #57341F; font: 700 19px/1.05 var(--ecriture);
  text-align: center;
}
/* À 150 px dans le journal, les heures seules restent lisibles. */
@container (max-width: 60px) {
  .palier { grid-template-columns: 1.7em 1em; justify-content: center; font-size: 9px; }
  .palier-message { display: none; }
}
#screen-today .jr-gourde .gourde-bravo { font-size: 14px; }
@media (prefers-reduced-motion: reduce) { .palier { transition: none; } }
.eau-goutte { display: inline-flex; vertical-align: -3px; color: var(--bleu); }
.eau-goutte .icon { width: 16px; height: 16px; }
.eau-gestes { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
#reglage-eau {
  width: 92px; text-align: right; font-size: 24px;
  border: 1px solid var(--line); border-radius: 12px; padding: 8px 10px;
  background: var(--card); color: var(--espresso);
}

/* ── L'écran d'un chantier — 04/09 ────────────────────────────────────────────
   Centré, avec son pictogramme en grand : c'est une **page d'état**, pas une
   carte parmi d'autres. ⛔ Et pas d'animation ni de barre de progression : il
   n'y a rien à mesurer, et un pourcentage inventé serait une promesse de plus.
   Le ton reste celui du reste de l'application. */
.chantier { text-align: center; }
.chantier-icone {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; border-radius: 18px;
  background: var(--sand); color: var(--cinnamon); margin-bottom: 10px;
}
.chantier-icone .icon { width: 28px; height: 28px; }
.chantier .hint { max-width: 34ch; margin-inline: auto; }

/* ── Vérifier & Corriger ─────────────────────────────────────────────────── */

.review-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.review-title { flex: 1; min-width: 0; }
.review-title .page-title { font-size: 29px; }
.review-title .subtitle { font-size: 20px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#review-thumb { width: 54px; height: 54px; border-radius: 14px; object-fit: cover; flex: none; }

.icon-btn {
  width: 40px; height: 40px; min-height: 40px; flex: none;
  border: 1px solid var(--line); border-radius: 50%;
  background: var(--card); color: var(--espresso);
  font-size: 27.5px; line-height: 1; cursor: pointer;
}
.icon-btn.heart.on { background: var(--coeur); border-color: var(--coeur); color: #fff; }

.review-lead {
  background: #FFF3E2; border: 1px solid #F0DCBE; color: #7A5320;
  border-radius: 16px; padding: 13px 16px; margin: 0 0 16px; font-size: 21px;
}
:root[data-theme="dark"] .review-lead,
:root:not([data-theme="light"]) .review-lead {
  background: #3A2E1E; border-color: #55432A; color: #F0D9AE;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .review-lead {
    background: #FFF3E2; border-color: #F0DCBE; color: #7A5320;
  }
}

.muted-note { color: var(--muted); font-size: 19.5px; }

/* Colonne d'unité plus large que la quantité : « cuillère à café » y tient,
   là où une colonne étroite tronquait le libellé sans le dire. */
/* Cinq colonnes et non six depuis les molettes : la quantité et l'unité tenaient
   deux champs, elles tiennent un bouton. Il est plus large que la somme des deux
   ne l'était étroite — « 1 ½ c. à soupe » doit y tenir sans être tronqué, et
   c'est ce qu'on lit pour décider s'il y a une correction à faire. */
/* Six pistes : poignée, pastille de confiance, vignette de famille, nom,
   quantité, suppression. La vignette dit *ce que c'est*, la pastille *si on peut
   y croire* — deux questions distinctes, deux colonnes. */
.rrow {
  display: grid; grid-template-columns: 18px 10px 26px 1fr 128px 28px;
  gap: 7px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
/* La vignette du journal, à l'échelle d'une ligne d'édition. */
.rrow .diary-thumb { width: 26px; height: 26px; border-radius: 8px; }
.rrow .diary-thumb-empty .icon { width: 16px; height: 16px; }
/* Le bouton de quantité : il ressemble à un champ, parce qu'il en tient le rôle,
   mais il ouvre la feuille à molettes. 16 px comme tous les champs — sous ce
   plancher, iOS agrandit la page à la mise au point (§ 1 des conventions). */
.rrow .rqty {
  border: 1px solid var(--line); border-radius: 10px;
  /* `var(--espresso)` et non `var(--ink)`, qui n'existe pas dans la palette : la
     déclaration était ignorée en silence et la couleur retombait sur l'héritage. */
  background: var(--card); color: var(--espresso);
  font: inherit; font-size: 24px; text-align: center;
  min-height: 40px; padding: 0 6px; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Une ligne sans quantité porte un tiret, et le tiret doit se voir : c'est lui
   qui fait cliquer. */
.rrow.check .rqty { border-color: var(--amber); }
.rrow.check { background: color-mix(in srgb, var(--amber) 7%, transparent); }
/* La pastille orange dit qu'il y a un doute ; elle ne dit pas lequel. Un champ
   pré-rempli par une proposition se lit sinon comme un champ lu — et une valeur
   qui a l'air vérifiée ne se corrige pas plus qu'un vide. La mention court sous
   la ligne entière, colonnes comprises. */
.rrow .rguess {
  grid-column: 4 / -1; color: var(--cinnamon); font-size: 17px;
  margin-top: -2px;
}
.rrow .dot { align-self: center; }
/* Le champ d'unité est un champ comme un autre : sous 16 px, iOS zoome dès
   qu'on le touche. C'est la colonne qui s'élargit, pas le texte qui rétrécit
   — « cuillère à café » doit continuer d'y tenir en entier. */
.rrow .unit { font-size: 24px; }
.rrow:last-of-type { border-bottom: none; }

/* ── Les suggestions d'aliment, sous le champ — § 15 quater ──────────────────

   **Posées en absolu, et pas dans le flux.** Une liste qui pousse les lignes
   suivantes ferait sauter la fiche à chaque lettre : l'ingrédient qu'on visait
   ne serait plus sous le doigt au moment de le toucher, et c'est le mode d'échec
   du § 5 (« une hauteur qui apparaît est une hauteur réservée ») obtenu en
   croyant rendre service.

   ⚠️ **Et `.rrow` prend un `position: relative` pour ça** — la boîte se cale sur
   *sa* ligne, jamais sur la fenêtre : une couche fixe ici aurait rejoint le § 2
   et son recadrage, pour un objet qui n'a aucune raison de sortir de sa carte.

   ⛔ **Aucun `backdrop-filter`** (§ 6) : une ombre portée peint, un flou
   recompose la bande couverte à chaque image. */
.rrow { position: relative; }
/* La ligne qui suggère passe devant ses voisines — sans quoi la liste s'ouvre
   *sous* l'ingrédient d'en dessous, qui la recouvre à moitié. */
.rrow.suggere { z-index: 5; }
/* ⚠️ **Le décalage de gauche appartient à la ligne de recette**, pas à la
   boîte : 54 px y sont la poignée de glissement et la pastille de confiance.
   Le champ du frigo n'a ni l'une ni l'autre, et la boîte doit y commencer au
   bord. C'est le § *une règle qui désigne ses voisins par une mesure se trompe
   au premier champ qui n'a pas les mêmes*. */
.rsug {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 5;
  margin-top: 2px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 10px 26px rgb(0 0 0 / .16);
}
/* Ouverte vers le haut quand c'est en haut qu'il y a de la place — le cas d'un
   ingrédient qu'on vient d'ajouter, donc en bas de la carte. */
.rrow .rsug { left: 54px; }
/* ⚠️ **Et le formulaire du frigo a son rembourrage.** La boîte est calée sur la
   boîte de bordure de son ancêtre positionné, le champ sur sa boîte de contenu :
   mesuré au banc, 12 px d'écart — assez pour que la liste ne soit pas sous le
   mot qu'on tape. Le nombre est celui de `.add-item`, dix lignes plus haut. */
.add-item .rsug { left: 12px; right: 12px; }
.rsug.vers-le-haut {
  top: auto; bottom: 100%; margin-top: 0; margin-bottom: 2px;
}
.rsug-note {
  margin: 0; padding: 8px 12px 4px; font-size: 17px; color: var(--cinnamon);
}
.rsug-item {
  display: flex; flex-direction: column; gap: 1px;
  width: 100%; padding: 8px 12px; text-align: left;
  background: none; border: 0; font: inherit; color: var(--espresso);
  /* 44 px — la contrainte AA, et un ingrédient se choisit au pouce. */
  min-height: 44px; cursor: pointer;
}
.rsug-item + .rsug-item { border-top: 1px solid var(--line); }
.rsug-item strong { font-size: 22.5px; font-weight: 600; }
.rsug-item small { font-size: 18px; color: var(--muted); }
/* Le survol se pose sur le pointeur, jamais sur une largeur — § 10 : un iPad en
   paysage et un portable font tous deux 1 194 px, l'un se touche et l'autre se
   survole. */
@media (hover: hover) and (pointer: fine) {
  .rsug-item:hover { background: var(--cream); }
}
.rrow input {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px; background: var(--cream); color: inherit;
  min-height: 40px; width: 100%; font-size: 24px;
}
.rrow input.name { background: transparent; border-color: transparent; padding-left: 2px; }
.rrow input.name:focus { background: var(--cream); border-color: var(--line); }
.rrow input.qty { text-align: right; }

/* Un `placeholder` doit se lire comme une proposition, jamais comme une saisie.
   En tons chocolat, `--muted` sur `--cream` était assez proche du texte courant
   pour que « unité » passe pour une unité choisie : on lisait « viande — unité »
   sur un champ vide. L'italique tranche là où la seule couleur n'y suffisait
   pas — et il tranche aussi en contraste forcé, où les couleurs sautent. */
.rrow input::placeholder,
.weigh-row input::placeholder {
  color: var(--muted); opacity: .75; font-style: italic;
}

/* Poignée de réordonnancement. `grab`/`grabbing` plutôt que `move` : le geste
   est un glisser, et le curseur doit le dire avant qu'on essaie. */
.rgrip {
  cursor: grab; color: var(--muted); font-size: 22.5px;
  text-align: center; user-select: none; line-height: 1;
}

.rdel {
  border: none; background: none; color: var(--muted);
  font-size: 24.5px; cursor: pointer; min-height: 32px; padding: 0;
}
.rdel:hover { color: var(--danger); }

.review-actions { display: flex; gap: 10px; align-items: stretch; margin-bottom: 8px; }
/* ⚠️ **Le vert du bouton n'est pas le vert des étiquettes.** `--sage` rendait
   3,66:1 sous du blanc en clair et 2,73:1 en sombre : « Analyser la recette »
   naît grisé et s'allume dès qu'une URL est tapée, donc sa couleur d'état actif
   échappait à l'œil comme aux mesures. Même famille, assez foncé pour porter
   une encre claire (R05, 21/09). */
.review-actions .btn-primary { flex: 1; background: var(--bouton-valide-fond); }

.evidence {
  grid-column: 4 / -1; font-size: 18px; padding-top: 2px;
}
.evidence a { color: var(--cinnamon); text-decoration: none; }

@media (max-width: 430px) {
  /* Les champs gardent leurs 16 px — c'est ce qui retient iOS de zoomer à la
     mise au point. Ce sont le rembourrage et les colonnes voisines qui cèdent
     la place : le texte plus large les remplit de toute façon, alors que le nom
     de l'ingrédient est la colonne qu'on lit.

     **Cinq pistes, comme au-dessus.** Il en restait six, héritées de l'époque
     où quantité et unité tenaient deux champs : la sixième n'avait plus
     personne à porter et mangeait 24 px + 5 px d'écart au nom de l'ingrédient
     — sur **tous** les iPhone, puisqu'ils passent tous sous 430 px. Une piste
     de grille vide ne se voit pas, elle se mesure. */
  .rrow { grid-template-columns: 14px 9px 22px 1fr 100px 24px; gap: 5px; }
  .rrow .diary-thumb { width: 22px; height: 22px; border-radius: 7px; }
  .rrow input { padding: 7px 6px; }
}

/* ── Modifier la fiche — § 8 ─────────────────────────────────────────────────
   L'écran reprend les rangées d'ingrédient de la relecture (`.rrow`) : c'est le
   même geste sur le même objet, et deux dessins différents pour une même ligne
   se mettraient à diverger comme deux éditeurs le feraient. Ce qui suit ne
   couvre donc que ce que la relecture n'a pas — le titre, la photo, les temps
   et les étapes. */

.edit-label {
  display: block; font-size: 19.5px; color: var(--muted); margin-bottom: 6px;
}
/* 16 px, plancher inclus (§ 1 des conventions) : sous cette taille iOS agrandit
   la page à la mise au point et la laisse décalée quand le clavier se referme. */
.edit-field {
  width: 100%; border: 1px solid var(--line); border-radius: 12px;
  padding: 11px 12px; background: var(--cream); color: inherit;
  min-height: 44px; font-size: 24px; font-family: inherit;
}
.edit-field:focus {
  outline: none; border-color: var(--terra);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 18%, transparent);
}

.edit-times { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; align-items: end; }
.edit-times .edit-label { margin-bottom: 0; }
.edit-times .edit-field { grid-row: 2; }

.edit-photo { display: flex; gap: 14px; align-items: flex-start; }
.edit-photo-preview {
  width: 96px; height: 96px; flex: none; border-radius: 14px;
  background: var(--cream) center / cover no-repeat;
  border: 1px solid var(--line); display: grid; place-items: center;
  color: var(--muted); font-size: 18px; text-align: center; padding: 6px;
}
.edit-photo-actions { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.edit-photo-actions .btn-ghost { min-height: 44px; }
.edit-photo-actions .hint { margin: 0; }

/* ── La pellicule : quatre moments de la vidéo — § 12 ────────────────────────
   Une rangée qui **ne défile pas**. Quatre vignettes tiennent sur 390 px moins
   les marges, et une rangée qui défile cacherait la quatrième derrière un geste
   qu'aucun signe n'annonce — c'est le défaut que `#pick-list` et les rails
   d'accueil acceptent parce qu'ils sont longs par nature. Ici le nombre est
   connu et petit : la grille est le bon outil, et elle s'adapte toute seule si
   une vidéo n'en rend que deux ou trois. */
.pellicule { margin-top: 14px; }
.pellicule-lead { margin: 0 0 8px; }
.pellicule-rang {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 8px;
}
.pellicule-vignette {
  position: relative;
  padding: 0; border: none; background: none; cursor: pointer;
  border-radius: 12px; overflow: hidden;
  /* Le cadre est **toujours là**, transparent quand la vignette n'est pas
     retenue : une bordure qui apparaît décalerait les trois voisines de deux
     pixels au moment du choix — la « hauteur qui apparaît » du § 5, en largeur. */
  outline: 2px solid transparent; outline-offset: 2px;
  /* Une vignette est un rectangle, quelle que soit la vidéo : un format 9:16 et
     un 16:9 côte à côte donneraient une rangée en escalier. */
  aspect-ratio: 1 / 1;
  min-height: 0;
}
.pellicule-vignette img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pellicule-vignette small {
  position: absolute; left: 4px; bottom: 4px;
  padding: 1px 5px; border-radius: 6px;
  background: rgba(30, 24, 20, .62); color: #FFFFFF;
  font-size: 16.5px; line-height: 1.5;
}
.pellicule-vignette.on { outline-color: var(--terra); }
/* Le focus clavier reste distinct du choix retenu : les deux peuvent coexister
   sur des vignettes différentes, et une seule couleur les confondrait. */
.pellicule-vignette:focus-visible { outline-color: var(--cinnamon); }
.btn-ghost.danger { color: var(--danger-texte); }

/* Une étape est un bloc, pas une ligne : son instruction est du texte long, et
   la mettre sur une seule ligne la rend intapable dès la deuxième phrase. */
.erow {
  border: 1px solid var(--line); border-radius: 14px;
  padding: 10px 12px 12px; margin-bottom: 10px; background: var(--card);
}
.erow-head { display: grid; grid-template-columns: 18px 1fr 28px; gap: 8px; align-items: center; }
.erow input, .erow textarea {
  width: 100%; border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 10px; background: var(--cream); color: inherit;
  font-size: 24px; font-family: inherit;
}
.erow input.etitle {
  background: transparent; border-color: transparent; font-weight: 600; padding-left: 2px;
}
.erow input.etitle:focus { background: var(--cream); border-color: var(--line); }
.erow textarea { min-height: 74px; margin-top: 8px; resize: vertical; line-height: 1.45; }
.erow-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; }
.erow-meta label { display: block; font-size: 19px; color: var(--muted); margin-bottom: 4px; }

.edit-empty { color: var(--muted); font-size: 20px; padding: 6px 0; }

/* ── Photographier une recette — § 10 bis, habillée en P17 le 13/09 ─────────
   Le grand cadre à gauche, les deux portes et les conseils à droite, la rangée
   des pages sous les deux. Sous 360 px la colonne de droite passe dessous
   plutôt que d'écraser le cadre. */

.pc-decor-cuillere { position: absolute; right: 6px; bottom: 0; width: 84px; transform: rotate(-30deg); }
.pc-decor-pate { position: absolute; left: 0; bottom: 0; width: 64px; }

.pc-haut { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 10px; align-items: start; }
/* ⚠️ **Une proportion, pas un étirement.** Tiré à la hauteur de la colonne
   voisine, le cadre suivait la longueur des conseils — 535 px mesurés à 390,
   un écran entier avant la rangée des pages. 3/5 est celle de la maquette. */
.pc-apercu {
  position: relative; aspect-ratio: 3 / 5; display: grid; place-items: center; overflow: hidden;
  border: 1.5px dashed var(--sand); border-radius: 18px; background: var(--card);
}
.pc-apercu.plein { border-style: solid; border-color: var(--line); background: var(--cream); }
/* `contain` : une capture d'écran est haute, et la rogner cacherait ce qu'on
   vient justement vérifier. */
.pc-apercu img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
.pc-apercu-rang {
  position: absolute; left: 8px; top: 8px; padding: 3px 9px; border-radius: 999px;
  background: var(--espresso); color: var(--card); font-size: 19px; font-weight: 600;
}
.pc-apercu-vide {
  display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 14px 10px; text-align: center;
}
.pc-apercu-vide strong { font-size: 22.5px; font-weight: 600; }
.pc-apercu-vide small { color: var(--muted); font-size: 19px; line-height: 1.3; }

.pc-cote { display: grid; gap: 8px; align-content: start; }
.pc-porte { min-height: 60px; padding: 8px 10px; }
/* Une porte de prise de vue porte un titre et une précision : `opacity: .5`
   effaçait les deux (R05, revue de 03b). */
.pc-porte:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--line); cursor: default;
}
.pc-porte:disabled small, .pc-porte:disabled strong { color: var(--bouton-inactif-texte); }
.pc-porte span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pc-porte strong { font-weight: 600; }
.pc-porte small { color: var(--muted); font-size: 19px; }
/* ⚠️ **Une souris n'a pas d'appareil photo à désigner** : sur un ordinateur,
   `capture` est ignoré et le bouton rouvrirait la même boîte de fichiers que
   la galerie — deux portes pour une pièce. Ce qu'une souris change ne se
   déduit pas d'une largeur (§ 10). */
@media (hover: hover) and (pointer: fine) { #page-shoot { display: none; } }

.pc-conseils {
  padding: 12px; border-radius: 16px;
  background: color-mix(in srgb, var(--sage) 13%, var(--card));
}
.pc-conseils h2 {
  display: flex; align-items: center; gap: 6px; margin: 0 0 8px;
  font-size: 21px; font-weight: 600; line-height: 1.25;
}
.pc-conseils h2 .icon { flex: none; width: 20px; height: 20px; color: var(--sage); }
.pc-conseils ul { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.pc-conseils li { display: flex; gap: 6px; font-size: 19.5px; line-height: 1.3; color: var(--muted); }
.pc-conseils li .icon {
  flex: none; width: 16px; height: 16px; margin-top: 1px; padding: 2px; border-radius: 50%;
  background: var(--sage); color: var(--card);
}

.pc-pages-tete { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 20px 0 8px; }
.pc-pages-tete h2 { margin: 0; font-family: var(--ecriture); font-weight: 400; font-size: 27.5px; line-height: 1.2; }
.pc-compte { color: var(--muted); font-size: 22.5px; font-variant-numeric: tabular-nums; }
.pc-note { margin-top: 10px; }
#screen-page .review-actions { margin-top: 4px; }
#screen-page .nr-continuer { margin-top: 12px; }

@media (max-width: 359px) {
  .pc-haut { grid-template-columns: 1fr; }
  .pc-apercu { aspect-ratio: 4 / 3; }
  .pc-decor-cuillere { width: 64px; right: 2px; }
  .pc-decor-pate { width: 46px; left: -6px; }
}
/* ⚠️ **Une proportion suit la largeur, et la largeur a grandi** — mesuré à
   820 px : un cadre de 602 px et des vignettes de 380, pour montrer les mêmes
   pages. Au-delà du premier palier (§ 10) les deux se bornent ; la colonne ne
   change pas de forme. */
@media (min-width: 820px) {
  .pc-apercu { aspect-ratio: auto; height: 440px; }
  /* Scopée sur l'écran : `.page-shots` est déclarée plus bas, et à égalité de
     spécificité la dernière écrite gagnait — mesuré, la borne ne tenait pas. */
  #screen-page .page-shots { grid-template-columns: repeat(4, minmax(0, 132px)); }
}

/* La rangée : quatre cases, toujours — le nombre de places **est** la limite,
   et elle se voit avant d'avoir rien choisi. L'ordre compte : c'est celui dans
   lequel les pages sont lues, et le recto avant le verso n'est pas un détail. */
.page-shots { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 8px; }
.page-shot {
  position: relative; aspect-ratio: 3 / 4; border-radius: 12px; overflow: hidden;
  background: var(--cream); border: 1px solid var(--line);
}
.page-shot.choisie { border: 2px solid var(--terra); }
.page-shot-voir {
  display: block; width: 100%; height: 100%; padding: 0; border: 0; background: none; cursor: pointer;
}
.page-shot-voir img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Le rang, lisible sur n'importe quelle photo : une pastille opaque plutôt
   qu'un texte posé sur l'image, dont on ne maîtrise ni le fond ni le contraste. */
.page-shot-rank {
  position: absolute; left: 5px; bottom: 5px; min-width: 22px; height: 22px; pointer-events: none;
  display: grid; place-items: center; border-radius: 11px; padding: 0 6px;
  background: var(--espresso); color: var(--card); font-size: 19px; font-weight: 600;
}
/* 44 px de cible tactile (§ 11, contrainte AA), obtenus par la zone cliquable
   et non par le dessin — un bouton visuellement plus gros mangerait la photo. */
.page-shot-del {
  position: absolute; right: 0; top: 0; width: 44px; height: 44px;
  display: grid; place-items: center; border: none; cursor: pointer;
  /* Blanc fixe, pas `--card` : posée sur une photo, la croix ne suit pas le
     thème — en sombre elle devenait noire sur une page blanche. */
  background: none; color: #fff;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .8));
}
.page-shot-del .icon { width: 20px; height: 20px; }
.pc-case {
  aspect-ratio: 3 / 4; border: 1.5px dashed var(--sand); border-radius: 12px; background: transparent;
}
.pc-case-ajout {
  display: grid; place-items: center; padding: 0; background: var(--card); color: var(--terra); cursor: pointer;
}
.pc-case-ajout .icon { width: 26px; height: 26px; }

/* ── Dicter une recette — § 15 ter, habillée en P18 le 14/09 ────────────────
   Le micro au centre, le texte en cours replié, l'aperçu de ce qui a été
   compris, et le bouton pleine largeur. On dicte **et** on corrige. */

.dt-decor-cuillere { position: absolute; right: 8px; bottom: 0; width: 84px; transform: rotate(-30deg); }

.dt-micro-zone {
  display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center; gap: 10px; margin: 18px 0 4px;
}
/* ⚠️ **Les barres ne mesurent rien.** Aucun volume n'est capté : elles disent
   « j'écoute » et s'immobilisent au repos. `transform` seul — ce qui s'anime
   ne relayoute pas (§ 6). */
.dt-barres { display: flex; align-items: center; justify-content: center; gap: 4px; height: 48px; min-width: 0; overflow: hidden; }
.dt-barres i {
  flex: none; width: 4px; height: 100%; border-radius: 2px;
  background: color-mix(in srgb, var(--terra) 40%, transparent);
  transform: scaleY(.18); transform-origin: center;
}
.dt-barres i:nth-child(2n) { transform: scaleY(.3); }
.dt-barres i:nth-child(3n) { transform: scaleY(.24); }
.dt-micro-zone.ecoute .dt-barres i { animation: dt-onde 1.1s ease-in-out infinite alternate; }
.dt-micro-zone.ecoute .dt-barres i:nth-child(2n) { animation-delay: -.35s; animation-duration: .9s; }
.dt-micro-zone.ecoute .dt-barres i:nth-child(3n) { animation-delay: -.7s; animation-duration: 1.3s; }
@keyframes dt-onde { from { transform: scaleY(.2); } to { transform: scaleY(.95); } }

/* La marge latérale est celle des deux halos : sans elle, ils passent sous les
   barres voisines. */
.dt-micro {
  display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 0 26px;
  padding: 26px 0; border: 0; background: none; color: var(--terra); font: inherit; cursor: pointer;
}
.dt-micro-rond {
  display: grid; place-items: center; width: 104px; height: 104px; border-radius: 50%;
  background: var(--terra); color: #fff;
  box-shadow: 0 0 0 12px color-mix(in srgb, var(--terra) 14%, transparent),
              0 0 0 26px color-mix(in srgb, var(--terra) 6%, transparent);
}
.dt-micro-rond .icon { width: 42px; height: 42px; }
.dt-micro:focus-visible { outline: none; }
.dt-micro:focus-visible .dt-micro-rond { outline: 3px solid var(--espresso); outline-offset: 30px; }
/* L'écoute se voit sans clignoter : une pulsation lente dit « ça tourne » sans
   attirer l'œil pendant qu'on parle. Coupée pour qui a demandé moins de
   mouvement — la règle vaut pour toute animation qui n'informe pas seule. */
.dictee-micro.on .dt-micro-rond { animation: dictee-souffle 2s ease-in-out infinite; }
@keyframes dictee-souffle { 50% { transform: scale(1.05); } }
@media (prefers-reduced-motion: reduce) {
  .dictee-micro.on .dt-micro-rond, .dt-micro-zone.ecoute .dt-barres i { animation: none; }
}

.dt-chrono {
  margin: 4px 0 0; text-align: center; color: var(--terra);
  font-weight: 600; font-size: 24px; font-variant-numeric: tabular-nums;
}
.dt-etat { margin: 2px 0 12px; text-align: center; }

/* Les trois commandes de la maquette. Une commande impossible dans l'état
   courant est **désactivée**, pas masquée : la rangée ne saute pas quand on
   touche le micro, et on voit ce qui deviendra possible. */
.dt-commandes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px; }
.dt-commande {
  display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 48px; padding: 6px 8px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--card);
  color: var(--terra); font: inherit; font-size: 22px; font-weight: 600; line-height: 1.15; text-align: center; cursor: pointer;
}
.dt-commande .icon { flex: none; width: 18px; height: 18px; }
.dt-commande-arret { background: color-mix(in srgb, var(--terra) 9%, var(--card)); }
/* ⚠️ **Sous 400 px, l'icône passe au-dessus** — mesuré au banc : « Reprendre »
   touchait les bords de son bouton à 390 px, « Mettre en pause » prenait trois
   lignes à 320. Le libellé garde toute la largeur ; c'est la hauteur qui cède,
   jamais le texte (§ 1). */
@media (max-width: 399px) {
  .dt-commande { flex-direction: column; gap: 3px; padding: 7px 4px; font-size: 20px; }
}
/* Même couple que partout ailleurs : « Reprendre » grisé reste un mot qu'on
   lit — c'est lui qui dit pourquoi rien ne se passe (R05, revue de 03b). */
.dt-commande:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--line); cursor: default;
}
/* En pause, les barres restent au repos mais pâlissent : on n'écoute plus. */
.dt-micro-zone.en-pause .dt-barres { opacity: .5; }

.dt-carte-tete { display: flex; align-items: center; gap: 4px; margin-bottom: 6px; }
.dt-carte-titre {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; margin: 0;
  font-family: var(--ecriture); font-weight: 400; font-size: 26.5px; line-height: 1.2;
}
.dt-carte-titre .icon { flex: none; width: 22px; height: 22px; color: var(--terra); }
.dt-lien {
  display: inline-flex; align-items: center; gap: 5px; min-height: 44px; padding: 0 8px;
  border: 0; background: none; color: var(--muted); font: inherit; font-size: 21px; cursor: pointer;
}
.dt-lien .icon { width: 18px; height: 18px; }
/* Le texte replié : trois lignes, puis « Modifier » l'ouvre en entier. */
.dt-texte-vue {
  margin: 0; color: var(--muted); font-size: 22.5px; line-height: 1.45; white-space: pre-line;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
}
.dt-texte-vue.vide { font-style: italic; }
.dt-sous-titre { margin: 14px 0 6px; font-size: 21px; font-weight: 600; }
#dictee-carte-titre .edit-field { margin-bottom: 0; }
.dt-lu-vide { margin: 0; color: var(--muted); font-size: 21px; }
.dt-suite { margin: 12px 0 0; }
.dt-note { margin-top: 10px; }
#screen-dictee .nr-continuer { margin-top: 12px; }
#screen-dictee .form-error { margin-top: 12px; }

@media (max-width: 359px) {
  .dt-micro-rond { width: 84px; height: 84px; }
  .dt-micro-rond .icon { width: 34px; height: 34px; }
  .dt-barres i:nth-child(n+6) { display: none; }
}

#dictee-texte {
  width: 100%; margin-top: 10px; border: 1px solid var(--line); border-radius: 12px;
  padding: 11px 12px; background: var(--cream); color: inherit;
  font: inherit; font-size: 24px; line-height: 1.5; resize: vertical;
}
#dictee-texte:focus {
  outline: none; border-color: var(--terra);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 18%, transparent);
}

/* La lecture, montrée avant d'enregistrer. Une ligne sans quantité se signale
   ici plutôt qu'à la relecture : c'est le seul moment où on peut la redire. */
.dictee-lu { display: grid; gap: 5px; margin-top: 4px; }
/* ⚠️ **La lecture locale est provisoire, et elle en a l'air** (P18) : pâlie,
   jusqu'à ce que le serveur ait répondu. */
.dictee-lu.provisoire { opacity: .55; }
.dictee-ligne {
  border-left: 3px solid var(--sage); padding: 4px 0 4px 9px;
  font-size: 20px; line-height: 1.35;
}
.dictee-ligne.sans-quantite { border-left-color: var(--amber); }
.dictee-ligne small { display: block; color: var(--cinnamon); font-size: 17px; }
/* L'entrée de la base à laquelle la ligne sera comptée. Discrète par défaut :
   un rattachement juste est le cas normal, et le dire fort ferait du bruit sur
   toute une liste. Ce sont les deux écarts qui se voient — celui qu'on n'a pas
   su rattacher, et celui qu'on n'a rapproché que par le sens. */
/* ⚠️ **Scopé sur la ligne, sinon il perd** — P18, 14/09. Écrit `.dictee-
   rattachement`, il avait la spécificité de `.dictee-ligne small` déclarée
   plus haut… et le banc les montrait tous en cannelle : c'est `.dictee-ligne
   small` (0,1,1) qui l'emportait sur (0,1,0). La discrétion voulue n'a jamais
   été affichée. */
.dictee-ligne small.dictee-rattachement { color: var(--muted); }
.dictee-ligne small.dictee-rattachement.approche { color: var(--cinnamon); }
.dictee-ligne small.dictee-rattachement.absent { color: var(--terra); }

/* ── Corriger une fiche produit — § 13 bis ────────────────────────────────────
   Une proposition n'est pas une donnée : la zone se distingue du reste de la
   fiche sans crier, parce qu'elle porte une question, pas une alerte. */

.correction { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.correction-ask {
  background: color-mix(in srgb, var(--sage) 10%, transparent);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 8px;
}
.correction-lead { margin: 0 0 4px; font-size: 21px; line-height: 1.4; }
.correction-ask .hint { margin: 0 0 8px; }
/* Les deux réponses à égalité de poids : suggérer laquelle donner reviendrait à
   demander une confirmation en ayant déjà choisi. */
.correction-actions { display: flex; gap: 8px; }
.correction-actions .btn-ghost { flex: 1; min-height: 44px; font-size: 21px; }
#correction-open { font-size: 20px; }

.corr-grid { display: grid; gap: 4px 12px; }
/* ⛔ **Les deux marges de la grille sont mortes depuis 03d-2** : `.corr-grid`
   n'existe que dans `#corr-sheet`, passée au carnet, et ses règles y posent la
   grille à deux colonnes, les marges et l'alignement. Deux déclarations qui ne
   gagnent jamais se lisent comme un réglage actif ; elles sont retirées plutôt
   que laissées à faire croire qu'on peut les modifier. */
/* Le pied de `#corr-sheet` est celui du socle `.sheet-actions` depuis le 20/09 :
   « Annuler » en `btn-ghost` à gauche, « Proposer la correction » à droite. Le
   lien qui vivait sous le bouton se lisait comme une note de bas de page, sur
   la seule sortie de la feuille avec le voile. */

/* ── Rattacher un ingrédient à la base — § 7.3 ────────────────────────────────
   Le bloc vit **dans** la rangée : posé en frère, le glisser-déposer aurait
   déplacé la ligne sans son arbitrage. D'où `grid-column: 1 / -1`, qui lui fait
   traverser les six pistes de `.rrow`. */

.rattach { grid-column: 1 / -1; margin-top: 6px; }
.rattach-lead { display: block; color: var(--cinnamon); font-size: 17px; margin-bottom: 5px; }
.rattach-choix { display: flex; flex-wrap: wrap; gap: 6px; }
/* Une puce porte le nom **et** l'énergie : deux entrées CIQUAL peuvent avoir des
   libellés très proches et des valeurs qui ne le sont pas — cru et cuit vont du
   simple au double. Choisir sans voir le chiffre, c'est choisir à l'aveugle. */
.rattach-puce {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
  color: var(--espresso); padding: 6px 10px; cursor: pointer;
  min-height: 44px; text-align: left; font: inherit;
}
.rattach-puce b { font-size: 19.5px; font-weight: 600; }
.rattach-puce small { color: var(--muted); font-size: 16.5px; }
.rattach-puce:hover { border-color: var(--terra); }
.rattach-chercher { align-self: center; font-size: 19px; min-height: 44px; }

/* Le bandeau de tête. Ton d'information et non d'alerte : la ligne existe, elle
   est juste incomplète — et c'est réparable en un tap. */
.review-lead.rattach-lead {
  background: color-mix(in srgb, var(--amber) 10%, transparent);
  border-radius: 12px; padding: 10px 12px; font-size: 20px;
}

/* ── Grande carte (suggestion, sélection) ────────────────────────────────── */

/* ⚠️ **`.feature` est partie avec « À la une » — refonte du 10/09.** Cette carte
   pleine largeur à photo de fond n'avait qu'un dessinateur, `featureCard`, et
   qu'un emploi, la mise en avant du jour sur l'accueil. La suggestion
   personnelle vit désormais dans le tri « Pour toi », qui emploie la carte
   commune. */

/* ── Catégories ──────────────────────────────────────────────────────────── */

/* ⚠️ **`.category` est partie le 10/09** : les catégories d'ingrédient
   dessinaient un rail de vignettes posé sous celui des collections — deux
   rangées de cartes à photo, titre et compte, dont la seconde n'avait pas de
   titre. Elles sont descendues dans le panneau de filtres, en puces comme les
   quatre autres facettes. */

/* ── Grille de recettes ──────────────────────────────────────────────────── */

/* ⛔ **Deux colonnes fixes sur un téléphone, et non un `auto-fill` — mesuré au
   banc le 10/09.** `minmax(158px, 1fr)` avec 16 px d'écart demande 332 px pour
   deux colonnes ; un iPhone SE de 3ᵉ génération en offre **320**, et un
   iPhone 12 mini **320** aussi. La grille retombait donc à **une** colonne sur
   toute une famille d'appareils : des cartes de 320 px pleine largeur, là où
   P02 demande une grille.

   ⚠️ **Le défaut ne se voyait pas à 390 px**, la largeur de la maquette et celle
   qu'on regarde — c'est le § *ce qu'il fallait vérifier est le chemin que la
   personne emprunte*, appliqué à un palier qu'on ne teste pas.

   ⚠️ **Et l'`auto-fill` garde tout son sens au-delà du premier palier du § 10**,
   où il est redéclaré : là-bas c'est bien la largeur qui doit décider du nombre
   de colonnes, et non un nombre écrit à la main. */
.recipe-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.recipe-card-wrap { position: relative; display: flex; }
.recipe-card-wrap .recipe-card { width: 100%; }
.recipe-card {
  background: var(--card); border-radius: 18px; overflow: hidden;
  box-shadow: var(--shadow); cursor: pointer; border: none; padding: 0;
  text-align: left; color: inherit; transition: transform .18s ease;
  /* Colonne, pour que la ligne de temps et de calories puisse être poussée en
     bas : sans ça elle remonte sous les titres courts et redescend sous les
     longs, et rien ne s'aligne d'une carte à l'autre. */
  display: flex; flex-direction: column;
}
.recipe-card:hover { transform: translateY(-3px); }
.recipe-card img, .recipe-card .thumb-fallback {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
  background: linear-gradient(135deg, #EFE3D8, #E3D3C4);
}
.recipe-card .thumb-fallback { display: grid; place-items: center; color: var(--sand); }
.recipe-card .thumb-fallback .icon { width: 34px; height: 34px; }

/* ── Le dessin de l'aliment, quand il n'y a pas de photo — 24/08 ─────────────
   Une grille de fiches sans couverture montrait dix casseroles grises
   identiques : le repli existait, il ne disait simplement rien de la fiche. Le
   titre nomme presque toujours l'aliment, et 269 illustrations l'attendent sous
   cette clé (mesuré : 16 des 18 fiches sans image en trouvent une).

   ⚠️ **`contain` et non `cover`** — c'est un dessin sur fond transparent, pas
   une photo : le recadrer lui couperait les bords. Le rembourrage le pose au
   milieu de la vignette, et le fond reste celui du repli, donc une rangée mixte
   photos/dessins garde le même rythme.

   ⛔ **Et le fond ne bascule pas avec le thème.** Ces dessins sont tracés pour
   un fond clair ; sur le brun du mode sombre, leurs traits foncés
   disparaîtraient. Même arbitrage que les trois boutons posés sur la photo
   d'une fiche : la surface est un littéral, la couleur doit l'être aussi. */
.recipe-card .thumb-illus {
  object-fit: contain;
  padding: 13%;
  background: linear-gradient(135deg, #EFE3D8, #E3D3C4);
}
/* Le titre occupe **toujours deux lignes**, qu'il en remplisse une ou deux, et
   se coupe au-delà. C'est ce qui aligne les photos, les titres et les lignes de
   temps sur toute une rangée : à hauteur libre, un titre long décalait tout ce
   qui le suivait et les cartes voisines ne se répondaient plus.

   Le rembourrage du bas est passé sur `small`, et il **doit** y rester : tant
   qu'il appartenait au titre, la découpe tombait 3 px sous la deuxième ligne
   au lieu de tomber dessus, et le haut de la troisième passait par cette fente
   — c'est le fantôme de texte qu'on lisait par-dessus les calories. La hauteur
   est fixe et non minimale, pour que la fente ne puisse pas rouvrir. */
.recipe-card b {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; padding: 11px 12px 0; font-weight: 600; font-size: 22px;
  line-height: 1.3; height: calc(2 * 1.3em + 11px); flex: none;
}
.recipe-card small {
  display: block; padding: 3px 38px 13px 12px; color: var(--muted); font-size: 19px;
  margin-top: auto;          /* toujours en bas, jamais collée au titre */
}

.empty {
  color: var(--muted); background: var(--card); padding: 22px;
  border-radius: 18px; box-shadow: var(--shadow);
}
/* R04, 21/09 — la panne de la bibliothèque, posée au-dessus des cartes qu'elle
   laisse en place : le texte et sa reprise sur une ligne quand ils tiennent. */
.library-erreur {
  display: flex; align-items: center; justify-content: space-between; gap: 10px 14px;
  flex-wrap: wrap; margin: 0 0 14px; padding: 14px 16px; color: var(--cinnamon);
}
.library-erreur .btn-mini { min-height: 44px; }

/* ── Collections ─────────────────────────────────────────────────────────── */

/* Cartes volontairement plus grandes que celles des recettes : la maquette en
   fait des couvertures, et une collection se choisit à l'image avant le nom. */
/* 150px et non 210 : à 210 une seule colonne tient sur un écran de téléphone,
   et une collection vide occupait alors la moitié de la hauteur utile. */
.collection-grid {
  display: grid; gap: 16px; margin-top: 16px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.collection-card {
  position: relative; background: var(--card); border-radius: 20px;
  overflow: hidden; box-shadow: var(--shadow); cursor: pointer;
  border: none; padding: 0; text-align: left; color: inherit;
  transition: transform .18s ease;
}
.collection-card:hover { transform: translateY(-3px); }
.collection-card img, .collection-card .thumb-fallback {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover; display: block;
  background: linear-gradient(135deg, #EFE3D8, #E3D3C4);
}
.collection-card .thumb-fallback { display: grid; place-items: center; }
.collection-body { padding: 12px 14px 14px; }
.collection-body b { display: block; font-size: 24px; font-weight: 600; }
.collection-body small { color: var(--muted); font-size: 19px; }

/* Retour à gauche, titre centré — **et l'unique déclaration**. Elle en avait une
   seconde 800 lignes plus haut, en grille, que celle-ci réécrivait sans que rien
   ne le signale : c'est « un objet qui se déclare à deux endroits finit toujours
   par n'être déclaré qu'à un seul », appliqué à la feuille de style.

   ⚠️ **Et l'alignement est en haut, pas au centre.** Trois de ces huit en-têtes
   portent un sous-titre, et il n'a pas de hauteur fixe : celui d'« À vérifier »
   fait trois lignes sur un iPhone 14 et quatre sur un SE de 1ʳᵉ génération.
   Centré, le bouton de retour descendait à mi-hauteur du bloc — il flottait au
   milieu du sous-titre au lieu de répondre au titre, et il descendait d'autant
   plus que la phrase s'allongeait. C'est ce que la déclaration perdue demandait
   déjà. */
.detail-head {
  display: flex; align-items: start; gap: 12px; margin-bottom: 20px;
}
.detail-head > div { flex: 1; min-width: 0; text-align: center; }
.detail-head .page-title {
  font-size: 29px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Ce qu'on fait de ce plat — maquette du 06/09 ──────────────────────────
   **Une grille de quatre, jamais une rangée qui se replie.** Sept puces de
   largeurs inégales sur deux lignes ne se parcourent pas : rien n'y dit lequel
   est le geste principal, et une puce qui passe seule à la ligne se lit comme
   une exclue plutôt que comme la quatrième d'une série.

   ⚠️ **`1fr` sur quatre colonnes, et le texte se replie dedans.** « Ajouter au
   planning » tient sur deux lignes à 390 px ; la tuile porte donc une hauteur
   minimale, sinon celle qui tient sur une ligne serait plus courte que ses
   voisines et la rangée se lirait de travers. ⛔ Et surtout pas un
   `minmax(0, 1fr)` avec un `overflow: hidden` : la fin du libellé
   disparaîtrait sans un signe — le § 5, appliqué à un bouton. */
/* ⚠️ **Une colonne depuis P04 — 10/09.** C'était une grille de quatre tuiles
   égales ; le geste principal est désormais un bouton pleine largeur, et les
   trois rangements une rangée sous lui. Les libellés ont maigri avec — « Ajouter
   au planning » est devenu « Planning », parce qu'une tuile de tiers de largeur
   ne porte pas trois mots.

   ⚠️ **Ce qui reste vrai du § du 06/09** : mesuré sur un iPhone SE de 1ʳᵉ
   génération, trois colonnes y font 88 px, ce qui tient — là où quatre en
   faisaient 64 et poussaient « Ajouter au planning » sur trois lignes. */
.recipe-actions { margin: 4px 0 14px; }
.recipe-action {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 7px; min-height: 78px; padding: 12px 4px;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: inherit; cursor: pointer;
  font: inherit; font-size: 18px; text-align: center; line-height: 1.3;
}
.recipe-action > .icon { width: 22px; height: 22px; flex: none; }
/* ⚠️ **Le chevron suit le dernier mot, il ne prend pas sa ligne.** Posé en
   troisième enfant de la colonne, il en occupait une à lui seul : la tuile
   passait à trois lignes et devenait la plus haute des quatre, donc la seule
   qui attire l'œil — l'inverse de ce qu'une rangée de pairs doit dire. Inline,
   il se lit comme la ponctuation du libellé.

   ⚠️ **Et il tourne par `transform`, jamais par un second dessin** : ce qui
   s'anime ne relayoute pas (§ 6). */
.recipe-action .chevron {
  width: 13px; height: 13px; vertical-align: -1px;
  transition: transform .18s ease;
}
.recipe-action[aria-expanded="true"] {
  background: var(--terra); border-color: var(--terra); color: #FFFFFF;
}
.recipe-action[aria-expanded="true"] .chevron { transform: rotate(180deg); }

/* ── Le panneau des courses ────────────────────────────────────────────────── */
/* ⛔ **Aucun `overflow: hidden` ici, et c'est une panne mesurée.** Il ne servait
   qu'à border les coins arrondis — ce dont rien n'a besoin, aucun enfant ne
   peignant dans les angles. En revanche il met la **taille minimale
   automatique** d'un élément de grille à zéro : au-delà du palier de 1 180 px
   du § 10, où `#screen-recipe` devient une grille, le panneau se réduisait à
   **40 px pour 754 de contenu**. Le § 2 dans sa version la plus coûteuse — la
   carte est peinte, un test de présence la trouve, et il n'y a rien à lire
   dedans. ⚠️ **Et le défaut n'existe pas sur un téléphone** : à 390 px le
   parent est un bloc, et un bloc ne rétrécit pas ses enfants. */
.courses-panneau { padding: 0; }
.cp-tete {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 16px 16px 12px; border-bottom: 1px solid var(--line);
}
.cp-tete > span:nth-child(2) { flex: 1; display: flex; flex-direction: column; gap: 3px; }
.cp-tete strong { font-size: 24px; }
.cp-tete small { color: var(--muted); font-size: 19px; }
.cp-icone { color: var(--terra); display: grid; place-items: center; }
.cp-icone .icon { width: 22px; height: 22px; }
.cp-fermer { flex: none; transform: rotate(90deg); }

.cp-barre {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 0 10px;
  padding: 12px 16px 8px; font-size: 20px;
}
/* ⚠️ **Aucun des deux ne se coupe au milieu d'un mot**, mesuré à 390 px :
   « Tout sélectionner » passait sur deux lignes et poussait sa case sous le
   texte, où plus rien ne disait qu'elles allaient ensemble. ⚠️ **La barre, elle,
   se replie** : à 320 px les deux demandent 269 px pour 248, et une commande
   passée à la ligne se lit encore — un titre coupé à l'ellipse, non. */
.cp-tout { white-space: nowrap; }
/* ⚠️ **C'est le titre qui cède, pas la commande.** « Ingrédients (8 portions) »
   se coupe à l'ellipse si la place manque ; « Tout sélectionner » et sa case,
   elles, restent entières — une commande à moitié écrite ne se presse pas. */
.cp-barre > span:first-child {
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cp-barre small { color: var(--muted); }
/* ⚠️ **Un label, pas un `<span>` à côté** : la zone de tap couvre alors le mot
   *et* la case — 44 px de haut sur toute la largeur du texte, là où une case de
   18 px seule demande de viser. */
.cp-tout {
  display: flex; align-items: center; gap: 8px;
  color: var(--muted); font-size: 18px; cursor: pointer; min-height: 44px;
}
/* ⛔ **Masquée, pas supprimée.** `display: none` la sortirait de l'ordre de
   tabulation et des lecteurs d'écran : c'est elle qui porte l'état, le carré
   dessiné n'en est que le reflet. */
.cp-tout input { position: absolute; opacity: 0; width: 0; height: 0; }

/* ⚠️ **Les gouttières se mesurent, elles ne s'estiment pas.** Relevé à 390 px :
   la case, la vignette, trois gouttières de 11 px et la quantité ne laissaient
   que **141 px** à la phrase d'état, qui en demande 141 pour « Dans mon frigo ·
   450 g » — donc deux lignes sur la ligne la plus banale de la liste. Serrer de
   12 px suffit, et rien d'autre ne bouge. */
.cp-ligne {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 16px; border-top: 1px solid var(--line); cursor: pointer;
}
.cp-ligne .diary-thumb { width: 30px; height: 30px; border-radius: 9px; flex: none; }
.cp-ligne .diary-thumb-empty .icon { width: 17px; height: 17px; }
.cp-corps { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cp-nom { font-size: 22px; }
/* La quantité de la recette, à droite, comme sur un ticket : c'est ce qu'on
   compare d'une ligne à l'autre. */
.cp-qte { flex: none; font-weight: 600; font-size: 21px; }
.cp-qte.unknown { color: var(--muted); font-weight: 400; font-style: italic; }

/* Même repli, même raison : à 320 px les deux libellés demandent plus que la
   carte, et c'est le compte — le seul des deux qui change — qui se faisait
   couper par l'`overflow: hidden` du panneau. */
.cp-pied {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 4px 10px;
  padding: 12px 16px; border-top: 1px solid var(--line);
  color: var(--muted); font-size: 18px;
}
.cp-pied > * { white-space: nowrap; }
.cp-pied .link-btn { font-size: 18px; display: flex; align-items: center; gap: 6px; }
/* ⛔ **`width: auto` ne remplit pas une pilule, il la rétrécit.** Un `<button>`
   garde une largeur d'ajustement au contenu même passé en `display: flex` :
   mesuré à 390 px, il faisait **324 px pour 310 de carte** et son libellé se
   faisait couper par l'`overflow: hidden` du panneau. C'est le § 2 dans sa
   version de bordure — le bouton est peint, la fin de sa phrase ne l'est pas. */
.cp-valider { display: flex; align-items: center; justify-content: center; gap: 9px;
  width: calc(100% - 32px); margin: 0 16px 14px; min-height: 50px; padding: 13px 14px;
  background: var(--bouton-fond); border-radius: 999px; font-size: 21px;
  white-space: nowrap; }
.cp-valider .icon { width: 20px; height: 20px; }
.cp-recette { padding: 0 16px 16px; display: flex; flex-direction: column; gap: 4px; }

/* La case, dessinée : celle du navigateur ne se teint pas de la même façon d'un
   système à l'autre, et c'est le seul repère de sélection de tout l'écran. */
.cp-case {
  width: 24px; height: 24px; flex: none; border-radius: 8px;
  border: 1.5px solid var(--line); background: transparent;
  display: grid; place-items: center; color: transparent;
}
.cp-ligne.on .cp-case, .cp-tout input:checked + .cp-case {
  background: var(--terra); border-color: var(--terra); color: #FFFFFF;
}
.cp-case .icon { width: 16px; height: 16px; }

.collect-hint { color: var(--muted); font-size: 19px; }

/* ── Le geste qui ferme la page — 06/09 ────────────────────────────────────
   ⚠️ **Pleine largeur, parce que ce n'est pas une note de bas de page.**
   `.btn-ghost` posé à sa taille naturelle en ferait un lien discret sous une
   liste d'étapes, c'est-à-dire l'endroit exact où l'on ne regarde plus — le
   § 8, mot pour mot, sur le bouton de pellicule. */
.fin-de-recette { margin: 4px 0 24px; text-align: center; }
.fin-de-recette .btn-ghost {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; min-height: 50px;
}
.fin-de-recette .icon { width: 19px; height: 19px; }
.fin-de-recette .hint { margin: 8px 0 0; }
/* Sous les tags, hors de toute rangée : un `<span>` nu ne prendrait pas sa
   ligne, et le rappel se collerait au dernier tag. */
#recipe-collect-hint { display: block; margin: 0 0 10px; }
#recipe-collect-hint[hidden] { display: none; }
.hero-collect.on { background: var(--bouton-fond); color: var(--bouton-texte); }

/* ── Recherche et filtres ────────────────────────────────────────────────── */

.search-row { display: flex; gap: 10px; margin-bottom: 14px; }
.search-row input {
  flex: 1; border: 1px solid var(--line); border-radius: 14px;
  padding: 11px 14px; background: var(--card); color: inherit; min-height: 44px;
  /* ⚠️ **R12, 21/09 — la largeur d'un champ suit la taille de son texte.** Un
     `<input>` a une largeur intrinsèque d'une vingtaine de caractères, et
     `min-width: auto` — la valeur d'un élément flex — interdit de descendre en
     dessous. À 24 px de Caveat, `#search` réclamait 271 px dans une rangée de
     280 : à 320 px le bouton de filtre et la loupe de la Communauté sortaient
     de la colonne. Ici la largeur vient du `flex`, pas du contenu. */
  min-width: 0;
}

/* Une seule rangée qui défile horizontalement plutôt qu'une rangée par
   groupe : cinq rangées de puces repoussaient les recettes hors de l'écran,
   et les noms de groupes n'étaient de toute façon pas affichés. */
/* ── Les étiquettes, dans le panneau de filtres — 10/09 ──────────────────────
   **Elles vivaient sous la barre de recherche, en rail horizontal.** Deux lignes
   de filtres s'y suivaient — les étiquettes et les catégories — alors que le
   bouton juste à droite du champ ouvre le panneau qui les porte toutes.

   ⚠️ **Ici elles se replient au lieu de défiler.** Un rail cache ce qu'il ne
   montre pas ; un panneau a la hauteur qu'il faut, et c'est là qu'on vient
   choisir précisément. ⛔ Et chaque groupe porte son **nom** : dans le rail il
   vivait en infobulle, donc nulle part au doigt — « Dessert » et « Goûter » se
   ressemblaient sans qu'on sache que l'un est un type et l'autre un moment. */
.tag-groupe {
  margin: 12px 0 6px; color: var(--muted); font-size: 19px; font-weight: 600;
}
.tag-groupe:first-child { margin-top: 0; }
.tag-puces { display: flex; flex-wrap: wrap; gap: 7px; }

.chip {
  border: 1px solid var(--line); background: var(--card);
  color: var(--bouton-second-texte);
  border-radius: 999px; padding: 7px 14px; cursor: pointer;
  font-size: 20px; white-space: nowrap; min-height: 38px;
}
/* ⚠️ **Choisie, elle devient un bouton plein** — donc les jetons du bouton
   (R05). Sur `--cinnamon`, qui est une couleur d'encre, le blanc rendait
   2,19:1 en sombre : « Tous » était un filtre qu'on ne savait plus lire. */
.chip.on {
  background: var(--bouton-fond); border-color: var(--bouton-fond);
  color: var(--bouton-texte);
}

/* ── « Pour cette quantité » / « Pour 100 g » — § 6.5 ─────────────────────────
   Deux onglets exclusifs, pas des filtres : ils ne restreignent rien, ils
   changent la **base** de ce qu'on lit. Ils reprennent `.chip` parce que c'est
   la même forme à l'œil, et `aria-selected` parce que ce n'en est pas la même
   fonction.

   ⚠️ **44 px et non 38.** `.chip` en fait 38, ce qui est déjà une dette connue
   (119 cibles sous 44 px, relevé du 19/08) ; une cible neuve n'a aucune raison
   de la creuser. Les deux boutons partagent la largeur — la feuille est étroite
   et « Pour cette quantité » n'y tient pas deux fois. */
.base-bascule { gap: 6px; margin: 10px 0 0; overflow: visible; padding-bottom: 0; }
.base-bascule .chip { flex: 1; min-height: 44px; text-align: center; white-space: normal; }
/* Le groupe de repas est un choix **exclusif** parmi quatre, pas un filtre : il
   ne restreint aucune liste, il dit où l'entrée va se ranger. D'où `aria-pressed`
   plutôt que `.chip.on`, que le § des pastilles de rappel réserve à ce qui filtre.
   Même teinte assumée — dans une feuille à quatre options, « retenu » ne se
   confond avec rien. */
/* ⚠️ Un choix exclusif **ne défile pas**. `.filters` est écrit pour de longues
   rangées de puces et les fait défiler horizontalement en masquant la barre :
   sur 390 px, la quatrième — « Collation » — sortait du cadre, coupée en deux.
   Une valeur retenue invisible est pire que pas de sélecteur du tout, et rien
   n'indiquait qu'il fallait faire glisser. Alors on enroule sur deux lignes :
   quatre options tiennent toujours à l'écran, sur tous les téléphones. */
.meal-groups {
  margin-bottom: 12px;
  flex-wrap: wrap;
  overflow-x: visible;
}
/* R05, revue du 21/09 — le groupe retenu est un bouton plein, mêmes jetons. */
.meal-groups .chip[aria-pressed="true"] {
  background: var(--bouton-fond); border-color: var(--bouton-fond); color: var(--bouton-texte);
}
.chip:disabled { cursor: default; }

.active-filters {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 14px; color: var(--muted); font-size: 20px;
}

.tag-row { display: flex; gap: 7px; flex-wrap: wrap; margin-bottom: 16px; }
/* ⚠️ **Une rangée vide garde sa marge, et ça se voit dès qu'on la regarde.** La
   règle vivait sous le palier de 1 180 px, où une grille donne sa rangée à un
   enfant sans hauteur ; sur téléphone, le blanc de 24 px existait aussi et
   passait inaperçu **tant que le panneau du titre était une carte encadrée**.
   Bord à bord, il se lit comme un trou entre le titre et le premier geste.
   ⛔ Une règle qui ne vaut qu'au-delà d'un palier est une seconde mise en page :
   c'est le même défaut aux deux largeurs, donc la même règle. */
.tag-row:empty { display: none; }
.tag {
  background: var(--card); border: 1px solid var(--line); color: var(--muted);
  border-radius: 999px; padding: 4px 11px; font-size: 19px;
}

/* ── Cartes ──────────────────────────────────────────────────────────────── */

.card {
  background: var(--card); border-radius: var(--radius);
  padding: 20px; margin-bottom: 16px; box-shadow: var(--shadow);
}
.card-title { font-size: 25px; margin: 0 0 14px; }

.card textarea {
  width: 100%; border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit;
  resize: vertical; margin-bottom: 12px; font-size: 24px;
}

.soon { text-align: center; }
.soon .hint { text-align: left; margin-top: 12px; }

.stats { list-style: none; margin: 0; padding: 0; }
.stats li {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 22px;
}
.stats li:last-child { border-bottom: none; }
.stats b { font-weight: 600; }

/* ── La fiche produit détaillée — § 13 bis ────────────────────────────────────
   Un tableau de composition, pas un graphique : les micronutriments se lisent en
   liste, avec la part de la VNR en barre. La barre est un `<i>` dans une piste —
   même forme que `.calc-bar`, qui sert déjà la part d'un ingrédient dans le
   détail du calcul : la même question posée à une autre échelle ne se dessine
   pas deux fois. */
.micro-list { list-style: none; margin: 0; padding: 0; }
.micro-ligne {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 2px 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.micro-ligne:last-child { border-bottom: none; }
.micro-nom { font-size: 22px; min-width: 0; }
.micro-valeur { font-size: 21px; color: var(--muted); font-variant-numeric: tabular-nums; }
.micro-part { font-size: 21px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Sur toute la largeur, sous les trois colonnes qu'elle résume. */
.micro-piste {
  grid-column: 1 / -1; display: block; height: 4px;
  background: var(--line); border-radius: 2px; overflow: hidden;
}
.micro-piste i { display: block; height: 100%; background: var(--sage); }

/* Le texte d'ingrédients est **contributif** : recopié à la main de l'emballage,
   souvent en majuscules et d'un seul tenant. `word-break` évite qu'un nom de
   E-additif de trente caractères ne déborde la carte. */
.fd-ingredients {
  margin: 0; font-size: 22px; line-height: 1.55; color: var(--muted);
  overflow-wrap: anywhere;
}

/* Les photos d'étiquette. Une grille de deux : le tableau nutritionnel est au
   dos, donc il y en a souvent deux, et les empiler à pleine largeur ferait deux
   écrans de défilement pour une carte de vérification. */
.fd-etiquettes {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.fd-etiquette {
  width: 100%; border-radius: 12px; border: 1px solid var(--line);
  background: var(--cream);
}

/* 16 px minimum — § 1, verrou n° 4 : en dessous, iOS agrandit la page à la mise
   au point et la laisse décalée quand le clavier se referme. */
#fd-note {
  width: 100%; font: inherit; font-size: 24px; line-height: 1.5;
  border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit;
  resize: vertical;
}
#fd-note-ok { margin-top: 0; }

/* ── P28 — la fiche aliment ou produit du mockup, 15/09 ─────────────────────
   ⚠️ Toutes les règles nouvelles sont scopées sur `#screen-fooddetail`. */
#screen-fooddetail .fd-head { display: flex; align-items: center; margin-bottom: 4px; }

/* La grande illustration à gauche, le nom en serif à droite. */
#screen-fooddetail .fd-hero {
  display: grid; grid-template-columns: minmax(0, 42%) minmax(0, 1fr); align-items: center; gap: 14px;
  margin-bottom: 14px;
}
/* Posée librement, comme sur la maquette — retour de Luca du 15/09 : la boîte
   pâle en faisait une vignette de liste. Le repli sans image garde un rond. */
#screen-fooddetail .fd-hero-image .diary-thumb {
  width: 100%; height: auto; aspect-ratio: 1; border-radius: 50%; background: none;
}
#screen-fooddetail .fd-hero-image .diary-thumb img { object-fit: contain; filter: drop-shadow(0 10px 14px rgb(46 36 29 / 14%)); }
#screen-fooddetail .fd-hero-image .diary-thumb-empty { background: color-mix(in srgb, var(--terra) 8%, var(--card)); }
#screen-fooddetail .fd-hero-image .diary-thumb-empty .icon { width: 44%; height: 44%; }
#screen-fooddetail .fd-hero-texte { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; min-width: 0; }
#screen-fooddetail .fd-pastilles { display: flex; flex-wrap: wrap; gap: 6px; }
#screen-fooddetail .fd-pastilles:not(:has(:not([hidden]))) { display: none; }
#screen-fooddetail .fd-nom {
  margin: 0; font-family: var(--ecriture); font-weight: 400; font-size: 32.5px; line-height: 1.1;
  overflow-wrap: anywhere;
}
#screen-fooddetail .fd-pastille {
  display: inline-block; border-radius: 999px; padding: 4px 11px; font-size: 21px;
  background: color-mix(in srgb, var(--sage) 16%, var(--card)); color: color-mix(in srgb, var(--sage) 55%, var(--espresso));
}
#screen-fooddetail .fd-pastille-famille { background: color-mix(in srgb, var(--terra) 10%, var(--card)); color: var(--cinnamon); }

/* L'énergie en tuile, puis les trois anneaux en tuiles. */
#screen-fooddetail .fd-resume { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
#screen-fooddetail .fd-energie {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: 16px;
  background: color-mix(in srgb, var(--terra) 8%, var(--card)); border: 1px solid var(--line);
}
#screen-fooddetail .fd-energie-icone {
  flex: none; width: 48px; height: 48px; border-radius: 12px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--terra) 16%, var(--card)); color: var(--danger);
}
#screen-fooddetail .fd-energie-icone .icon { width: 28px; height: 28px; }
#screen-fooddetail .fd-energie-texte { display: flex; flex-direction: column; min-width: 0; }
#screen-fooddetail .fd-energie-texte b { font-family: var(--ecriture); font-weight: 400; font-size: 32.5px; line-height: 1.1; }
#screen-fooddetail .fd-energie-texte small { color: var(--muted); font-size: 21px; }
/* ── Le résumé de la maquette — retour de Luca du 15/09 ─────────────────────
   L'énergie en tuile à gauche, quatre jauges à droite dans une carte. Elles
   passent sous l'énergie quand la colonne ne tient plus quatre noms entiers. */
#screen-fooddetail .fd-resume {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin-bottom: 14px;
}
#screen-fooddetail .fd-energie { padding: 10px 12px; }
#screen-fooddetail .fd-energie-icone { width: 44px; height: 44px; }
#screen-fooddetail .fd-energie-texte b { font-size: 31px; }
#screen-fooddetail .fd-jauges {
  list-style: none; margin: 0; padding: 6px; border-radius: 16px;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px;
  background: var(--card); border: 1px solid var(--line);
}
/* ⚠️ `min-width: 0` : un élément de grille vaut `min-width: auto` et déborde sa
   colonne au lieu de céder (la leçon des anneaux du 16/08). Et le nom ne se
   coupe jamais — c'est la rangée qui passe sous l'énergie. */
#screen-fooddetail .fd-jauge {
  display: flex; flex-direction: column; gap: 3px; min-width: 0;
  padding: 8px 8px 10px; border-radius: 12px; border: 1px solid var(--line);
}
#screen-fooddetail .fd-jauge-nom { font-size: 20px; color: var(--espresso); white-space: nowrap; }
#screen-fooddetail .fd-jauge b { font-size: 22.5px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
@media (max-width: 379px) {
  #screen-fooddetail .fd-jauges { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
#screen-fooddetail .fd-jauge-piste {
  display: block; height: 6px; margin-top: 4px; border-radius: 3px; overflow: hidden;
  background: var(--line);
}
#screen-fooddetail .fd-jauge-piste i { display: block; height: 100%; border-radius: 3px; }
@media (min-width: 600px) {
  #screen-fooddetail .fd-resume { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); align-items: stretch; }
}

/* Les rubriques : cinq ancres sur une rangée, la courante en terre cuite. */
#screen-fooddetail .fd-rubriques {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 4px;
  padding: 4px; margin-bottom: 14px; border-radius: 16px;
  background: var(--card); border: 1px solid var(--line);
}
#screen-fooddetail .fd-rubriques button {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 56px; min-width: 0; padding: 6px 2px; border: none; border-radius: 12px;
  background: none; color: var(--espresso); font: inherit; font-size: 19.5px; cursor: pointer;
}
#screen-fooddetail .fd-rubriques button .icon { width: 21px; height: 21px; color: var(--cinnamon); }
/* ⚠️ `class="on"` est écrit dans `index.html` : la rubrique retenue est peinte
   **dès l'ouverture** de P28, donc ce couple-là n'attendait aucun geste pour
   rendre 2,19:1 en thème sombre. */
#screen-fooddetail .fd-rubriques button.on { background: var(--bouton-fond); color: var(--bouton-texte); }
#screen-fooddetail .fd-rubriques button.on .icon { color: var(--bouton-texte); }
#screen-fooddetail .fd-rubriques button[hidden] { display: none; }
@media (max-width: 359px) { #screen-fooddetail .fd-rubriques button { font-size: 17px; } }

#screen-fooddetail .card { scroll-margin-top: 12px; }
#screen-fooddetail .fd-titre {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
  font-family: var(--ecriture); font-weight: 400; font-size: 27px;
}
#screen-fooddetail .fd-titre .icon { flex: none; width: 22px; height: 22px; color: var(--cinnamon); }
#screen-fooddetail .fd-titre-ligne { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
#screen-fooddetail .fd-titre-ligne .fd-titre { margin-bottom: 10px; }

/* Détail et vitamines dans une carte, en deux colonnes séparées d'un filet,
   comme sur la maquette ; l'une sous l'autre sous 360 px. */
#screen-fooddetail .fd-base {
  flex: none; padding: 5px 12px; border-radius: 999px; font-size: 21px; margin-bottom: 10px;
  background: color-mix(in srgb, var(--terra) 9%, var(--card)); color: var(--cinnamon);
}
#screen-fooddetail .fd-colonnes { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
@media (min-width: 360px) {
  #screen-fooddetail .fd-colonnes { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0; }
  #screen-fooddetail .fd-colonnes > :first-child { padding-right: 12px; border-right: 1px solid var(--line); }
  #screen-fooddetail .fd-colonnes > .fd-micro { padding-left: 12px; }
}
#screen-fooddetail .fd-sous-titre { margin: 0 0 4px; font-family: var(--ecriture); font-size: 22px; font-weight: 600; }
#screen-fooddetail .fd-micro .micro-ligne { grid-template-columns: minmax(0, 1fr) auto auto; gap: 6px; padding: 6px 0; }
#screen-fooddetail .fd-micro .micro-nom { font-size: 20px; }
#screen-fooddetail .fd-micro .micro-valeur { font-size: 19.5px; }
#screen-fooddetail .fd-micro .micro-part { font-size: 19.5px; color: var(--muted); font-weight: 400; min-width: 2.6em; text-align: right; }
#screen-fooddetail .fd-tout-voir {
  min-height: 44px; padding: 0; border: none; background: none; cursor: pointer;
  color: var(--cinnamon); font: inherit; font-size: 21px; font-weight: 600;
}
#screen-fooddetail .fd-note-ar { margin: 2px 0 0; text-align: right; color: var(--muted); font-size: 19px; }
#screen-fooddetail .fd-table { list-style: none; margin: 0; padding: 0; }
#screen-fooddetail .fd-table li {
  display: flex; justify-content: space-between; gap: 8px; padding: 7px 0;
  border-bottom: 1px solid var(--line); font-size: 22px;
}
#screen-fooddetail .fd-table li:last-child { border-bottom: none; }
#screen-fooddetail .fd-table li.fd-sous-ligne { padding-top: 0; margin-top: -4px; color: var(--muted); font-size: 19.5px; padding-left: 10px; }
#screen-fooddetail .fd-table b { font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
#screen-fooddetail .fd-table li > span { min-width: 0; }
#screen-fooddetail .fd-table-note { margin-top: 8px; }

/* Les portions : des tuiles, touchables seulement quand elles mènent quelque part. */
#screen-fooddetail .fd-perso {
  min-height: 44px; padding: 6px 12px; margin-bottom: 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--terra) 30%, var(--line));
  background: color-mix(in srgb, var(--terra) 8%, var(--card)); color: var(--cinnamon);
  font: inherit; font-size: 21px;
}
#screen-fooddetail .fd-portions { display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: 8px; }
#screen-fooddetail .fd-portion {
  display: flex; flex-direction: column; align-items: center; gap: 1px; min-height: 44px;
  padding: 8px 4px 10px; border-radius: 14px; text-align: center;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso); font: inherit;
}
#screen-fooddetail .fd-portion .diary-thumb { width: 52px; height: 40px; background: none; margin-bottom: 4px; }
#screen-fooddetail .fd-portion .diary-thumb img { object-fit: contain; }
#screen-fooddetail button.fd-portion { cursor: pointer; }
#screen-fooddetail .fd-portion b { font-weight: 500; font-size: 21px; overflow-wrap: anywhere; }
#screen-fooddetail .fd-portion small { color: var(--muted); font-size: 19.5px; }
#screen-fooddetail .fd-portion > span:last-child { font-size: 22.5px; font-weight: 500; color: var(--espresso); margin-top: 2px; }
@media (hover: hover) and (pointer: fine) {
  #screen-fooddetail button.fd-portion:hover { border-color: var(--terra); }
}

/* L'historique : un résumé en une ligne, sans chevron. */
#screen-fooddetail .fd-journal { display: flex; align-items: center; gap: 12px; }
#screen-fooddetail .fd-journal-icone { flex: none; color: var(--cinnamon); }
#screen-fooddetail .fd-journal-icone .icon { width: 26px; height: 26px; }
#screen-fooddetail .fd-journal-texte { display: flex; flex-direction: column; min-width: 0; }
#screen-fooddetail .fd-journal-texte small { color: var(--muted); font-size: 20px; }
#screen-fooddetail .fd-journal-texte b { font-weight: 400; font-size: 24px; }

#screen-fooddetail .fd-note-pied { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
#screen-fooddetail .fd-note-etat { color: var(--muted); font-size: 20px; }

/* « Ajouter au journal » reste sous le pouce pendant qu'on lit — `sticky`, dans
   l'écran qui défile, donc aucune couche fixe à recadrer (§ 2). */
#screen-fooddetail .fd-actions {
  /* ⚠️ Mesuré au banc : l'écran porte `padding-bottom: var(--tabbar-h) + 32px`,
     et l'aimant `sticky` s'arrête **au bord de ce rembourrage**. À `bottom: 0`,
     32 px de contenu défilaient entre le bouton et la barre d'onglets ; à
     `--tabbar-h`, 100 px. `-32px` rend exactement ces 32 px — le bouton se pose
     sur la barre, et sur le bas de l'écran quand le rail la remplace (§ 10). */
  position: sticky; bottom: -32px; z-index: 2;
  margin: 12px -20px 0; padding: 12px 20px;
  background: var(--cream); border-top: 1px solid var(--line);
}
#screen-fooddetail .fd-actions .btn-primary {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; min-height: 52px; font-size: 24px;
}
#screen-fooddetail .fd-actions .icon { width: 20px; height: 20px; }

/* ── Le panier de la sélection multiple — § 13 ────────────────────────────────
   `position: fixed` : il reste sous le pouce pendant qu'on fait défiler les
   résultats. Il rejoint donc la règle de recadrage en tête de ce fichier (§ 2) —
   sans elle, il ferait 1280 px de large devant une colonne de 720.

   ⛔ **Fond opaque, et pas de `backdrop-filter`.** C'était la cause n° 1 de la
   saccade du 16/08, et la correction spontanée — garder la translucidité sans le
   flou — est pire : on voit les résultats glisser dessous en clair. */
.fs-panier {
  /* ⚠️ **Au-dessus de la barre d'onglets, pas dessous.** Posé à `bottom: 0`
     comme elle et au même `z-index`, il passait **derrière** : mesuré au banc,
     le total et le bouton « Ajouter les 2 » étaient entièrement recouverts, et
     le panier ne montrait que ses deux premières lignes. Un pied d'écran dont
     l'action est cachée par une autre couche fixe est le § 2 dans sa version la
     plus coûteuse — la couche existe, elle est juste inatteignable. */
  position: fixed; bottom: var(--tabbar-h); z-index: 41;
  background: var(--card); border-top: 1px solid var(--line);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -6px 20px rgb(0 0 0 / 8%);
  padding: 10px 14px 12px;
}
/* La liste des lignes retenues défile si elle dépasse : quatre entrées tiennent,
   la cinquième pousserait le bouton d'ajout hors de vue — le défaut du § 5, à
   l'envers. Plafond mesuré sur trois lignes plus une amorce, pour que la
   présence d'une suite se voie. */
.fs-panier-liste {
  list-style: none; margin: 0 0 8px; padding: 0;
  max-height: 148px; overflow-y: auto;
}
.fs-panier-ligne {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 0; min-height: 44px;
}
.fs-panier-nom {
  flex: 1; min-width: 0; font-size: 22px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fs-panier-qte { color: var(--muted); font-size: 19.5px; font-variant-numeric: tabular-nums; }
.fs-panier-retirer {
  flex: none; width: 44px; height: 44px; border: none; background: none;
  color: var(--muted); cursor: pointer; font: inherit; font-size: 26.5px;
  line-height: 1;
}
.fs-panier-pied { display: flex; align-items: center; gap: 12px; }
.fs-panier-total { margin: 0; flex: 1; font-size: 21px; color: var(--muted); }
.fs-panier-total b { color: var(--espresso); font-size: 24px; }
.fs-panier-pied .btn-primary { flex: none; width: auto; padding: 12px 18px; }

/* Et l'écran réserve la place du panier quand il est ouvert : sans ça, les
   derniers résultats de la liste vivent sous lui et on coche à l'aveugle. La
   classe est posée sur l'écran, pas une hauteur devinée en JavaScript — le
   panier fait entre 100 et 250 px selon le nombre de lignes, et le mesurer à
   chaque ajout remettrait une lecture de disposition dans un geste (§ 6). */
#screen-foodsearch.a-un-panier { padding-bottom: calc(var(--tabbar-h) + 260px); }

/* La ligne retenue se marque dans la liste des résultats : sans ça, toucher
   deux fois le même aliment est le geste évident, et rien ne dit qu'il y est
   déjà. */
/* `inset` plutôt qu'une bordure : une bordure de 2 px décalerait la ligne de
   deux pixels au moment où on la coche, et toute la liste sauterait sous le
   doigt. Le liseré se dessine à l'intérieur, rien ne bouge. */
.recall.fs-retenu {
  box-shadow: inset 0 0 0 2px var(--terra);
  border-radius: 12px; border-top-color: transparent;
  padding-left: 8px; padding-right: 8px;
}
/* Le mode est un état de l'écran : le bouton le dit (`.fs-multi`, P27). */

/* ── Une ligne qui ouvre autre chose — § 16 ───────────────────────────────────
   Le chevron dit qu'il y a une suite ; c'est ce qui la distingue d'une ligne de
   `.stats`, qui affiche une valeur. 44 px de haut, comme toute cible du § 11 :
   une ligne de liste se vise au pouce en descendant, pas en s'arrêtant. */
.row-link {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; min-height: 44px; gap: 12px;
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 22px; color: inherit; text-align: left;
}
.row-link span { color: var(--muted); font-size: 25px; line-height: 1; }
.prefs-liens li { padding: 0; }

/* ── Les préférences, dans leur feuille ──────────────────────────────────────
   Une ligne de réglage comme celles de C05 et C30 (`.reglage-ligne` + la
   bascule sauge) : ce qui se règle au même geste se dessine pareil. Il ne reste
   ici que le filet qui sépare les sept lignes et leur typographie — le reste
   viendrait en double. */
.pref-ligne { border-bottom: 1px solid var(--line); min-height: 44px; }
.pref-ligne:last-of-type { border-bottom: none; }
/* ⚠️ **Deux classes, sinon `.reglage-ligne small` gagne par sa place** : elle
   est déclarée plus bas dans la feuille et repeindrait le détail en cannelle. */
.reglage-ligne.pref-ligne strong { display: block; font-size: 22.5px; font-weight: 600; }
.reglage-ligne.pref-ligne small { color: var(--muted); font-size: 19.5px; line-height: 1.4; }
/* Ce qu'on montre quand le serveur n'a rien rendu : la phrase et le geste qui
   la lève, au même retrait que le titre. */
.prefs-panne { padding: 2px 14px 6px; }
.prefs-panne .hint { margin-bottom: 8px; }

/* ── Les vidéos importées — § 16 ──────────────────────────────────────────────
   Vignette, titre, durée : les trois choses par lesquelles on se souvient d'une
   vidéo. La durée est en pied de ligne et non sur la vignette — posée dessus,
   elle demanderait un fond translucide au-dessus d'une image, c'est-à-dire une
   couche de plus sur le chemin d'un défilement (§ 6). */
.video-list { list-style: none; margin: 0; padding: 0; }
.video-row {
  display: grid; grid-template-columns: 64px 1fr; gap: 12px; align-items: center;
  width: 100%; min-height: 44px; padding: 10px 0;
  border-bottom: 1px solid var(--line);
  background: none; border-left: none; border-right: none; border-top: none;
  cursor: pointer; font: inherit; color: inherit; text-align: left;
}
.video-list li:last-child .video-row { border-bottom: none; }
.video-thumb {
  width: 64px; height: 48px; border-radius: 10px; object-fit: cover;
  background: var(--cream);
}
.video-thumb-vide {
  display: flex; align-items: center; justify-content: center; color: var(--muted);
}
.video-titre {
  font-size: 22.5px; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.video-meta { font-size: 19.5px; color: var(--muted); margin-top: 3px; }

/* ── Vidéos enregistrées — P23 de la refonte, 14/09 ─────────────────────────
   L'en-tête de P15/P22 (`.ip-head`), une note et un cadre de vidéo dessinés en
   code, la recherche pleine largeur, puis une carte par source : vignette avec
   sa durée, titre de la fiche, créateur, date, et une ligne « Voir la recette »
   par fiche réellement sortie de la vidéo. */
.vs-decor { width: 118px; height: 104px; }
.vs-note {
  position: absolute; left: 8px; top: 8px; width: 90px; min-height: 82px;
  padding: 10px 8px; box-sizing: border-box; border-radius: 8px;
  border: 5px solid color-mix(in srgb, var(--sand) 70%, var(--card));
  background: color-mix(in srgb, var(--sand) 22%, var(--card));
  box-shadow: 0 6px 16px rgba(48, 41, 35, .12); transform: rotate(-6deg);
  font-family: var(--ecriture); font-style: italic; font-size: 18px; line-height: 1.25;
  color: var(--cinnamon); text-align: center;
}
.vs-note-dos { left: 22px; top: 0; transform: rotate(4deg); opacity: .7; min-height: 82px; }
.vs-note:not(.vs-note-dos) { z-index: 1; }
.vs-lecture { z-index: 2; }
.vs-lecture {
  position: absolute; right: -4px; bottom: -10px; display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%; background: var(--terra); color: #FFFFFF;
  box-shadow: 0 4px 10px rgba(48, 41, 35, .18);
}
.vs-lecture .icon { width: 16px; height: 16px; }
@media (max-width: 359px) {
  .vs-decor { width: 96px; height: 90px; }
  .vs-note { width: 76px; min-height: 70px; font-size: 16px; padding: 7px 5px; }
}

.vs-recherche {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px; padding: 0 14px;
  min-height: 48px; border: 1px solid var(--line); border-radius: 14px; background: var(--card);
}
.vs-recherche:focus-within { border-color: color-mix(in srgb, var(--terra) 55%, var(--line)); }
.vs-recherche .icon { flex: none; width: 20px; height: 20px; color: var(--muted); }
/* Un champ ne descend jamais sous 16 px (§ 1). */
.vs-recherche input {
  flex: 1; min-width: 0; min-height: 44px; border: none; background: transparent;
  color: var(--espresso); font: inherit; font-size: 24px; outline: none;
}

.vs-barre { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 12px; }
.vs-compte { margin: 0; color: var(--muted); font-size: 21px; }
.vs-suivi {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px;
  border-radius: 999px; border: 1px solid var(--line); background: var(--card);
  color: var(--espresso); font: inherit; font-size: 21px; cursor: pointer; white-space: nowrap;
}
.vs-suivi .icon { width: 17px; height: 17px; color: var(--sage); }

.vs-liste { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.vs-carte {
  position: relative; display: grid; grid-template-columns: 92px minmax(0, 1fr) 44px;
  grid-template-areas: "vignette corps source" "recettes recettes recettes";
  column-gap: 12px; row-gap: 10px; padding: 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--shadow);
}
.vs-vignette {
  grid-area: vignette; position: relative; width: 92px; height: 92px; padding: 0;
  border: none; border-radius: 12px; overflow: hidden; background: var(--cream); cursor: pointer;
}
.vs-vignette > img, .vs-vignette > .thumb-fallback {
  display: grid; place-items: center; width: 100%; height: 100%; object-fit: cover;
}
.vs-vignette > .thumb-fallback .icon { width: 32px; height: 32px; color: var(--sand); }
.vs-duree {
  position: absolute; right: 5px; bottom: 5px; padding: 2px 6px; border-radius: 6px;
  background: rgba(30, 24, 19, .78); color: #FFFFFF; font-size: 18px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.vs-corps { grid-area: corps; min-width: 0; align-self: center; }
.vs-titre {
  margin: 0 0 4px; font-family: var(--ecriture); font-weight: 400; font-size: 25px;
  line-height: 1.2; overflow-wrap: anywhere;
}
.vs-meta {
  display: flex; align-items: center; gap: 6px; margin: 0 0 2px; min-width: 0;
  color: var(--muted); font-size: 21px;
}
.vs-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vs-meta .icon { flex: none; width: 16px; height: 16px; color: var(--espresso); }
.vs-date { margin: 0; color: var(--muted); font-size: 20px; }
.vs-source {
  grid-area: source; align-self: start; display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 50%; color: var(--muted);
}
.vs-source .icon { width: 20px; height: 20px; }
.vs-source:hover { color: var(--cinnamon); }

.vs-recettes { grid-area: recettes; display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; min-width: 0; }
.vs-plusieurs { margin: 0 2px; color: var(--muted); font-size: 20px; }
.vs-recette {
  box-sizing: border-box; min-width: 0; display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px; padding: 6px 12px 6px 6px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--cream);
  color: var(--espresso); font: inherit; text-align: left; cursor: pointer;
}
.vs-recette-img { flex: none; width: 36px; height: 36px; border-radius: 8px; overflow: hidden; background: var(--card); }
.vs-recette-img > img, .vs-recette-img > .thumb-fallback {
  display: grid; place-items: center; width: 100%; height: 100%; object-fit: cover;
}
.vs-recette-img > .thumb-fallback .icon { width: 18px; height: 18px; color: var(--sand); }
.vs-recette-titre { flex: 1; min-width: 0; font-size: 22px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vs-recette-voir {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  color: var(--cinnamon); font-size: 21px; font-weight: 600;
}
.vs-recette-voir .icon { width: 16px; height: 16px; }
.vs-voir {
  justify-self: end; display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  padding: 0 16px; border-radius: 999px; border: none; cursor: pointer; font: inherit;
  font-size: 22.5px; font-weight: 600; color: var(--cinnamon);
  background: color-mix(in srgb, var(--terra) 12%, var(--card));
}
.vs-voir .icon { width: 17px; height: 17px; }
.vs-voir:focus-visible { outline: 3px solid color-mix(in srgb, var(--terra) 55%, transparent); outline-offset: 2px; }
.vs-vignette:focus-visible, .vs-recette:focus-visible, .vs-source:focus-visible,
.vs-suivi:focus-visible, .vs-ajouter:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--terra) 55%, transparent); outline-offset: 2px;
}
/* Au-delà de 600 px, les lignes de recette passent à droite de la carte, comme
   sur la maquette. */
@media (min-width: 600px) {
  .vs-carte {
    grid-template-columns: 104px minmax(0, 1fr) minmax(220px, 300px) 44px;
    grid-template-areas: "vignette corps recettes source";
  }
  .vs-vignette { width: 104px; height: 104px; }
  .vs-recettes { align-self: center; }
}

.vs-ajouter {
  display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%;
  margin: 14px 0 0; padding: 16px; border: 1.5px dashed color-mix(in srgb, var(--terra) 35%, var(--line));
  border-radius: 16px; background: transparent; color: var(--espresso); font: inherit; cursor: pointer;
}
.vs-ajouter-plus {
  display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%;
  background: var(--terra); color: #FFFFFF;
}
.vs-ajouter-plus .icon { width: 16px; height: 16px; }
.vs-ajouter-texte { display: grid; gap: 2px; text-align: center; }
.vs-ajouter-texte strong { color: var(--cinnamon); font-size: 24px; }
.vs-ajouter-texte small { color: var(--muted); font-size: 20px; }

/* ── Centre d'aide — P36, 18/09 ──────────────────────────────────────────────
   Un `<details>` par question. Pas d'accordéon fait main : l'élément natif
   gère l'ouverture, le clavier et le lecteur d'écran, et il ne met rien sur le
   chemin du défilement.

   ⚠️ Scopé sur `#screen-aide` pour ce que P36 ajoute — § *une règle qui ne
   gagne que par sa place se perd*. `.aide-liste` et `.aide-item` restent nus :
   ils existaient avant, et rien d'autre ne les porte. */
#screen-aide .aide-intro {
  margin: -8px 0 14px; color: var(--muted); font-size: 22.5px; line-height: 1.45;
}
#screen-aide .aide-recherche {
  display: flex; align-items: center; gap: 10px; padding: 0 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  min-height: 52px; margin-bottom: 14px;
}
#screen-aide .aide-recherche > .icon { flex: none; width: 20px; height: 20px; color: var(--muted); }
/* 16 px : § 1, verrou n° 4. Et `appearance: none` retire la croix native de
   Safari, qui doublerait la nôtre sans porter son libellé. */
#screen-aide .aide-recherche input {
  flex: 1; min-width: 0; border: none; background: none; color: inherit;
  font: inherit; font-size: 24px; padding: 0; outline: none;
  appearance: none; -webkit-appearance: none;
}
#screen-aide .aide-recherche input::-webkit-search-cancel-button { display: none; }
#screen-aide .aide-effacer { flex: none; width: 36px; height: 36px; color: var(--muted); }

/* Deux colonnes sur un téléphone, quatre dès qu'il y a la place — le pas se
   déduit de la largeur (§ 10), il ne se décrète pas par palier. */
#screen-aide .aide-rubriques {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px; margin-bottom: 26px;
}
/* ⚠️ **Mesuré au banc : huit tuiles en colonne faisaient 1 000 px** — les
   questions commençaient sous le pli, et la fiche demande précisément que le
   haut ne repousse pas ce qu'on vient lire. L'icône passe à gauche du nom, le
   détail tient sur une ligne : la rangée descend de 130 px à 78. */
#screen-aide .aide-rubrique {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 8px;
  align-items: center; align-content: start;
  min-height: 44px; padding: 12px; cursor: pointer;
  text-align: left; background: var(--card); border: 1px solid var(--line);
  border-radius: 16px; font: inherit; color: var(--espresso);
}
#screen-aide .aide-rubrique > .icon {
  grid-row: span 2; width: 24px; height: 24px; color: var(--cinnamon);
}
#screen-aide .aide-rubrique strong {
  font-family: var(--ecriture); font-size: 24px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ⚠️ **Le détail se replie, il ne se tronque pas** — mesuré à 1 280 px : la
   colonne de lecture tient quatre tuiles de 167 px, et « Ajouter, retrouver »
   y sortait « Ajouter, retrou… ». Une grille égalise les hauteurs de sa
   rangée, donc un repli ne coûte rien ; une troncature coûte un mot. */
#screen-aide .aide-rubrique small {
  font-size: 19px; color: var(--muted); line-height: 1.3;
}
/* ⚠️ **Sous 360 px, la grille tombe à une colonne** — 150 px de pas ne tiennent
   pas deux fois dans 280. Mesuré à 320 × 568 : huit tuiles à deux lignes
   repoussaient « Questions fréquentes » à 640 px du haut. C'est le détail qui
   cède, jamais le nom : c'est lui qui dit de quoi parle la rubrique.
   ⛔ Et pas un pas plus court pour forcer deux colonnes : « Communauté » s'y
   tronquerait, ce qui coûte l'information qu'on venait de garder. */
@media (max-width: 359px) {
  /* Deux colonnes forcées : `minmax(150px, …)` n'en tient qu'une dans 280, et
     huit tuiles en pile repoussaient le titre des questions à 821 px du haut.
     ⚠️ **Mesuré, pas estimé** : le plus large des huit noms — « Communauté » —
     fait 109 px à 16 px, pour 111 disponibles dans une colonne de 135. Deux
     pixels ne se défendent pas, donc le nom passe à 15 px (102 px) et l'icône
     revient au-dessus de lui plutôt qu'à côté. */
  #screen-aide .aide-rubriques { grid-template-columns: repeat(2, 1fr); }
  #screen-aide .aide-rubrique {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: 2px; padding: 10px;
  }
  #screen-aide .aide-rubrique strong { font-size: 22.5px; }
  #screen-aide .aide-rubrique small { display: none; }
}

/* La rubrique ouverte se marque, elle ne se masque pas : escamoter la ligne
   active ferait douter d'avoir compris où l'on en est. */
#screen-aide .aide-rubrique[aria-pressed="true"] {
  border-color: var(--terra);
  background: color-mix(in srgb, var(--terra) 10%, var(--card));
}

#screen-aide .aide-tete {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
}
#screen-aide .aide-titre {
  margin: 0; font-family: var(--ecriture); font-weight: 700; font-size: 27.5px;
  color: var(--espresso);
}
#screen-aide .aide-compte { display: block; color: var(--muted); font-size: 19.5px; margin-top: 2px; }
#screen-aide #aide-tout { flex: none; }
#screen-aide #aide-vide { margin-top: 10px; }

#screen-aide .aide-astuce {
  display: flex; align-items: center; gap: 12px; margin: 18px 0;
  padding: 14px 16px; border-radius: 18px; color: var(--muted);
  font-size: 21px; line-height: 1.45;
  background: color-mix(in srgb, var(--sage) 10%, var(--card));
  border: 1px solid color-mix(in srgb, var(--sage) 24%, var(--line));
}
#screen-aide .aide-astuce-icone {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; color: var(--sage);
  background: color-mix(in srgb, var(--sage) 16%, var(--card));
}
#screen-aide .aide-astuce-icone .icon { width: 22px; height: 22px; }

/* Un lien dans une réponse est un vrai geste : il se voit comme un lien. */
#screen-aide .aide-reponse a {
  color: var(--cinnamon); font-weight: 600; text-decoration: underline;
  text-underline-offset: 2px;
}

.aide-liste { display: grid; gap: 10px; }
.aide-item {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0 14px;
}
.aide-item > summary {
  cursor: pointer; list-style: none; padding: 14px 0;
  font-size: 23px; font-weight: 600; min-height: 44px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.aide-item > summary::-webkit-details-marker { display: none; }
.aide-item > summary::after {
  content: "⌄"; color: var(--muted); font-size: 24px; line-height: 1;
}
.aide-item[open] > summary::after { content: "⌃"; }
.aide-reponse {
  margin: 0; padding: 0 0 14px; color: var(--muted);
  font-size: 22px; line-height: 1.55;
}
.aide-reponse b { color: var(--espresso); font-weight: 600; }

.theme-picker { display: flex; gap: 8px; }
.theme-picker button {
  flex: 1; border: 1px solid var(--line); background: var(--cream);
  color: var(--bouton-second-texte); border-radius: 14px; padding: 10px 8px;
  cursor: pointer; font-size: 20px;
}
/* R05, revue du 21/09 : au repos `--muted` rendait 3,35:1 en clair — lisible
   pour une phrase d'appoint, pas pour le libellé d'un choix. Retenu, le fond
   passe aux jetons du bouton plein. */
.theme-picker button.on {
  background: var(--bouton-fond); border-color: var(--bouton-fond); color: var(--bouton-texte);
}

/* ── Lots d'import ───────────────────────────────────────────────────────── */

/* `.bar` sert encore le suivi du profil et l'accueil. */
.bar { height: 8px; border-radius: 999px; background: var(--line); overflow: hidden; margin: 10px 0 8px; }
.bar > i { display: block; height: 100%; background: var(--sage); transition: width .4s ease; }

/* ── Imports en masse — P15 de la refonte, 13/09 ─────────────────────────── */

/* Le décor : la marmite de P14 et trois cartes de plateformes dessinées en
   code, qui dépassent du bord comme sur la maquette. Aucun texte. */
.il-decor .ip-decor-marmite { bottom: 0; }
.il-carte {
  position: absolute; width: 34px; height: 44px; display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 3px 8px rgba(48, 41, 35, .12); color: var(--espresso);
}
.il-carte .icon { width: 18px; height: 18px; }
.il-carte-1 { left: 28px; top: 4px; transform: rotate(-14deg); }
.il-carte-2 { left: 56px; top: 0; }
.il-carte-3 { left: 84px; top: 6px; transform: rotate(13deg); }
@media (max-width: 359px) {
  .il-carte { width: 26px; height: 34px; }
  .il-carte .icon { width: 14px; height: 14px; }
  .il-carte-1 { left: 18px; } .il-carte-2 { left: 38px; } .il-carte-3 { left: 58px; }
}

/* Les deux onglets : le segmenté des autres écrans de la refonte. */
.il-onglets {
  display: inline-flex; max-width: 100%; gap: 4px; padding: 4px; margin: 0 0 14px;
  background: color-mix(in srgb, var(--sand) 35%, var(--card)); border-radius: 999px;
}
.il-onglet {
  min-height: 44px; padding: 0 16px; white-space: nowrap; border: none; border-radius: 999px;
  background: none; color: var(--muted); font: inherit; font-size: 22.5px; cursor: pointer;
}
/* `--card` et non du blanc : en sombre la terre cuite s'éclaircit, et un texte
   blanc dessus ne se lit plus. */
.il-onglet.on { background: var(--cinnamon); color: var(--card); font-weight: 600; }
/* ⚠️ Écrite **après** la règle de base, et c'est voulu : posée plus haut avec le
   décor, elle perdait au départage à spécificité égale — le § *une règle qui ne
   gagne que par sa place se perd*, mesuré au banc (3 px de trop à 320). */
@media (max-width: 359px) { .il-onglets .il-onglet { padding: 0 10px; font-size: 20px; } }

/* La saisie : **une ligne qui grandit**, et « Importer » à droite. Le champ
   carré de 112 px prenait un tiers d'écran pour un geste qui est presque
   toujours un collage : il démarre à la hauteur d'un bouton, et c'est le
   contenu qui l'agrandit (`ajusterChampLiens`), jusqu'à 180 px où il défile.
   ⚠️ 16 px de texte, § 1 — c'est le rembourrage qui a cédé. */
.il-saisie { padding: 10px; }
.il-saisie-rang { display: flex; gap: 8px; align-items: flex-end; }
.il-champ { position: relative; flex: 1 1 auto; min-width: 0; }
.il-champ-icone { position: absolute; left: 12px; top: 15px; width: 20px; height: 20px; color: var(--muted); pointer-events: none; }
.card .il-saisie-rang textarea {
  display: block; width: 100%; margin: 0; height: 50px; min-height: 50px; max-height: 180px;
  padding: 13px 12px 13px 40px; resize: none; line-height: 22px; overflow-y: hidden;
  border: 1px solid var(--line); border-radius: 14px; background: var(--card);
  white-space: pre; overflow-x: hidden; text-overflow: ellipsis;
}
.card .il-saisie-rang textarea.defile { overflow-y: auto; }
.il-lancer {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 50px; max-width: 124px; padding: 4px 14px; text-align: center; line-height: 1.15;
}
.il-lancer .icon { flex: none; width: 18px; height: 18px; }
/* ⚠️ Sous 375 px le bouton passe dessous : à 320, le champ n'aurait plus que
   130 px, soit « https://www » et rien d'autre (mesuré). */
@media (max-width: 374px) {
  .il-saisie-rang { flex-direction: column; align-items: stretch; }
  .il-lancer { max-width: none; }
}

.il-presse {
  display: flex; align-items: center; gap: 12px; width: 100%; margin: 10px 0 4px;
  padding: 12px 14px; min-height: 56px; text-align: left; cursor: pointer; font: inherit;
  background: color-mix(in srgb, var(--terra) 6%, var(--card));
  border: 1px solid var(--line); border-radius: 16px; color: inherit;
}
.il-presse > .icon { flex: none; width: 24px; height: 24px; color: var(--terra); }
.il-presse strong { display: block; color: var(--cinnamon); font-size: 23px; font-weight: 600; }
.il-presse small { color: var(--muted); font-size: 19.5px; }

.il-chargement { margin: 18px 0; }

/* Les sections : le titre serif, son compte en pastille, le lien à droite. */
.il-section { margin-top: 22px; }
.il-section-tete { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.il-section-tete h2 { margin: 0; font-family: var(--ecriture); font-weight: 400; font-size: 28px; line-height: 1.15; }
.il-compte {
  min-width: 26px; padding: 2px 8px; border-radius: 999px; text-align: center;
  background: color-mix(in srgb, var(--sage) 22%, var(--card)); font-size: 19.5px; color: var(--espresso);
}
.il-compte-erreur { background: color-mix(in srgb, var(--danger) 16%, var(--card)); }
.il-lien {
  margin-left: auto; min-height: 44px; padding: 0 4px; border: none; background: none;
  color: var(--cinnamon); font: inherit; font-size: 22.5px; cursor: pointer; white-space: nowrap;
}
.il-global { margin: -2px 0 10px; }
.il-lots { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
.il-borne { margin-top: 12px; text-align: center; }

/* La carte d'un lot. */
.il-lot { background: var(--card); border: 1px solid var(--line); border-radius: 18px; overflow: hidden; }
.il-lot-tete {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 12px 10px 10px;
  border: none; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.il-lot-vignette { flex: none; width: 76px; height: 64px; }
.il-lot-vignette > img, .il-lot-vignette > div {
  display: block; width: 76px; height: 64px; border-radius: 12px; object-fit: cover; background: var(--cream);
}
.il-lot-vignette .thumb-illus { object-fit: contain; padding: 6px; }
.il-lot-vignette .thumb-fallback { display: grid; place-items: center; color: var(--sand); }
.il-lot-vignette .thumb-fallback .icon { width: 28px; height: 28px; }
.il-lot-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.il-lot-texte strong { font-size: 23px; font-weight: 600; line-height: 1.25; }
.il-lot-texte small { color: var(--muted); font-size: 19.5px; line-height: 1.3; }
.il-lot-barre { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.il-lot-chevron { flex: none; width: 18px; height: 18px; color: var(--muted); transition: transform .18s ease; }
.il-lot.ouvert .il-lot-tete .il-lot-chevron { transform: rotate(180deg); }

/* La barre en segments : une couleur par issue. */
.il-barre {
  flex: 1; display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--line);
}
.il-seg { display: block; height: 100%; }
.il-seg-importees { background: var(--sage); }
.il-seg-neutres { background: var(--sand); }
.il-seg-echecs { background: var(--danger); }
.il-seg-abandons { background: color-mix(in srgb, var(--muted) 45%, var(--line)); }
.il-barre-chiffre { flex: none; font-size: 19px; color: var(--muted); white-space: nowrap; }

.il-lot-erreur { margin: 0 12px 10px; }
.il-alerte {
  display: flex; align-items: center; gap: 12px; width: calc(100% - 20px); margin: 0 10px 10px;
  padding: 10px 12px; min-height: 52px; border: none; border-radius: 14px; cursor: pointer;
  background: color-mix(in srgb, var(--danger) 10%, var(--card)); color: inherit; font: inherit; text-align: left;
}
.il-alerte > .icon:first-child { flex: none; width: 22px; height: 22px; color: var(--danger); }
.il-alerte span { flex: 1; min-width: 0; }
.il-alerte strong { display: block; color: var(--danger); font-size: 22px; font-weight: 600; }
.il-alerte small { color: var(--muted); font-size: 19px; }
.il-alerte .il-lot-chevron { transform: rotate(-90deg); }

.il-lot-corps { padding: 0 12px 12px; }
.batch-items { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; border-top: 1px solid var(--line); }
/* ⚠️ **Une grille, pas un flex** — mesuré sur un lot réel : en flex, chaque
   rangée taillait ses colonnes sur son propre texte, « Analyse en cours… »
   écrasait le titre à « One pot … » et les plateformes ne s'alignaient pas
   d'une ligne à l'autre. Colonnes fixes, comme la maquette : l'état, le titre,
   la plateforme, le mot, le chevron — même vide. */
.batch-item {
  display: grid; grid-template-columns: 20px minmax(0, 1fr) 70px 72px 14px;
  align-items: center; column-gap: 6px; width: 100%; min-height: 44px;
  font: inherit; font-size: 21px; padding: 8px 2px; color: inherit; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--line);
}
button.batch-item { cursor: pointer; }
.batch-item:last-child { border-bottom: none; }
.batch-item .st { flex: none; width: 22px; display: grid; place-items: center; color: var(--muted); }
.batch-item.is-done .st { color: var(--sage); }
.batch-item.is-failed .st { color: var(--danger); }
.batch-item.is-running .st { color: var(--terra); }
.batch-item .tt { flex: 1; min-width: 0; }
.il-ligne-titre { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.batch-item .tt small { display: block; color: var(--muted); font-size: 18px; white-space: normal; }
.il-ligne-plateforme { color: var(--muted); font-size: 19.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.il-ligne-etat { font-size: 19px; line-height: 1.2; color: var(--muted); text-align: right; white-space: nowrap; }
/* Une ligne de compte n'a ni plateforme ni état : son texte prend la rangée. */
.batch-item.il-ligne-compte { grid-template-columns: 20px minmax(0, 1fr); }
.il-ligne-compte .tt { color: var(--muted); }
.il-toutes { margin: 2px 0 0 26px; padding: 0; min-height: 44px; text-align: left; }
.batch-item.is-done .il-ligne-etat { color: var(--sage); }
.batch-item.is-failed .il-ligne-etat { color: var(--danger); }
.batch-item .il-lot-chevron { transform: rotate(-90deg); width: 16px; height: 16px; }
.il-ligne-compte { color: var(--muted); }
/* La plateforme reste à 390, comme la maquette ; elle part sous 375 px, où le
   titre n'aurait plus que 60 px. */
@media (max-width: 374px) {
  .batch-item { grid-template-columns: 20px minmax(0, 1fr) 72px 14px; }
  .il-ligne-plateforme { display: none; }
}

/* Une ligne qu'on n'a pas analysée s'efface : déjà présente, ou abandonnée. Elles
   restent lisibles — les escamoter ferait un lot dont les comptes ne tombent
   pas — mais ne réclament pas l'œil, qui doit aller aux échecs. */
.batch-item.is-duplicate,
.batch-item.is-canceled { opacity: .62; }

/* Les gestes de pilotage. `wrap` : à 320 px ils ne tiennent pas sur une ligne,
   et un débordement horizontal dans une carte est ce que le § 2 interdit. */
/* Des liens, en pied de carte : la maquette n'en montre aucun, et deux gros
   boutons fantômes sous l'aperçu prenaient la place de trois lignes. La
   relance, elle, reste un vrai bouton — c'est le seul geste qu'on vient faire. */
.batch-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 18px; margin-top: 8px; }
.batch-actions > button { min-height: 44px; }
.batch-actions .il-lien { margin-left: 0; padding: 0; }
.batch-actions .batch-relancer { margin-left: auto; }

/* ── À corriger — § 7.3, § 12 ─────────────────────────────────────────────── */

/* Deux boutons côte à côte dans le profil : « suivre » et « corriger » sont deux
   destinations de même rang, et les empiler ferait paraître la seconde secondaire.
   `wrap` pour l'iPhone SE, où les deux libellés ne tiennent pas sur une ligne. */
.profile-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; }
.profile-actions > * { flex: 1 1 auto; }

/* La ligne du profil qui annonce ce qui cloche. Teintée, pas rouge : c'est un
   travail en attente, pas une erreur — et un profil qui alarme à chaque ouverture
   apprend à ne plus être lu. */
.corrections-lead.warn { color: var(--cinnamon); font-weight: 600; }

/* ── À vérifier — P22 de la refonte, 14/09 ───────────────────────────────────
   L'en-tête reprend celui de P15 (`.ip-head`) : même grille, le décor cède la
   place au titre. La note de papier est dessinée en code ; la cuillère est
   l'illustration du dépôt. */
.av-decor { width: 118px; height: 104px; }
.av-note {
  position: absolute; left: 0; top: 4px; width: 88px; min-height: 84px;
  padding: 12px 10px; box-sizing: border-box; border-radius: 6px;
  background: color-mix(in srgb, var(--sand) 28%, var(--card));
  box-shadow: 0 6px 16px rgba(48, 41, 35, .12); transform: rotate(-5deg);
  font-family: var(--ecriture); font-style: italic; font-size: 19.5px; line-height: 1.3;
  color: var(--cinnamon); text-align: center;
}
.av-decor-cuillere { right: 2px; top: -8px; height: 112px; transform: rotate(18deg); }
@media (max-width: 359px) {
  .av-decor { width: 104px; height: 100px; }
  .av-note { width: 80px; min-height: 78px; font-size: 18px; padding: 9px 7px; }
  .av-decor-cuillere { height: 104px; }
}

.av-resume { margin: -2px 0 12px; color: var(--muted); font-size: 21px; line-height: 1.4; }

/* Les quatre puces du mockup, sur **une** rangée, sans défilement : c'est le
   texte qui rétrécit, pas la rangée qui déborde (retour de Luca du 14/09). */
.av-filtres { display: flex; gap: 6px; margin: 0 0 12px; }
.av-filtre {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: center;
  gap: 4px; min-height: 44px; padding: 5px 7px; border-radius: 12px; border: 1px solid var(--line);
  background: var(--card); color: var(--espresso); font: inherit; font-size: 18px;
  line-height: 1.15; text-align: center; cursor: pointer;
}
.av-filtre .icon { flex: none; width: 14px; height: 14px; color: var(--muted); }
/* Le libellé tient sur sa ligne, le compte se range dessous : une puce de
   deux lignes nettes plutôt que trois lignes coupées au hasard. */
.av-filtre-texte {
  min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 1px;
  white-space: nowrap;
}
.av-filtre .av-compte { color: var(--muted); font-size: 17px; }
/* Sur téléphone l'icône coûte la ligne ; elle revient dès qu'il y a la place. */
@media (max-width: 599px) { .av-filtre .icon { display: none; } }
@media (max-width: 379px) { .av-filtre { font-size: 17px; padding-inline: 4px; } .av-filtres { gap: 5px; } }
@media (max-width: 359px) { .av-filtre-texte { white-space: normal; } }
.av-filtre.on {
  background: color-mix(in srgb, var(--terra) 14%, var(--card));
  border-color: color-mix(in srgb, var(--terra) 45%, var(--line));
  color: var(--cinnamon); font-weight: 600;
}
.av-filtre.on .icon, .av-filtre.on .av-compte { color: var(--cinnamon); }
.av-filtre:focus-visible, .av-completer:focus-visible, .av-suite:focus-visible, .av-outil-go:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--terra) 55%, transparent); outline-offset: 2px;
}
@media (min-width: 600px) { .av-filtre { font-size: 21px; gap: 8px; padding-inline: 12px; } .av-filtre .icon { width: 16px; height: 16px; } .av-filtre-texte { flex-direction: row; gap: 4px; } .av-filtre .av-compte { font-size: inherit; } }

/* Deux boutons de même poids côte à côte, et la note du rattrapage dessous en
   une ligne discrète. Une carte à deux rangées se lisait comme une notice. */
.av-outils { margin: 0 0 14px; }
/* Le bilan et l'échec restent lisibles même quand plus rien n'est à rattraper. */
.av-outils:not(:has(.av-outil-go:not([hidden]), .av-bilan:not([hidden]), .form-error:not([hidden]))) { display: none; }
.av-outils-rang { display: flex; gap: 8px; }
.av-outil-go {
  flex: 1 1 0; min-width: 0; display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; min-height: 44px; padding: 6px 12px; border-radius: 999px; cursor: pointer;
  font: inherit; font-size: 22.5px; font-weight: 600; line-height: 1.2; text-align: center;
  background: var(--card); color: var(--espresso); border: 1px solid var(--line);
}
.av-outil-go .icon { flex: none; width: 18px; height: 18px; color: var(--sage); }
.av-outil-go.av-outil-chaine { background: var(--bouton-fond); color: var(--bouton-texte); border-color: transparent; }
.av-outil-go.av-outil-chaine .icon { color: var(--bouton-texte); }
.av-outil-go:disabled { cursor: progress; }
.av-outil-note { margin: 8px 4px 0; color: var(--muted); font-size: 19.5px; line-height: 1.35; }
.av-outils .bar { margin: 10px 0 0; }
.av-outils .form-error { margin: 8px 4px 0; }
.av-bilan { color: var(--sage); font-weight: 600; }
/* Sous 420 px, l'icône coûte la ligne : libellé et compte passent d'abord.
   Écrite après les règles qu'elle corrige — à égalité, c'est l'ordre qui décide. */
@media (max-width: 419px) {
  .av-outil-go .icon { display: none; }
  .av-outil-go { font-size: 21px; padding-inline: 6px; letter-spacing: -.01em; }
}

.av-liste { display: grid; gap: 10px; }

/* La carte de la maquette : la photo à gauche, le titre, la conséquence, les
   motifs, et « Compléter ». Sous 600 px le bouton passe sous le texte — à côté,
   il écrasait les motifs sur trois lignes. */
.av-carte {
  display: grid; grid-template-columns: 72px minmax(0, 1fr);
  grid-template-areas: "vignette corps" "pied pied";
  column-gap: 12px; row-gap: 8px; padding: 10px 12px 10px 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--shadow);
}
.av-vignette {
  grid-area: vignette; align-self: start; width: 72px; height: 72px;
  border-radius: 12px; overflow: hidden; background: var(--cream);
}
.av-vignette > img, .av-vignette > .thumb-fallback {
  display: grid; place-items: center; width: 100%; height: 100%; object-fit: cover;
}
.av-vignette > .thumb-fallback .icon { width: 32px; height: 32px; color: var(--sand); }
.av-corps { grid-area: corps; min-width: 0; align-self: center; }
.av-titre {
  margin: 0 0 4px; font-family: var(--ecriture); font-weight: 400; font-size: 25px;
  line-height: 1.2; overflow-wrap: anywhere;
}
.av-alerte {
  display: flex; align-items: flex-start; gap: 7px; margin: 0;
  color: var(--cinnamon); font-size: 22px; line-height: 1.35;
}
.av-alerte-douce { color: var(--muted); }
.av-point {
  flex: none; display: grid; place-items: center; width: 19px; height: 19px; margin-top: 1px;
  border-radius: 50%; background: color-mix(in srgb, var(--terra) 70%, var(--card));
  color: #FFFFFF; font-size: 18px; font-weight: 700;
}
.av-pied {
  grid-area: pied; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.av-motifs {
  flex: 1 1 auto; display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; list-style: none;
}
.av-motifs li {
  padding: 4px 10px; border-radius: 999px; font-size: 19.5px;
  background: color-mix(in srgb, var(--sand) 30%, var(--card)); color: var(--espresso);
}
.av-completer {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  padding: 0 16px 0 18px; border-radius: 999px; font-size: 22.5px;
  background: var(--terra);
}
.av-completer .icon { width: 18px; height: 18px; }
@media (min-width: 600px) {
  .av-carte { grid-template-columns: 96px minmax(0, 1fr); grid-template-areas: "vignette corps" "vignette pied"; }
  .av-vignette { width: 96px; height: 96px; }
}
.av-suite { width: 100%; margin-top: 12px; }

.card.av-vide {
  display: flex; align-items: center; gap: 16px; margin: 8px 0 0; padding: 18px;
}
.av-vide .hint { margin: 0 0 12px; }
.av-vide-titre { margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400; font-size: 27.5px; }
.av-vide-marmite { flex: none; width: 120px; }
@media (max-width: 420px) {
  .card.av-vide { flex-direction: column; text-align: center; }
  .av-vide-marmite { width: 132px; }
}

/* Le compteur de la chaîne, posé en tête du bandeau de relecture. Il répond à la
   seule question qu'on se pose au milieu d'un travail répétitif : combien il en
   reste. `block` pour qu'il ne se mêle pas à la phrase qui suit. */
.chain-counter {
  display: block; margin-bottom: 4px;
  font-size: 18px; font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase; color: var(--sage);
}

/* ── Écran d'analyse ─────────────────────────────────────────────────────── */
/*
   **Le seul écran qu'on regarde sans rien faire**, et le plus long du parcours :
   médiane 27,8 s, jusqu'à 2 min 57 sur une vidéo longue. Refait le 26/08.

   ⚠️ **Trois animations infinies y tournaient sans échappatoire
   `prefers-reduced-motion`** — `rattle`, `rise`, `pulse` — là où le minuteur de
   cuisson, la bascule d'écran et le micro de la dictée en ont chacun une. Ce
   n'est pas un détail de conformité : c'est le seul écran de l'application où
   une animation tourne une demi-minute d'affilée devant quelqu'un qui **ne peut
   pas partir**, et un mouvement continu est précisément ce qui déclenche une
   gêne vestibulaire. Les trois en ont une désormais, et le pot garde alors sa
   forme — l'écran reste lisible, il cesse seulement de bouger.

   ⛔ **Et rien ici n'anime une propriété qui relayoute.** La barre de
   remplissage montait par sa `height` ; elle monte par un `transform: scaleY`
   ancré en bas, ce qui est le même dessin sans mise en page. Le § 6 vise le
   chemin d'un geste et une barre ne bouge qu'une fois par étape — le coût réel
   était donc faible ; ⚠️ **la raison de la corriger est qu'on ajoute du mouvement
   sur cet écran**, et étendre la forme qu'on sait mauvaise est la façon dont ces
   choses grandissent.

   ⛔ **Pas de bibliothèque d'animation** : son poids serait téléchargé par tous
   les écrans pour n'en servir qu'un seul. */

/* ⚠️ **`env(safe-area-inset-top)`, et c'est le seul endroit de l'app qui en a
   besoin aujourd'hui.** `viewport-fit=cover` est posé depuis toujours, mais rien
   ne réserve le haut — trois règles réservent le bas, aucune le haut. Or la
   vapeur monte **au-dessus** du pot (`inset: -36px`) : mesuré à 390 × 844, elle
   occupait y = 4 à 28, donc entièrement sous l'encoche d'un iPhone installé en
   PWA, dont la zone sûre commence vers 47 px. Le pot serait apparu sans fumée,
   ce qui n'a l'air d'aucun défaut — juste d'un dessin plus pauvre. */
/* P19, pièces G03–G05 générées le 14/09. L'ouverture de la marmite souriante
   est mesurée sur l'illustration embarquée dans le SVG (viewBox 1370 × 871).
   Les masques, la surface et les trajectoires dérivent de cette seule largeur.
   Le test de géométrie relit l'image, pas le relevé de l'ancienne marmite. */
#screen-analysis {
  /* P19, 14/09 : la marmite est la scène de la maquette, pas une vignette. Un
     seul nombre à reprendre, les autres suivent. */
  --marmite-w: min(224px, 58vw);
  --marmite-h: calc(var(--marmite-w) * .6358);
  --dedans-ry: calc(var(--marmite-w) * .07184);
  --ouverture-haut: calc(var(--marmite-h) * .0219);
  --course: calc(var(--marmite-w) * .14368);
  --ciel: calc(var(--marmite-w) * .36);
  text-align: center;
  /* ⚠️ **Le haut se réserve, et c'est le seul écran de l'app qui en a besoin.**
     `viewport-fit=cover` est posé depuis toujours mais trois règles réservent le
     bas et aucune le haut — or la vapeur et les ingrédients qui tombent vivent
     **au-dessus** de la marmite, et la zone sûre d'un iPhone en PWA commence
     vers 47 px. Sans cette réserve, la chute commencerait sous l'encoche. */
  padding-top: calc(12px + env(safe-area-inset-top));
}

/* ⚠️ **Un écran court rétrécit la marmite, il ne rogne rien d'autre.** Mesuré :
   à 320 × 568 (iPhone SE de 1ʳᵉ génération) le contenu fait 825 px pour 568 —
   l'écran défile, ce qu'il faisait déjà avant, mais le dessin lui avait ajouté
   62 px. Un seul nombre à reprendre, et les cinq autres suivent : c'est tout
   l'intérêt de les avoir dérivés. */
/* ⚠️ **Sur un téléphone court, c'est le décor qui cède, jamais le statut ni la
   sortie** : la flèche rejoint la ligne du titre, la marmite rétrécit, et le
   conseil perd son pictogramme. Mesuré à 360 × 740 : la sortie passait sous la
   barre d'onglets de 77 px. */
@media (max-height: 760px) {
  #screen-analysis { --marmite-w: min(156px, 46vw); }
  /* ⚠️ Préfixés par l'écran : les règles de base vivent plus bas, à
     spécificité égale elles gagneraient par leur place (vu au banc). */
  #screen-analysis .an-head { padding: 0 46px; }
  #screen-analysis .an-head > .icon-btn { top: -2px; }
  #screen-analysis .an-conseil-icone { display: none; }
}
@media (max-height: 620px) {
  #screen-analysis { --marmite-w: 136px; }
}
@media (min-width: 820px) and (min-height: 900px) {
  #screen-analysis { --marmite-w: 280px; }
}
/* Le couvercle posé déborde sous la marmite d'un dixième de sa largeur : la
   marge suit la même largeur, sinon il touche la carte en dessous. */
#screen-analysis .pot { margin-bottom: calc(8px + var(--marmite-w) * .04); }

/* ⚠️ **Le ciel est une marge de la marmite, plus un rembourrage d'écran.**
   L'en-tête et la frise sont désormais au-dessus : la vapeur et les
   ingrédients vivent entre la frise et le rebord, et c'est cette marge qui leur
   garde la place. */
/* Le nombre animé s'hérite depuis le pot. Une onde créée à mi-transition
   reçoit ainsi le niveau visible, pas la cible déjà écrite en style inline.
   Ce nombre ne sert qu'aux transforms, jamais à une propriété de disposition.
   Sans @property, le niveau change directement et la scène reste alignée. */
@property --niveau {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --tour-rayon {
  syntax: "<number>";
  inherits: true;
  initial-value: .15;
}
.pot {
  --ingredient-w: calc(var(--marmite-w) * .24);
  --touille-duree: 3.4s;
  --tour-rayon: .15;
  position: relative; width: var(--marmite-w); height: var(--marmite-h);
  margin: var(--ciel) auto 22px;
  transition: --niveau .8s ease, --tour-rayon .4s ease;
}
.pot.verse { --tour-rayon: .04; }
.pot[data-expression="ingredients"] { --touille-duree: 2.8s; }
.pot[data-expression="calcul"] { --touille-duree: 3s; }
/* Le plan de travail : une ombre posée, dans la même boîte, peinte avant tout
   le reste. ⛔ **Pas les tomates, l'ail et le basilic de la maquette** au pied
   de la marmite : sur cet écran un aliment dessiné se lit comme un ingrédient
   de la recette, et c'est justement ce que l'écran ne sait pas encore. */
.pot::before {
  content: ""; position: absolute; left: -22%; right: -22%; bottom: -9%;
  height: 26%; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(94, 56, 16, .2), rgba(94, 56, 16, 0));
}

.pot-photo { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* P19 — le visage vit au-dessus d'un émail sans expression imprimée.
   Les formes restent vectorielles ; seuls transform et opacity s'animent.
   Les expressions changent sur le statut réel, pas sur un chronomètre. */
.pot-personnage { position: absolute; inset: 0; transform-origin: 50% 96%; }
.pot-visage { position: absolute; left: 25%; top: 32%; width: 50%; overflow: visible; pointer-events: none; }
.pot-mimique { transform-origin: 160px 80px; transition: transform .4s ease; }
.pot-sourcil-g { transform-origin: 84px 28px; }
.pot-sourcil-d { transform-origin: 236px 28px; }
.pot-sourcils path, .pot-yeux-ouverts, .pot-yeux-heureux, .pot-joues {
  transition: transform .4s ease, opacity .25s ease;
}
.pot-clignement { transform-origin: 160px 65px; animation: pot-cligne 5.6s linear infinite; }
.pot-regard { animation: pot-reflechit 6.4s ease-in-out infinite; }
.pot-bouches > * { opacity: 0; transition: opacity .2s ease; }
.pot-yeux-heureux { opacity: 0; }
.pot-sourcil-g { transform: translateY(-4px) rotate(-12deg); }
.pot-sourcil-d { transform: translateY(3px) rotate(9deg); }
.pot-bouche-pensive { transform-origin: 165px 112px; }
.pot[data-expression="pensive"] .pot-bouche-pensive,
.pot[data-expression="calcul"] .pot-bouche-pensive { opacity: 1; animation: pot-moue 4.8s ease-in-out infinite; }
.pot[data-expression="ecoute"] .pot-bouche-ecoute { opacity: 1; }
.pot[data-expression="lecture"] .pot-bouche-attentive,
.pot[data-expression="ingredients"] .pot-bouche-attentive,
.pot[data-expression="observation"] .pot-bouche-ecoute { opacity: 1; }
.pot[data-expression="ecoute"] .pot-regard { animation-name: pot-ecoute; }
.pot[data-expression="ecoute"] .pot-mimique { transform: rotate(-4deg); }
.pot[data-expression="lecture"] .pot-regard,
.pot[data-expression="observation"] .pot-regard { animation-name: pot-lit; animation-duration: 3.6s; }
.pot[data-expression="lecture"] .pot-sourcil-g,
.pot[data-expression="ingredients"] .pot-sourcil-g { transform: rotate(9deg); }
.pot[data-expression="lecture"] .pot-sourcil-d,
.pot[data-expression="ingredients"] .pot-sourcil-d { transform: rotate(-9deg); }
.pot[data-expression="ingredients"] .pot-regard { animation-name: pot-suit; animation-duration: 2.8s; }
.pot[data-expression="observation"] .pot-sourcils path { transform: translateY(-7px); }
.pot[data-expression="calcul"] .pot-sourcil-g { transform: translateY(4px) rotate(16deg); }
.pot[data-expression="calcul"] .pot-sourcil-d { transform: translateY(1px) rotate(-14deg); }
.pot[data-expression="calcul"] .pot-mimique { transform: rotate(3deg); }
.pot[data-expression="calcul"] .pot-regard { animation-direction: reverse; }
.pot[data-expression="triste"] .pot-bouche-triste,
.pot[data-expression="incertaine"] .pot-bouche-incertaine { opacity: 1; }
.pot[data-expression="triste"] .pot-sourcil-g,
.pot[data-expression="incertaine"] .pot-sourcil-g { transform: translateY(-3px) rotate(-24deg); }
.pot[data-expression="triste"] .pot-sourcil-d,
.pot[data-expression="incertaine"] .pot-sourcil-d { transform: translateY(-3px) rotate(24deg); }
.pot[data-expression="triste"] .pot-mimique { transform: translateY(7px); }
.pot[data-expression="triste"] .pot-regard { transform: translateY(7px); }
.pot[data-expression="incertaine"] .pot-regard { transform: translate(-7px, 3px); }
.pot[data-expression="triste"] .pot-joues { opacity: .4; }
.pot[data-expression="triste"] .pot-cuillere,
.pot[data-expression="incertaine"] .pot-cuillere { transform: rotate(43deg) translateY(calc(var(--marmite-w) * .018)); }
.pot[data-expression="reussite"] .pot-yeux-ouverts { opacity: 0; }
.pot[data-expression="reussite"] .pot-yeux-heureux,
.pot[data-expression="reussite"] .pot-bouche-heureuse { opacity: 1; }
.pot[data-expression="reussite"] .pot-sourcils { opacity: 0; }
.pot-etincelles { position: absolute; left: -15%; top: -38%; width: 130%; pointer-events: none; }
.pot-etincelles path { opacity: 0; transform-box: fill-box; transform-origin: center; }

@keyframes pot-cligne {
  0%, 38%, 42%, 76%, 80%, 84%, 100% { transform: scaleY(1); }
  40%, 78%, 82% { transform: scaleY(.08); }
}
@keyframes pot-reflechit {
  0%, 12%, 92%, 100% { transform: translate(0, 0); }
  25%, 46% { transform: translate(9px, -7px); }
  60%, 78% { transform: translate(-7px, -3px); }
}
@keyframes pot-moue {
  0%, 20%, 85%, 100% { transform: translateX(0) scaleX(1); }
  35%, 65% { transform: translateX(8px) scaleX(.8); }
}
@keyframes pot-ecoute {
  0%, 20%, 85%, 100% { transform: translate(-6px, 0); }
  35%, 65% { transform: translate(-11px, -4px); }
}
@keyframes pot-lit {
  0%, 12%, 100% { transform: translate(-10px, -2px); }
  45%, 56% { transform: translate(10px, -2px); }
  68%, 82% { transform: translate(-8px, 4px); }
}
@keyframes pot-suit {
  0%, 10%, 100% { transform: translate(-8px, -9px); }
  55%, 75% { transform: translate(-3px, 4px); }
}
/* Un salut bref, puis la fiche. La cuillère et le visage suivent le même
   rebond que le récipient ; le couvercle et l'ombre restent sur le plan. */
.pot.joue-issue[data-expression="reussite"] .pot-personnage { animation: pot-reussite .95s ease-in-out both; }
.pot.joue-issue.verse-final[data-expression="reussite"] .pot-personnage { animation-duration: 1.3s; }
.pot.joue-issue[data-expression="reussite"] .pot-cuillere { animation: pot-salut .95s ease-in-out both; }
.pot.joue-issue[data-expression="reussite"] .pot-etincelles path { animation: pot-etincelle .65s ease-out .18s both; }
.pot.joue-issue[data-expression="reussite"] .pot-etincelles path:nth-child(2) { animation-delay: .28s; }
.pot.joue-issue[data-expression="reussite"] .pot-etincelles path:nth-child(3) { animation-delay: .29s; }
.pot.joue-issue[data-expression="triste"] .pot-personnage,
.pot.joue-issue[data-expression="incertaine"] .pot-personnage { animation: pot-deception .7s ease-in-out both; }
.pot.joue-issue[data-expression="triste"] .pot-cuillere,
.pot.joue-issue[data-expression="incertaine"] .pot-cuillere { animation: pot-pose-cuillere .7s ease-in-out both; }
@keyframes pot-reussite {
  0%, 100% { transform: translateY(0) scale(1); }
  16% { transform: translateY(0) scale(1.025, .96); }
  42% { transform: translateY(calc(var(--marmite-w) * -.035)) rotate(-2deg) scale(.98, 1.03); }
  65% { transform: translateY(0) rotate(1deg) scale(1.025, .975); }
  82% { transform: translateY(calc(var(--marmite-w) * -.012)) scale(1); }
}
@keyframes pot-salut {
  0%, 100% { transform: rotate(26deg); }
  38% { transform: translateY(calc(var(--marmite-w) * .005)) rotate(40deg); }
  65% { transform: rotate(23deg); }
}
@keyframes pot-etincelle {
  0% { opacity: 0; transform: translateY(8px) scale(.3) rotate(-15deg); }
  28%, 55% { opacity: 1; }
  100% { opacity: 0; transform: translateY(-10px) scale(1.15) rotate(12deg); }
}
@keyframes pot-deception {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.012, .965) rotate(-1.5deg); }
  72% { transform: scale(1.005, .985) rotate(.4deg); }
}
@keyframes pot-pose-cuillere {
  from { transform: rotate(26deg); }
  to { transform: rotate(43deg) translateY(calc(var(--marmite-w) * .018)); }
}

/* ── Le couvercle s'enlève, et c'est tout ce qu'il fait — 30/08 ───────────
   **Il était posé de côté, et il ne servait à rien** — signalé le jour même. La
   raison que je lui avais donnée, « il explique que la marmite soit ouverte »,
   est un raisonnement d'auteur : personne ne regarde un couvercle au sol en se
   demandant pourquoi le pot est ouvert. Un objet qui n'a qu'une justification
   n'a pas de rôle.

   → **Il ferme la marmite au premier instant, et s'écarte.** Le geste dure une
   seconde et il dit la seule chose que cet écran a à dire au début : on
   commence. ⚠️ **Et ce qu'il découvre n'a besoin d'aucune apparition écrite** —
   la cuillère et l'intérieur sont là depuis le début, simplement cachés sous
   lui. Une animation d'apparition pour chacun aurait été trois mouvements à
   synchroniser au lieu d'un seul objet qui bouge.

   ⚠️ **La pose au repos est l'état de base, l'ouverture est l'animation** — et
   pas l'inverse. Sous `prefers-reduced-motion` il n'y a donc rien à réécrire :
   on retire le mouvement et le couvercle est déjà de côté. Le contraire aurait
   laissé la marmite **fermée pour toujours** chez quelqu'un qui a demandé moins
   de mouvement, c'est-à-dire sans son niveau — une information effacée par une
   préférence de confort. */
.pot-couvercle {
  z-index: 3;
  position: absolute; left: -18%; bottom: calc(var(--marmite-w) * .05);
  width: calc(var(--marmite-w) * .43);
  transform: rotate(-68deg);
  filter: drop-shadow(0 3px 3px rgba(65, 32, 10, .15));
}
.pot.ouvre .pot-couvercle {
  animation: ouvrir 1.15s cubic-bezier(.38, 0, .22, 1) .1s backwards;
}
.pot.a-ouvrir .pot-couvercle {
  transform: translate(calc(var(--marmite-w) * .4657), calc(var(--marmite-w) * -.416)) scale(1.71, .77);
}
.pot.a-ouvrir .pot-cuillere-support { opacity: 0; }
.pot.ouvre .pot-cuillere-support { animation: reveler-cuillere 1.15s ease-out .1s backwards; }
@keyframes reveler-cuillere {
  0%, 32% { opacity: 0; transform: translateY(calc(var(--marmite-w) * .04)); }
  68%, 100% { opacity: 1; transform: translateY(0); }
}
@keyframes ouvrir {
  /* L'ellipse du couvercle généré est plus haute que celle du pot : la pose
     fermée la projette dans le même plan. Le centre du disque rejoint celui
     de l'ouverture ; la poignée conserve du volume à l'ouverture. */
  0%, 8% { transform: translate(calc(var(--marmite-w) * .4657),
                                calc(var(--marmite-w) * -.416)) rotate(0deg) scale(1.71, .77); }
  30% { transform: translate(calc(var(--marmite-w) * .45),
                             calc(var(--marmite-w) * -.56)) rotate(-6deg) scale(1.65, .92); }
  82% { transform: translate(0, calc(var(--marmite-w) * -.01)) rotate(-72deg) scale(1); }
  100% { transform: translate(0, 0) rotate(-68deg) scale(1); }
}

/* ── L'intérieur, et le liquide qui y monte ───────────────────────────────
   **Une seule image de marmite, et pas deux moitiés.** L'astuce classique — un
   arrière-plan de pot, le liquide, puis une paroi avant par-dessus — demande
   deux dessins parfaitement alignés, et trois pixels d'écart se voient. Ici le
   liquide est simplement **découpé à l'ouverture** : il ne peut donc pas
   déborder sur la paroi, et il n'y a rien à caler.

   ⚠️ **La surface est une ellipse, pas une ligne.** Une marmite cylindrique vue
   de trois quarts montre un liquide dont la surface a *exactement* l'ellipse du
   rebord, simplement plus bas — d'où le `border-radius` du haut, aux mêmes
   rayons que l'ouverture. Une arête droite aurait fait une coupe de schéma. */
.pot-dedans {
  position: absolute; inset: 0;
  clip-path: ellipse(35.26% 11.30% at 50.07% 13.49%);
}

/* ⚠️ **Le niveau s'écrit une fois, pour les deux.** Le remous doit se produire
   à la surface, donc à la même hauteur que le liquide : deux déclarations en
   feraient deux sources de « où en est l'analyse », dont la seconde se tairait
   le jour où la première change. C'est le § *un écran a une source de vérité*,
   appliqué à un nombre.

   ⛔ **Et c'est un `transform`, pas un `top`.** Le § 6 : ce qu'on anime ne
   relayoute pas. */
.pot-liquide, .pot-remous, .pot-trajectoire, .pot-sillage {
  transform: translateY(calc((1 - var(--niveau, 0)) * var(--course)));
}
.pot-liquide {
  position: absolute; left: 14.81%; width: 70.52%; top: var(--ouverture-haut); bottom: -30%;
  border-radius: 50% 50% 0 0 / var(--dedans-ry) var(--dedans-ry) 0 0;
  background: linear-gradient(180deg, #F6CE83 0 5%, #DFAB58 24%, #BD792F 70%);
  /* ⚠️ **Sans elle, le plein se lit comme un couvercle jaune.** Une surface
     plate et uniforme qui remplit l'ouverture n'a aucune profondeur ; l'ombre
     portée vers l'intérieur redonne la paroi qui la borde. */
  box-shadow: inset 0 0 10px rgba(94, 56, 16, .38);
  overflow: hidden;
}

.pot-liquide::before {
  content: ""; position: absolute; inset: 0 0 auto; height: calc(var(--dedans-ry) * 2);
  border-radius: 50%;
  background: radial-gradient(ellipse at 34% 25%, rgba(255, 244, 209, .4), transparent 68%);
  box-shadow: inset 0 1px 1px rgba(255, 246, 211, .65), inset 0 -2px 4px rgba(137, 82, 26, .12);
}

/* Deux bulles, dans le liquide et sous son découpage : elles n'apparaissent
   qu'une fois le niveau assez haut pour les contenir. `transform` et `opacity`
   seulement. */
.pot-bulles { position: absolute; inset: 0; pointer-events: none; }
.pot-bulles span {
  position: absolute; top: calc(var(--dedans-ry) * .95);
  width: calc(var(--marmite-w) * .028); height: calc(var(--marmite-w) * .028); border-radius: 50%;
  background: rgba(255, 252, 246, .55); opacity: 0;
  animation: bulle 2.8s ease-in-out infinite;
}
.pot-bulles span:nth-child(1) { left: 34%; animation-delay: .2s; }
.pot-bulles span:nth-child(2) { left: 58%; animation-delay: 1.5s; }
@keyframes bulle {
  0%   { opacity: 0; transform: translateY(0) scale(.6); }
  25%  { opacity: .8; }
  100% { opacity: 0; transform: translateY(calc(var(--dedans-ry) * -.8)) scale(1.1); }
}

/* Le remous, quand un ingrédient touche la surface. Une enveloppe qui porte le
   **niveau**, un enfant qui porte le **mouvement** : les deux transformations
   ne peuvent pas vivre sur la même propriété du même élément. */
.pot-remous { position: absolute; left: 0; width: 100%; top: var(--ouverture-haut); height: 0; }
.pot-remous i {
  position: absolute; left: calc(var(--marmite-w) * var(--impact-x));
  top: calc(var(--dedans-ry) * .9);
  width: calc(var(--marmite-w) * .23); margin-left: calc(var(--marmite-w) * -.115);
  height: calc(var(--dedans-ry) * .5); margin-top: calc(var(--dedans-ry) * -.25);
  border-radius: 50%; border: 1.5px solid rgba(255, 252, 246, .65);
  animation: remous .8s ease-out forwards;
}
@keyframes remous {
  from { opacity: .9; transform: scale(.28); }
  to   { opacity: 0;  transform: scale(1.5); }
}

/* Le creux décrit une ellipse, de droite à gauche par l'avant, puis revient
   par le fond. Le manche accompagne ce déplacement ; il ne pivote plus sur
   un point fixe. Pendant le versement, le tour se resserre à droite pour
   laisser entrer les aliments. Le sillage suit le même rythme et le niveau. */
.pot-cuillere-support { z-index: 2; }
.pot-cuillere {
  position: absolute; left: 61.75%; top: calc(var(--ciel) - var(--marmite-w) * .33);
  width: calc(var(--marmite-w) * .115);
  transform-origin: 50% 82%;
  transform: rotate(26deg);
  animation: touille var(--touille-duree) linear infinite;
}
.pot-sillage { position: absolute; inset: 0; pointer-events: none; }
.pot-sillage i {
  position: absolute; left: 67.5%; top: calc(var(--ouverture-haut) + var(--dedans-ry) * .95);
  width: calc(var(--marmite-w) * .13); height: calc(var(--marmite-w) * .023);
  margin-left: calc(var(--marmite-w) * -.065); border-radius: 50%;
  border: 1px solid rgba(255, 250, 219, .5); border-left-color: transparent;
  opacity: .55; animation: sillage var(--touille-duree) linear infinite;
}
@keyframes touille {
  0%, 100% { transform: translate(0, 0) rotate(26deg) scaleX(.96); }
  12.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -.293), calc(var(--marmite-w) * .017)) rotate(21deg); }
  25% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1), calc(var(--marmite-w) * .024)) rotate(8deg) scaleX(1.04); }
  37.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1.707), calc(var(--marmite-w) * .017)) rotate(-5deg); }
  50% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -2), 0) rotate(-10deg) scaleX(.96); }
  62.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1.707), calc(var(--marmite-w) * -.017)) rotate(-5deg) scaleX(.92); }
  75% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1), calc(var(--marmite-w) * -.024)) rotate(8deg) scaleX(.88); }
  87.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -.293), calc(var(--marmite-w) * -.017)) rotate(21deg) scaleX(.92); }
}
@keyframes sillage {
  0%, 100% { transform: translate(0, 0); }
  12.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -.293), calc(var(--marmite-w) * .017)); }
  25% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1), calc(var(--marmite-w) * .024)); }
  37.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1.707), calc(var(--marmite-w) * .017)); }
  50% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -2), 0); }
  62.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1.707), calc(var(--marmite-w) * -.017)); }
  75% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -1), calc(var(--marmite-w) * -.024)); }
  87.5% { transform: translate(calc(var(--marmite-w) * var(--tour-rayon) * -.293), calc(var(--marmite-w) * -.017)); }
}

/* ── Ce que la recette contient tombe dedans — 29/08 ──────────────────────
   Le dépôt porte 416 illustrations d'aliments, déjà servies par la bibliothèque
   et le journal : ce sont elles qui tombent, et elles ne coûtent ni dessin ni
   déploiement.

   ⚠️ **Ce sont celles de *cette* recette, ou rien.** Le serveur ne les publie
   qu'après l'extraction : avant, il ne sait pas ce que la vidéo contient, et
   faire tomber une tomate générique sur un gâteau au chocolat donnerait à
   l'écran l'aspect d'une information — la règle d'`icone_de`, transposée. Le
   premier ingrédient tombe donc à l'instant où la chaîne a fini de lire, et ce
   changement **est** l'information.

   ⛔ **L'écart horizontal est une variable lue par le `translate3d`**, jamais un
   `left` : le § 6 refuse qu'une mise en page entre dans une animation qui
   tourne une demi-minute. */
/* Le ciel et la moitié avant de l'ellipse forment un masque commun : même
   avec un niveau bas, aucun ingrédient ne peut ressortir sur l'émail.
   Les sommets suivent l'ouverture mesurée, tous dérivés de la largeur. */
.pot-chute { z-index: 1; }
.pot-chute, .pot-cuillere-support {
  position: absolute; left: 0; right: 0; top: calc(0px - var(--ciel));
  height: calc(var(--ciel) + var(--marmite-h)); pointer-events: none;
  clip-path: polygon(0 0, 100% 0, 100% var(--ciel),
    85.33% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry)),
    82.65% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.383),
    75% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.707),
    63.56% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.924),
    50.07% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 2),
    36.58% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.924),
    25.14% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.707),
    17.49% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry) * 1.383),
    14.81% calc(var(--ciel) + var(--ouverture-haut) + var(--dedans-ry)), 0 var(--ciel));
}
.pot-trajectoire {
  position: absolute; left: 0; right: 0;
  top: calc(var(--ciel) + var(--ouverture-haut));
  height: calc(var(--dedans-ry) * .9);
  /* L'image démarre entièrement dans le ciel réservé, quel que soit le
     niveau. Auparavant, sa première moitié de trajet était hors du masque. */
  --depart-chute: calc(0px - var(--ciel) - var(--ouverture-haut)
    - (1 - var(--niveau, 0)) * var(--course) - var(--dedans-ry) * .9
    + var(--ingredient-w) * .9 + var(--marmite-w) * .045);
  /* La chute passe devant la soupe jusqu'au contact, puis s'enfonce sous
     cette limite. Ce masque suit le niveau avec son enveloppe. */
  clip-path: inset(calc(0px - var(--ciel) - var(--ouverture-haut)
    - (1 - var(--niveau, 0)) * var(--course)) -30% 0);
}
.pot-chute img {
  position: absolute; left: calc(var(--marmite-w) * var(--impact-x));
  top: calc(var(--dedans-ry) * .9 - var(--ingredient-w) * .9);
  margin-left: calc(var(--ingredient-w) * -.5);
  width: var(--ingredient-w); height: var(--ingredient-w); object-fit: contain; opacity: 0;
  filter: drop-shadow(0 3px 4px rgba(46, 36, 29, .18));
  animation: chute 1.25s cubic-bezier(.32, 0, .7, .65) both;
}
@keyframes chute {
  from { opacity: 0; transform: translate3d(calc(var(--marmite-w) * var(--derive)),
    var(--depart-chute), 0) rotate(-18deg) scale(.8); }
  12% { opacity: 1; }
  to { opacity: 1; transform: translate3d(0, 0, 0) rotate(8deg) scale(.8); }
}
.pot-chute img.plonge { animation: plonge .32s ease-out both; }
/* Les dessins arrivés avec la fiche participent au salut final. Leur cadence
   se resserre, sans retenir la fiche pour la longue file du mode d'attente. */
.pot.verse-final .pot-chute img { animation-duration: .6s; }
.pot.verse-final .pot-chute img.plonge { animation-duration: .16s; }
@keyframes plonge {
  from { opacity: 1; transform: translate3d(0, 0, 0) rotate(8deg) scale(.8); }
  to { opacity: 0; transform: translate3d(0, calc(var(--ingredient-w) * .8), 0) rotate(16deg) scale(.5); }
}

/* La vapeur. ⚠️ **Trois fois la même image**, à trois tailles et trois retards :
   trois dessins différents auraient coûté trois téléchargements pour un effet
   que personne ne distingue. */
.pot-vapeur { position: absolute; left: 0; right: 0; top: calc(var(--ouverture-haut) + var(--dedans-ry)); height: 0; pointer-events: none; }
.pot-vapeur img {
  position: absolute; bottom: 0; width: calc(var(--marmite-w) * .17);
  opacity: 0; transform-origin: bottom center;
  animation: fumee 4.4s ease-in-out infinite;
}
.pot-vapeur img:nth-child(1) { left: 26%; }
.pot-vapeur img:nth-child(2) { left: 43%; animation-delay: 1.5s; animation-duration: 5.2s; }
.pot-vapeur img:nth-child(3) { left: 60%; animation-delay: 2.9s; }
@keyframes fumee {
  /* ⛔ **Elle ne part pas d'un point.** À `scale(.5)` la volute est un petit
     amas dense posé sur le rebord — vu à l'arrêt, ça ressemble à une pincée de
     farine, pas à de la vapeur. Elle naît déjà étalée et ne fait que s'ouvrir. */
  0%   { opacity: 0;   transform: translateY(14px) scale(.72); }
  40%  { opacity: .5; }
  72%  { opacity: .34; }
  100% { opacity: 0;   transform: translateY(-36px) scale(1.05); }
}

/* ── L'en-tête de la maquette ──────────────────────────────────────────── */
.an-head { position: relative; padding-top: 46px; margin-bottom: 14px; }
.an-head > .icon-btn { position: absolute; left: 0; top: 0; }
.an-titre {
  margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400;
  font-size: clamp(31px, 8.98vw, 39px); line-height: 1.08; color: var(--espresso);
}
.an-lead { margin: 0 auto; max-width: 34ch; color: var(--muted); font-size: 22.5px; line-height: 1.4; }
/* Un titre de plateforme peut faire 90 caractères : deux lignes, et la suite
   se devine. La frise et la marmite restent à leur place. */
.an-lead-recette {
  color: var(--espresso); font-family: var(--ecriture); font-size: 24.5px;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}

/* ── La frise des étapes ───────────────────────────────────────────────────
   **Une colonne par étape, de deux à cinq selon la source** (`--etapes`, posé
   par `startAnalysis`). La ligne qui relie deux ronds est le `::before` du
   second : elle se colore quand l'étape qu'elle mène est atteinte, donc elle
   dit le chemin parcouru sans un élément de plus. */
.steps-list {
  list-style: none; padding: 0; margin: 0 auto 10px; max-width: 520px;
  display: grid; grid-template-columns: repeat(var(--etapes, 5), minmax(0, 1fr));
  column-gap: 4px;
}
.steps-list li {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 0; color: var(--muted); font-size: 19.5px; line-height: 1.2; letter-spacing: -.01em;
  text-align: center; transition: color .3s ease;
  hyphens: auto; overflow-wrap: break-word;
}
.steps-list li + li::before {
  content: ""; position: absolute; top: 21px; height: 2px; border-radius: 1px;
  right: calc(50% + 26px); left: calc(-50% + 22px); background: var(--line);
}
.steps-list li.done::before, .steps-list li.sans-effet::before,
.steps-list li.active::before { background: var(--terra); }
.steps-list i {
  width: 44px; height: 44px; flex: none; border-radius: 50%; position: relative;
  display: grid; place-items: center;
  background: var(--card); border: 1.5px solid var(--line); color: var(--muted);
}
.steps-list i svg { display: block; }
.steps-list li.done { color: var(--espresso); }
.steps-list li.done i { background: var(--sage); border-color: var(--sage); color: #fff; }
.steps-list li.done i svg, .steps-list li.sans-effet i svg { display: none; }
.steps-list li.done i::after {
  content: ""; position: absolute; left: 17px; top: 12px;
  width: 7px; height: 14px; border: solid #fff; border-width: 0 2.5px 2.5px 0; transform: rotate(45deg);
}
/* Franchie, mais sans rien produire. Un tiret et non une coche : la bande son
   n'a pas été transcrite, elle n'a pas pu l'être. Ni vert ni rouge — ce n'est
   pas une faute, c'est une source qui a manqué, et la suite l'a peut-être
   compensée par l'image. */
/* ⛔ **La classe s'appelait `empty`, et `.empty` est la carte d'état vide de
   toute l'application** — fond `--card`, 22 px de rembourrage, une ombre. Le
   pas « franchi sans rien produire » héritait donc d'une pastille blanche au
   milieu d'une liste plate. Une collision de nom ne se voit dans aucun des deux
   fichiers : il faut les avoir sous les yeux en même temps. */
.steps-list li.sans-effet { color: var(--muted); }
.steps-list li.sans-effet i { border-color: var(--sand); }
.steps-list li.sans-effet i::after {
  content: ""; position: absolute; left: 14px; top: 20px;
  width: 13px; height: 2px; border-radius: 1px; background: var(--sand);
}
.steps-list li.active { color: var(--cinnamon); font-weight: 600; }
.steps-list li.active i { background: var(--terra); border-color: var(--terra); color: #fff; }
/* ⛔ **L'onde était un `box-shadow` animé**, donc un repeint de la zone à chaque
   image ; c'est un anneau en pseudo-élément, mis à l'échelle par `transform`.
   Le dessin est le même, le fil compositeur suffit à le jouer. */
.steps-list li.active i::before {
  content: ""; position: absolute; inset: -2px; border-radius: 50%;
  border: 2px solid var(--terra); animation: onde 1.3s ease-out infinite;
}
@keyframes onde {
  0%   { opacity: .5; transform: scale(1); }
  70%  { opacity: 0;  transform: scale(1.45); }
  100% { opacity: 0;  transform: scale(1.45); }
}
/* Sous 360 px, cinq ronds de 44 px ne laissent plus de place au texte : ils
   rétrécissent, le libellé garde sa taille. */
@media (max-width: 359px) {
  .steps-list i { width: 36px; height: 36px; }
  .steps-list i svg { width: 17px; height: 17px; }
  .steps-list li + li::before { top: 17px; right: calc(50% + 22px); left: calc(-50% + 22px); }
  .steps-list li.done i::after { left: 13px; top: 8px; }
  .steps-list li.sans-effet i::after { left: 10px; top: 16px; }
}

.analysis-note { color: var(--cinnamon); min-height: 1.5em; margin: 6px auto 0; max-width: 36ch; font-size: 22px; }

/* ── Bon à savoir, et la sortie ───────────────────────────────────────────── */
.an-conseil {
  display: flex; gap: 14px; align-items: center; text-align: left;
  margin: 0 auto 12px; max-width: 520px; padding: 12px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 18px;
}
.an-conseil-icone {
  flex: none; width: 48px; height: 48px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--terra) 12%, var(--card)); color: var(--terra);
}
.an-conseil-titre { display: block; font-family: var(--ecriture); font-weight: 400; font-size: 25px; color: var(--espresso); margin-bottom: 2px; }
.an-conseil p { margin: 0; color: var(--muted); font-size: 21px; line-height: 1.35; }
.an-attente { margin: 0 auto 10px; max-width: 40ch; font-size: 21px; }
.an-sortie {
  display: flex; align-items: center; justify-content: center;
  width: 100%; max-width: 520px; margin: 0 auto 8px; min-height: 52px;
  border-color: color-mix(in srgb, var(--terra) 45%, var(--line));
  background: color-mix(in srgb, var(--terra) 6%, var(--card));
  color: var(--cinnamon); font-size: 24px;
}

/* ── Une issue : la scène se tait ───────────────────────────────────────────
   Sous un échec ou un fil perdu, rien ne fume ni ne touille : un mouvement
   d'attente dirait qu'on attend encore. Le niveau reste, c'est une information. */
.pot.au-repos .pot-vapeur img, .pot.au-repos .pot-bulles span { animation: none; opacity: 0; }
.pot.au-repos .pot-cuillere { animation: none; }
.pot.au-repos .pot-couvercle { animation: none; }
.pot.au-repos .pot-cuillere-support { animation: none; }
.pot.au-repos .pot-sillage i { animation: none; opacity: 0; }
.pot.au-repos .pot-clignement, .pot.au-repos .pot-regard,
.pot.au-repos .pot-bouche-pensive { animation: none; }
.pot.au-repos, .pot.en-pause { transition: none; }
#pot.en-pause *, #pot.en-pause *::before { animation-play-state: paused; }
.pot.en-pause .pot-liquide, .pot.en-pause .pot-trajectoire { transition: none; }
#screen-analysis:has(.pot.en-pause) .steps-list li.active i::before { animation-play-state: paused; }

/* **L'échappatoire, et elle couvre les quatre mouvements de l'écran.** Le pot
   garde son niveau — c'est une information, pas une décoration —, il cesse
   seulement de trembler, de fumer, de bouillonner et de pulser. ⚠️ La
   transition du niveau part aussi : elle est courte, mais c'est le seul
   mouvement qui reste, et le laisser reviendrait à dire « presque ». */
@media (prefers-reduced-motion: reduce) {
  #pot *, #pot *::before { animation: none; transition: none; }
  .pot-clignement, .pot-regard,
  .pot[data-expression="pensive"] .pot-bouche-pensive,
  .pot[data-expression="calcul"] .pot-bouche-pensive { animation: none; }
  .pot { transition: none; }
  .pot-vapeur img, .pot-bulles span, .steps-list li.active i::before { animation: none; }
  /* ⚠️ **Rien à réécrire pour le couvercle** : sa pose de repos *est* l'état de
     base, l'ouverture n'est qu'une animation. On la retire, il est déjà de
     côté — et la marmite n'est jamais fermée sur son propre niveau. */
  .pot.ouvre .pot-couvercle { animation: none; }
  .pot.ouvre .pot-cuillere-support { animation: none; }
  /* La cuillère garde sa pose — elle dit que ça cuit — et cesse de touiller. */
  .pot-cuillere { animation: none; }
  .pot-sillage i { animation: none; opacity: 0; }
  .pot-bulles span, .pot-vapeur img { opacity: 0; }
  .pot-liquide, .pot-remous, .pot-trajectoire { transition: none; }
  /* ⚠️ **Deux garde-fous pour la chute, et ce n'est pas une ceinture de trop.**
     Le JavaScript ne crée aucune image sous ce réglage — c'est lui qui évite
     d'aller chercher 70 Ko par ingrédient — mais une image déjà en vol au
     moment où quelqu'un change son réglage continuerait sa course. Le CSS
     l'arrête et la cache : une image figée au milieu de sa chute est un dessin
     qui flotte. */
  .pot-chute img, .pot-remous { animation: none; display: none; }
}

/* ── Fiche recette ───────────────────────────────────────────────────────── */

.hero {
  position: relative; border-radius: var(--radius); overflow: hidden;
  min-height: 250px; display: flex; flex-direction: column; justify-content: flex-end;
  padding: 22px; margin-bottom: 16px; color: #fff;
  background: linear-gradient(135deg, #C9A88E, #8E5B43);
  background-size: cover; background-position: center;
  box-shadow: var(--shadow);
}
.hero-shade { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,.05) 35%, rgba(0,0,0,.62)); }
/* ⛔ **Le fond est fixe, donc la couleur aussi — corrigé le 24/08.** Ces trois
   boutons se posent sur une **photo**, pas sur le fond de l'application : leur
   pastille blanche translucide est ce qui les rend lisibles sur n'importe quelle
   couverture, et elle ne bascule donc pas avec le thème.

   La couleur, elle, venait de `var(--espresso)` — qui vaut #2E241D en clair mais
   **#F3EADD en sombre**. Résultat : une icône presque blanche sur une pastille
   blanche, invisible. Les boutons restaient là, cliquables, parfaitement vides.

   ⚠️ **Le défaut date du premier commit du dépôt** et n'a jamais été vu, parce
   qu'il ne se produit qu'en thème sombre — c'est la règle « un élément qui pose
   une couleur la prend dans le même jeu que la surface derrière lui » : ici la
   surface est un littéral, la couleur doit l'être aussi. */
.hero-back, .hero-fav, .hero-menu, .hero-collect, .hero-share {
  position: absolute; top: 14px; z-index: 2;
  width: 40px; height: 40px; min-height: 40px; border-radius: 50%; border: none;
  background: rgba(255,255,255,.9); color: #2E241D;
  font-size: 27.5px; line-height: 1; cursor: pointer;
}
.hero-back { left: 14px; font-size: 29px; }
.hero-fav { right: 14px; color: var(--coeur); }
/* Le favori garde le coin : c'est le geste fréquent. Le menu se range à sa
   gauche — 40 px de bouton plus la même gouttière que sur les bords.

   ⚠️ **Et la collection se glisse entre les deux — 06/09.** Le pas de 48 px
   s'écrit une fois et se répète : trois boutons alignés depuis la droite, et
   c'est la maquette. ⛔ Trois valeurs indépendantes auraient divergé au
   quatrième bouton, qui serait posé à l'œil. */
.hero-menu, .hero-collect, .hero-share { display: grid; place-items: center; }
.hero-collect { right: 62px; }
.hero-menu { right: 110px; }
.hero-fav.on { background: var(--coeur); color: #fff; }
.hero-title { position: relative; font-size: 30.5px; margin: 0 0 8px; line-height: 1.2; }
.hero-meta { position: relative; display: flex; gap: 16px; flex-wrap: wrap; font-size: 21px; opacity: .95; }

.review-banner {
  background: #FFF3E2; border: 1px solid #F0DCBE; color: #7A5320;
  border-radius: 16px; padding: 14px 16px; margin-bottom: 16px; font-size: 22px;
}
:root[data-theme="dark"] .review-banner,
:root:not([data-theme="light"]) .review-banner {
  background: #3A2E1E; border-color: #55432A; color: #F0D9AE;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .review-banner {
    background: #FFF3E2; border-color: #F0DCBE; color: #7A5320;
  }
}
.review-banner strong { display: block; margin-bottom: 2px; }

.nutrition-card { display: grid; grid-template-columns: auto 96px 1fr; gap: 18px; align-items: center; }
.kcal { display: flex; flex-direction: column; }
/* Le chiffre et son « i » sur la même ligne : c'est **le chiffre** qu'on
   conteste, donc c'est à côté de lui que doit se trouver la porte. Posé sous la
   carte ou dans un menu, il faudrait déjà savoir qu'il existe. */
.kcal-line { display: flex; align-items: center; gap: 9px; }
.kcal-value { font-size: 36.5px; line-height: 1; }
.kcal-unit { color: var(--muted); font-size: 19px; }
.precision {
  margin-top: 7px; align-self: flex-start; min-height: 0;
  border: 1px dashed var(--line); background: none; color: var(--muted);
  /* `cursor: pointer` et non `help` depuis le 17/08 : ce bouton n'affiche plus
     une infobulle, il ouvre le calcul ligne à ligne. Le curseur était le seul
     indice qu'on avait sur ce que le geste fait, et il annonçait autre chose. */
  border-radius: 999px; padding: 2px 9px; font-size: 17px; cursor: pointer;
}

.ring { width: 96px; height: 96px; transform: rotate(-90deg); }
.ring circle { fill: none; stroke-width: 11; stroke-linecap: round; }
.ring-bg { stroke: var(--line); }
.seg-p { stroke: var(--terra); }
.seg-g { stroke: var(--amber); }
.seg-l { stroke: var(--sage); }
/* La pastille de légende reprend la couleur du segment par sa classe : une
   seule déclaration par macro, l'anneau et la liste ne peuvent pas diverger. */
.macro-pastille {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 7px; vertical-align: 1px;
}
.macro-pastille.seg-p { background: var(--terra); }
.macro-pastille.seg-g { background: var(--amber); }
.macro-pastille.seg-l { background: var(--sage); }

.macros { list-style: none; margin: 0; padding: 0; font-size: 21px; }
.macros li { display: flex; justify-content: space-between; gap: 10px; padding: 2.5px 0; }
.macros b { font-weight: 600; }
.macros span { color: var(--muted); }

.servings-card { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; }
/* ⚠️ **La note prend sa propre ligne, jamais la place du pas à pas** — 06/09.
   La carte est un `space-between` à deux enfants ; un troisième s'y serait
   glissé **entre** le libellé et les boutons, en les repoussant hors du pouce.
   `flex-basis: 100%` la fait passer dessous sans toucher aux deux autres. */
.serv-note {
  flex: 0 0 100%; margin-top: 8px; color: var(--muted); font-size: 19.5px;
}
/* Le pas à pas des convives est **la** commande de sa feuille : centré et seul sur
   sa ligne, plutôt que rangé au bord comme dans la liste où il accompagne un
   titre. Une marge généreuse dessous sépare le réglage de son explication. */
.conv-row { display: flex; justify-content: center; margin: 6px 0 12px; }
.conv-row .stepper { gap: 14px; }

.stepper { display: flex; align-items: center; gap: 8px; }
.stepper button {
  position: relative;   /* repère de la zone de 44 px — voir en tête de fichier */
  width: 40px; height: 40px; min-height: 40px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--cream);
  color: var(--cinnamon); font-size: 26.5px; cursor: pointer;
}
.stepper output { min-width: 34px; text-align: center; font-weight: 600; font-size: 24.5px; }

/* § 7.6 — pesée du plat fini. Deux lignes de même gabarit : le libellé prend
   la place qu'il faut, le champ garde une largeur fixe pour que les deux
   colonnes de chiffres s'alignent l'une sous l'autre. */
.weigh-row {
  display: flex; align-items: center; gap: 8px; margin-top: 10px;
}
.weigh-row label { flex: 1; font-size: 22px; }
.weigh-row input {
  width: 82px; min-height: 40px; padding: 0 10px; text-align: right;
  border-radius: 12px; border: 1px solid var(--line);
  background: var(--cream); color: inherit; font-size: 24px;
}
.weigh-unit { color: var(--muted); font-size: 21px; width: 12px; }
.weigh-result { font-weight: 600; font-size: 24px; min-width: 84px; text-align: right; }
.weigh-card .hint { margin: 0; }

/* ── Vue semaine — § 13 ─────────────────────────────────────────────────────
   Une seule série, donc **une seule couleur pour toutes les barres**. Les
   teinter selon la cible double-encoderait la hauteur en teinte et, surtout,
   noterait les journées — ce que le § 10 refuse explicitement. Pas de rouge au
   dépassement, ici comme ailleurs.

   La moyenne porte le chiffre, les barres portent la forme : une valeur au
   sommet de chacune des sept ne se lirait pas. Le détail exact vit dans
   l'infobulle et dans l'étiquette d'accessibilité de chaque colonne. */
.week-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.week-average { margin: 0; font-size: 21px; color: var(--muted); }
.week-average b { color: var(--espresso); font-size: 24px; }

.week-plot {
  position: relative; display: flex; justify-content: space-between;
  align-items: flex-end; gap: 2px; height: 108px; margin: 14px 0 0;
}

/* La colonne est la cible tactile — bien plus large que la barre qu'elle
   contient, parce qu'on vise au pouce. */
.week-day {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; gap: 6px; height: 100%;
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; color: var(--muted); min-height: 0;
}
.week-track {
  width: 100%; max-width: 24px; flex: 1;
  display: flex; align-items: flex-end; justify-content: center;
}
/* Bout de donnée arrondi, pied carré sur la ligne de base. */
.week-bar {
  width: 100%; background: var(--terra);
  border-radius: 4px 4px 0 0; min-height: 3px;
}
/* **Un jour non saisi n'est pas un jour à zéro.** Pas de barre — une trace en
   pointillé, qui se distingue par la forme et pas seulement par la couleur. */
.week-bar.empty {
  background: none; border: 1px dashed var(--line);
  border-bottom: none; border-radius: 4px 4px 0 0; height: 100%;
  min-height: 0; opacity: .8;
}
/* La fenêtre affichée. Même forme que les autres puces, mais ce sont des
   **onglets** — ils ne restreignent aucune liste, ils changent la période dont
   les deux cartes parlent. D'où `aria-selected`, comme la bascule du § 6.5. */
.week-spans { gap: 6px; margin: 12px 0 0; overflow: visible; padding-bottom: 0; }
.week-spans .chip { min-height: 44px; }

.week-letter { font-size: 18px; line-height: 1; }
/* Vide en fenêtre longue — trente initiales se chevauchent — mais la ligne garde
   sa hauteur, sinon le graphique grandirait en changeant de fenêtre et la carte
   sauterait sous le doigt. */
.week-letter:empty { min-height: 12px; }
.week-day.current .week-letter { color: var(--terra); font-weight: 600; }

/* Repère de cible : filet d'un pixel, plein et discret. Jamais en pointillé —
   le pointillé sert déjà à dire « pas de saisie » juste à côté. */
.week-goal {
  position: absolute; left: 0; right: 0; height: 0;
  border-top: 1px solid var(--line); pointer-events: none;
}
.week-card .hint { margin: 10px 0 0; }

/* ── « Rends-la plus légère » — § 7.9 ─────────────────────────────────────────
   L'économie en gras et en tête de ligne : c'est le seul chiffre qui décide, et
   il doit se lire avant les deux noms. Les kcal/100 g en italique et entre
   parenthèses — ils justifient le calcul sans le disputer au résultat. */
.alleger { margin-top: 12px; }
.alleger-liste { list-style: none; margin: 12px 0 0; padding: 0; }
.alleger-ligne {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-top: 1px solid var(--line);
}
.alleger-texte { flex: 1; min-width: 0; font-size: 21px; line-height: 1.5; }
.alleger-texte b { font-size: 24px; color: var(--sage); }
.alleger-texte span { color: var(--muted); }
.alleger-texte i { font-style: normal; font-size: 19px; opacity: .75; }
.alleger-texte small {
  display: block; margin-top: 2px; color: var(--muted); font-size: 19px;
}
.alleger-go { flex: none; width: auto; padding: 10px 14px; min-height: 44px; }

/* ── L'invitation à chercher par le sens — § 9 ────────────────────────────────
   Centrée sous le message d'absence, dont elle est la suite : un bloc aligné à
   gauche se lirait comme une seconde section, et le § 7.3 demande qu'un refus
   **propose** — donc que la proposition tienne dans le même regard que le refus. */
.sens-invite { text-align: center; margin-top: 4px; }
.sens-invite .hint { margin: 0 auto 14px; max-width: 40ch; }

/* ── La bascule d'unités de la fiche — § 4 ────────────────────────────────────
   Quatre puces sur une ligne, qui défile horizontalement sur un petit écran :
   les tronquer ferait choisir entre « Métriq… » et « Impéri… ». Même forme que
   les autres rangées de puces, et **pas** un `<select>` — un menu déroulant
   cache trois des quatre choix, et celui qu'on cherche est justement celui qu'on
   ne voit pas. */
.unit-switch {
  gap: 6px; margin: 0 0 14px; padding-bottom: 2px;
}
.unit-switch .chip { min-height: 44px; white-space: nowrap; }

/* La liste du consentement : trois lignes, pour que « des données » devienne
   trois choses nommées. Un consentement à un mot n'en est pas un.

   ⚠️ **Illustrées depuis le 20/09**, sur le dessin des entrées de feuille : une
   puce de liste à 20 px de retrait se lisait comme des conditions générales, là
   où c'est justement l'écran qu'on doit avoir envie de lire. Ce ne sont pas des
   `.sheet-item` : rien ne s'y tape, donc rien n'y prend le survol ni le curseur
   d'un bouton. */
.consent-liste {
  list-style: none; margin: 0 0 12px; padding: 0;
}
.consent-liste li {
  display: flex; align-items: flex-start; gap: 14px; padding: 9px 14px;
}
.consent-liste li > span:last-child {
  display: flex; flex-direction: column; gap: 1px; min-width: 0;
}
.consent-liste .sheet-icon { margin-top: 2px; }
.consent-liste strong { font-size: 22.5px; font-weight: 600; }
.consent-liste small { color: var(--muted); font-size: 19px; line-height: 1.45; }
/* Où vont les données, dans un encart et non dans un paragraphe de plus : c'est
   la seule phrase de la feuille qui engage, et la seule qui nomme une sortie. */
.consent-encart {
  display: flex; align-items: flex-start; gap: 12px;
  margin: 0 14px 12px; padding: 12px 14px;
  background: var(--cream); border-radius: 14px;
}
.consent-encart .sheet-icon { margin-top: 2px; }
.consent-encart p {
  margin: 0; font-size: 20px; line-height: 1.5; color: var(--espresso);
}
/* Le texte suit le retrait du titre (14 px), comme les autres feuilles. */
.consent-amorce, .consent-retrait { margin-inline: 14px; }

/* ── Conseil du jour — § 13 ───────────────────────────────────────────────────
   Une carte basse, sans titre : le conseil **est** la carte. Un « Conseil du
   jour » en surtitre au-dessus d'une phrase de deux lignes ferait deux fois plus
   de hauteur pour la même information — et le mot « conseil » n'ajoute rien à
   « 3 articles de ton frigo périment dans les trois jours ». */
.conseil-card { border-left: 3px solid var(--amber); }
.conseil-texte { margin: 0; font-size: 22.5px; line-height: 1.5; }
.conseil-card .link-btn { margin-top: 10px; }

/* ── « Pour toi » — § 9 ───────────────────────────────────────────────────────
   La raison prend la place de la ligne de chiffres, elle ne s'y ajoute pas : la
   carte fait 150 px de large et deux lignes sous le titre s'y couperaient toutes
   les deux. Sur deux lignes au maximum — au-delà, ce n'est plus une raison,
   c'est une explication, et sa place est la ligne de tête du bloc. */
.card-raison {
  color: var(--terra);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
/* ── De qui est la carte — § 17, 04/09 ────────────────────────────────────────
   Devant la ligne du bas, jamais sur une troisième : la carte fait 150 px et le
   § ci-dessus dit pourquoi deux lignes y sont un plafond. Assez marqué pour se
   lire d'un coup d'œil sur une grille mêlée, assez discret pour ne pas passer
   devant le titre — c'est une **provenance**, pas une raison. */
.card-de { color: var(--terra); font-weight: 600; }

#pourtoi-note { margin: 0 0 12px; }
.pourvous-block { margin-top: 8px; }

/* ── La courbe de poids — § 13 ────────────────────────────────────────────────
   Un tracé et non des barres, et ce n'est pas un goût : une barre part de la
   ligne de base, donc dit « combien », et le poids ne se lit pas comme une
   quantité empilée. Ce qu'on regarde est une **pente**, et une pente se dessine.

   ⛔ **Et l'axe ne part pas de zéro** — c'est la seule règle qui rende cette
   carte lisible. De 0 à 80 kg, deux kilos perdus valent 2,5 % de la hauteur du
   cadre : la courbe est plate quoi qu'il arrive. Les bornes s'ajustent aux
   pesées, et la carte les écrit — sans quoi une variation d'un kilo se lirait
   comme une chute. */
/* 132 px, et c'est le **même nombre** que le `viewBox` de `renderWeights`. Ils
   ne peuvent pas diverger sans remettre une mise à l'échelle — donc la
   déformation des ronds et des bornes qu'on vient de retirer. */
.weight-plot {
  position: relative; height: 132px; margin: 14px 0 0;
}
.weight-plot svg { display: block; width: 100%; height: 100%; }
.weight-line { fill: none; stroke: var(--terra); stroke-width: 2; }
.weight-line { stroke-linecap: round; stroke-linejoin: round; }
.weight-dot { fill: var(--card); stroke: var(--terra); stroke-width: 2; }
/* Les deux bornes de l'axe, en clair sur les côtés : c'est ce qui empêche de
   lire une pente pour une chute. */
.weight-axis {
  fill: var(--muted); font-size: 16.5px; font-variant-numeric: tabular-nums;
}
.weight-card .hint { margin: 10px 0 0; }
.weight-card .btn-ghost { margin-top: 12px; }
/* Une carte vide ne montre pas un cadre vide : elle montre son bouton. */
.weight-plot:empty { display: none; }

/* ── D'où viennent les calories — § 13 ────────────────────────────────────────
   La barre se réutilise telle quelle (`.calc-bar`), qui sert déjà la part d'un
   ingrédient dans le détail du calcul : c'est la même question posée à une autre
   échelle, et deux barres dessinées séparément auraient fini par ne plus se
   ressembler. */
.fam-card .hint { margin: 10px 0 0; }
.fam-list { list-style: none; margin: 0; padding: 0; }
.fam-row {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center;
  gap: 3px 10px; padding: 8px 0; border-bottom: 1px solid var(--line);
}
.fam-row:last-child { border-bottom: none; }
.fam-row .diary-thumb { width: 26px; height: 26px; border-radius: 8px; grid-row: span 2; }
.fam-row .diary-thumb-empty .icon { width: 16px; height: 16px; }
.fam-name { font-size: 22.5px; }
.fam-part { font-size: 22.5px; font-variant-numeric: tabular-nums; color: var(--muted); }
.fam-row .calc-bar { grid-column: 2 / -1; margin-top: 0; }


/* L'intertitre de groupe dans l'éditeur. Même registre que `.section-name` de la
   fiche — c'est le même objet vu à un autre endroit — mais il n'est pas un champ :
   renommer un groupe reste à faire, et un titre qui aurait l'air modifiable sans
   rien enregistrer serait pire que pas de titre. */
/* L'intertitre de l'éditeur est un **champ** depuis le 22/08 — voir `app.js`.
   ⛔ **Donc 16 px, et pas les 13 px de son voisin d'affichage** (`.section-name`,
   qui reste un `<p>`) : le § 1 des conventions n'a pas d'exception, et c'est
   celle-là qui coûte le plus cher — iOS agrandit la page à la mise au point d'un
   champ sous 16 px, puis la laisse zoomée **et décalée** quand le clavier se
   referme. C'est de là que venaient les « fenêtres décentrées ».

   La casse et la couleur font le reste du travail : l'intertitre se lit comme un
   intertitre sans avoir besoin d'être petit. */
.edit-section {
  font-size: 24px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 18px 0 4px;
  width: 100%; border: 0; background: none; padding: 0;
  font-family: inherit; font-weight: 600;
}
.edit-section::placeholder { color: var(--muted); opacity: .6; }
.edit-section:first-child { margin-top: 0; }

.section-name {
  font-size: 19.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 16px 0 8px;
}
.section-name:first-child { margin-top: 0; }

/* ⚠️ **Elle se replie, et il a fallu que la pastille du frigo s'allonge pour
   le voir.** Six colonnes sur une ligne sans repli — vignette, quantité, nom,
   grammage, pastille de confiance, pastille de frigo — débordaient déjà du
   cadre à 390 px : mesuré, **5 lignes sur 7** sortaient de la carte, et « au
   frigo » y était depuis le 30/08. Ce qui l'a rendu visible est « prévu pour
   « Gratin dauphinois » », trois fois plus long. ⛔ Et surtout pas en coupant
   le grammage : « ≈ 300 g » sur trois lignes est ce que le repli sans
   `nowrap` produisait. */
.ing {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 11px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.ing:last-child { border-bottom: none; }
/* La vignette de famille, posée en tête de ligne : c'est ce qui se parcourt en
   premier quand on cherche un ingrédient dans une liste de quinze. Centrée et
   non alignée sur la ligne de base, qui est le repère du texte et poserait une
   image de 28 px de travers. */
.ing .diary-thumb { width: 28px; height: 28px; border-radius: 9px; align-self: center; }
.ing .diary-thumb-empty .icon { width: 17px; height: 17px; }
.ing .qty { font-weight: 600; min-width: 64px; }
.ing .qty.unknown { color: var(--muted); font-weight: 400; font-style: italic; }
.ing .name { flex: 1; }
.ing .gram { color: var(--muted); font-size: 19px; white-space: nowrap; }

/* Pastilles de confiance — § 6.4 */
.dot { width: 9px; height: 9px; border-radius: 50%; flex: none; align-self: center; }
.dot.green  { background: var(--sage); }
.dot.orange { background: var(--amber); }
.dot.red    { background: #C4573F; }

.coverage { margin: 14px 0 0; color: var(--muted); font-size: 19.5px; }

/* ── D'où vient ce chiffre ───────────────────────────────────────────────────
   Le bouton de précision cesse d'être un ornement : c'est là qu'on va quand on
   doute du total, donc c'est de là que part le détail. */
.calc-lead { margin: 0 0 4px; padding: 0 14px; color: var(--muted); font-size: 19.5px; }
.calc-lead b { color: var(--espresso); }
/* La phrase sous les lignes prend leur retrait : `.hint` n'en a pas, et elle
   démarrait 14 px à gauche du tableau qu'elle commente. */
#calc-note { padding-inline: 14px; }

.calc-row {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
  width: 100%; text-align: left; border: none; background: none; color: inherit;
  padding: 10px 14px; border-bottom: 1px solid var(--line); cursor: pointer;
}
.calc-row .cr-label { font-size: 22.5px; }
.calc-row .cr-kcal { font-size: 22.5px; font-variant-numeric: tabular-nums; }

/* **La ligne qui dit ce à quoi la ligne s'accroche.** C'est elle qu'on est venu
   chercher : le libellé au-dessus est ce qu'on a saisi soi-même, il ne peut rien
   apprendre à personne. La densité y est toujours, même quand elle est juste —
   une valeur qui n'apparaît que lorsqu'elle est fausse ne se compare à rien. */
.calc-row .cr-food {
  grid-column: 1 / -1; color: var(--muted); font-size: 19px;
}
.calc-row .cr-food b { color: var(--espresso); font-weight: 600; }

/* La part du total, en barre. Le tableau seul demandait de diviser de tête pour
   savoir laquelle des sept lignes portait le chiffre ; la barre le dit sans
   lecture — les 2 kg de viande occupaient 73 % du plat. */
.calc-bar {
  grid-column: 1 / -1; height: 4px; margin-top: 6px;
  border-radius: 999px; background: var(--line); overflow: hidden;
}
.calc-bar i { display: block; height: 100%; background: var(--terra); border-radius: 999px; }

/* Une ligne qui ne compte pas le dit, et le dit en nommant sa conséquence
   (§ 13) : « ne compte pas dans les calories », jamais « non rattachée ». */
/* « Choisi parmi 55 fiches ». Discret par défaut — c'est le cas de 130 lignes du
   corpus et la plupart sont justes ; l'appui n'arrive que si l'écart déplace
   vraiment le plat. Une mention permanente en couleur ne serait plus lue. */
.cr-cohorte { color: var(--muted); font-size: 18px; }
.cr-cohorte.pese { color: var(--amber); }

.calc-row.off .cr-kcal { color: var(--muted); }
.calc-row.off .cr-label { color: var(--muted); }
.calc-note-off { color: var(--terra); }

/* § G28 — « rattachée par ressemblance ». En ambre et non en terre cuite : ce
   n'est pas une ligne qui ne compte pas (`.calc-note-off`), c'est une ligne qui
   compte peut-être **le mauvais aliment** — le même registre que la cohorte qui
   pèse, puisque c'est la même question posée autrement. Le palier lui-même reste
   en gris : il sert à diagnostiquer, il ne porte pas le message. */
.calc-note-devine { color: var(--amber); font-size: 18px; }
.calc-note-palier { color: var(--muted); }

/* ── Les étapes, en bulles reliées — corrigé le 10/09 ────────────────────────
   **La maquette les dessine en pastilles pleines reliées par un fil**, et la
   première réalisation avait gardé des numéros pâles séparés par des filets
   horizontaux. La différence n'est pas décorative : un fil vertical dit
   *« ceci est une suite »*, un filet horizontal dit *« ceci est une liste »* —
   et une recette est une suite, c'est même tout ce qu'elle est.

   ⚠️ **Le fil vit sur la pastille, pas sur un élément à lui.** Un trait posé en
   couche par-dessus une liste qui défile est le mode d'échec du § 6 ; un
   `::after` sur la puce descend avec elle, sans rien à recomposer. Il part du
   bas de la bulle et court jusqu'au bas de l'étape — d'où le `bottom: -12px`,
   qui est l'écart entre deux étapes. */
.steps { list-style: none; counter-reset: s; margin: 0; padding: 0; }
.steps li { counter-increment: s; display: flex; gap: 14px; padding: 12px 0; position: relative; }

/* ⚠️ **Masquer, et non retirer du document.** Une étape repliée reste dans le
   `<ol>` : `counter-increment` continue de compter, donc « 4 » reste « 4 » quand
   on déplie. Reconstruire la liste à chaque bascule aurait renuméroté, et un
   `display: none` sur les étapes en trop ne coûte rien à peindre.

   Le fil de la dernière étape **visible** se retire, sinon il descend dans le
   vide juste au-dessus du bouton qui annonce déjà la suite. ⚠️ **Et
   `:last-child` avec lui** : `derniere-visible` n'est posée que sur une liste
   assez longue pour se replier — une recette de deux étapes n'en porte aucune,
   et son fil pendait sous la dernière bulle. */
.steps li[hidden] { display: none; }
/* `.steps li` pose `display: flex`, qui l'emporte sur le `display: none` que le
   navigateur attache à `[hidden]` : sans la règle ci-dessus, replier une étape
   ne la cachait pas. C'est le piège habituel de `hidden` sur un enfant flex. */
#steps-more { margin-top: 12px; position: relative; }   /* repère de la zone de 44 px */
.steps li::before {
  content: counter(s); flex: none; position: relative; z-index: 1;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--terra); color: #FFFFFF;
  display: grid; place-items: center; font-size: 20px; font-weight: 600;
}
/* Le fil qui relie une bulle à la suivante. ⚠️ **Il s'arrête à la dernière
   étape visible** — celle du repli comprise —, sinon il descend dans le vide
   sous « Voir les 5 étapes » et promet une suite que le bouton annonce déjà.
   ⛔ Et il est **derrière** la bulle (`z-index`), sans quoi son trait traverse
   le chiffre. */
.steps li::after {
  content: ""; position: absolute; left: 13px; top: 40px; bottom: -12px;
  width: 2px; background: var(--line);
}
.steps li.derniere-visible::after,
.steps li:last-child::after { display: none; }
.step-body { flex: 1; }
.step-body b { display: block; margin-bottom: 3px; }
.step-body p { margin: 0; font-size: 22px; }
.step-tags { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 7px; }
.step-tags a, .step-tags span {
  font-size: 19px; border-radius: 999px; padding: 3px 10px;
  background: var(--cream); color: var(--muted); text-decoration: none;
  border: 1px solid var(--line);
}
.step-tags a { color: var(--cinnamon); }

.source-card p { margin: 0 0 8px; font-size: 21px; }
.source-card a { color: var(--cinnamon); }
.disclaimer { color: var(--muted); font-size: 19px; }

/* Une valeur que l'application a choisie faute de mieux, pas une valeur lue
   dans la vidéo. Le souligné pointillé est le signe conventionnel d'« il y a
   une explication ici » ; la couleur reste celle du texte courant, parce que
   cette valeur n'est ni une alerte ni une erreur — elle est simplement moins
   sûre que sa voisine. Défini avec les deux thèmes en tête : la teinte vient
   des variables, jamais d'un littéral. */
.approx {
  border-bottom: 1px dotted var(--line);
  cursor: help;
}

@media (max-width: 430px) {
  .nutrition-card { grid-template-columns: auto 80px; }
  .macros { grid-column: 1 / -1; }
}

.draft-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* Même gabarit que son voisin : deux bandeaux de la même fiche qui ne se
   ressemblent pas se lisent comme deux niveaux d'alerte différents. */
.hors-ligne-banner { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.hors-ligne-banner span { color: var(--muted); }
.draft-banner .link-btn { white-space: nowrap; }

/* ── Signaler un problème ────────────────────────────────────────────────────

   Même découpe que la feuille de filtres, et pour la même raison : l'en-tête et
   le bouton d'envoi restent en vue, seul le milieu défile. Une feuille de
   signalement dont on doit chercher le bouton « Envoyer » perd exactement les
   signalements qu'on voulait recueillir. */
.report-sheet {
  display: flex; flex-direction: column;
  max-height: min(82vh, calc(100dvh - var(--tabbar-h) - 48px - var(--hors-reseau-h, 0px)));
  overflow: hidden; padding: 16px;
}
.report-sheet .sheet-head { flex: none; }
.report-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.report-sheet .filters { margin: 0 0 14px; }
/* Le premier libellé ne décolle pas du titre : `.champ-libelle:first-child`
   s'en charge, la bande qui défile commence par lui. */

.report-sheet textarea {
  width: 100%; border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit;
  resize: vertical; font-size: 24px; line-height: 1.45;
}
.report-sheet textarea:focus {
  outline: none; border-color: var(--terra);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--terra) 18%, transparent);
}

/* Le `label` porte le déclencheur du champ de fichier, qui est masqué : c'est
   le seul moyen d'obtenir un bouton dessiné comme les autres, le sélecteur natif
   n'étant pas stylable. D'où `display: block` — sans lui, `min-height` ne
   s'applique pas et la cible tactile retombe sous les 44 px. */
.report-attach {
  display: block; width: 100%; margin: 12px 0 0;
  text-align: center; min-height: 44px; padding: 11px 20px; cursor: pointer;
}
/* L'icône reste dans le fil du texte : le bouton garde son `display: block`,
   qui est ce qui fait tenir les 44 px de cible tactile. */
.report-attach .icon { width: 18px; height: 18px; margin-right: 6px; }

.report-thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.report-thumb {
  position: relative; width: 62px; height: 62px; padding: 0; min-height: 62px;
  border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  background: var(--cream); cursor: pointer;
}
.report-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* La croix est dessinée par-dessus la vignette : une capture sans moyen visible
   de la retirer se remplace en rouvrant le sélecteur, et on en joint deux. */
.report-thumb::after {
  content: "×"; position: absolute; top: 2px; right: 4px;
  color: #FFFFFF; font-size: 24.5px; line-height: 1;
  text-shadow: 0 1px 3px rgba(30, 24, 20, .85);
}

/* ⚠️ **L'encart crème, et non un indice de plus.** Ce qui est joint sans qu'on
   l'ait tapé — l'écran, la taille, le thème, la langue, le terminal — est la
   moitié utile d'un signalement : en gris sous le formulaire, il se lisait
   comme une mention légale qu'on ne lit pas. Même fond que les encarts de
   confidentialité des autres feuilles, pas une nouvelle couleur. */
.report-joint {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 14px 0 0; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--cream);
  color: var(--muted); font-size: 19px; line-height: 1.45;
}
.report-joint .icon { width: 18px; height: 18px; color: var(--sage); margin-top: 1px; }
/* Le contexte porte un user-agent entier : sans coupure, il élargit la feuille
   au lieu de passer à la ligne. */
#report-context { min-width: 0; overflow-wrap: anywhere; }

.notif-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }


/* ── Mon frigo — § 15 quater ─────────────────────────────────────────────── */

/* Ce qui presse, en tête. Le bandeau n'existe que quand il a quelque chose à
   dire : un bandeau permanent devient un décor qu'on ne lit plus. */
.pantry-alert {
  background: var(--cream); border: 1px solid var(--terra);
  border-radius: 12px; padding: 12px 14px; margin-bottom: 14px;
  color: var(--terra); font-size: 21px;
}
/* ── Deux exemplaires du même produit — cahier § 52 ─────────────────────────
   La tête d'un lot **déplie**, elle n'ouvre pas de fiche : ses quantités et ses
   dates sont celles de plusieurs articles. Le chevron le dit, et les membres se
   décalent pour qu'on voie d'un coup d'œil ce qui appartient à quoi. */
.pantry-lot-chevron { margin-left: auto; padding-left: 10px; color: var(--muted); font-size: 22.5px; }
.pantry-lot-membre { padding-left: 18px; }
/* ⚠️ **Un liseré, pas un fond.** Une couleur de fond sur les membres ferait une
   seconde carte dans la liste ; le trait dit l'appartenance sans rien peser. */
.pantry-lot-membre { border-left: 2px solid var(--line); margin-left: 12px; }

.pantry-item .diary-detail.warn { color: var(--terra); }
/* Le rouge est réservé au **déjà périmé**. L'employer aussi pour « bientôt »
   rendrait les deux indiscernables, et un frigo tout rouge ne se lit plus. */
.pantry-item .diary-detail.danger { color: var(--danger, #b3261e); font-weight: 600; }

/* Le bouton de date d'une ligne. **Même gabarit que le « × » qui le suit** :
   deux gestes de même rang sur la même ligne, et une liste dense ne supporte
   pas deux tailles de bouton. L'urgence le colore comme elle colore la ligne —
   c'est le même fait, et un frigo se parcourt en cherchant ce qui est rouge. */
.pantry-quand {
  width: 26px; height: 26px; min-height: 26px;
  border: none; background: none; color: var(--muted);
}
.pantry-quand .icon { width: 17px; height: 17px; }
.pantry-quand.warn { color: var(--terra); }
.pantry-quand.danger { color: var(--danger, #b3261e); }
/* Sans date, l'icône s'efface : elle **propose** une date, elle ne la réclame
   pas. Un frigo rempli au scan enchaîné n'en a aucune, et vingt icônes pleines
   se liraient comme vingt choses à faire. */
.pantry-quand.sans-date { opacity: .45; }

#pd-nom { margin: 0 0 10px; }
#pd-date { width: 100%; }

/* ── La bande « dernier rangé » du scan enchaîné — § 15 quater ─────────────
   Elle vit dans la feuille du scanner et appartient au frigo : c'est le seul
   moment où la date est sous les yeux, l'emballage encore dans la main. */
/* Lot 05A, 22/09 — une grille pour poser « +1 » à droite du nom, sur son rang :
   la bande ne gagne pas de ligne (voir le pli de 320 px ci-dessous). */
.scan-dernier {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 4px 8px;
  background: var(--cream); border-radius: 12px;
  padding: 8px 10px; margin: 8px 0 2px;
}
/* ⚠️ **Une seule ligne, coupée s'il le faut.** Mesuré à 320 px : « Crème
   fraîche épaisse 30 % » passait sur deux lignes et la bande coûtait 127 px
   dans une feuille qui n'en avait plus que 54 — les deux liens de secours du
   § 8 passaient sous le pli. Le nom est un rappel de ce qu'on vient de ranger,
   pas une fiche : sa fin ne porte rien qu'on doive lire. */
.sd-nom {
  font-size: 20px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ⚠️ **Un sélecteur de date natif est large**, et le formulaire d'ajout du
   frigo lui donne déjà sa propre ligne pour cette raison — à 390 px il
   s'affichait tronqué en « mm ». Ici il prend toute la place qui reste. */
.sd-date {
  display: flex; align-items: center; gap: 8px;
  font-size: 20px; color: var(--muted);
  grid-column: 1 / -1;
}
.sd-date input { flex: 1; min-width: 0; }
.sd-plus { grid-column: 2; grid-row: 1; min-width: 56px; padding: 0 14px; }

/* ⚠️ **La date a besoin de sa ligne.** La grille de la liste de courses tient
   sur cinq colonnes parce que ses champs sont courts ; un sélecteur de date
   natif ne l'est pas, et à 390 px il s'affichait tronqué en « mm ». Un champ
   qu'on ne peut pas lire est un champ qu'on ne remplit pas — et la péremption
   est justement ce que ce mode a de plus utile.

   Deux rangs, donc : quantité et unité sur le premier, la date pleine largeur
   sur le second avec les deux boutons. */
/* ⚠️ **Une case de moins depuis le 05/09** : la quantité et son unité tenaient
   deux colonnes, la molette n'en demande qu'une — et le bouton porte le choix
   en clair *(« 6 boîtes »)*, donc il vaut mieux large que étroit. Les zones
   sont nommées par identifiant, et c'est ce qui rend un champ retiré sans
   danger : rien ici ne dépend d'un rang. */
#add-pantry .add-detail {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto;
  grid-template-areas:
    "qte qte scan ajouter"
    "lieu lieu lieu lieu"
    "date date date date"
    "toujours toujours toujours toujours";
  row-gap: 8px;
}
#pantry-location { grid-area: lieu; width: 100%; }
#pantry-quantite { grid-area: qte; }
#pantry-expires { grid-area: date; width: 100%; }

/* ── Mon frigo — P09, 12/09 ───────────────────────────────────────────────────

   **Un réfrigérateur ouvert, composé et non photographié.** La référence est
   `design/nouveau-visuel/images/P09-mon-frigo.png` ; le décor vide qui lui
   correspond (G01) n'est pas encore livré, donc le meuble est dessiné ici :
   plafond éclairé, parois, une clayette par zone, un bac, une porte.

   ⛔ **Ce qui a été retiré, et pourquoi.** La grille du 31/08 posait les cartes
   sur `frigo-vide.webp`, mis à l'échelle de la largeur : la hauteur d'une
   rangée était donc **fixée par la photo**, un titre sur trois lignes sortait
   de sa travée, et au-delà de cinq planches le meuble devenait une couleur
   plate. La fiche P09 refuse les deux — *ni coordonnées calées sur une image,
   ni fond derrière un nombre variable de rangées*. Ici chaque zone porte sa
   clayette **sous elle** : la hauteur suit ce qui est posé, texte agrandi
   compris, et il n'y a rien à aligner. */

/* P09 — 23/09 : le meuble rejoint les matières du carnet commun.
   Les jetons du carnet portent aussi leurs équivalents sombres. */
#screen-pantry {
  --frigo-papier: var(--carnet-papier);
  --frigo-carte: var(--carnet-carte);
  --frigo-filet: var(--carnet-filet);
  --frigo-encre: var(--carnet-encre);
  --frigo-encre-douce: var(--carnet-encre-douce);
}
.frigo-tete {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; margin: -6px 0 12px;
}
.frigo-titre { min-width: 0; }
.frigo-tete .accueil-titre { margin: 0; font-size: 36px; line-height: 1.05; color: var(--frigo-encre); }
.frigo-tete .hint { margin: 0; color: var(--frigo-encre-douce); }
/* P09, 24/09 : la tête resserrée de la maquette illustrée — titre, compte,
   recherche, bandeau, puis le meuble sans les 24 px d'écart d'un écran. */
#screen-pantry .frigo-tete { margin-top: -14px; }
#screen-pantry .frigo-recherche { margin: 0 0 12px; }
#screen-pantry .frigo-bandeau { margin-top: 0; }
#screen-pantry .frigo { margin-top: 14px; }
/* Le brin de sauge de la maquette, dans la marge : décor seul. */
.frigo-brin { flex: none; width: 36px; height: 58px; margin: 0 4px 0 0; transform: rotate(16deg); pointer-events: none; }
.frigo-recherche { position: relative; align-items: stretch; gap: 10px; }
.frigo-loupe {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; color: var(--frigo-encre-douce); pointer-events: none;
}
.frigo-recherche .pantry-chercher {
  flex: 1; min-width: 0; min-height: 54px; padding-left: 46px;
  background: var(--frigo-carte); border-color: var(--frigo-filet); border-radius: 18px;
  color: var(--frigo-encre);
}
.frigo-recherche .pantry-chercher::placeholder { color: var(--frigo-encre-douce); opacity: 1; }

#screen-pantry #add-pantry {
  margin-top: 14px; padding: 14px; background: var(--frigo-papier);
  border: 1px solid var(--frigo-filet); border-radius: 20px;
}
#screen-pantry #add-pantry input, #screen-pantry #add-pantry select,
#screen-pantry .pantry-quantite {
  background: var(--frigo-carte); border-color: var(--frigo-filet); color: var(--frigo-encre);
}
#pantry-items .pantry-item {
  margin: 0 0 10px; padding: 12px; border: 1px solid var(--frigo-filet);
  border-radius: 18px; background: var(--frigo-carte); color: var(--frigo-encre);
}
#pantry-items .diary-detail:not(.warn):not(.urgent):not(.danger) { color: var(--frigo-encre-douce); }

/* Le bouton de regroupement et son menu. ⛔ `absolute`, pas `fixed` : le menu
   suit son bouton dans la colonne, et une couche fixe devrait choisir son camp
   au § 2. */
.frigo-axes { position: relative; flex: none; }
.frigo-axes > .icon-btn {
  width: 52px; height: 100%; min-height: 54px; border-radius: 18px;
  background: var(--frigo-papier); border: 1px solid var(--frigo-filet);
  color: var(--cinnamon);
}
.frigo-axes > .icon-btn[aria-expanded="true"] { background: var(--carnet-sauge); border-color: var(--carnet-sauge-filet); }
.frigo-axes-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 6;
  display: flex; flex-direction: column; min-width: 200px; padding: 6px;
  background: var(--frigo-papier); border: 1px solid var(--frigo-filet); border-radius: 18px;
  color: var(--frigo-encre); box-shadow: var(--carnet-ombre);
}
.frigo-axes-menu button {
  min-height: 44px; padding: 0 14px; border: 0; border-radius: 10px;
  background: none; color: inherit; font: inherit; font-size: 23px; text-align: left;
}
.frigo-axes-menu button.on { background: var(--carnet-sauge); font-weight: 600; color: var(--carnet-sauge-trait); }
.frigo-axes-menu button:disabled { color: var(--muted); cursor: default; }
.frigo-axes-menu .hint { margin: 0 14px 8px; font-size: 21px; max-width: 220px; }

/* ── Le bandeau : ce qui presse, et l'ajout ──────────────────────────────────
   ⚠️ **Côte à côte quand ça tient, l'un sous l'autre sinon.** Les noms du
   bandeau sont de vraies données : réduits pour imiter la capture, ils
   passeraient sous 14 px. C'est la largeur qui décide, pas un palier. */
/* ⚠️ **Une ligne, comme la maquette — demandé le 12/09.** Empilés, les deux
   occupaient 180 px avant la première clayette. Les noms tiennent donc sur une
   ligne coupée : la liste entière est au bout du geste, et le nom accessible du
   bouton la porte en entier. ⛔ Et le titre ne descend pas sous 14 px pour tenir. */
.frigo-bandeau { display: flex; gap: 10px; margin-bottom: 6px; align-items: stretch; }
.frigo-bandeau[hidden] { display: none; }
.frigo-urgences {
  flex: 1 1 0; min-width: 0; min-height: 52px;
  display: flex; align-items: center; gap: 8px; padding: 6px 8px 6px 10px;
  text-align: left; color: inherit; cursor: pointer;
  /* P09, 24/09 : le bandeau pêche très léger de la maquette illustrée. */
  background: color-mix(in srgb, var(--terra) 8%, var(--frigo-papier));
  border: 1px solid color-mix(in srgb, var(--terra) 28%, var(--frigo-filet));
  border-radius: 18px;
}
.fu-signe {
  flex: none; display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 50%; background: var(--danger-fond); color: var(--danger-fond-texte); font-weight: 700;
}
.fu-texte { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.fu-texte strong { color: var(--danger-texte); font-size: 21px; line-height: 1.2; }
.fu-texte small {
  color: var(--frigo-encre-douce); font-size: 21px; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fu-chevron { flex: none; width: 18px; height: 18px; transform: rotate(-90deg); color: var(--danger-texte); }
.frigo-ajouter {
  /* ⚠️ **Étroit, sur deux lignes — mesuré le 12/09.** Sur une ligne il prenait
     210 px à 390 et laissait 132 px à la banderole, qui passait sur six lignes.
     Le libellé se replie plutôt que de rétrécir : 15 px, jamais moins. */
  flex: none; max-width: 144px; min-height: 54px; padding: 10px 14px; border-radius: 18px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 22.5px; line-height: 1.2; text-align: left;
  /* ⚠️ **La terre cuite du carnet, pour ce bouton seulement** — P09, 24/09.
     Le couple des commandes (`--bouton-fond` / `--bouton-texte`), posé ici en
     toutes lettres : `.btn-primary` ne change pas ailleurs. */
  background: var(--bouton-fond); color: var(--bouton-texte);
  box-shadow: 0 8px 14px -10px color-mix(in srgb, var(--bouton-fond) 80%, #000);
}
.frigo-ajouter .icon { flex: none; width: 22px; height: 22px; }
/* Seul, quand rien ne presse, il reprend la ligne sur une seule ligne : un
   bouton étroit calé à gauche d'une ligne vide se lit comme un oubli. */
.frigo-urgences[hidden] + .frigo-ajouter { flex: 1; max-width: none; white-space: nowrap; }
/* ⚠️ **Côte à côte seulement quand les textes tiennent — seuil mesuré le 24/09.**
   Le titre « 4 à consommer bientôt » (21 px) demande ~170 px, le signe et le
   chevron ~70, le bouton sur deux lignes 144 : ~400 px de bandeau, soit une
   fenêtre de 440 px. À 390 le titre passait sur deux lignes et les noms se
   coupaient au premier : en dessous de 480 px les deux s'empilent, le bandeau
   pleine largeur, le bouton pleine largeur sur une ligne. Au-delà de 820 px
   la colonne fait 700 px, donc ils restent côte à côte. */
@media (max-width: 479px) {
  .frigo-bandeau { flex-wrap: wrap; }
  .frigo-urgences { flex-basis: 100%; }
  .frigo-ajouter { flex: 1; max-width: none; white-space: nowrap; }
}

/* ── Le meuble — frigo illustré, 24/09 ──────────────────────────────────────
   Référence : `design/nouveau-visuel/images/P09-frigo-illustre-v01.png`, la
   direction retenue par Luca. Coque émaillée ivoire à arêtes cacao fines,
   lumière en haut, parois ombrées, clayettes de verre, un bac, la porte ouverte
   à droite. **Tout est composé en CSS** : aucune capture en fond, aucune
   hauteur calée sur l'image — chaque zone porte sa clayette, et la hauteur suit
   ce qui est posé dessus, texte agrandi compris.

   Une grille de zones nommées, et c'est elle qui déplace la porte au palier :
   le DOM garde l'ordre intérieur → porte → bac. La coque et l'intérieur sont
   les deux pseudo-éléments de la grille, placés par les lignes nommées des
   zones : ils couvrent la colonne quelle que soit la hauteur des rangées. */
.frigo {
  --frigo-coque: #FCF5EA;
  --frigo-coque-ombre: #EBDDC8;
  --frigo-arete: color-mix(in srgb, var(--carnet-encre) 34%, transparent);
  --frigo-mur-haut: #FFFCF6;
  --frigo-mur-bas: #F5EADB;
  --frigo-paroi-ombre: rgba(122, 88, 54, .13);
  --frigo-trou: rgba(122, 88, 54, .22);
  --frigo-lumiere: rgba(255, 224, 158, .6);
  --frigo-lampe: #FFFCEF;
  --frigo-verre-haut: rgba(222, 238, 231, .4);
  --frigo-verre: rgba(208, 230, 221, .88);
  --frigo-verre-reflet: rgba(255, 255, 255, .95);
  --frigo-verre-bord: rgba(190, 216, 205, .95);
  --frigo-verre-bord-bas: rgba(150, 188, 172, .85);
  --frigo-verre-tranche: rgba(236, 246, 241, .98);
  --frigo-bac-avant: rgba(214, 229, 204, .5);
  --frigo-ombre-portee: rgba(90, 62, 36, .26);
  --porte-w: 30px;

  /* ── Le meuble peint de Luca — clair seulement, 24/09 ───────────────────
     Les tranches de `img/frigo/illustre-v1/` (747 px de large pour le corps,
     252 pour la porte), posées en fond et mises à l'échelle **en largeur** :
     rien n'est étiré sauf les parois, dont c'est le rôle. Chaque valeur est
     une variable que les deux blocs sombres remettent à `initial` : chaque
     usage porte alors en repli la valeur du meuble CSS, qui reste le sombre
     et le repli d'une image absente. */
  --paroi: 0px;
  --fp-img: block;
  --fp-css: none;
  --fp-haut: url("/static/img/frigo/illustre-v1/meuble-haut.webp");
  --fp-paroi: url("/static/img/frigo/illustre-v1/meuble-paroi.webp");
  --fp-clayette: url("/static/img/frigo/illustre-v1/meuble-clayette.webp");
  --fp-bac: url("/static/img/frigo/illustre-v1/meuble-bac.webp");
  --fp-porte-haut: url("/static/img/frigo/illustre-v1/porte-haut.webp");
  --fp-porte-paroi: url("/static/img/frigo/illustre-v1/porte-paroi.webp");
  --fp-porte-bas: url("/static/img/frigo/illustre-v1/porte-bas.webp");
  --fp-porte-balconnet: url("/static/img/frigo/illustre-v1/porte-balconnet.webp");
  --fp-plafond-h: auto; --fp-plafond-ratio: 747 / 286; --fp-plafond-mb: -21%;
  --fp-int-rangs: note-start / bac-start;
  --fp-int-bord: 0px; --fp-int-rayon: 0px; --fp-int-ombre: none; --fp-int-motif: none;
  /* La zone utile entre les parois peintes : x 60 → 690 sur 747. */
  --fp-clay-pad: 0 7.631% 4px 8.032%;
  --fp-nue-g: -9.524%; --fp-nue-d: -9.048%; --fp-nue-h: auto; --fp-nue-ratio: 747 / 87;
  --fp-pz-pad: 0 7.631% 0 8.032%;
  --fp-bac-marge: 0px; --fp-bac-bord: 0px; --fp-bac-rayon: 0px; --fp-bac-ombre: none;
  --fp-bac-verre: none; --fp-bac-ratio: 747 / 330; --fp-bac-pad: .7% 8.4% 17.8%;
  --fp-bac-zone-pad: 2px 0 0; --fp-bac-alim: 2 / 1; --fp-bac-alim-flex: none;
  --fp-bac-etiq-pos: absolute; --fp-bac-etiq-g: 50%; --fp-bac-etiq-bas: 4px; --fp-bac-etiq-tr: translateX(-50%);
  --fp-bac-etiq-mt: 0px;
  /* La porte : sur un téléphone, sa tranche droite seulement (l'image à ~3,9
     fois la largeur du bord, calée à droite) ; à partir de 820 px, entière. */
  --fp-porte-echelle: 390%;
  --fp-porte-pad: calc(var(--fp-porte-echelle) * 1.127) 0 calc(var(--fp-porte-echelle) * .8175);
  --fp-porte-bord: 0px; --fp-porte-rayon: 0px; --fp-porte-ombre: none; --fp-porte-motif: none;
  --fp-porte-debord: hidden;

  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--porte-w);
  grid-template-areas:
    "plafond porte"
    "note    porte"
    "clay    porte"
    "pz      porte"
    "bac     porte"
    "socle   porte";
  column-gap: calc(var(--paroi) + 4px);
  margin: 18px -12px 22px;
  padding: var(--paroi) 0 0 var(--paroi);
}
.frigo[hidden] { display: none; }
.frigo-corps { display: contents; }
/* La coque : l'émail ivoire, bordé d'un trait cacao fin, avec le socle. */
.frigo::before {
  content: ""; grid-column: 1; grid-row: plafond-start / socle-end;
  margin: calc(-1 * var(--paroi)) calc(-1 * var(--paroi)) 0;
  border: 1.5px solid var(--frigo-arete);
  border-radius: 30px 30px 16px 16px;
  background: linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque) 5%, var(--frigo-coque) 95%, var(--frigo-coque-ombre));
  box-shadow: 0 16px 26px -20px var(--frigo-ombre-portee), inset 0 0 0 3px rgba(255, 255, 255, .25);
}
/* L'intérieur : paroi du fond éclairée d'en haut, flancs ombrés, crémaillères. */
.frigo::after {
  content: ""; grid-column: 1; grid-row: plafond-start / bac-end;
  border: 1px solid var(--frigo-arete);
  border-radius: 21px 21px 10px 10px;
  background:
    radial-gradient(ellipse 58% 130px at 50% 0, var(--frigo-lumiere), transparent 78%),
    radial-gradient(ellipse 1.6px 4.5px, var(--frigo-trou) 80%, transparent 100%) left 6px top 46px / 5px 74px repeat-y,
    radial-gradient(ellipse 1.6px 4.5px, var(--frigo-trou) 80%, transparent 100%) right 6px top 46px / 5px 74px repeat-y,
    linear-gradient(90deg, var(--frigo-paroi-ombre), transparent 18px, transparent calc(100% - 18px), var(--frigo-paroi-ombre)),
    linear-gradient(180deg, var(--frigo-mur-haut), var(--frigo-mur-bas));
  box-shadow: inset 0 9px 14px -9px var(--frigo-ombre-portee), inset 0 -8px 12px -10px var(--frigo-ombre-portee);
}
.frigo > *, .frigo-corps > * { position: relative; }

/* La lampe, en haut de l'intérieur : une réglette chaude, et son halo. */
.frigo-plafond { grid-area: plafond; height: 30px; }
.frigo-plafond::after {
  content: ""; position: absolute; left: 30%; right: 30%; top: 11px; height: 5px;
  border-radius: 999px; background: var(--frigo-lampe);
  box-shadow: 0 0 16px 7px var(--frigo-lumiere), 0 1px 0 var(--frigo-arete);
}

.frigo-clayettes, .frigo-porte-zones {
  display: grid;
  /* ⚠️ **Deux zones par clayette au plus, une seule quand les noms ne
     tiennent plus.** 34 % interdit la troisième colonne à toute largeur ;
     sous 140 px par zone, une seule — mesuré au banc à 320 px, deux zones de
     130 px coupaient « Poissons » au milieu du mot. */
  grid-template-columns: repeat(auto-fill, minmax(max(140px, 34%), 1fr));
}
.frigo-clayettes { grid-area: clay; padding-bottom: 4px; }
.frigo-porte-zones { grid-area: pz; }
.frigo-porte-zones:empty { display: none; }

/* Une zone est un bouton entier : le groupe d'aliments posé sur sa clayette,
   puis sa petite étiquette accrochée au bord du verre. */
.frigo-zone {
  /* ⚠️ **Le repère de sa clayette.** Sans lui, les clayettes et les rebords de
     balconnet se posent tous au bas du conteneur, empilés en une seule bande —
     mesuré au banc à 1 440 px. */
  position: relative;
  container-type: inline-size;
  display: flex; flex-direction: column; align-items: center;
  min-width: 0; padding: 14px 6px 8px;
  background: none; border: 0; color: inherit; font: inherit;
  text-align: center; cursor: pointer;
}
/* ⚠️ **Seule sur sa clayette, elle la prend en entier** — une demi-planche
   au bout d'un meuble se lit comme une planche cassée. */
.frigo-zone:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* ⚠️ **Mais son dessin garde la taille d'une demi-planche — 25/09, Luca** :
   étiré sur toute la largeur, « Sucré » seul en bas du meuble était deux fois
   plus grand que ses voisins, et sa rangée plus haute que les autres. La boîte
   garde toute la largeur (la clayette peinte se cale sur elle) et prend
   **exactement** la hauteur de celle d'une demi-zone : sans espace entre les
   colonnes, une demi-zone a pour contenu (100cqw + 12px) / 2 − 12px, et sa
   boîte 2/1 la moitié de ça, soit 25cqw − 3px. Le dessin suit la hauteur.
   (Un 4/1 sur la largeur entière le laissait 50 % plus large — relu le 25/09.)
   ⛔ Seulement à deux colonnes, et pas le bac, seul dans le sien. */
@container clayettes (min-width: 280px) {
  .frigo-zone:last-child:nth-child(odd):not(.frigo-zone-bac) .fz-aliments {
    aspect-ratio: auto; height: min(128px, calc(25cqw - 3px));
  }
  /* Et la largeur d'une demi-zone (50cqw − 6px) : un groupe allongé comme
     « Sucré » s'étalait sinon au-delà de ce qu'il occupe à côté d'un voisin. */
  .frigo-zone:last-child:nth-child(odd):not(.frigo-zone-bac) .fz-aliments img {
    max-width: min(100%, calc(50cqw - 6px));
  }
}

/* Ce qui est posé. ⚠️ **Une boîte 3/2, la proportion des groupes peints**,
   plafonnée pour qu'une zone seule sur sa clayette ne devienne pas une affiche :
   les deux zones d'une rangée ont la même largeur, donc la même boîte, et leurs
   clayettes s'alignent même quand un nom passe sur deux lignes. */
/* ⚠️ **Une boîte 2/1 pour tous — resserrée le 24/09.** La même proportion
   pour les deux zones d'une rangée, donc la même hauteur et la même clayette
   même quand un nom passe sur deux lignes ; plafonnée pour qu'une zone seule
   sur sa clayette ne devienne pas une affiche. */
.fz-aliments {
  position: relative; align-self: stretch;
  display: flex; justify-content: center; align-items: flex-end;
  aspect-ratio: 2 / 1; max-height: 128px; min-height: 0;
}
.fz-aliments img {
  position: relative; z-index: 1; display: block;
  width: auto; height: auto; max-width: 100%; max-height: 100%; min-width: 0;
  object-fit: contain; object-position: center bottom;
}
/* ⚠️ **Le vide transparent du haut de chaque groupe est rogné**, sinon il se
   lisait comme un étage vide entre deux clayettes (relu le 24/09 : 150 px à
   390). Les groupes font tous 768 × 512 et reposent sur y = 500 ; `--haut` est
   la première ligne peinte, moins une marge. `cover` calé en bas ne retire que
   ce vide — la largeur, donc la proportion du dessin, reste celle de la zone. */
.fz-aliments .fz-groupe {
  aspect-ratio: 768 / calc(512 - var(--haut, 0));
  object-fit: cover;
}
.fz-groupe-fruit { --haut: 120; } .fz-groupe-viande { --haut: 82; }
.fz-groupe-poisson { --haut: 128; } .fz-groupe-oeuf { --haut: 22; }
.fz-groupe-laitier { --haut: 19; } .fz-groupe-fromage { --haut: 72; }
.fz-groupe-legume { --haut: 150; } .fz-groupe-surgele { --haut: 172; }
.fz-groupe-plat { --haut: 174; } .fz-groupe-cereale { --haut: 72; }
.fz-groupe-pain { --haut: 78; } .fz-groupe-noix { --haut: 262; }
.fz-groupe-condiment { --haut: 14; } .fz-groupe-huile { --haut: 14; }
.fz-groupe-boisson { --haut: 14; } .fz-groupe-sucre { --haut: 168; }
.fz-groupe-gateau { --haut: 234; } .fz-groupe-glace { --haut: 14; }
.fz-groupe-autre { --haut: 14; }
/* Deux groupes pour un emplacement : côte à côte, un peu mêlés. */
.fz-duo img { width: 56%; margin: 0 -3%; }
/* Le repli sur les anciens dessins de la catégorie, plus petits. */
.fz-dessins img { width: auto; height: 64px; max-width: 40%; margin: 0 -5px; }
/* La pastille neutre : « Autres », ou rien de peint à poser. */
.fz-picto {
  position: relative; z-index: 1; display: grid; place-items: center;
  width: 58px; height: 58px; margin-bottom: 10px; border-radius: 50%;
  background: var(--frigo-carte); border: 1px solid var(--frigo-filet);
  color: var(--cinnamon); box-shadow: 0 5px 8px -6px var(--frigo-ombre-portee);
}
.fz-picto .icon { width: 28px; height: 28px; }

/* La clayette de verre, sous chaque zone : le dessus translucide où reposent
   les aliments, puis la tranche verte et son reflet. Deux zones voisines font
   une planche continue parce que la grille n'a pas d'écart entre ses colonnes. */
.fz-aliments::before, .frigo-clayette-nue::after {
  /* ⚠️ **Une dalle, pas un filet — relu le 24/09.** Le dessus translucide
     (18 px, plus clair vers l'avant), l'arête de lumière, la tranche avant
     claire qui fonce vers le bas, puis l'ombre portée sur la paroi. */
  content: ""; position: absolute; left: -6px; right: -6px; bottom: -13px; height: 31px;
  border-top: 1px solid var(--frigo-verre-reflet);
  border-bottom: 1px solid var(--frigo-verre-bord-bas);
  background:
    linear-gradient(104deg, transparent 18%, color-mix(in srgb, var(--frigo-verre-reflet) 45%, transparent) 21%, transparent 27%) 0 0 / 100% 18px no-repeat,
    linear-gradient(180deg,
      var(--frigo-verre-haut) 0, var(--frigo-verre) 17px,
      var(--frigo-verre-reflet) 17px, var(--frigo-verre-reflet) 19px,
      var(--frigo-verre-tranche) 19px, var(--frigo-verre-bord) 100%);
  box-shadow: 0 13px 12px -9px var(--frigo-ombre-portee);
}
.frigo-clayette-nue { position: relative; grid-column: 1 / -1; height: 96px; }
.frigo-clayette-nue::after { left: 0; right: 0; bottom: 12px; }
.frigo-sans-stock .frigo-clayette-nue { height: 62px; }

/* L'étiquette : un petit carton ivoire accroché au bord du verre, qui laisse
   voir les aliments. Un vrai texte, jamais inscrit dans le décor. */
.fz-etiquette {
  position: relative; z-index: 3;
  display: flex; flex-direction: column; align-items: center;
  min-width: min(100%, 116px); max-width: 100%;
  margin-top: -20px; padding: 4px 14px 6px;
  background: var(--frigo-carte); border: 1px solid var(--frigo-filet);
  border-radius: 8px; color: var(--frigo-encre);
  box-shadow: 0 5px 9px -5px var(--frigo-ombre-portee);
}
.frigo-zone .pc-texte { display: flex; flex-direction: column; align-items: center; min-width: 0; max-width: 100%; }
.frigo-zone .pc-texte strong {
  font-family: var(--ecriture); font-size: 24px; font-weight: 600; line-height: 1.1;
  overflow-wrap: break-word; max-width: 100%;
}
.frigo-zone .pc-texte small { color: var(--frigo-encre-douce); font-size: 20px; line-height: 1.15; }
.frigo-zone .pc-presse {
  position: absolute; top: -11px; right: -11px;
  display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
  background: var(--danger-fond); color: var(--danger-fond-texte); font-size: 19.5px; font-weight: 700;
  box-shadow: 0 0 0 2px var(--frigo-carte);
}
.frigo-zone:active .fz-etiquette { transform: scale(.985); }
.frigo-zone:focus-visible { outline: none; }
.frigo-zone:focus-visible .fz-etiquette { outline: 3px solid var(--terra); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
  .frigo-zone:hover .fz-etiquette { border-color: var(--carnet-filet-vif); box-shadow: 0 0 0 1px var(--carnet-filet-vif), 0 5px 9px -5px var(--frigo-ombre-portee); }
}

/* La note du frigo vide, du chargement raté. Posée dans le meuble, opaque. */
.frigo-note {
  grid-area: note; margin: 4px 12px 14px; padding: 14px 16px;
  background: var(--frigo-carte); border: 1px solid var(--frigo-filet);
  color: var(--frigo-encre); border-radius: 16px; box-shadow: 0 6px 12px -8px var(--frigo-ombre-portee);
}
.frigo-note p { margin: 0; font-size: 23px; line-height: 1.45; }
.frigo-note .btn-ghost { margin-top: 12px; width: 100%; background: var(--frigo-papier); }

/* Le bac : un seul tiroir de verre en bas de l'intérieur, vide ou non. Les
   légumes y sont vus à travers la façade, l'étiquette posée dessus. */
.frigo-bac {
  grid-area: bac; display: flex; align-items: stretch; min-height: 72px;
  margin: 10px 8px 12px;
  border: 1.5px solid var(--frigo-verre-bord); border-top-color: var(--frigo-verre-reflet);
  border-radius: 8px 8px 20px 20px;
  background: linear-gradient(180deg, var(--frigo-verre-haut), color-mix(in srgb, var(--carnet-sauge) 55%, transparent));
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--frigo-verre-reflet) 45%, transparent), 0 8px 12px -9px var(--frigo-ombre-portee);
}
.frigo-bac:empty::after, .frigo-zone-bac::after {
  /* La façade du tiroir, devant le bas des légumes, avec sa poignée creusée. */
  content: ""; position: absolute; left: 3px; right: 3px; bottom: 3px; height: 52%;
  z-index: 2; pointer-events: none;
  border-top: 2px solid var(--frigo-verre-reflet); border-radius: 10px 10px 17px 17px;
  background:
    radial-gradient(ellipse 18% 9px at 50% 0, color-mix(in srgb, var(--frigo-verre-bord) 55%, transparent), transparent 100%),
    linear-gradient(180deg, color-mix(in srgb, var(--frigo-verre-reflet) 30%, transparent), var(--frigo-bac-avant));
  box-shadow: inset 0 1px 0 var(--frigo-verre-bord);
}
.frigo-zone-bac { flex: 1; padding: 4px 8px 12px; }
.frigo-zone-bac .fz-aliments::before { display: none; }
/* Le groupe du bac remplit la largeur du tiroir, rogné comme les autres. */
.frigo-zone-bac .fz-aliments { aspect-ratio: 768 / 362; max-height: 180px; }
.frigo-zone-bac .fz-etiquette { margin-top: -30px; min-width: min(100%, 176px); }

/* La porte. ⚠️ **Sur un téléphone elle est un bord, pas un rangement** : ses
   zones y seraient des étiquettes de 60 px de large, et la fiche refuse de
   réduire les commandes pour tenir. Ses catégories descendent donc en
   balconnets dans le meuble, et le bord ouvert reste visible à droite. */
.frigo-porte {
  grid-area: porte; margin-top: calc(-1 * var(--paroi));
  /* Relu le 24/09 : un bord nu ne se lisait pas comme une porte. Elle a
     maintenant son épaisseur (joint, contre-porte), ses rebords de balconnets
     en verre et ses deux charnières — du décor seul, sans aucun produit. */
  border: 1.5px solid var(--frigo-arete); border-radius: 8px 20px 20px 8px;
  background:
    repeating-linear-gradient(180deg,
      transparent 0 104px,
      var(--frigo-verre-reflet) 104px 106px, var(--frigo-verre) 106px 116px,
      var(--frigo-verre-bord-bas) 116px 118px, transparent 118px 150px) 5px 64px / calc(100% - 9px) calc(100% - 110px) no-repeat,
    linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque) 26%, var(--frigo-coque) 62%, var(--frigo-coque-ombre));
  box-shadow: -3px 0 6px -3px var(--frigo-ombre-portee),
    inset 3px 0 0 var(--frigo-coque), inset 4px 0 0 var(--frigo-arete),
    inset 0 0 0 4px color-mix(in srgb, var(--frigo-coque-ombre) 55%, transparent);
}
/* Les charnières, entre la coque et la porte. */
.frigo-porte::before, .frigo-porte::after {
  content: ""; position: absolute; left: -9px; width: 8px; height: 26px;
  border: 1px solid var(--frigo-arete); border-radius: 3px;
  background: linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque));
}
.frigo-porte::before { top: 60px; }
.frigo-porte::after { bottom: 60px; }
/* Un balconnet : le rebord de verre passe devant le bas des aliments. */
.frigo-zone-porte .fz-aliments::before {
  bottom: -10px; height: clamp(14px, 24%, 24px); z-index: 2;
  border: 1px solid var(--frigo-verre-bord); border-top: 2px solid var(--frigo-verre-reflet);
  border-radius: 3px 3px 10px 10px;
  background: linear-gradient(180deg, color-mix(in srgb, var(--frigo-verre-reflet) 35%, transparent), var(--frigo-verre));
}

/* Le socle, sous la coque : la grille d'aération. */
.frigo-socle {
  grid-area: socle; height: 24px;
  background: repeating-linear-gradient(90deg, var(--frigo-arete) 0 1.5px, transparent 1.5px 6px) center 9px / 34% 6px no-repeat;
}

/* ⚠️ **Au-delà du premier palier du § 10, la porte s'ouvre à droite** et porte
   ses propres zones, une par balconnet. Le meuble reprend une largeur et se
   centre : un réfrigérateur de 1 160 px n'est plus un réfrigérateur. ⛔ Et ce
   n'est pas une seconde disposition — les mêmes zones, dans une autre case. */
@media (min-width: 820px) {
  .frigo {
    --porte-w: 27%;
    /* La porte peinte entière, ses balconnets alignés sur ses zones. */
    --fp-porte-echelle: 100%;
    --fp-pz820-pad: 112.7% 0 12px;
    --fp-pz-zone-fond: var(--fp-porte-balconnet);
    /* La hauteur suit le groupe et son étiquette ; le balconnet peint se cale
       en bas. Figée au ratio du dessin (252/136), la zone faisait passer
       l'étiquette sur le groupe, qui disparaissait derrière la façade. */
    --fp-pz-zone-ratio: auto;
    --fp-pz-zone-pad: 8px 9% 10px 7%;
    --fp-pz-etiq-pos: relative; --fp-pz-etiq-g: auto;
    max-width: 780px; margin-inline: auto;
    grid-template-areas:
      "plafond pz"
      "note    pz"
      "clay    pz"
      "bac     pz"
      "socle   pz";
  }
  #screen-pantry .frigo-tete, #screen-pantry .frigo-recherche,
  #screen-pantry .frigo-bandeau, #screen-pantry .frigo-tuiles,
  #screen-pantry #add-pantry, #pantry-items {
    max-width: 780px; margin-inline: auto;
  }
  .frigo-porte {
    grid-area: pz; border-radius: 10px 26px 26px 10px;
    background: linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque) 12%, var(--frigo-coque) 88%, var(--frigo-coque-ombre));
    box-shadow: -4px 0 8px -4px var(--frigo-ombre-portee), inset 0 0 0 9px var(--frigo-coque), inset 0 0 0 10px var(--frigo-arete);
  }
  .frigo-porte-zones {
    /* Relu le 24/09 : les groupes flottaient au-dessus de leur rebord (vide
       transparent des dessins, désormais rogné). Les balconnets se suivent
       depuis le haut à pas régulier ; les répartir sur toute la hauteur les
       écartait de 300 px dès que le meuble est plein. */
    grid-template-columns: minmax(0, 1fr); align-content: start; row-gap: 22px;
    padding: 40px 16px 12px; z-index: 1;
  }
  .frigo-porte-zones:empty { display: grid; }
  .frigo-zone-porte:last-child:nth-child(odd) { grid-column: auto; }
}

/* Le sombre : les mêmes matières, éteintes. ⛔ Pas de halo clair sous les
   illustrations, pas d'encre cacao sur du brun — les étiquettes prennent la
   carte et l'encre sombres du carnet. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .frigo {
    --frigo-coque: #3A2F26; --frigo-coque-ombre: #2C241D;
    --frigo-arete: rgba(255, 236, 214, .2);
    --frigo-mur-haut: #4A3D31; --frigo-mur-bas: #362C24;
    --frigo-paroi-ombre: rgba(0, 0, 0, .22); --frigo-trou: rgba(0, 0, 0, .4);
    --frigo-lumiere: rgba(255, 206, 136, .17); --frigo-lampe: #E9D7B6;
    --frigo-verre-haut: rgba(190, 214, 200, .05); --frigo-verre: rgba(190, 214, 200, .16);
    --frigo-verre-reflet: rgba(255, 244, 228, .32);
    --frigo-verre-bord: rgba(150, 186, 166, .42); --frigo-verre-bord-bas: rgba(110, 146, 128, .3);
    --frigo-verre-tranche: rgba(200, 224, 212, .34);
    --frigo-bac-avant: rgba(120, 140, 100, .28);
    --frigo-ombre-portee: rgba(0, 0, 0, .5);
    /* Le meuble peint a sa version sombre (25/09, Luca) : mêmes tranches,
       même géométrie, seules les images changent. */
    --fp-haut: url("/static/img/frigo/illustre-v1/meuble-haut-sombre.webp");
    --fp-paroi: url("/static/img/frigo/illustre-v1/meuble-paroi-sombre.webp");
    --fp-clayette: url("/static/img/frigo/illustre-v1/meuble-clayette-sombre.webp");
    --fp-bac: url("/static/img/frigo/illustre-v1/meuble-bac-sombre.webp");
    --fp-porte-haut: url("/static/img/frigo/illustre-v1/porte-haut-sombre.webp");
    --fp-porte-paroi: url("/static/img/frigo/illustre-v1/porte-paroi-sombre.webp");
    --fp-porte-bas: url("/static/img/frigo/illustre-v1/porte-bas-sombre.webp");
    --fp-porte-balconnet: url("/static/img/frigo/illustre-v1/porte-balconnet-sombre.webp");
}
}
:root[data-theme="dark"] .frigo {
  --frigo-coque: #3A2F26; --frigo-coque-ombre: #2C241D;
  --frigo-arete: rgba(255, 236, 214, .2);
  --frigo-mur-haut: #4A3D31; --frigo-mur-bas: #362C24;
  --frigo-paroi-ombre: rgba(0, 0, 0, .22); --frigo-trou: rgba(0, 0, 0, .4);
  --frigo-lumiere: rgba(255, 206, 136, .17); --frigo-lampe: #E9D7B6;
  --frigo-verre-haut: rgba(190, 214, 200, .05); --frigo-verre: rgba(190, 214, 200, .16);
  --frigo-verre-reflet: rgba(255, 244, 228, .32);
  --frigo-verre-bord: rgba(150, 186, 166, .42); --frigo-verre-bord-bas: rgba(110, 146, 128, .3);
  --frigo-verre-tranche: rgba(200, 224, 212, .34);
  --frigo-bac-avant: rgba(120, 140, 100, .28);
  --frigo-ombre-portee: rgba(0, 0, 0, .5);
  /* Le meuble peint a sa version sombre (25/09, Luca) : mêmes tranches,
     même géométrie, seules les images changent. */
  --fp-haut: url("/static/img/frigo/illustre-v1/meuble-haut-sombre.webp");
  --fp-paroi: url("/static/img/frigo/illustre-v1/meuble-paroi-sombre.webp");
  --fp-clayette: url("/static/img/frigo/illustre-v1/meuble-clayette-sombre.webp");
  --fp-bac: url("/static/img/frigo/illustre-v1/meuble-bac-sombre.webp");
  --fp-porte-haut: url("/static/img/frigo/illustre-v1/porte-haut-sombre.webp");
  --fp-porte-paroi: url("/static/img/frigo/illustre-v1/porte-paroi-sombre.webp");
  --fp-porte-bas: url("/static/img/frigo/illustre-v1/porte-bas-sombre.webp");
  --fp-porte-balconnet: url("/static/img/frigo/illustre-v1/porte-balconnet-sombre.webp");
}

/* ── Le meuble peint, posé sur le meuble CSS — 24/09 ─────────────────────────
   Toutes ces règles lisent les variables `--fp-*` de `.frigo` ; en sombre
   elles valent `initial`, et chaque propriété retombe sur la valeur du meuble
   CSS écrite en repli — c'est la même valeur que plus haut, donc rien ne bouge.
   Une tranche absente laisse la teinte ivoire de la page et la clayette CSS
   minimale peinte sous la clayette dessinée. */
.frigo::before { display: var(--fp-css, block); }
.frigo::after {
  grid-row: var(--fp-int-rangs, plafond-start / bac-end);
  border-width: var(--fp-int-bord, 1px);
  border-radius: var(--fp-int-rayon, 21px 21px 10px 10px);
  box-shadow: var(--fp-int-ombre, inset 0 9px 14px -9px var(--frigo-ombre-portee), inset 0 -8px 12px -10px var(--frigo-ombre-portee));
  /* La paroi nue, **étirée** entre le haut et le bac : répétée, elle faisait
     des bandes. */
  background:
    var(--fp-paroi, none) 0 0 / 100% 100% no-repeat,
    var(--fp-int-motif,
      radial-gradient(ellipse 58% 130px at 50% 0, var(--frigo-lumiere), transparent 78%),
      radial-gradient(ellipse 1.6px 4.5px, var(--frigo-trou) 80%, transparent 100%) left 6px top 46px / 5px 74px repeat-y,
      radial-gradient(ellipse 1.6px 4.5px, var(--frigo-trou) 80%, transparent 100%) right 6px top 46px / 5px 74px repeat-y,
      linear-gradient(90deg, var(--frigo-paroi-ombre), transparent 18px, transparent calc(100% - 18px), var(--frigo-paroi-ombre)),
      linear-gradient(180deg, var(--frigo-mur-haut), var(--frigo-mur-bas)));
}
/* Le haut peint (coque, lampe, haut des parois), à sa proportion. La première
   rangée remonte sur sa moitié basse : les aliments se posent sous la lampe,
   comme dans la maquette, au lieu d'un étage vide. */
.frigo-plafond {
  height: var(--fp-plafond-h, 30px);
  aspect-ratio: var(--fp-plafond-ratio, auto);
  margin-bottom: var(--fp-plafond-mb, 0px);
  background: var(--fp-haut, none) center top / 100% auto no-repeat;
}
.frigo-plafond::after, .frigo-socle { display: var(--fp-css, block); }
.frigo-clayettes { padding: var(--fp-clay-pad, 0 0 4px); }
.frigo-porte-zones { padding: var(--fp-pz-pad, 0); }
.frigo-clayettes, .frigo-porte-zones { container: clayettes / inline-size; }

/* La clayette peinte, sous chaque rangée. ⚠️ **Une seule par rangée**, portée
   par sa première zone et large de tout le meuble (parois et supports
   compris) : deux moitiés d'image se seraient raccordées au milieu. `cqw` est
   la largeur de la zone ; la zone utile fait 630 des 747 px du dessin, d'où
   les rapports. Le verre (y ≈ 12 → 58 sur 87) passe sous le bas du groupe. */
.fz-aliments::before { display: var(--fp-css, block); }
.fz-aliments::after {
  content: ""; display: var(--fp-img, none); position: absolute; z-index: 0;
  top: 100%; left: calc(-6px - (100cqw + 12px) * .095238);
  width: calc((100cqw + 12px) * 1.185714); aspect-ratio: 747 / 87;
  transform: translateY(-60%); pointer-events: none;
  /* Le bas de la tranche porte l'ombre de la clayette sur la paroi : fondu
     sur ses dernières lignes, il ne laisse pas de raccord sous le verre. */
  -webkit-mask-image: linear-gradient(180deg, #000 86%, transparent);
  mask-image: linear-gradient(180deg, #000 86%, transparent);
  background:
    var(--fp-clayette, none) 0 0 / 100% 100% no-repeat,
    linear-gradient(180deg, transparent 14%, var(--frigo-verre) 14% 68%, var(--frigo-verre-bord) 68% 84%, transparent 84%);
}
@container clayettes (min-width: 280px) {
  .frigo-zone:nth-child(odd):not(:last-child) .fz-aliments::after {
    left: calc(-6px - (100cqw + 12px) * .190476);
    width: calc((100cqw + 12px) * 2.371429);
  }
  .frigo-zone:nth-child(even) .fz-aliments::after { display: none; }
}
.frigo-clayette-nue::after {
  left: var(--fp-nue-g, 0); right: var(--fp-nue-d, 0);
  height: var(--fp-nue-h, 31px); aspect-ratio: var(--fp-nue-ratio, auto);
  background-image: var(--fp-clayette, none), linear-gradient(180deg, var(--frigo-verre-haut) 0, var(--frigo-verre) 17px, var(--frigo-verre-reflet) 17px, var(--frigo-verre-reflet) 19px, var(--frigo-verre-tranche) 19px, var(--frigo-verre-bord) 100%);
  background-size: 100% 100%; background-repeat: no-repeat;
}

/* Le bac peint : le tiroir, le fond de coque et le socle d'une seule tranche,
   calée en bas ; si l'étiquette agrandie demande plus haut, la paroi prend le
   dessus (60 % de la hauteur, jamais sous les pieds transparents). */
.frigo-bac {
  margin: var(--fp-bac-marge, 10px 8px 12px);
  border-width: var(--fp-bac-bord, 1.5px);
  border-radius: var(--fp-bac-rayon, 8px 8px 20px 20px);
  box-shadow: var(--fp-bac-ombre, inset 0 0 0 2px color-mix(in srgb, var(--frigo-verre-reflet) 45%, transparent), 0 8px 12px -9px var(--frigo-ombre-portee));
  aspect-ratio: var(--fp-bac-ratio, auto);
  padding: var(--fp-bac-pad, 0);
  background:
    var(--fp-bac, none) center bottom / 100% auto no-repeat,
    var(--fp-paroi, none) center top / 100% 60% no-repeat,
    var(--fp-bac-verre, linear-gradient(180deg, var(--frigo-verre-haut), color-mix(in srgb, var(--carnet-sauge) 55%, transparent)));
}
.frigo-bac:empty::after, .frigo-zone-bac::after { display: var(--fp-css, block); }
.frigo-zone-bac { justify-content: flex-end; padding: var(--fp-bac-zone-pad, 4px 8px 12px); }
.frigo-zone-bac .fz-aliments {
  aspect-ratio: var(--fp-bac-alim, 768 / 362);
  /* Peint : les légumes gardent leur hauteur et dépassent du tiroir par le
     haut, comme sur la maquette ; comprimés au creux du bac (87 px à 390),
     l'étiquette les couvrait. */
  flex: var(--fp-bac-alim-flex, 0 1 auto);
}
.frigo-zone-bac .fz-aliments::after { display: none; }
/* L'étiquette du bac sur sa façade, et les légumes au fond du tiroir. */
.frigo-zone-bac .fz-etiquette {
  position: var(--fp-bac-etiq-pos, relative);
  left: var(--fp-bac-etiq-g, auto); bottom: var(--fp-bac-etiq-bas, auto);
  transform: var(--fp-bac-etiq-tr, none);
  margin-top: var(--fp-bac-etiq-mt, -30px);
}

/* La porte peinte : haut et bas à leur proportion, la paroi étirée entre les
   deux (dans la boîte de contenu, que le rembourrage cale sous le haut et
   au-dessus du bas). Sur un téléphone, seule sa tranche droite paraît. */
.frigo-porte {
  padding: var(--fp-porte-pad, 0);
  overflow: var(--fp-porte-debord, visible);
  border-width: var(--fp-porte-bord, 1.5px);
  border-radius: var(--fp-porte-rayon, 8px 20px 20px 8px);
  box-shadow: var(--fp-porte-ombre, -3px 0 6px -3px var(--frigo-ombre-portee), inset 3px 0 0 var(--frigo-coque), inset 4px 0 0 var(--frigo-arete), inset 0 0 0 4px color-mix(in srgb, var(--frigo-coque-ombre) 55%, transparent));
  background:
    var(--fp-porte-haut, none) right top / var(--fp-porte-echelle, 100%) auto no-repeat border-box border-box,
    var(--fp-porte-bas, none) right bottom / var(--fp-porte-echelle, 100%) auto no-repeat border-box border-box,
    var(--fp-porte-paroi, none) right top / var(--fp-porte-echelle, 100%) 100% no-repeat content-box content-box,
    var(--fp-porte-motif,
      repeating-linear-gradient(180deg,
        transparent 0 104px,
        var(--frigo-verre-reflet) 104px 106px, var(--frigo-verre) 106px 116px,
        var(--frigo-verre-bord-bas) 116px 118px, transparent 118px 150px) 5px 64px / calc(100% - 9px) calc(100% - 110px) no-repeat,
      linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque) 26%, var(--frigo-coque) 62%, var(--frigo-coque-ombre)));
}
.frigo-porte::before, .frigo-porte::after { display: var(--fp-css, block); }

@media (min-width: 820px) {
  .frigo-porte {
    border-radius: var(--fp-porte-rayon, 10px 26px 26px 10px);
    box-shadow: var(--fp-porte-ombre, -4px 0 8px -4px var(--frigo-ombre-portee), inset 0 0 0 9px var(--frigo-coque), inset 0 0 0 10px var(--frigo-arete));
    background:
      var(--fp-porte-haut, none) right top / var(--fp-porte-echelle, 100%) auto no-repeat border-box border-box,
      var(--fp-porte-bas, none) right bottom / var(--fp-porte-echelle, 100%) auto no-repeat border-box border-box,
      var(--fp-porte-paroi, none) right top / var(--fp-porte-echelle, 100%) 100% no-repeat content-box content-box,
      var(--fp-porte-motif, linear-gradient(90deg, var(--frigo-coque-ombre), var(--frigo-coque) 12%, var(--frigo-coque) 88%, var(--frigo-coque-ombre)));
  }
  /* Les zones de la porte commencent sous son haut peint ; chacune porte son
     balconnet, calé en bas, et y pose son groupe. L'étiquette se lit sur la
     façade du balconnet. */
  .frigo-porte-zones { padding: var(--fp-pz820-pad, 40px 16px 12px); }
  .frigo-zone-porte {
    justify-content: flex-end;
    aspect-ratio: var(--fp-pz-zone-ratio, auto);
    padding: var(--fp-pz-zone-pad, 14px 6px 8px);
    background: var(--fp-pz-zone-fond, none) center bottom / 100% auto no-repeat;
  }
  .frigo-zone-porte .fz-aliments::after { display: none; }
  .frigo-zone-porte .fz-etiquette {
    position: var(--fp-pz-etiq-pos, relative);
    /* Dans le flux et centrée : le décalage du bac (`translateX(-50%)`, calé
       sur un `left: 50%`) poussait l'étiquette hors de la porte. */
    bottom: auto; left: auto; transform: none;
    align-self: center; max-width: 100%; box-sizing: border-box;
  }
}

/* ── Les portes du remplissage — au carnet, 24/09 ───────────────────────────────
   Les cartes d'action des menus (famille A du cadre) : rien de propre au frigo
   hormis l'écart sous le bloc et le masquage d'un bloc entier. */
#pantry-gestes { margin-bottom: 20px; }
#pantry-gestes[hidden] { display: none; }
#pantry-gestes .carnet-separateur { margin: 6px 3px 0; }
#pantry-gestes .sheet-item[hidden] { display: none; }


/* La case « toujours disponible » du formulaire d'ajout : une ligne à elle,
   parce que son libellé est plus long que les champs qui l'entourent. */
/* ⛔ **Qualifiée par `#add-pantry`, et ce n'est pas de la coquetterie.**
   `.add-detail > :nth-child(4)` place le quatrième enfant sur la cellule du
   scanner, sous 470 px, pour le formulaire des courses. Cette case est devenue
   le quatrième enfant du formulaire du frigo : elle s'est donc rangée **sous le
   bouton de scan**, peinte, trouvable par un test de présence, et impossible à
   cocher. Mesuré au banc — le clic était intercepté 60 fois de suite.

   ⚠️ **C'est le § 2 dans sa version la plus discrète**, et la cause est une
   règle qui désigne ses enfants par leur **rang** : elle se trompe de cible le
   jour où quelqu'un insère un champ, sans qu'aucune ligne ne change de sens.
   L'identifiant du formulaire suffit à la battre, et un test vérifie désormais
   que chaque champ du frigo porte sa propre cellule. */
#add-pantry .pantry-toujours {
  grid-area: toujours; display: flex; align-items: center; gap: 8px;
  font-size: 20px; color: var(--muted);
}
#add-pantry .pantry-toujours input { width: auto; margin: 0; }

/* Ranger ses achats au frigo — cahier § 8 et 41. En tête de la liste : c'est
   le geste qu'on fait en rentrant, avant de reprendre à cocher. */
.list-au-frigo { width: 100%; margin-bottom: 16px; }
.pac-actions { display: flex; align-items: center; gap: 14px; margin-top: 10px; }
.pac-actions .btn-primary { min-height: 44px; padding: 0 18px; }
#pantry-apres-courses p { margin: 0; }

/* ⚠️ **Le nom d'une ligne de relecture se corrige, donc c'est un champ** — et
   un champ ne descend jamais sous 16 px (§ 1) : iOS agrandit la page dès qu'on
   met au point un texte plus petit, puis la laisse zoomée et décalée. ⛔ **Et il
   ne se déguise pas en bouton** : sans bordure au repos, rien ne dirait qu'il
   s'édite ; avec une bordure permanente, quatorze lignes ressembleraient à un
   formulaire. Le trait paraît à la mise au point. */
.fp-nom {
  font: inherit; font-size: 24px; font-weight: 600; color: inherit;
  width: 100%; min-width: 0; padding: 2px 4px; margin-left: -4px;
  border: 1px solid transparent; border-radius: 8px; background: none;
}
.fp-nom:focus { border-color: var(--line); background: var(--card); outline: none; }


/* ── Ce qui est entré et sorti — cahier § 54 et 61 ; P12 le 13/09 ────────── */
.fh-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 16px; }
.fh-head > div { min-width: 0; }
.fh-titre { margin: 0 0 4px; font-family: var(--ecriture); font-weight: 400; font-size: 32.5px; line-height: 1.1; }
.fh-intro { margin: 0; color: var(--muted); font-size: 22.5px; }

/* Les filtres : les puces de sens à gauche, la période à droite. Sous 360 px la
   période passe sous les puces plutôt que de les écraser. */
.fh-filtres {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 14px;
}
.fh-sens { display: flex; flex-wrap: wrap; gap: 8px; }
.fh-periode-choix { position: relative; }
.fh-periode-btn {
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 8px 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  color: inherit; font: inherit; font-size: 20px; cursor: pointer; white-space: nowrap;
}
.fh-periode-btn .icon { width: 18px; height: 18px; flex: none; color: var(--muted); }
.fh-caret { transition: transform .18s ease; }
.fh-periode-btn[aria-expanded="true"] .fh-caret { transform: rotate(180deg); }
/* Le menu local, ancré sous son bouton — même forme que celui des axes du
   frigo. ⛔ Il n'est pas `position: fixed` : posé ainsi il ignorerait le
   `max-width` de `#app` (§ 2), pour trois lignes qui vivent sous un bouton. */
.fh-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 5;
  min-width: 190px; padding: 6px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 12px 28px rgba(48, 41, 35, .16);
}
.fh-menu-item {
  display: block; width: 100%; min-height: 44px; padding: 10px 12px;
  background: none; border: none; border-radius: 10px;
  color: inherit; font: inherit; font-size: 22px; text-align: left; cursor: pointer;
}
.fh-menu-item.on { background: var(--cream); font-weight: 600; }

/* Le bilan : trois compteurs de mouvements, et la légende qui nomme l'unité une
   fois. Chaque cellule **est** le filtre de son sens. */
.fh-bilan {
  padding: 14px 8px 12px; margin-bottom: 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
}
.fh-bilan-trio { display: grid; grid-template-columns: repeat(3, 1fr); }
.fh-cellule {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 6px 8px; border: none; border-left: 1px solid var(--line);
  background: none; color: inherit; font: inherit; cursor: pointer; border-radius: 12px;
}
.fh-cellule:first-child { border-left: none; }
.fh-cellule.on { background: var(--cream); }
.fh-cellule b { font-size: 29px; font-weight: 600; }
.fh-cellule small { color: var(--muted); font-size: 19px; }
.fh-bilan-legende { display: block; margin-top: 8px; text-align: center; color: var(--muted); font-size: 19px; }

/* La pastille d'un mouvement : un **cercle ouvert** portant un plus, un moins
   ou une poubelle — le dessin de la maquette. ⚠️ **Le trait, pas un disque
   plein** : trois disques colorés dans une liste de quarante lignes pèsent plus
   que les noms qu'on y cherche. */
.fh-pastille {
  width: 32px; height: 32px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  border: 1.5px solid currentColor;
}
.fh-pastille .icon { width: 17px; height: 17px; }
.fh-pastille.entree { color: var(--sage); }
.fh-pastille.sortie { color: var(--terra); }
/* Ce qui part à la poubelle se lit en rouge : c'est la seule ligne d'un journal
   de stock qu'on aimerait ne pas écrire. */
.fh-pastille.jete { color: var(--danger, #b3261e); }

.fh-gaspillage {
  display: flex; flex-direction: column; gap: 6px;
  background: var(--cream); border-radius: 16px; padding: 14px 16px; margin-bottom: 18px;
}
.fh-gaspillage b { font-size: 23px; }
.fh-gaspillage small { color: var(--muted); font-size: 19px; line-height: 1.45; }
.fh-portee { margin-top: -2px; }
/* Un motif et son compte tiennent ensemble ; c'est l'espace qui sépare les
   paires, pas un point médian qui se confond avec celui du milieu. */
.fh-motifs { display: flex; flex-wrap: wrap; gap: 4px 14px; }

/* Le jour d'un groupe — serif, comme la maquette : c'est un repère de lecture,
   pas une commande. */
.fh-jour {
  margin: 18px 0 8px; font-family: var(--ecriture); font-weight: 400; font-size: 27px;
}
.fh-jour:first-child { margin-top: 4px; }

.fh-ligne {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; margin-bottom: 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
}
/* Le dessin de l'aliment, à la taille des lignes du frigo. */
.fh-ligne > .diary-thumb { width: 46px; height: 46px; border-radius: 12px; }
.fh-ligne > span:nth-child(2) { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fh-ligne strong { font-size: 22.5px; }
.fh-ligne small { color: var(--muted); font-size: 19px; }
/* Une entrée et une sortie se distinguent d'un coup d'œil : c'est la seule
   chose qu'on cherche en parcourant un journal de stock. */
.fh-ligne.entree strong { color: var(--sage, #7C8B63); }
/* Le signe et son motif, d'un bloc : « + Ajouté », « − Utilisé », « 🗑 Jeté ».
   La quantité suit en gris, séparée par un point médian posé en CSS — c'est le
   seul endroit où les deux se touchent, et coller les deux en une chaîne rendait
   « erreur dans mon frigo · 1 je l'ai donné » illisible ailleurs. */
.fh-signe { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; }
.fh-signe-icone { width: 14px; height: 14px; flex: none; }
.fh-signe.entree { color: var(--sage); }
.fh-signe.sortie { color: var(--terra); }
.fh-signe.jete { color: var(--danger, #b3261e); }
.fh-combien::before { content: " · "; }
.fh-quand { flex: none; }

.fh-erreur { display: flex; flex-direction: column; gap: 8px; align-items: center; }
.fh-erreur .btn-ghost { min-height: 48px; }

/* ── Ce que le frigo dit d'un ingrédient — cahier § 34, revu le 06/09 ───────

   **Trois couleurs, et elles disent la même chose partout** : la liste
   d'ingrédients de la fiche et le panneau des courses lisent le même jeu de
   classes, donc une ligne verte ici est verte là-bas. ⛔ Deux barèmes auraient
   divergé au premier état ajouté — et c'est exactement ce qui vient
   d'arriver avec « réservé ».

   | | |
   |---|---|
   | vert | c'est au frigo, et le compte y est |
   | orange | c'est au frigo, et le compte n'y est **pas** — trop peu, ou déjà promis à un repas prévu |
   | rouge | ce n'est pas au frigo |

   ⚠️ **L'orange réunit deux causes, et c'est voulu.** « Il en manque 50 ml » et
   « le gratin de jeudi le prend » demandent le **même** geste — décider si on
   en rachète —, là où le vert et le rouge n'en demandent aucun ou un seul. Ce
   qui les distingue est écrit dans la pastille, pas dans sa couleur.

   ⚠️ **Et le rouge est une révision du 30/08, demandée le 06/09.** L'arbitrage
   d'alors — *« l'absence ne crie pas, la peindre en rouge ferait de la
   bibliothèque un mur de reproches »* — visait juste **sur une grille de
   cartes**. Ici on est dans la fiche ouverte, sur la ligne d'un ingrédient
   qu'on est venu lire : un code à trois couleurs se parcourt d'un coup d'œil, un
   code à deux couleurs et un gris demande de lire le texte. Le fond reste pâle
   pour la même raison qu'alors — ce n'est pas un avertissement, c'est un
   repère. */
/* ⚠️ **`margin-left: auto` la garde à droite, ligne repliée comprise.** Sans
   lui, une pastille passée à la ligne se collerait sous la vignette, où plus
   rien ne dit à quel ingrédient elle appartient. */
.ing-frigo {
  flex: none; margin-left: auto; max-width: 100%;
  font-size: 18px; font-weight: 600;
  border-radius: 999px; padding: 2px 9px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.ing-frigo.dispo, .ing-frigo.present {
  background: color-mix(in srgb, var(--sage, #7C8B63) 16%, transparent);
  color: var(--sage, #7C8B63);
}
.ing-frigo.insuffisant, .ing-frigo.reserve {
  background: color-mix(in srgb, var(--amber) 18%, transparent);
  color: color-mix(in srgb, var(--amber) 78%, var(--espresso));
}
.ing-frigo.absent {
  background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger);
}

/* Le même code, en pastille, dans le panneau des courses : ce qui s'y lit est
   une phrase, la couleur est portée par le point qui l'ouvre. */
/* ⚠️ **Le point s'aligne sur la première ligne, jamais sur le milieu du
   bloc.** Une phrase qui se replie sur deux lignes posait sa pastille entre les
   deux, où elle n'appartient à aucune — mesuré à 390 px sur « prévu pour
   « Gratin dauphinois » ». */
.cp-etat { display: flex; align-items: flex-start; gap: 6px; font-size: 17px; line-height: 1.35; }
.cp-etat::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: currentColor; margin-top: 4px;
}
.cp-etat.dispo, .cp-etat.present { color: var(--sage); }
.cp-etat.insuffisant, .cp-etat.reserve { color: var(--amber); }
.cp-etat.absent { color: var(--danger); }
.cp-etat.inconnu { color: var(--muted); }
.cp-etat.inconnu::before { background: var(--line); }

/* ── J'ai cuisiné cette recette — cahier § 44-50, 30/08 ; P07, 11/09 ─────── */

/* Le rappel du plat : vignette, titre en serif, durée et rendement. */
.cu-plat { display: flex; align-items: center; gap: 16px; margin: 2px 0 18px; }
.cu-plat-image .diary-thumb { width: 80px; height: 60px; border-radius: 14px; flex: none; }
.cu-plat-image .diary-thumb img { width: 100%; height: 100%; object-fit: cover; }
.cu-plat-texte { min-width: 0; }
.cu-plat-titre {
  margin: 0; font-family: var(--ecriture); font-size: 26px; line-height: 1.2;
  color: var(--espresso);
}
.cu-plat-meta {
  margin: 6px 0 0; display: flex; flex-wrap: wrap; gap: 4px 16px;
  color: var(--muted); font-size: 20px;
}
.cu-plat-meta span { display: inline-flex; align-items: center; gap: 6px; }
.cu-plat-meta .icon { width: 17px; height: 17px; }
.cu-grand-titre { margin: 0 0 6px; }
.cu-lead { margin: 0 0 18px; }

/* ⚠️ **Le libellé cède, pas le pas à pas.** À 320 px la carte ne tient pas
   l'icône, la phrase et les trois commandes sur une ligne : c'est la phrase qui
   passe à la ligne, jamais un bouton qui rétrécit sous sa cible. */
/* ⚠️ **10 px de gouttière et 14 px de texte, et c'est une mesure.** À 390 px
   « Portions préparées » demandait ~150 px pour 146 et passait sur deux
   lignes ; la maquette le tient sur une. */
.cu-portions {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; margin: 0 0 22px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
}
.cu-portions-icone { flex: none; display: grid; color: var(--terra); }
.cu-portions-icone .icon { width: 24px; height: 24px; }
.cu-portions-libelle {
  flex: 1; min-width: 0; font-size: 21px; font-weight: 600; color: var(--espresso);
}
.cu-portions .stepper { flex: none; gap: 4px; }
.cu-portions .stepper button { border-radius: 50%; }
/* Le « − » des convives en cuisine : même couple que les autres steppers. */
.cu-portions .stepper button:disabled {
  background: var(--bouton-inactif-fond); color: var(--bouton-inactif-texte);
  border-color: var(--line); cursor: default;
}
/* 16 px minimum, comme tout champ (§ 1) — il en fait 17. */
.cu-portions input {
  width: 40px; min-height: 40px; padding: 0; text-align: center;
  border: 0; background: transparent; color: var(--espresso);
  font-size: 24.5px; font-weight: 600;
}
.cu-portions input:focus-visible { outline: 2px solid var(--terra); border-radius: 8px; }

.cu-barre {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 0 6px;
}
.cu-barre h2 {
  margin: 0; min-width: 0; font-family: var(--ecriture); font-size: 26.5px;
  color: var(--espresso);
}
.cu-barre .cp-tout { flex: none; color: var(--cinnamon); font-size: 21px; }

/* L'état intermédiaire de « Tout cocher » : un trait plutôt qu'une coche. */
.cp-tout input:indeterminate + .cp-case {
  background: var(--terra); border-color: var(--terra);
}
.cp-tout input:indeterminate + .cp-case .icon { display: none; }
.cp-tout input:indeterminate + .cp-case::after {
  content: ""; width: 11px; height: 2px; border-radius: 2px; background: #FFFFFF;
}
.cp-tout input:focus-visible + .cp-case,
.cu-ligne .cu-case:focus-visible + .cp-case { outline: 2px solid var(--terra); outline-offset: 2px; }

.cu-ligne {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 4px 10px 12px; margin-bottom: 8px; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
}
.cu-ligne .diary-thumb { width: 48px; height: 48px; border-radius: 12px; flex: none; }
/* Ce qui n'est pas au frigo s'efface **sans disparaître** : le retirer ferait
   croire que la recette n'en demande pas, et c'est justement la ligne qu'on
   voudra ajouter aux courses. ⚠️ Le chevron, lui, ne s'efface pas : c'est la
   seule correction que la ligne admette. */
.cu-ligne.absent { cursor: default; }
.cu-ligne.absent .diary-thumb, .cu-ligne.absent .cu-texte { opacity: .55; }
.cu-texte { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.cu-texte strong { font-size: 22.5px; font-weight: 500; color: var(--espresso); }
.cu-texte small { color: var(--muted); font-size: 19.5px; }
.cu-texte .cu-note { color: var(--cinnamon); }
/* ⛔ **Masquée, pas supprimée**, comme celle de « Tout cocher » : c'est elle qui
   porte l'état et le focus. Hors du flux, elle n'ajoute pas de gouttière. */
.cu-ligne .cu-case { position: absolute; opacity: 0; width: 0; height: 0; }
.cu-ligne .cu-case:checked + .cp-case {
  background: var(--terra); border-color: var(--terra); color: #FFFFFF;
}
/* ⚠️ **44 px, la cible qui n'était pas négociable** : `.chip` en fait 38, et
   une cible neuve n'a aucune raison de creuser cette dette. */
.cu-chevron {
  flex: none; width: 44px; min-height: 44px; padding: 0;
  display: grid; place-items: center;
  background: none; border: 0; color: var(--muted); cursor: pointer;
}
.cu-chevron .icon { width: 18px; height: 18px; transform: rotate(-90deg); }

.cu-carte-sub {
  display: flex; align-items: center; gap: 14px; width: 100%;
  margin: 6px 0 10px; padding: 14px 10px 14px 16px; min-height: 64px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.cu-carte-icone { flex: none; display: grid; color: var(--terra); }
.cu-carte-icone .icon { width: 24px; height: 24px; }
.cu-carte-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cu-carte-texte b { font-size: 22.5px; color: var(--espresso); }
.cu-carte-texte small { color: var(--muted); font-size: 19.5px; }
.cu-chevron-signe { flex: none; width: 24px; color: var(--muted); font-size: 27.5px; text-align: center; }

/* La note privée — P07, 11/09. ⚠️ **16 px dans le champ, jamais moins** (§ 1) :
   iOS agrandirait la page à la mise au point et la laisserait décalée. */
.cu-note-carte {
  display: flex; align-items: flex-start; gap: 14px;
  margin: 0 0 22px; padding: 14px 16px; cursor: text;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
}
.cu-note-carte .cu-carte-icone { margin-top: 1px; }
.cu-note-carte textarea {
  display: block; width: 100%; min-height: 64px; margin: 8px 0 6px; padding: 8px 10px;
  border: 1px solid var(--line); border-radius: 10px; resize: vertical;
  background: var(--cream); color: var(--espresso);
  font-size: 24px; line-height: 1.4;
}
.cu-note-carte textarea:focus-visible { outline: 2px solid var(--terra); outline-offset: 1px; }

/* ⚠️ **12 px de rembourrage, 8 de gouttière et 14 px de texte, et c'est une
   mesure** : à 320 px « Enregistrer ma préparation » faisait 228 px pour 228
   disponibles à 15 px — donc deux lignes. 14 px laisse 12 px de marge ; 14,5 en
   laissait 4, trop peu d'un moteur de rendu à l'autre. */
.cu-valider {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 52px; border-radius: 999px; padding-inline: 12px;
  background: var(--bouton-fond); font-size: 21px;
}
.cu-valider .icon { width: 20px; height: 20px; }
.cu-plus-tard {
  display: block; margin: 8px auto 0; min-height: 44px; padding: 4px 16px;
  text-decoration: underline; text-underline-offset: 3px;
}

/* La liste du frigo dans la feuille de substitution — même forme qu'une ligne
   de frigo : on choisit un article, et on le reconnaît à son dessin. */
.sub-choix {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 10px 12px; margin-bottom: 6px; min-height: 56px;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  cursor: pointer; text-align: left; color: inherit;
}
.sub-choix span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sub-choix strong { font-size: 22px; }
.sub-choix small { color: var(--muted); font-size: 19px; }
/* ⚠️ **Le retour en arrière est une ligne de la liste, pas un bouton ailleurs.**
   C'est le même geste — décider ce qu'on a pris —, donc le même endroit. */
.sub-retour { justify-content: center; color: var(--muted); font-size: 19.5px; }

.cu-bilan { margin-top: 24px; }
.cu-bilan h2 { font-size: 24.5px; margin: 0 0 14px; }
.cu-bilan-ligne {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 22px;
}
.cu-bilan-ligne b { color: var(--muted); font-weight: 600; }
#cu-epuises { margin: 18px 0; }
/* Proposer les courses n'est pas une note de bas de page (§ 8) : pleine largeur. */
#cu-epuises .btn-ghost { width: 100%; min-height: 48px; }
#cu-fini { margin-top: 20px; }

/* ── La fiche d'un aliment — cahier § 22, 30/08 ; P10 le 13/09 ──────────────
   La maquette P10 : en-tête aligné à gauche avec un ⋮, photo encadrée à gauche
   du nom, une carte à trois colonnes pour le stock, des cartes d'information à
   chevron, une rangée de gestes, le bandeau des recettes, puis la nutrition.
   Les couleurs sont celles du cadre commun ; rien ici n'est une seconde palette. */
.pi-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.pi-head .pi-surtitre { flex: 1; min-width: 0; margin: 0; font-size: 24px; font-weight: 600; }
.pi-tete { display: flex; align-items: flex-start; gap: 16px; margin: 0 0 18px; }
/* ⚠️ **La vignette est un cadre, pas un dessin qui flotte.** Un carré ivoire à
   coins arrondis, la photo à fond perdu dedans, le dessin avec une marge — c'est
   ce qui la fait lire comme la photo d'un produit et non comme un pictogramme.
   Sous 360 px (iPhone SE de 1ʳᵉ génération : 320) la colonne du nom tomberait
   à 150 px ; la vignette passe alors au-dessus, et le titre garde sa largeur. */
.pi-vignette .diary-thumb {
  width: 164px; height: 164px; border-radius: 20px;
  background: var(--card); border: 1px solid var(--line); padding: 6px;
}
.pi-vignette .diary-thumb img { object-fit: contain; }
.pi-vignette .diary-thumb-empty .icon { width: 56px; height: 56px; }
.pi-identite { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; padding-top: 4px; }
.pi-titre {
  margin: 0; font-family: var(--ecriture); font-weight: 400;
  font-size: 31px; line-height: 1.12; overflow-wrap: anywhere;
}
.pi-categorie {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px 8px 10px; border-radius: 14px;
  background: color-mix(in srgb, var(--sand) 38%, var(--card));
  color: var(--espresso); font-size: 22px;
}
.pi-categorie-icone { display: inline-flex; color: var(--terra); }
.pi-categorie-icone .icon { width: 20px; height: 20px; }
.pi-sous { margin: 0; color: var(--muted); font-size: 21px; line-height: 1.35; }
.pi-sous:empty { display: none; }
@media (max-width: 359px) {
  .pi-tete { flex-direction: column; gap: 12px; }
  .pi-vignette .diary-thumb { width: 140px; height: 140px; }
}

/* La carte du stock : trois colonnes séparées d'un filet, comme la maquette.
   Chaque colonne empile son icône, son nom, sa valeur — et sa troisième ligne :
   le délai pour la péremption, les `±` pour une quantité qui se compte. */
/* ⚠️ La première colonne un peu plus large : « Toujours disponible » et
   « 4 unités › » y vivent, là où « 13 sept. » se contente de moins. */
.pi-resume {
  display: grid; grid-template-columns: 1.1fr .9fr 1fr;
  margin-bottom: 12px; padding: 6px 0;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
}
.pi-cellule {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  min-width: 0; padding: 10px 12px;
  border: none; border-left: 1px solid var(--line); background: none;
  color: inherit; font: inherit; text-align: left;
}
.pi-cellule:first-child { border-left: none; }
.pi-cellule-icone { width: 22px; height: 22px; color: var(--espresso); margin-bottom: 2px; }
.pi-cellule-date .pi-cellule-icone { color: var(--terra); }
.pi-cellule-nom { color: var(--muted); font-size: 19.5px; line-height: 1.25; padding-right: 18px; }
/* ⛔ Pas `overflow-wrap: anywhere` ici : dans 95 px il coupait « Toujours
   disponible » lettre à lettre. Les mots se replient entre eux, c'est tout. */
.pi-cellule b { font-size: 24px; font-weight: 600; line-height: 1.2; }
.pi-cellule small { font-size: 19.5px; color: var(--muted); }
.pi-cellule small.warn { color: var(--terra); }
.pi-cellule small.urgent { color: var(--terra); font-weight: 700; }
.pi-cellule small.danger { color: var(--danger, #b3261e); font-weight: 700; }
.pi-cellule-valeur { display: inline-flex; align-items: center; gap: 4px; }
/* La cellule de date est un bouton : elle ouvre C22. Le chevron est posé dans
   son coin, tourné vers la droite par `transform` — aucun second dessin. */
.pi-cellule-date { cursor: pointer; }
.pi-chevron { width: 16px; height: 16px; flex: none; color: var(--muted); transform: rotate(-90deg); }
.pi-cellule > .pi-chevron { position: absolute; right: 8px; top: 12px; }
/* Ce qui se pèse se tape : le nombre devient une cible, et il le dit. */
#pi-nombre.saisissable { cursor: pointer; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 4px; }
/* Les `±` d'un article qui se compte : deux boutons ronds de 30 px — la cible
   de 44 px vient du `::after` commun. */
.pi-quantite { display: flex; align-items: center; gap: 10px; margin-top: 2px; }
.pi-quantite .btn-round { width: 30px; height: 30px; min-height: 30px; padding: 0; font-size: 26.5px; box-shadow: none; }
/* Sous 360 px, trois colonnes font 87 px et « 300 g › » n'y tient plus : la
   carte s'empile, un filet entre chaque rangée. */
@media (max-width: 359px) {
  .pi-resume { grid-template-columns: 1fr; }
  .pi-cellule { border-left: none; border-top: 1px solid var(--line); }
  .pi-cellule:first-child { border-top: none; }
}

/* Les cartes d'information : icône, libellé et valeur, chevron. Une carte par
   réglage, comme la maquette — pas une liste qui les colle. */
.pi-carte {
  display: flex; align-items: center; gap: 14px;
  width: 100%; min-height: 64px; padding: 12px 16px; margin-bottom: 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  color: inherit; font: inherit; font-size: 22.5px; text-align: left; cursor: pointer;
}
.pi-carte > .icon { width: 24px; height: 24px; flex: none; color: var(--espresso); }
.pi-carte-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.pi-carte-texte > span { color: var(--muted); font-size: 20px; }
.pi-carte-texte > small { color: var(--muted); font-size: 19px; }
.pi-carte b { font-size: 24px; font-weight: 600; overflow-wrap: anywhere; }
.pi-carte .interrupteur { margin: 0; }

/* La rangée des gestes : trois tuiles égales, comme sur P04. ⚠️ Mesuré sur un
   iPhone SE de 1ʳᵉ génération : trois colonnes y font 87 px, ce qui tient —
   c'est pour ça qu'il n'y en a pas quatre. ⛔ Et pas de « Supprimer ». */
.pi-actions {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  margin: 14px 0;
}
.pi-action {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 4px; min-height: 92px; padding: 12px 6px;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: inherit; cursor: pointer;
  font: inherit; text-align: center; line-height: 1.25;
}
.pi-action > .icon { width: 24px; height: 24px; flex: none; color: var(--terra); margin-bottom: 4px; }
.pi-action b { font-size: 20px; font-weight: 600; }
.pi-action small { font-size: 19px; color: var(--muted); }
/* Un article « toujours disponible » n'a rien à finir : la rangée passe à deux. */
.pi-actions.deux { grid-template-columns: repeat(2, 1fr); }
/* Mesuré à 320 px : trois tuiles de 87 px, et « ou tout, et dire pourquoi »
   les montait à 160 px de haut — la rangée occupait le quart de l'écran. Le
   sous-titre s'efface ; le libellé suffit, et c'est lui que la feuille répète. */
@media (max-width: 359px) {
  .pi-action small { display: none; }
  .pi-action { min-height: 80px; }
}

/* Le bandeau des recettes, sur un fond sauge léger — c'est une porte vers la
   bibliothèque, pas un geste de stock : il ne ressemble pas aux tuiles. */
.pi-recettes {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 14px; margin: 0 0 22px;
  border: 1px solid color-mix(in srgb, var(--sage) 35%, var(--line));
  border-radius: 16px;
  background: color-mix(in srgb, var(--sage) 14%, var(--card));
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.pi-recettes-icone {
  width: 40px; height: 40px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--sage) 28%, var(--card)); color: var(--sage);
}
.pi-recettes-icone .icon { width: 22px; height: 22px; }
.pi-recettes-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pi-recettes-texte b { font-size: 22.5px; font-weight: 600; }
.pi-recettes-texte small { font-size: 19.5px; color: var(--muted); }

.pi-section { font-size: 27.5px; margin: 0 0 10px; }
/* La nutrition en une rangée : icône, titre et portée, les quatre valeurs, le
   chevron vers la fiche détaillée. Les valeurs passent à la ligne sous 420 px. */
.pi-nutrition {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px;
  width: 100%; padding: 14px 16px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
/* ⚠️ L'en-tête prend sa rangée entière. Posé en `flex: 1` à côté des valeurs,
   il ne gardait que 60 px et son titre débordait **par-dessus** les nombres —
   mesuré à 390 px. Le chevron reste dans le coin, la rangée des valeurs dessous. */
.pi-nutrition { position: relative; padding-right: 36px; }
.pi-nutrition-tete { display: flex; align-items: center; gap: 10px; flex-basis: 100%; min-width: 0; }
.pi-nutrition-tete .icon { width: 22px; height: 22px; flex: none; color: var(--terra); }
.pi-nutrition-tete span { display: flex; flex-direction: column; gap: 2px; }
.pi-nutrition-tete strong { font-size: 22px; }
.pi-nutrition-tete small { font-size: 19px; color: var(--muted); }
.pi-nutrition-valeurs { display: flex; gap: 14px; align-items: baseline; flex-wrap: wrap; }
.pi-nutrition-valeurs span { display: flex; flex-direction: column; align-items: center; }
.pi-nutrition-valeurs b { font-size: 24px; }
.pi-nutrition-valeurs small { color: var(--muted); font-size: 18px; }
.pi-nutrition > .pi-chevron { position: absolute; right: 12px; top: 18px; }
#pi-repas { width: 100%; margin-top: 10px; min-height: 48px; }

/* Les raisons d'une sortie : des puces, pas une liste déroulante. Elles sont
   cinq, elles se lisent d'un coup d'œil, et **aucune n'est obligatoire** — on
   peut valider sans en choisir. */
.ps-motifs { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 16px; }
.ps-motifs .chip.on { background: var(--bouton-fond); color: var(--bouton-texte); border-color: var(--bouton-fond); }

/* ── Où c'est rangé — 29/08, et ce n'est plus un titre de groupe ────────────
   La bascule d'emplacement a d'abord posé un intertitre par étagère
   (`.pantry-lieu` : « BAC À LÉGUMES · 4 »), puis est passée aux cartes de
   `cartesRangement`, qui portent le même compte dans leur propre en-tête. Les
   trois règles de l'intertitre ont survécu à ce changement sans que rien ne les
   nomme — retirées le 07/09. ⚠️ **L'arbitrage du 29/08, lui, n'a pas bougé** :
   le lieu reste une bascule d'affichage et non le défaut, parce qu'un
   groupement disperserait dans six sections les trois articles qui pressent. */

/* La troisième couleur de la pastille : une date connue et rien qui presse. */
.pantry-quand.ok { color: var(--sage, #7C8B63); }

/* Les deux champs de la feuille d'article. Le libellé au-dessus plutôt qu'à
   côté : un menu d'emplacement et un sélecteur de date natifs sont larges tous
   les deux, et à 320 px il ne reste rien pour le mot qui les nomme. */
.pd-champ {
  display: block; margin: 0 0 12px;
  font-size: 20px; color: var(--muted);
}
.pd-champ select, .pd-champ input { display: block; width: 100%; margin-top: 5px; }
#add-pantry .btn-scan { grid-area: scan; }
#add-pantry button[type="submit"] { grid-area: ajouter; }

/* ── Ce que la photo du frigo a vu — § 15 quater, 29/08 ────────────────────
   La ligne **entière** bascule la case : c'est un `<label>`, donc le
   navigateur s'en charge. Vingt lignes à relire ne doivent pas demander de
   viser vingt interrupteurs de 40 px. */
/* ── La ligne de relecture, en deux rangs — 05/09 ────────────────────────────
   **Le nom est ce qu'on lit, donc il prend la largeur.** Mesuré à 390 px : la
   vignette, le nombre, la date et la case en occupaient 306 des 350, laissant
   **44 px** au libellé — « yaourt nature » s'y lisait « yao ». ⚠️ **Et c'est la
   durée type qui l'a révélé** : la date n'apparaissait qu'en mode courses, elle
   est maintenant proposée sur presque toutes les lignes.

   ⛔ **Pas un libellé raccourci ni une date plus étroite** : c'est le § *ni le
   nom ni le chiffre ne se tronquent*, et le nom est devenu un **champ** — un
   champ de 44 px ne se corrige pas. On empile. */
/* ── P11, 13/09 : chaque ligne est une carte, avec son chevron ─────────────── */
.fp-head { display: flex; align-items: center; margin-bottom: 12px; }
.fp-titre { margin: 0 0 6px; font-family: var(--ecriture); font-weight: 400; font-size: 32.5px; line-height: 1.12; }
.fp-intro { margin: 0 0 16px; color: var(--muted); font-size: 22.5px; line-height: 1.4; }
/* L'aperçu : la photo à fond perdu dans un cadre de 16 / 10, et la porte pour
   en ajouter une posée dessus, comme sur la maquette. */
.fp-apercu {
  position: relative; margin-bottom: 18px;
  border-radius: 16px; overflow: hidden; background: var(--card);
  aspect-ratio: 16 / 10; max-width: 100%;
}
.fp-apercu img { display: block; width: 100%; height: 100%; object-fit: cover; }
.fp-apercu-ajouter, .fp-apercu-suivante {
  position: absolute; bottom: 12px;
  display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; padding: 10px 16px; border: none; border-radius: 12px;
  background: rgba(255, 252, 246, .92); color: var(--espresso);
  font: inherit; font-size: 22.5px; font-weight: 600; cursor: pointer;
}
.fp-apercu-ajouter { right: 12px; }
.fp-apercu-ajouter .icon { width: 20px; height: 20px; }
.fp-apercu-suivante { left: 12px; font-weight: 500; }
.fp-barre {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 4px 0 10px;
}
.fp-compte { margin: 0; font-family: var(--ecriture); font-weight: 400; font-size: 27px; }
.fp-tout { display: flex; align-items: center; gap: 10px; color: var(--terra); font-size: 22.5px; cursor: pointer; }
.fp-tout .interrupteur { margin: 0; }
/* Les cases de la relecture et de la fiche d'article prennent la terre cuite
   de la maquette ; celles des réglages gardent leur accent commun. */
.fp-tout .interrupteur, .fp-ligne > .interrupteur, .pi-carte .interrupteur { accent-color: var(--terra); }
.fp-sous-compte { margin: -4px 0 10px; color: var(--muted); font-size: 20px; }

.fp-ligne {
  cursor: pointer;
  display: grid; align-items: center;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas:
    "vignette texte  case chevron"
    "vignette champs case chevron"
    "detail   detail detail detail";
  column-gap: 8px; row-gap: 6px;
  padding: 10px 10px 10px 12px; margin-bottom: 8px;
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
}
.fp-ligne > .diary-thumb { grid-area: vignette; width: 48px; height: 48px; }
.fp-ligne > .diary-text { grid-area: texte; }
.fp-ligne > .fp-champs { grid-area: champs; display: flex; gap: 8px; align-items: center; }
.fp-ligne > .interrupteur { grid-area: case; }
.fp-ligne > .fp-chevron { grid-area: chevron; width: 36px; height: 36px; }
.fp-chevron .icon { width: 16px; height: 16px; color: var(--muted); transition: transform .18s ease; }
.fp-chevron[aria-expanded="true"] .icon { transform: rotate(180deg); }
/* Ce qui n'est pas retenu s'efface, sans disparaître : un doublon ou une
   supposition écartée reste lisible — la retirer se lirait comme une lecture
   manquée (§ *un doublon se montre, il ne s'escamote pas*).
   ⚠️ **La case de la ligne, et elle seule** : `input:not(:checked)` attrapait
   aussi le champ du nom et celui de la date — jamais « cochés » —, donc toute
   ligne datée s'affichait éteinte. */
.fp-ligne:has(> .interrupteur:not(:checked)) { opacity: .55; }
.fp-ligne .diary-detail.warn { color: var(--terra); }
/* Le détail déplié : des champs nommés, sur le fond crème, sous la ligne. */
.fp-detail {
  grid-area: detail; cursor: default;
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin-top: 4px; padding: 12px; border-radius: 12px; background: var(--cream);
}
.fp-detail-champ { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: 19.5px; color: var(--muted); }
.fp-detail-champ select, .fp-detail-champ input { width: 100%; margin: 0; font-size: 24px; padding: 8px 10px; }
.fp-detail-note { grid-column: 1 / -1; margin: 0; font-size: 20px; line-height: 1.35; }
@media (max-width: 359px) { .fp-detail { grid-template-columns: 1fr; } }

/* Le champ de date d'une ligne de relecture — cahier § 14-15. Étroit : il vit
   entre le libellé et la case, et le libellé est ce qu'on lit. */
.fp-date { width: 132px; flex: none; font-size: 19.5px; padding: 8px 10px; }
/* ⚠️ **Ce qui manque se voit sans se peindre en rouge.** Une date absente n'est
   pas une faute — c'est la moitié des emballages —, mais c'est là qu'on veut
   poser le doigt. Le pointillé le dit sans crier. */
.fp-date.manquante { border-style: dashed; color: var(--muted); }

/* La quantité — cahier § 13, 14. **Un bouton depuis le 06/09**, et non plus un
   champ : il ouvre la molette du frigo, celle du formulaire d'ajout et de la
   fiche d'un article.

   ⚠️ **C'est ce qui lui rend son unité.** Le champ libre écrivait « unite » en
   dur ; on ne pouvait pas dire « 500 g », et `_a_deduire` compare les unités
   des deux côtés.

   ⚠️ **13 px comme la date d'à côté, et l'exception du § 1 tombe avec le
   clavier.** Le champ décimal d'avant faisait zoomer iOS à la mise au point et
   laissait la page décalée à la fermeture du clavier ; un bouton n'ouvre aucun
   clavier — c'est exactement la raison pour laquelle la date vit à 13 px.

   ⚠️ **Et la largeur suit le contenu** : « — », « 4 unités » et « 500 g » n'ont
   pas la même longueur, et 62 px coupaient les deux derniers. `max-width` garde
   la place du libellé, qui est ce qu'on lit. */
.fp-qte {
  flex: none; font-size: 19.5px; padding: 8px 10px; text-align: center;
  min-width: 62px; max-width: 118px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
  background: var(--card); color: inherit;
  border: 1px solid var(--line); border-radius: 12px;
}
.fp-qte.manquante { border-style: dashed; color: var(--muted); }

/* Une date lue sans savoir à qui elle est — cahier § 17. En tête de la liste :
   c'est une question à trancher avant de ranger, pas une note en bas de page. */
.fp-orphelines { margin-bottom: 16px; }
.fp-orpheline {
  background: var(--cream); border-radius: 14px; padding: 12px 14px; margin-bottom: 8px;
}
.fp-orpheline p { margin: 0 0 10px; font-size: 20px; }
.fp-choix { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
/* « Aucun de ceux-là » est une commande isolée : 44 px, comme les puces à côté. */
.fp-choix .link-btn { min-height: 44px; }

/* ⛔ **En bas du contenu, jamais en couche fixe.** Deux couches au même bord se
   recouvrent — le piège mesuré du 21/08 sur le panier de la sélection multiple
   — et cet écran n'a rien qui justifie d'y retomber : on valide **après** avoir
   parcouru la liste, ce qui est précisément le geste qui amène en bas. */
.fp-actions { display: flex; flex-direction: column; gap: 10px; margin: 22px 0 8px; }

#pantry-recipes-block { margin-top: 26px; }
#pantry-recipes-block h2 {
  font-size: 21px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 8px;
}

/* ── Le rapport de fin d'import ────────────────────────────────────────────────
   Il s'affiche seul sur un lot terminé : un import en masse ne passe par aucun
   écran de relecture, et rien n'obligeait à déplier 163 lignes pour découvrir
   que trois vidéos avaient été écartées.

   ⚠️ **Les couleurs disent le registre, pas la gravité.** Le groupe des doublons
   est neutre et non alarmant — « 8 étaient déjà là » est une bonne nouvelle, et
   le peindre comme un échec la ferait lire comme une perte. */
.batch-rapport { margin-top: 12px; display: grid; gap: 10px; }
.rapport-groupe {
  padding: 10px 12px; border-radius: 12px;
  background: var(--cream); border: 1px solid var(--sand);
}
.rapport-groupe.est-echec { border-color: var(--danger); }
.rapport-titre { margin: 0; font-size: 20px; font-weight: 600; color: var(--espresso); }
.rapport-sous { margin: 3px 0 0; font-size: 18px; color: var(--muted); line-height: 1.35; }
.rapport-ligne {
  margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line);
  display: grid; gap: 2px; font-size: 19px;
}
.rapport-ligne small { color: var(--muted); line-height: 1.3; }
/* Le nom sur une seule ligne : un titre de TikTok fait volontiers trois lignes
   d'emoji et de hashtags, et le rapport se lit en diagonale. */
.rapport-nom {
  color: var(--espresso); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rapport-reste { margin: 7px 0 0; font-size: 18px; color: var(--muted); font-style: italic; }

/* Réordonner les collections — deux flèches, prises au doigt. */
.order-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.order-row:last-child { border-bottom: none; }
.order-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 44 px, la cible tactile du § 11 : une flèche est faite pour être touchée
   plusieurs fois de suite, et c'est le pire endroit pour la rendre petite. */
.order-row .icon-btn { flex: none; width: 44px; height: 44px; min-height: 44px; font-size: 24px; }
/* Une seule flèche du sprite, retournée pour monter. */
.order-row .icon-btn .icon { width: 20px; height: 20px; }
.order-row .icon-haut { transform: rotate(180deg); }
/* Le refus du serveur se pose entre la liste et les boutons, aligné sur le
   retrait du pied commun : la liste réordonnée reste visible au-dessus. */
#order-erreur { margin: 8px 14px 0; }

/* ── La cloche, et ce qu'elle a manqué ────────────────────────────────────── */
.cloche { position: relative; }
.cloche-pastille {
  position: absolute; top: 2px; right: 2px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--danger); color: #fff;
  font-size: 15px; line-height: 16px; font-weight: 700; text-align: center;
  /* Le chiffre est une **information**, pas une cible : le bouton entier l'est
     déjà, et une pastille cliquable volerait le tap destiné à la cloche. */
  pointer-events: none;
}
/* ── La page des notifications — C08, 17/09 ─────────────────────────────────
   Une ligne : portrait de qui a agi, phrase et date, icône du genre, vignette
   de la recette, point terre cuite sur les non-lues. Scopé sur
   `#screen-notifications`. */
#screen-notifications .notifs-puces { margin: 4px 0 10px; padding: 2px 0 8px; gap: 8px; }
#screen-notifications .notifs-puces .chip { flex: 0 0 auto; }
#screen-notifications .notifs-liste { display: flex; flex-direction: column; gap: 6px; }
#screen-notifications .notif {
  position: relative; display: grid; grid-template-columns: 44px 1fr auto auto; align-items: center;
  gap: 12px; width: 100%; text-align: left; min-height: 64px;
  padding: 10px 12px; border: none; border-radius: 16px;
  background: var(--card); color: inherit; font: inherit; cursor: pointer;
}
#screen-notifications div.notif { cursor: default; }
#screen-notifications .notif-portrait {
  width: 44px; height: 44px; border-radius: 50%; overflow: hidden; flex: none;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso);
  font-size: 24.5px; font-weight: 600;
}
#screen-notifications .notif-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
#screen-notifications .notif-app { background: #E4E9DA; color: #5E6E48; }
#screen-notifications .notif-app .icon { width: 22px; height: 22px; }
#screen-notifications .notif-texte { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
#screen-notifications .notif-phrase {
  font-size: 22px; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
#screen-notifications .notif-phrase b { font-weight: 700; }
#screen-notifications .notif-date { font-size: 19px; color: var(--muted); }
#screen-notifications .notif-genre { display: grid; place-items: center; width: 28px; color: var(--coeur); }
#screen-notifications .notif-genre .icon { width: 22px; height: 22px; }
#screen-notifications .notif-genre-abonne, #screen-notifications .notif-genre-application { color: #5E6E48; }
#screen-notifications .notif-genre-enregistrement { color: var(--espresso); }
#screen-notifications .notif-vignette {
  width: 48px; height: 48px; border-radius: 12px; overflow: hidden; flex: none; display: block;
}
#screen-notifications .notif-vignette img, #screen-notifications .notif-vignette .thumb-fallback,
#screen-notifications .notif-vignette .thumb-illus { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 1; display: block; }
#screen-notifications .notif-vignette .thumb-illus { padding: 6px; object-fit: contain; background: #F1E7DA; }
/* Une non-lue se distingue par un point, pas par un fond : les lignes sont
   déjà des cartes, et un second aplat les rendrait illisibles. */
#screen-notifications .notif-point {
  position: absolute; left: 4px; top: 50%; width: 8px; height: 8px; margin-top: -4px;
  border-radius: 50%; background: var(--coeur);
}
#screen-notifications .notif-vignette + .notif-point { left: 4px; }

/* Un réglage : ce qu'il fait à gauche, la commande à droite. */
.reglage-ligne {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
}
.reglage-ligne span { display: flex; flex-direction: column; min-width: 0; }
.reglage-ligne small { color: var(--cinnamon); }
.reglage-ligne select {
  flex: none; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: inherit; padding: 8px 10px; min-height: 44px;
  /* 16 px, jamais moins : sous ce plancher iOS agrandit la page à la mise au
     point et la laisse décalée (§ 1). Un `select` y expose comme un champ. */
  font: inherit; font-size: 24px;
}
/* La cible fait 44 px même si la case n'en fait que 24 : c'est le doigt qui
   décide de la taille, pas le dessin. */
.interrupteur { flex: none; width: 24px; height: 24px; margin: 10px; }

/* ═══════════════════════════════════════════════════════════════════════════
   Tablette et bureau — 24/08

   **L'application est née sur un téléphone, et une colonne de 720 px sur un
   écran de 1 440 n'est pas « une app mobile qui marche partout » : c'est un
   écran vide de chaque côté.** Ce chapitre ajoute deux paliers, et rien d'autre
   ne change — le HTML est le même, les écrans sont les mêmes, le carrousel
   reste la source de vérité de la navigation (§ 3).

   | | Ce qui change | Pourquoi ce nombre |
   |---|---|---|
   | **≥ 820 px** | la colonne passe à 940, les grilles se densifient | un iPad portrait fait 834 px de large, un iPad Pro 1 024 : le palier doit tomber **sous** le plus étroit des deux, sinon la tablette qu'on visait garde la mise en page du téléphone |
   | **≥ 1 180 px** | la barre d'onglets devient un **rail latéral**, la colonne passe à 1 160 | un iPad en paysage fait 1 194 px, une fenêtre de portable 1 280 à 1 440 : au-delà de ce seuil il y a la place d'un rail **et** d'une colonne pleine, en dessous il faudrait choisir |

   ⛔ **Et surtout pas un troisième jeu d'écrans.** La tentation, sur une
   application qui a des invariants d'interface aussi serrés, est d'écrire une
   « version bureau » à côté : c'est deux fois le code, deux fois les défauts, et
   la seconde prend du retard dès la semaine suivante — exactement ce que
   `/admin` assume à dessein pour une page que **personne** ne partage avec
   l'app, et qui n'a donc rien à tenir en commun.

   ⚠️ **Ce que les paliers ne touchent pas, et c'est volontaire** : les quatre
   verrous d'échelle (§ 1) restent posés — une tablette est un appareil tactile,
   et le zoom à la mise au point d'un champ y fait exactement le même dégât ; le
   défilement reste dans les pages, jamais dans la fenêtre ; et aucune couche
   fixe ne cesse d'être recadrée, elle se recadre seulement sur une colonne qui
   n'est plus au milieu de l'écran.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Palier 1 : la colonne s'élargit ─────────────────────────────────────── */

@media (min-width: 820px) {
  :root { --app-w: 940px; }

  /* ⚠️ **Un écran large n'est pas un écran étiré.** Le rembourrage devient la
     gouttière : `max()` garde les 20 px du téléphone tant que la place manque,
     et centre la colonne de lecture dès qu'elle est plus étroite que la coque.
     C'est ce qui permet à un écran de saisie de rester à sa largeur **sans un
     seul conteneur en plus** dans le HTML — un enveloppement par écran aurait
     été quinze occasions d'oublier. */
  .pane, .screen:not(.pane) {
    padding-top: 34px;
    padding-inline: max(20px, calc((100% - var(--lecture-w)) / 2));
  }
  /* L'accueil rend sa gouttière au bandeau bord à bord : ses enfants la
     reprennent, avec la même formule. */
  #screen-home { --gouttiere: max(20px, calc((100% - var(--lecture-w)) / 2)); }
  #screen-library, #screen-communaute, #screen-profile {
    --gouttiere: max(20px, calc((100% - var(--lecture-w)) / 2));
  }

  /* Le registre des écrans qui ne s'étirent pas, et la raison est la même pour
     les trois familles : on y **lit** ou on y **saisit**, et ni l'un ni l'autre
     ne se fait sur 1 100 px. Un écran absent d'ici prend toute la coque. */
  #screen-goal, #screen-welcome, #screen-create, #screen-import, #screen-videos,
  #screen-aide, #screen-review, #screen-edit, #screen-page, #screen-dictee,
  #screen-analysis, #screen-corrections, #screen-imports, #screen-profile,
  #screen-fooddetail, #screen-foodsearch, #screen-pantry, #screen-frigo-photo,
  #screen-pantry-item, #screen-cuisine, #screen-frigo-histoire, #screen-cuisinees,
  #screen-bloques, #screen-plate {
    --lecture-w: 700px;
  }
  /* Une fiche de recette se lit comme un article : un peu plus large qu'un
     formulaire, jamais autant qu'une grille. */
  #screen-recipe { --lecture-w: 860px; }

  /* Les grilles ne changent pas de forme, seulement de pas : `auto-fill` fait
     déjà le reste, et une carte de 158 px agrandie à 200 rend les photos
     lisibles au lieu de multiplier les colonnes. */
  .recipe-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
  /* ⛔ **Deux cartes ne s'étirent pas avec la fenêtre.** La grille des derniers
     ajouts garde ses deux colonnes — c'est la composition de P01 — mais bornée :
     à 1 800 px, deux cartes prendraient 890 px chacune, soit des photos de plat
     plus larges que hautes. Même arbitrage que `.today-colonnes`, qui déduit un
     nombre de colonnes d'une largeur plutôt que de l'étirer. */
  .recipe-grid.grille-ajouts { max-width: 620px; gap: 16px; }
  .collection-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
  .rail .recipe-card-wrap { width: 196px; }
  /* La photo d'une fiche grandit avec sa colonne — mais elle est bornée par une
     hauteur, jamais par un rapport : `cover` recadre, et un 16/9 sur 860 px
     ferait 480 px de haut, soit la moitié d'un écran de portable avant le
     premier ingrédient. */
  .hero { min-height: 320px; }

  /* ⚠️ **Ce qui est une ligne de saisie garde sa largeur de ligne de saisie.**
     Un champ « Ajouter un article » de 940 px ne se remplit pas mieux qu'un de
     600 ; il éloigne seulement son bouton de son texte. */
  /* La pastille remonte à côté du bonjour : c'est la disposition de la
     planche, qui est à 834 pt. En dessous elle descend d'une ligne — voir le
     commentaire de `.bonjour` dans `index.html`. */
  .bonjour {
    flex-direction: row; align-items: center; justify-content: space-between;
    flex-wrap: wrap;
  }
  .add-item, .search-row, .wizard-choices,
  .today-goal-line, .auth-card form, .segmented { max-width: 660px; }
  /* Une bascule à deux entrées étalée sur 1 100 px se lit comme deux onglets de
     navigation, ce qu'elle n'est pas : c'est un filtre de la même liste. */
  .segmented { max-width: 420px; }

  /* ⚠️ **Un titre centré sur une colonne large n'est plus un titre, c'est une
     bannière.** Sur téléphone, les deux flancs de `.pane-head` valent 76 px
     chacun et le titre tombe au milieu de ce qui reste — soit à peu près là où
     commence le contenu. À 1 160 px, il flotte à 400 px de la première carte,
     et le regard qui descend la page ne le rencontre plus.

     Le flanc gauche **garde sa colonne** : la moitié de ces en-têtes y portent
     un bouton de retour, et le faire disparaître ici serait le § 8 — une porte
     retirée d'un écran au motif qu'elle est vide sur un autre. Seul le cas
     réellement vide se replie. */
  /* ⛔ **Sauf la marque, qui n'est pas un titre.** Le paragraphe ci-dessus vise
     le titre d'un écran — celui que le regard rencontre en descendant la page.
     Une marque est un fronton : elle se lit une fois, en haut, au milieu, et
     c'est ce que fait la planche à 834 pt. */
  .tete-marque .head-title { text-align: center; }
  .pane-head { grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, auto); }
  .pane-head > .head-side:empty { min-width: 0; }
  .head-title { text-align: left; }

  /* Quatre raccourcis étalés sur toute la largeur deviennent quatre pancartes.
     Ils gardent leur pas, la rangée ne s'étire pas. */
  .shortcuts { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 620px; }
}

/* ── Palier 2 : le rail, et deux colonnes là où il y a deux colonnes ──────── */

@media (min-width: 1180px) {
  :root {
    /* ⚠️ **Une largeur fixe laisse un écran large aux trois quarts vide** —
       signalement du 24/08, capture d'un 3 440 px : la coque de 1 412 flottait
       au milieu de **2 000 px de fond**, soit un peu plus de place perdue que
       de place occupée. Ce n'était pas une question de position mais de
       **quantité** : la même coque à 2 000 px de large laisse 700 px de chaque
       côté, ce qui se lit comme une marge et non comme un vide.

       La colonne suit donc la fenêtre, avec un plafond. ⛔ **Et le plafond n'est
       pas décoratif** : sans lui, une bibliothèque s'étalerait sur 3 400 px —
       quatorze cartes par rangée, un œil qui traverse 90 cm, et des lignes de
       texte que personne ne relit jusqu'au bout. 1 800 px est la largeur d'un
       plan de travail confortable ; au-delà, c'est le fond qui gagne, et c'est
       très bien. */
    --app-w: min(1800px, calc(100vw - var(--rail-w) - 24px));
    --rail-w: 252px;
    /* **Zéro, et ce n'est pas un contournement.** Le jeton dit « ce que la barre
       du bas prend en hauteur » : elle n'est plus en bas, donc elle ne prend
       rien. Tout ce qui s'en servait pour se dégager — le rembourrage des pages,
       le plafond des feuilles, le panier de la sélection multiple (§ 2) — se
       remet en place tout seul, sans qu'aucune de ces règles ait à connaître le
       rail. */
    --tabbar-h: 0px;
  }


  /* La coque tient les deux : le rail est un rembourrage, pas un frère. C'est ce
     qui garde `#app` seul maître de la largeur totale — le carrousel, lui,
     continue de ne connaître que la sienne. */
  #app {
    max-width: calc(var(--app-w) + var(--rail-w));
    padding-left: var(--rail-w);
  }

  /* ── La barre d'onglets devient un rail ─────────────────────────────────
     **Le même élément, le même ordre, les mêmes gestes** : `tabOrder()` lit
     toujours `.tab[data-goto]`, donc le carrousel reste ordonné par la barre
     (§ 3). Ce qui change est sa forme.

     ⚠️ **Et son bord gauche est celui de la coque, pas celui de la fenêtre.**
     Collé à l'écran, il flotterait à 40 cm du contenu sur un 27 pouces ; `max()`
     le ramène au bord dès que la fenêtre est plus étroite que la coque, où
     c'est au contraire la seule place possible. */
  .tabbar {
    top: 0; bottom: 0; height: auto;
    left: max(0px, calc(50% - (var(--app-w) + var(--rail-w)) / 2));
    transform: none;
    width: var(--rail-w);
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: min-content;
    align-content: start;
    gap: 3px;
    padding: 20px 14px;
    overflow-y: auto;
    border-top: none;
    border-right: 1px solid var(--line);
  }
  /* `order` et non l'ordre du HTML : la marque est écrite en tête de la barre,
     mais le ➕ remonte au-dessus de tout (`order: -1`) pour être la première
     chose du rail. Il lui faut donc un rang à lui. */
  .rail-marque { display: flex; order: -2; }
  /* ⛔ **Et la marque ne s'écrit pas deux fois dans le même champ de vision.**
     Le rail porte le verrou complet en tête de colonne ; le mot de l'en-tête
     serait alors le même nom, à 400 px de lui, sur le même écran. C'est le
     § *doublon d'attention* — la même chose présentée deux fois se lit comme un
     défaut d'affichage, pas comme deux informations. Le fronton reste au rail,
     qui est celui des deux qu'on ne peut pas déplacer. */
  /* ⚠️ **Le rail porte la marque au-delà de 1 180 px** : l'écrire aussi dans
     l'en-tête la dirait deux fois sur le même écran. Vaut pour l'accueil comme
     pour la bibliothèque, qui partagent cet en-tête depuis le 10/09. */
  .tete-marque .wordmark { display: none; }

  .tab {
    flex-direction: row; justify-content: flex-start; gap: 12px;
    font-size: 22.5px; padding: 10px 12px; border-radius: 14px;
  }
  .tab .icon { width: 22px; height: 22px; }
  /* L'onglet courant se dit par un aplat, pas seulement par une couleur : sur
     une colonne de cinq lignes, la graisse seule ne se voit plus. */
  .tab.active { background: var(--cream); }

  /* Le ➕ redevient ce qu'il est : l'action principale, en tête de rail. Sur la
     barre du bas il devait être rond et centré pour tenir la troisième cellule
     de cinq ; ici il a la place de dire ce qu'il fait. */
  .tab-add {
    order: -1;
    justify-self: stretch; align-self: auto; margin-top: 0;
    width: auto; height: auto; min-height: 48px;
    /* Crème et non terra : la cocotte est orange, et un dessin orange sur une
       pilule terra ne se lit pas. C'est l'aplat de l'onglet courant, sur
       lequel le contour beige du dessin se détache juste assez. */
    background: var(--cream); color: var(--espresso);
    border-radius: 16px; font-size: 24px; font-weight: 600;
    display: flex; align-items: center; justify-content: center; gap: 9px;
    padding: 4px 12px; margin-bottom: 10px;
  }
  .tab-add-cocotte { width: 43px; height: 36px; }
  .tab-add-mot { display: inline; }
  /* ⛔ Pas de `scale` sur un bouton large : ce qui passe pour un rebond sur une
     pastille de 54 px se lit comme un tremblement sur une pilule de 224. */
  .tabbar .tab-add:hover { transform: none; filter: brightness(1.06); background: var(--cream); }

  /* ── Une feuille redevient un dialogue ──────────────────────────────────
     Une feuille est ancrée en bas parce que c'est là qu'est le pouce. À la
     souris, il n'y a pas de pouce : le regard est au centre, et une feuille
     collée au bord d'un écran de 900 px de haut fait traverser tout l'écran
     pour lire son titre. Le plafond, les bandes et le défilement du § 5 ne
     changent pas — c'est l'ancrage qui bouge, et lui seul. */
  .sheet {
    top: 50%; bottom: auto;
    transform: translate(-50%, -50%);
    width: min(520px, calc(100% - 80px));
    max-height: min(78vh, 760px);
    /* L'animation doit suivre l'ancrage : celle du bas repose `transform` sans
       le décalage vertical, et la feuille sautait au coin haut-gauche le temps
       de son entrée. */
    animation-name: sheet-in-centre;
  }
  /* Une feuille posée au milieu de l'écran n'a plus de bord bas contre lequel
     s'appuyer : les épaules du carnet s'égalisent (DA01). */
  .sheet.carnet { border-radius: 28px; }

  /* ── Le journal, en deux colonnes ───────────────────────────────────────
     **Sept cartes indépendantes empilées sur 1 160 px de large, c'est une
     colonne de 400 px et deux tiers d'écran vides.** Elles se répartissent donc
     en deux colonnes.

     ⛔ **Et en multi-colonnes, pas en grille.** Une grille à deux colonnes place
     chaque carte dans une **rangée**, dont la hauteur est celle de la plus
     grande des deux : une carte « Cette semaine » de 400 px en face d'un budget
     de 180 laisse 220 px de blanc sous le budget, et le trou se déplace à chaque
     fois qu'une carte se masque — or la moitié de ces cartes décident
     elles-mêmes de s'afficher (§ *une répartition juge la donnée qu'elle a*).
     Le multi-colonnes coule les cartes l'une après l'autre et équilibre les
     hauteurs lui-même : rien à placer, donc rien à replacer.

     ⚠️ **`column-span: all` pour ce qui n'appartient à aucune colonne** —
     l'en-tête, la barre de jour, et le rail de recettes qui a besoin de sa
     largeur pour montrer plus de deux cartes. */
  /* ⚠️ **Une largeur de colonne, jamais un nombre.** `columns: 2` donnait deux
     colonnes de 890 px sur un écran large — des cartes de repas plus larges que
     hautes, et un pointeur qui traverse la moitié de l'écran pour cocher une
     ligne. En déclarant la **largeur idéale**, le navigateur en met deux à
     1 160 px et trois à 1 800 : un seul réglage, aucun palier à ajouter le jour
     où quelqu'un branche un écran plus grand. */
  #screen-today .today-colonnes { columns: 460px; column-gap: 24px; }
  .today-colonnes > * { break-inside: avoid; margin-top: 0; margin-bottom: 24px; }
  /* ⚠️ **La pile des repas, elle, se coupe — sinon elle tient la colonne à elle
     seule.** Quatre cartes de repas font 600 px d'un bloc : indivisible, elle
     impose sa hauteur aux deux colonnes et laisse 400 px de blanc sous le
     budget. Mesuré au banc, c'est le seul enfant de cet écran dans ce cas.
     Ce sont donc les **cartes** qui deviennent insécables, et la pile qui cède.

     ⛔ Et `display: block` avec elles : un conteneur `grid` ne se fragmente pas
     entre deux colonnes — le navigateur le déplace en entier, donc la règle
     ci-dessus n'aurait rien fait, en silence. */
  .today-colonnes > .meals-stack { break-inside: auto; display: block; margin-bottom: 0; }
  .today-colonnes > .meals-stack > * { break-inside: avoid; margin-bottom: 24px; }
  .today-colonnes > .pourvous-block { column-span: all; }
  /* La barre de jour se range du côté de ce qu'elle commande, comme le titre. */
  #screen-today .day-nav { justify-content: flex-start; }

  /* Même raisonnement pour les rayons de la liste de courses : dix groupes
     repliables les uns sous les autres font défiler ce qui tiendrait d'un coup
     d'œil — et une liste de courses se lit **en entier**, c'est tout son objet. */
  #list-groups { columns: 420px; column-gap: 28px; }
  #list-groups > * { break-inside: avoid; }

  /* ── La fiche recette, en deux colonnes ─────────────────────────────────
     À gauche ce que le plat **est** et ce qu'il faut acheter, à droite ce qu'il
     faut **faire**. Le partage est écrit dans le HTML plutôt que calculé : sur
     une fiche, ce qui va ensemble ne se devine pas à la hauteur des cartes.

     ⚠️ **Une grille, et non le multi-colonnes du journal** — et la différence
     n'est pas un goût. Une grille se pose sans danger sur l'écran lui-même : ce
     qui dépasse allonge la page. Un multi-colonnes sur ce même écran, dont la
     hauteur est celle de la fenêtre, aurait rangé la moitié de la fiche dans
     une colonne hors cadre. Deux mises en page qui se ressemblent, une seule
     qui convient ici. */
  #screen-recipe {
    /* Deux colonnes, oui — de 690 px, pas de 890. Une liste d'ingrédients et
       une suite d'étapes sont du **texte** : elles suivent la règle de la
       largeur de lecture, la grille ne la suspend pas. */
    --lecture-w: min(1400px, 100%);
    display: grid; align-content: start;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 22px;
  }
  #screen-recipe > *:not(.recipe-col) { grid-column: 1 / -1; }
  #screen-recipe > .recipe-col { margin-top: 0; }

  /* Le mode cuisine garde sa mesure de lecture : ses 21 px de pas à pas sur
     1 160 px feraient des lignes de 160 caractères, que l'œil perd en revenant à
     la ligne — et on le lit à un mètre, une main dans la casserole. */
  .cook-body, .cook-head, .cook-foot, .cook-timers,
  .cook-rappel, .cook-progression { max-width: 820px; margin-inline: auto; }
  /* ⚠️ **Le rappel garde son alignement à gauche du bloc**, et non celui de
     la page : `margin-inline: auto` centre la bande, pas ce qu'elle porte. */
  .cook-quitter { margin-right: auto; }
}

/* ── Ce qu'une souris change, et qui n'a rien à voir avec la largeur ──────── */

/* ⚠️ **Deux appareils de la même largeur ne se manipulent pas pareil.** Un iPad
   en paysage et un portable font tous deux 1 194 px ; l'un se touche, l'autre se
   survole. Ces règles se posent donc sur le **pointeur**, jamais sur une taille
   de fenêtre — c'est la même distinction qu'entre `hidden` et `clip` sur le
   carrousel : deux valeurs qui se ressemblent, une seule qui dit ce qu'on veut. */
@media (hover: hover) and (pointer: fine) {
  /* ⚠️ **Un rail se pousse du doigt, il se traîne à la souris.** Les rangées
     horizontales de l'accueil cachent leur barre de défilement — sur un
     téléphone, c'est le geste qui la remplace, et une barre y prendrait 8 px de
     hauteur sur chaque rangée pour rien. À la souris il n'y a pas de geste : il
     reste la molette horizontale, que personne ne cherche, et une carte coupée
     au bord qui dit qu'il y a une suite **sans dire comment y aller**.

     La barre revient donc là où il y a un pointeur fin, et **seulement là** :
     c'est le § *un contenu qui déborde le dit*, avec cette fois de quoi agir. */
  .rail { scrollbar-width: thin; scrollbar-color: var(--sand) transparent; }
  .rail::-webkit-scrollbar { height: 8px; }
  .rail::-webkit-scrollbar-thumb { background: var(--sand); border-radius: 999px; }

  .tab:hover { background: var(--cream); }
  /* La cocotte n'a pas de fond : un aplat crème de 75 × 63 derrière elle au
     survol se lirait comme une carte. Le rail, lui, garde sa pilule crème
     (règle plus spécifique, dans son palier). */
  .tab-add:hover { background: none; }
  .sheet-item:hover, .shop-item:hover, .notif:hover { background: var(--cream); }
  .chip:hover { border-color: var(--sand); }
  .card-fav:hover, .card-menu:hover { transform: scale(1.08); }
}

/* ── La Famille — § 17 ────────────────────────────────────────────────────────

   ⚠️ **Le champ reste à 16 px, comme partout ailleurs.** iOS agrandit la page
   dès qu'on met au point un champ dont le texte fait moins de 16 px, puis la
   laisse zoomée **et décalée** quand le clavier se referme — c'est de là que
   venaient les « fenêtres décentrées », et le § 1 n'a pas d'exception. */
#famille-nom {
  width: 100%; border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit;
  font-size: 24px;
}

/* Le rail qui choisit la bibliothèque qu'on regarde. Il réutilise `.segmented`
   — c'est le même geste que « Toutes / Collections » juste au-dessus — et il
   défile quand les noms débordent : une famille de huit ne tient pas sur 390 px.

   ⚠️ **Il se pousse du doigt et se traîne à la souris**, d'où la barre de
   défilement sous `(hover: hover)` seulement : une carte coupée au bord dit
   qu'il y a une suite sans dire comment y aller. */
/* ⛔ **Le rail est une bande, pas une grille de deux.** `.segmented` pose
   `grid-template-columns: 1fr 1fr` : avec « Mes recettes », deux prénoms et la
   porte, les entrées passaient à la ligne au lieu de défiler, et `flex: 0 0
   auto` posé juste dessous ne pouvait rien y faire — une propriété flex sur
   l'enfant d'une grille ne fait rien, en silence. Depuis le 28/08 le rail porte
   toujours au moins deux entrées, donc le cas n'est plus théorique. */
.famille-rail {
  display: flex;
  grid-template-columns: none;
  overflow-x: auto;
  /* Même raison qu'au-dessus, trouvée par le même balayage : quatre règles
     déclarent un défilement latéral dans cette feuille, deux seulement s'en
     protégeaient. */
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.famille-rail::-webkit-scrollbar { display: none; }
.famille-rail button { flex: 0 0 auto; white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {
  .famille-rail { scrollbar-width: thin; }
  .famille-rail::-webkit-scrollbar { display: block; height: 6px; }
}

/* La porte vers l'écran Famille, en queue du rail. ⛔ **Elle ne peut pas
   ressembler à un cran** — elle n'en est pas un, et un bouton qui a l'air
   sélectionnable et qui change d'écran est le pire des deux. D'où le trait
   pointillé : il se lit comme « ajouter », pas comme « choisir ».
   ⚠️ Et la zone reste à 40 px de haut comme ses voisines : une porte qu'on
   manque du pouce est une porte qu'on n'a pas. */
/* ⚠️ **Deux classes, et pas un `!important`.** `.segmented button` pèse (0,1,1)
   et écraserait une classe seule ; `!important` réglerait le symptôme et
   deviendrait la première ligne qu'on ne peut plus surcharger. */
.famille-rail .famille-porte {
  color: var(--cinnamon); font-weight: 600;
  border: 1px dashed var(--line);
}
/* Une invitation qui attend n'est pas une action de plus dans une liste : c'est
   la seule chose de cet écran qui appelle une réponse. Elle se peint donc comme
   la pastille du Profil, dont elle est le report ici. */
.famille-rail .famille-porte-nouvelle {
  background: var(--cinnamon); color: #FFFFFF;
  border-color: transparent;
}

.famille-membres li { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.famille-membres small { display: block; color: var(--muted); font-size: 18px; }

.famille-invit { display: grid; gap: 10px; padding: 10px 0; }
.famille-invit small { display: block; color: var(--muted); font-size: 18px; }
.famille-gestes { display: flex; align-items: center; gap: 12px; }
.famille-gestes .btn-primary { width: auto; padding: 10px 18px; }

.famille-attente {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 6px 0;
}

/* La pastille du profil : combien d'invitations attendent. Même dessin que
   celle de la cloche, et elle n'apparaît qu'avec un nombre — une pastille vide
   est une inquiétude sans objet. */
.pastille {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; margin-left: 8px;
  border-radius: 10px; background: var(--cinnamon); color: #FFFFFF;
  font-size: 18px; font-weight: 700;
}

/* ── P31 — Ma famille, la maquette du 10/09 (16/09) ─────────────────────────
   ⚠️ Scopé sur `#screen-famille` et sa feuille d'invitation. Même vocabulaire
   que le profil (P30) : cartes bordées, titres serif, pastilles, portraits. */
#screen-famille { --tuile-sauge-i: #5E6E48; --tuile-sable-i: #B8742A; --fam-rose: #FBEBE4; --fam-rose-i: #A94A2A; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) #screen-famille { --tuile-sauge-i: #A9B78C; --tuile-sable-i: #E0B070; --fam-rose: #3A2720; --fam-rose-i: #EBA584; }
}
:root[data-theme="dark"] #screen-famille { --tuile-sauge-i: #A9B78C; --tuile-sable-i: #E0B070; --fam-rose: #3A2720; --fam-rose-i: #EBA584; }

#screen-famille .fam-head { margin-bottom: 10px; }
#screen-famille .fam-decor { width: 156px; height: 128px; }
#screen-famille .fam-note { left: auto; right: 0; top: 0; width: 94px; min-height: 112px; padding: 10px 8px 36px; font-size: 18px; transform: rotate(6deg); }
#screen-famille .fam-decor .ip-decor-marmite { left: 0; right: auto; bottom: -4px; width: 92px; }
#screen-famille .icone-sauge { color: var(--tuile-sauge-i); }
#screen-famille .icone-sable { color: var(--tuile-sable-i); }
#screen-famille .famille-portee { display: flex; gap: 8px; margin: 2px 4px 16px; font-size: 19.5px; line-height: 1.45; }
#screen-famille .famille-portee .icon { flex: none; width: 16px; height: 16px; margin-top: 2px; }

#screen-famille .fam-choix { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
#screen-famille .fam-puce {
  min-height: 44px; padding: 0 16px; border-radius: 999px; font: inherit; font-size: 22.5px;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso); cursor: pointer;
}
#screen-famille .fam-puce[aria-pressed="true"] { background: var(--fam-rose); border-color: transparent; color: var(--fam-rose-i); font-weight: 600; }

#screen-famille .fam-tuiles {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px;
}
#screen-famille .fam-tuile {
  display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0;
  min-height: 96px; padding: 12px 4px 10px; border-radius: 16px;
  border: 1px solid var(--line); background: var(--card); color: var(--espresso);
  font: inherit; text-align: center; cursor: pointer;
}
#screen-famille .fam-tuile.on { background: var(--fam-rose); border-color: transparent; }
#screen-famille .fam-tuile .icon { width: 28px; height: 28px; margin-bottom: 2px; color: var(--fam-rose-i); }
#screen-famille .fam-tuile .icone-sauge { color: var(--tuile-sauge-i); }
#screen-famille .fam-tuile .icone-sable { color: var(--tuile-sable-i); }
#screen-famille .fam-tuile b { font-size: 20px; font-weight: 600; line-height: 1.2; }
#screen-famille .fam-tuile small { min-height: 1.2em; font-size: 18px; color: var(--muted); line-height: 1.2; }

#screen-famille .fam-carte {
  margin: 0 0 14px; padding: 14px 16px; border-radius: 18px;
  background: var(--card); border: 1px solid var(--line);
}
#screen-famille .fam-carte-tete { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
#screen-famille .fam-carte-tete .fam-carte-titre { min-width: 0; }
#screen-famille .fam-carte-titre {
  display: flex; align-items: center; gap: 10px; margin: 4px 0;
  font-family: var(--ecriture); font-weight: 700; font-size: 26.5px; color: var(--espresso);
}
#screen-famille .fam-carte-titre .icon { width: 24px; height: 24px; flex: none; color: var(--fam-rose-i); }
#screen-famille .fam-carte-titre .icone-sauge { color: var(--tuile-sauge-i); }
#screen-famille .fam-carte > .hint { margin: 6px 0 8px; }
#screen-famille .fam-sous-titre { margin: 2px 0 4px; font-size: 20px; }
#screen-famille .fam-pilule {
  display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 14px;
  border-radius: 999px; border: none; background: var(--fam-rose); color: var(--fam-rose-i);
  font: inherit; font-size: 22px; font-weight: 600; white-space: nowrap; cursor: pointer;
}
#screen-famille .fam-pilule .icon { width: 17px; height: 17px; }
#screen-famille .fam-pilule-pleine { background: var(--terra, #C65A2E); color: #FFFFFF; }

#screen-famille .fam-lignes { list-style: none; margin: 0; padding: 0; }
#screen-famille .fam-ligne { display: flex; align-items: center; gap: 12px; padding: 10px 0; }
#screen-famille .fam-lignes > .fam-ligne + .fam-ligne { border-top: 1px solid var(--line); }
#screen-famille .fam-ligne-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#screen-famille .fam-ligne-texte b {
  font-size: 24px; font-weight: 600; color: var(--espresso); overflow-wrap: anywhere;
}
#screen-famille .fam-ligne-texte small { font-size: 20px; color: var(--muted); }
#screen-famille .fam-couronne .icon { width: 16px; height: 16px; color: #C08A2E; vertical-align: -2px; }
#screen-famille .fam-portrait {
  flex: none; width: 48px; height: 48px; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso);
  font-size: 26px; font-weight: 600;
}
#screen-famille .fam-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
#screen-famille .fam-portrait-invite { background: color-mix(in srgb, var(--muted) 30%, var(--card)); color: #FFFFFF; }
#screen-famille .fam-moi {
  flex: none; padding: 6px 12px; border-radius: 999px; font-size: 19.5px;
  background: color-mix(in srgb, var(--line) 70%, var(--card)); color: var(--espresso);
}
#screen-famille .fam-menu { flex: none; width: 44px; height: 44px; border: none; background: transparent; color: var(--muted); }
#screen-famille .famille-invit { flex-wrap: wrap; }
#screen-famille .famille-attente { flex-wrap: wrap; row-gap: 4px; }
#screen-famille .famille-attente .fam-ligne-texte { flex: 1 1 calc(100% - 116px); }
#screen-famille .famille-attente .fam-pilule { margin-left: 60px; }
#screen-famille .famille-attente .fam-ligne-texte b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal; }
#screen-famille .famille-gestes { display: flex; align-items: center; gap: 12px; }

#screen-famille .fam-frigo-compte { margin: 0 0 8px; }
/* Le sélecteur nu : c'est lui que la liste des rangées latérales nomme (§ 6). */
.fam-rail {
  list-style: none; margin: 0 0 8px; padding: 2px 0 6px;
  display: grid; grid-auto-flow: column; grid-auto-columns: 84px; gap: 8px;
  overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none;
}
.fam-rail::-webkit-scrollbar { display: none; }
#screen-famille .fam-rail-article {
  display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center; min-width: 0;
}
#screen-famille .fam-rail-article .diary-thumb {
  width: 76px; height: 64px; border-radius: 14px; background: color-mix(in srgb, var(--sand) 25%, var(--card));
}
#screen-famille .fam-rail-article b {
  font-size: 19.5px; font-weight: 600; line-height: 1.2; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#screen-famille .fam-rail-article small { font-size: 18px; color: var(--muted); }
#screen-famille .fam-carte .reglage-ligne { padding-inline: 0; }
#screen-famille .fam-carte .reglage-ligne small { color: var(--muted); }
#screen-famille .fam-carte .btn-ghost, #screen-famille .fam-carte .btn-primary { width: 100%; }
@media (max-width: 359px) {
  #screen-famille .fam-decor { width: 112px; height: 96px; }
  #screen-famille .fam-note { width: 76px; font-size: 16px; padding: 8px 6px 26px; min-height: 92px; }
  #screen-famille .fam-tuile small { font-size: 17px; }
  #screen-famille .fam-decor .ip-decor-marmite { width: 72px; }
  #screen-famille .fam-tuiles { gap: 6px; }
  #screen-famille .fam-tuile b { font-size: 19px; }
  #screen-famille .fam-carte-titre { font-size: 25px; }
}
@media (hover: hover) and (pointer: fine) {
  #screen-famille .fam-rail { scrollbar-width: thin; }
}

/* La feuille d'invitation — § 1 : champs à 16 px. */
#inviter-sheet .inviter-champ {
  width: 100%; min-height: 48px; padding: 10px 14px; font: inherit; font-size: 24px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--cream); color: var(--espresso);
}
#inviter-sheet .settings-block { padding: 2px 14px 12px; }
#inviter-sheet .settings-block + .settings-block { border-top: 1px solid var(--line); padding-top: 6px; }
#inviter-sheet > .hint { margin-inline: 14px; }
#inviter-sheet .settings-sous-titre {
  margin: 12px 0 8px; padding: 0; font-family: var(--ecriture); font-size: 19px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
#inviter-sheet .btn-primary { width: 100%; margin-top: 10px; }
#inviter-sheet .famille-trouves li { min-height: 48px; }
#inviter-sheet .famille-trouves .link-btn { min-height: 44px; }

/* ── P32 — Rejoindre une famille, la maquette du 10/09 (16/09) ──────────────
   Scopé sur `#screen-rejoindre`. Même vocabulaire que P31. */
#screen-rejoindre { --fam-rose: #FBEBE4; --fam-rose-i: #A94A2A; --tuile-sauge-i: #5E6E48; --tuile-sable-i: #B8742A; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) #screen-rejoindre { --fam-rose: #3A2720; --fam-rose-i: #EBA584; --tuile-sauge-i: #A9B78C; --tuile-sable-i: #E0B070; }
}
:root[data-theme="dark"] #screen-rejoindre { --fam-rose: #3A2720; --fam-rose-i: #EBA584; --tuile-sauge-i: #A9B78C; --tuile-sable-i: #E0B070; }
#screen-rejoindre .rj-head { margin-bottom: 16px; }
#screen-rejoindre .rj-head .ip-titre { font-weight: 700; font-size: clamp(31px, 8.72vw, 36.5px); }
#screen-rejoindre .rj-decor { width: 156px; height: 128px; }
#screen-rejoindre .rj-note { left: auto; right: 0; top: 0; width: 92px; min-height: 108px; padding: 12px 8px 36px; font-size: 19px; transform: rotate(6deg); }
#screen-rejoindre .rj-decor .ip-decor-marmite { left: 0; right: auto; bottom: -4px; width: 92px; }
#screen-rejoindre .rj-famille {
  display: flex; align-items: center; gap: 14px; padding: 16px;
  border-radius: 18px; background: var(--fam-rose); margin-bottom: 14px;
}
#screen-rejoindre .rj-famille-icone {
  flex: none; width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center;
  background: var(--card); color: var(--fam-rose-i);
}
#screen-rejoindre .rj-famille-icone .icon { width: 26px; height: 26px; }
#screen-rejoindre .rj-famille-texte { min-width: 0; }
#screen-rejoindre .rj-famille-nom {
  margin: 0; font-family: var(--ecriture); font-weight: 700; font-size: 27.5px; line-height: 1.15; overflow-wrap: anywhere;
}
#screen-rejoindre .rj-famille-membres { margin: 4px 0 0; color: var(--muted); font-size: 21px; }
#screen-rejoindre .rj-etat { margin: 0 2px 4px; font-size: 24.5px; font-weight: 600; }
#screen-rejoindre .rj-detail { margin: 0 2px 14px; }
#screen-rejoindre .rj-usages {
  list-style: none; margin: 0 0 14px; padding: 4px 16px; border-radius: 18px;
  background: var(--card); border: 1px solid var(--line);
}
#screen-rejoindre .rj-usages li { display: flex; align-items: flex-start; gap: 14px; padding: 12px 0; }
#screen-rejoindre .rj-usages li + li { border-top: 1px solid var(--line); }
#screen-rejoindre .rj-usages li > span:last-child { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
#screen-rejoindre .rj-usages b { font-family: var(--ecriture); font-size: 24.5px; font-weight: 700; }
#screen-rejoindre .rj-usages small { font-size: 20px; color: var(--muted); line-height: 1.4; }
#screen-rejoindre .rj-usage-icone {
  flex: none; width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center;
  background: var(--fam-rose); color: var(--fam-rose-i);
}
#screen-rejoindre .rj-usage-icone .icon { width: 24px; height: 24px; }
#screen-rejoindre .rj-usage-sauge { background: color-mix(in srgb, var(--sage) 18%, var(--card)); color: var(--tuile-sauge-i); }
#screen-rejoindre .rj-usage-sable { background: color-mix(in srgb, var(--amber) 18%, var(--card)); color: var(--tuile-sable-i); }
#screen-rejoindre .rj-promesse {
  display: flex; gap: 10px; align-items: flex-start; margin: 0 0 18px; padding: 12px 14px;
  border-radius: 16px; background: var(--fam-rose); color: var(--espresso); font-size: 20px; line-height: 1.45;
}
#screen-rejoindre .rj-promesse .icon { flex: none; width: 18px; height: 18px; margin-top: 2px; color: var(--fam-rose-i); }
#screen-rejoindre .rj-actions { display: flex; flex-direction: column; gap: 10px; }
#screen-rejoindre .rj-actions > * { width: 100%; margin: 0; min-height: 50px; }
#screen-rejoindre .rj-actions .link-btn { min-height: 44px; }
#screen-rejoindre .rj-refuser { color: var(--fam-rose-i); background: var(--fam-rose); border-color: transparent; }
@media (max-width: 359px) {
  #screen-rejoindre .rj-decor { width: 112px; height: 100px; }
  #screen-rejoindre .rj-note { width: 74px; font-size: 16px; padding: 8px 6px 26px; min-height: 90px; }
  #screen-rejoindre .rj-decor .ip-decor-marmite { width: 72px; }
}

/* ── Le bandeau d'une fiche partagée ─────────────────────────────────────────

   ⚠️ **Il est au-dessus des actions, pas sous elles.** Ce qu'il annonce décide
   si les boutons du dessous servent à quelque chose : le lire après les avoir
   essayés, c'est le § *un refus hors de vue n'existe pas*.

   ⛔ **Et il ne floute rien.** Une couche qui recouvre la page ne floute pas son
   arrière-plan (§ 6) — un `backdrop-filter` oblige à recomposer la bande
   couverte à chaque image de défilement. */
.partage-bandeau { display: grid; gap: 10px; }
.partage-bandeau p { margin: 0; }
/* Le bouton ne prend pas toute la largeur ici : c'est une proposition dans une
   phrase, pas l'action principale de l'écran — laquelle reste « Cuisiner ». */
.partage-bandeau .btn-ghost { margin: 0; justify-self: start; padding: 10px 18px; }

/* ── Le pseudo, et la recherche par pseudo — § 17, second lot ─────────────────

   ⚠️ **Le champ reste à 16 px** (§ 1), et l'arobase est un décor posé à côté :
   la mettre *dans* le champ obligerait à la retirer de la valeur saisie, donc à
   tenir deux vérités sur ce que la personne a tapé. */
.pseudo-ligne { display: flex; align-items: center; gap: 8px; }
.pseudo-arobase { color: var(--muted); font-size: 24px; font-weight: 600; }
.pseudo-ligne input {
  flex: 1; border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit; font-size: 24px;
}

.famille-recherche { margin: 10px 0; }
.famille-recherche input {
  width: 100%; border: 1px solid var(--line); border-radius: 14px;
  padding: 12px 14px; background: var(--cream); color: inherit; font-size: 24px;
}
.famille-trouves li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.famille-trouves small { display: block; color: var(--muted); font-size: 18px; }

/* ── Le profil public — §§ 14 à 17, lot 3 du 06/09 ─────────────────────────
   ⚠️ **Un carnet, pas un profil Instagram.** Le § 48 le demande en toutes
   lettres : « éviter les gros compteurs de followers ». Les deux nombres
   d'abonnés vivent donc dans une ligne de texte à la taille du reste, jamais
   dans une rangée de gros chiffres — c'est le § *la place qu'un écran occupe se
   lit comme un jugement*. Ce qu'on met en avant est le carnet. */
.pp-identite { display: flex; align-items: center; gap: 14px; }
.pp-portrait {
  width: 64px; height: 64px; border-radius: 999px; flex: none;
  background: var(--line) center/cover no-repeat;
  display: grid; place-items: center; font-size: 29px; color: var(--muted);
}
.pp-noms { min-width: 0; }
.pp-noms strong { display: block; font-size: 25px; line-height: 1.25; }
.pp-noms small { display: block; color: var(--muted); font-size: 20px; }
.pp-bio { margin: 12px 0 0; font-size: 22px; line-height: 1.5; }
.pp-liens { margin: 10px 0 0; color: var(--muted); font-size: 20px; }
.pp-tete .btn-primary, .pp-tete .btn-ghost { margin-top: 14px; width: 100%; }

.pp-chips { display: flex; flex-wrap: wrap; gap: 8px; }
/* ⛔ **Une spécialité n'est pas un filtre.** `.chip` seul se lit comme quelque
   chose qu'on peut cocher — le § *`.chip.on` est réservé à ce qui filtre* le
   dit déjà —, alors qu'ici c'est un constat. D'où une puce inerte, sans
   curseur ni hauteur de cible tactile : rien à toucher, rien à promettre. */
.pp-chip {
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: 999px; padding: 5px 12px; font-size: 19.5px; white-space: nowrap;
}

/* Le titre d'une section qui n'est pas une carte — la grille de recettes a
   besoin de toute la largeur (voir le commentaire dans `index.html`). */
.pp-section { margin: 4px 0 12px; font-size: 22.5px; font-weight: 600; }

.pp-stats { list-style: none; margin: 0; padding: 0; }
.pp-stats li {
  display: flex; justify-content: space-between; gap: 14px;
  padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 22px;
}
.pp-stats li:last-child { border-bottom: none; }
.pp-stats b { font-weight: 600; }

/* Le champ de présentation, dans les réglages. ⚠️ **16 px au moins** — § 1 :
   sous ce seuil, iOS agrandit la page à la mise au point et la laisse décalée
   quand le clavier se referme. */
#pp-bio-saisie {
  width: 100%; box-sizing: border-box; margin: 10px 0 0;
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px;
  background: var(--card); color: inherit; font: inherit; font-size: 24px;
  resize: vertical; min-height: 60px;
}

/* ⚠️ **L'attribution du § 23, sur la carte.** Discrète et lisible : elle doit
   se voir sans concurrencer le titre — c'est une provenance, pas un argument.
   Posée en pied de carte plutôt qu'en troisième ligne sous le titre, que les
   150 px de la carte ne peuvent pas porter. */
/* ⚠️ **La ligne du bas rend sa marge quand la provenance la suit.** `small`
   porte 13 px de bas pour fermer la carte ; avec une seconde ligne dessous, ces
   13 px deviennent un trou au milieu. `:has()` évite d'avoir à décider dans le
   JavaScript, où il faudrait poser une classe et penser à la retirer. */
.recipe-card small:has(+ .card-source) { padding-bottom: 2px; }

/* ⚠️ **Le même rembourrage horizontal que le titre et la ligne du bas.** La
   carte est à `padding: 0` — ce sont ses enfants qui le portent —, et la
   provenance s'écrivait donc **bord à bord**, la première lettre coupée par le
   coin arrondi. Vu à la capture, invisible à la lecture. */
.recipe-card .card-source {
  display: block; margin: 2px 0 0; padding: 0 38px 12px 12px;
  color: var(--muted); font-size: 16.5px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ⚠️ **Le bandeau de portée dit l'état sans qu'on ouvre le menu** — § 22. Un
   réglage qu'on ne peut lire qu'en ouvrant le geste qui le change est un
   réglage qu'on oublie, et c'est celui-là dont l'oubli coûte quelque chose. */
.recipe-portee {
  margin: 0 0 12px; padding: 9px 12px; border-radius: 12px;
  background: var(--card); border: 1px solid var(--line);
  color: var(--muted); font-size: 19.5px;
}

/* ── Les signaux d'une fiche publique — lot 5 du 07/09 ─────────────────────
   Deux gestes côte à côte : noter, et prendre la fiche chez soi. ⚠️ **Ils se
   replient sur deux lignes sous 340 px** — mesuré sur un iPhone SE de 1ʳᵉ
   génération, « Enregistrer chez moi » y fait 168 px pour 155 disponibles. */
.signaux-gestes { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.signaux-gestes .btn-ghost { flex: 1 1 150px; }

/* ── P33 — Communauté, seconde version : le fil vertical (16/09 soir) ────────
   Trois visuels de Luca. Fond crème uniforme — pas la fresque des menus —,
   marmite composée (le corps et le couvercle du dépôt, deux images posées
   l'une sur l'autre), trois onglets, six puces rondes, une carte « post » par
   recette. Scopé sur `#screen-communaute` ; la carte « post » est scopée sur
   `.recipe-card-wrap.post`. */
#screen-communaute { background: var(--accueil-fond); }
#screen-communaute > .menu-corps.comm-corps { background: var(--accueil-fond); min-height: 40vh; padding-top: 4px; }
/* Le bandeau illustré porte la zone sûre et le titre (22/09) : l'en-tête ne
   garde que les onglets, la recherche et les puces. */
#screen-communaute .comm-entete {
  padding: 12px var(--gouttiere) 0;
  background: var(--accueil-fond);
}
#screen-communaute .comm-marmite {
  position: relative; display: block; width: 76px; height: 62px;
}
#screen-communaute .comm-marmite-corps {
  position: absolute; left: 0; bottom: 0; width: 100%; height: auto; display: block;
}
/* Le couvercle est un peu moins large que la marmite (les anses dépassent) et
   se pose sur le rebord — mesuré sur les deux fichiers du dépôt. */
#screen-communaute .comm-marmite-couvercle {
  position: absolute; left: 12%; top: 0; width: 76%; height: auto; display: block;
}

/* Les trois onglets : une rangée de puces pleines, la courante en terre cuite. */
/* `.rail` : à 320 px les trois libellés ne tiennent pas, et la rangée défile
   au doigt plutôt que de couper « À la une » ; à 390 les trois remplissent. */
#screen-communaute .comm-onglets { display: flex; gap: 8px; margin: 0 0 12px; padding: 0; }
#screen-communaute .comm-onglet {
  flex: 1 0 auto; min-height: 44px; padding: 0 10px; border-radius: 999px;
  border: none; background: #EFE3D3; color: var(--espresso);
  font: inherit; font-size: 22px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
#screen-communaute .comm-onglet.on { background: var(--coeur); color: #FFFFFF; }

/* La recherche : loupe dans le champ, croix à droite quand il y a du texte. */
#screen-communaute .comm-recherche { position: relative; margin-bottom: 14px; }
#screen-communaute .comm-recherche input {
  padding-left: 42px; padding-right: 44px; border-radius: 999px; border-color: transparent;
  background: #F0E6D8; font-size: 24px;
}
/* La croix native du champ `search` doublerait la nôtre. */
#screen-communaute .comm-recherche input::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }
#screen-communaute .comm-loupe {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; color: var(--espresso); pointer-events: none;
}
#screen-communaute .comm-effacer {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  background: none; border-color: transparent;
}

/* Les six puces rondes : un disque pâle de 56 px, l'illustration dedans, le
   libellé dessous ; terre cuite quand active. ⚠️ Le libellé n'est jamais dans
   l'image (il change de langue) et le disque est peint ici — les pictogrammes
   G09 remplaceront les dessins d'aliments sans une ligne de CSS. */
/* Six puces sur 350 px : 6 × 54 + 5 × 4 = 344. Le libellé peut dépasser son
   disque d'un ou deux pixels de chaque côté, jamais couper. */
#screen-communaute .comm-puces { margin: 0; padding: 2px 0 10px; gap: 4px; }
#screen-communaute .comm-puce {
  display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 0 0 auto;
  width: 54px; min-height: 44px; padding: 0; border: none; background: none;
  color: var(--espresso); font: inherit; font-size: 17px; cursor: pointer; white-space: nowrap;
}
#screen-communaute .comm-puce-disque {
  width: 52px; height: 52px; border-radius: 50%; display: grid; place-items: center;
  background: #F1E4D2; box-shadow: inset 0 0 0 1.5px transparent; transition: background-color .18s ease;
}
#screen-communaute .comm-puce-disque img { width: 34px; height: 34px; object-fit: contain; display: block; }
#screen-communaute .comm-puce.on .comm-puce-disque { background: #F3D9C8; box-shadow: inset 0 0 0 2px var(--coeur); }
#screen-communaute .comm-puce.on { color: var(--coeur); font-weight: 600; }

/* L'écran de résultats : titre serif, Filtrer à droite avec son nombre, rangée
   de comptes, grille à deux colonnes. */
#screen-communaute .comm-resultats-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 6px 0 10px;
}
#screen-communaute .comm-resultats-titre {
  margin: 0; font-family: var(--ecriture); font-size: 26.5px; font-weight: 600;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#screen-communaute .comm-filtrer {
  position: relative; display: inline-flex; align-items: center; gap: 6px; flex: none;
  min-height: 40px; padding: 0 14px; border-radius: 999px; border: none;
  background: #EFE3D3; color: var(--espresso); font: inherit; font-size: 21px; font-weight: 600; cursor: pointer;
}
#screen-communaute .comm-filtrer .icon { width: 18px; height: 18px; }
#screen-communaute .comm-filtrer-nombre {
  position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px; background: var(--coeur); color: #FFFFFF; font-size: 16.5px; font-weight: 700;
  display: grid; place-items: center;
}
/* ⛔ **Chaque critère actif s'enlève d'une croix — fiche C10, 16/09.** La ligne
   les nommait sans donner de quoi les défaire : retirer « moins de 400 kcal »
   demandait de rouvrir la feuille, retrouver la puce et la retoucher — ou
   « Tout effacer », c'est-à-dire perdre le reste de sa recherche pour défaire
   un critère. */
#screen-communaute #comm-filtres-actifs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px;
}
#screen-communaute .comm-filtre-actif {
  display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px 7px 14px;
}
#screen-communaute .comm-filtre-actif .icon { width: 14px; height: 14px; }

#screen-communaute .comm-comptes { list-style: none; margin: 0 0 14px; padding: 2px 0 6px; gap: 14px; }
#screen-communaute .comm-compte {
  display: flex; flex-direction: column; align-items: center; gap: 2px; flex: 0 0 auto;
  width: 84px; border: none; background: none; padding: 0; font: inherit; color: var(--espresso); cursor: pointer;
}
#screen-communaute .comm-compte-portrait {
  width: 60px; height: 60px; border-radius: 50%; overflow: hidden; display: grid; place-items: center;
  background: var(--sand); color: var(--espresso); font-size: 27.5px; font-weight: 600;
  border: 3px solid var(--card); box-shadow: 0 0 0 2px var(--coeur); margin-bottom: 4px;
}
#screen-communaute .comm-compte-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
#screen-communaute .comm-compte b, #screen-communaute .comm-compte small {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#screen-communaute .comm-compte b { font-size: 20px; font-weight: 600; }
#screen-communaute .comm-compte small { font-size: 18px; color: var(--muted); }
#screen-communaute .comm-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
#screen-communaute .comm-onglet-vide { margin: 8px 0 20px; }

/* La première visite : marmite et vapeur, un titre, une phrase, le bouton. */
#screen-communaute .comm-bienvenue {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 24px 8px 20px; min-height: 60vh; justify-content: center;
}
#screen-communaute .comm-bienvenue-scene { position: relative; display: block; width: 180px; height: 220px; margin-bottom: 12px; }
#screen-communaute .comm-bienvenue-vapeur {
  position: absolute; left: 50%; top: 0; width: 70px; height: auto; transform: translateX(-50%);
  opacity: .8;
}
#screen-communaute .comm-marmite-grande { position: absolute; left: 0; bottom: 0; width: 180px; height: 146px; }
/* Sur la grande, le couvercle descend sur le rebord — mesuré au banc : posé à
   0, il flottait au-dessus de l'ouverture. */
#screen-communaute .comm-marmite-grande .comm-marmite-couvercle { top: 10px; left: 10%; width: 80%; }
#screen-communaute .comm-bienvenue-titre {
  margin: 0 0 10px; font-family: var(--ecriture); font-size: 30px; line-height: 1.15; color: var(--coeur);
}
#screen-communaute .comm-bienvenue-texte { margin: 0 0 22px; color: var(--muted); font-size: 23px; line-height: 1.45; max-width: 320px; }
#screen-communaute .comm-bienvenue .btn-primary { width: min(100%, 320px); min-height: 50px; }
#screen-communaute .comm-bienvenue .btn-ghost { margin-top: 10px; width: min(100%, 320px); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) #screen-communaute .comm-onglet,
  :root:not([data-theme="light"]) #screen-communaute .comm-filtrer { background: #3C3027; }
  :root:not([data-theme="light"]) #screen-communaute .comm-recherche input { background: #2A211A; }
  :root:not([data-theme="light"]) #screen-communaute .comm-puce-disque { background: #3C3027; }
  :root:not([data-theme="light"]) #screen-communaute .comm-puce.on .comm-puce-disque { background: #4A3A2E; }
}
:root[data-theme="dark"] #screen-communaute .comm-onglet,
:root[data-theme="dark"] #screen-communaute .comm-filtrer { background: #3C3027; }
:root[data-theme="dark"] #screen-communaute .comm-recherche input { background: #2A211A; }
:root[data-theme="dark"] #screen-communaute .comm-puce-disque { background: #3C3027; }
:root[data-theme="dark"] #screen-communaute .comm-puce.on .comm-puce-disque { background: #4A3A2E; }

/* ── Le fil communautaire — une colonne de « posts » (16/09) ────────────────
   Chaque section suit la précédente, sous son intertitre ; chaque carte est
   `recipeCard` en densité `post`. */
.fil-section { margin-bottom: 18px; }
.fil-section .pp-section { margin: 4px 0 10px; }

/* ── La carte « post » — une densité de `recipeCard`, pas une seconde carte ──
   Ligne d'auteur, photo pleine largeur avec le titre en surimpression et le
   cœur, ligne des compteurs (♥ 💬 ★ et le marque-page à droite), hashtags.
   ⚠️ **Le titre se pose sur un voile sombre**, sans quoi il est illisible sur
   une photo claire ; le voile est un `span` dans la carte, peint une fois —
   pas un `backdrop-filter` (§ 6). */
.recipe-card-wrap.post {
  display: flex; flex-direction: column; gap: 0; margin-bottom: 16px;
  background: var(--card); border-radius: 20px; box-shadow: var(--shadow); overflow: hidden;
}
.recipe-card-wrap.post .post-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px 10px;
}
.recipe-card-wrap.post .post-auteur {
  display: flex; align-items: center; gap: 10px; min-width: 0; cursor: pointer;
}
.recipe-card-wrap.post .post-portrait {
  width: 40px; height: 40px; border-radius: 50%; overflow: hidden; flex: none;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso);
  font-size: 24px; font-weight: 600;
}
.recipe-card-wrap.post .post-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.recipe-card-wrap.post .post-noms { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.recipe-card-wrap.post .post-noms b { font-size: 22.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recipe-card-wrap.post .post-noms small { font-size: 19px; color: var(--muted); }
.recipe-card-wrap.post .post-temps {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: 19.5px; color: var(--muted);
}
.recipe-card-wrap.post .post-temps .icon { width: 18px; height: 18px; color: var(--coeur); }
.recipe-card-wrap.post .recipe-card {
  position: relative; border-radius: 16px; margin: 0 10px; box-shadow: none;
  transition: none;
}
.recipe-card-wrap.post .recipe-card:hover { transform: none; }
.recipe-card-wrap.post .recipe-card img,
.recipe-card-wrap.post .recipe-card .thumb-fallback { aspect-ratio: 4 / 3; }
.recipe-card-wrap.post .post-voile {
  position: absolute; left: 0; right: 0; bottom: 0; height: 45%;
  background: linear-gradient(transparent, rgba(30, 20, 12, .72)); pointer-events: none;
}
.recipe-card-wrap.post .recipe-card > b {
  position: absolute; left: 14px; right: 56px; bottom: 12px; margin: 0; padding: 0;
  color: #FFFFFF; font-size: 26px; line-height: 1.2; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .35);
}
/* La ligne auteur · temps · kcal de la grille ne se répète pas ici : l'auteur
   est en tête, le temps aussi. La **raison** du classement, elle, reste. */
.recipe-card-wrap.post .recipe-card > small:not(.card-raison) { display: none; }
.recipe-card-wrap.post .recipe-card > small.card-raison {
  display: block; padding: 8px 4px 0; margin: 0; font-size: 19.5px; color: var(--coeur);
}
.recipe-card-wrap.post .recipe-card > .card-avis,
.recipe-card-wrap.post .recipe-card > .card-source { display: none; }
.recipe-card-wrap.post .card-fav {
  top: auto; bottom: 12px; right: 22px; width: 40px; height: 40px; min-height: 40px;
}
.recipe-card-wrap.post .card-fav .icon { width: 20px; height: 20px; }
.recipe-card-wrap.post .post-compteurs {
  display: flex; align-items: center; gap: 18px; padding: 12px 14px 4px;
}
.recipe-card-wrap.post .post-compte {
  display: inline-flex; align-items: center; gap: 5px; font-size: 22.5px; font-weight: 600; color: var(--espresso);
}
.recipe-card-wrap.post .post-compte .icon { width: 22px; height: 22px; color: var(--coeur); }
/* Le marque-page quitte le coin de la photo pour la fin de la ligne. */
.recipe-card-wrap.post .card-save {
  position: static; margin-left: auto; width: 40px; height: 40px; min-height: 40px;
  background: none; color: var(--espresso);
}
.recipe-card-wrap.post .card-save .icon { width: 20px; height: 20px; }
.recipe-card-wrap.post .card-save.on { background: none; color: var(--coeur); }
.recipe-card-wrap.post .post-hashtags { display: flex; flex-wrap: wrap; gap: 8px; padding: 6px 14px 14px; }
.recipe-card-wrap.post .post-hashtag {
  min-height: 30px; padding: 0 12px; border-radius: 999px; border: none;
  background: #F1E7DA; color: var(--espresso); font: inherit; font-size: 19.5px; cursor: pointer;
}
.recipe-card-wrap.post:not(:has(.post-hashtags)) .post-compteurs { padding-bottom: 14px; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .recipe-card-wrap.post .post-hashtag { background: #3C3027; }
}
:root[data-theme="dark"] .recipe-card-wrap.post .post-hashtag { background: #3C3027; }

/* ── Commentaires et questions — §§ 12, 13. Lot 7 du 07/09 ─────────────────
   ⚠️ **Une seule profondeur d'indentation.** Un fil qui s'emboîte sans fin
   devient illisible sur 390 px : à quatre niveaux on écrit dans une colonne de
   huit caractères. Le serveur rattache déjà toute réponse à la tête de fil ;
   le CSS n'a donc qu'un cran à dessiner. */
.commentaire { padding: 12px 0; border-bottom: 1px solid var(--line); }
.commentaire:last-child { border-bottom: none; }
.commentaire-tete { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.commentaire-tete strong { font-size: 21px; }
.commentaire-tete time { color: var(--muted); font-size: 18px; }
.commentaire-texte { margin: 4px 0 0; font-size: 22px; line-height: 1.5; white-space: pre-wrap; }
.commentaire-gestes { display: flex; gap: 12px; margin-top: 6px; }
.commentaire-gestes button { font-size: 19px; }
/* Le cran unique des réponses. */
.commentaire-reponses { margin: 8px 0 0 16px; border-left: 2px solid var(--line); padding-left: 12px; }

/* ⚠️ **L'étiquette du créateur est un constat, pas un rang.** Le § 13 demande
   qu'une réponse du créateur soit « clairement identifiée » ; elle se distingue
   par sa couleur, pas par sa taille — une réponse plus grosse que la question
   se lirait comme une réponse plus juste. */
.commentaire-createur {
  background: var(--cinnamon); color: #FFFFFF; border-radius: 999px;
  padding: 1px 8px; font-size: 16.5px; font-weight: 600;
}
.commentaire-question {
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: 1px 8px; font-size: 16.5px; color: var(--muted);
}

/* ⚠️ **16 px au moins** — § 1 : sous ce seuil, iOS agrandit la page à la mise
   au point et la laisse décalée quand le clavier se referme. */
#commentaire-texte {
  width: 100%; box-sizing: border-box; margin: 12px 0 0;
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px;
  background: var(--card); color: inherit; font: inherit; font-size: 24px;
  resize: vertical; min-height: 56px;
}

/* ── La recherche communautaire — §§ 24, 25, 26. Lot 8 du 07/09 ────────────
   ⚠️ **Les champs de la feuille à 16 px** — § 1, avant tout le reste : iOS
   agrandit la page à la mise au point d'un champ plus petit, et la laisse
   décalée quand le clavier se referme. */
.champ-libelle {
  display: block; margin: 16px 0 6px; font-size: 19.5px; font-weight: 600;
  color: var(--muted);
}
.champ-libelle:first-child { margin-top: 0; }
#cf-ingredients, #cf-sans {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px;
  background: var(--card); color: inherit; font: inherit; font-size: 24px;
}
#comm-filtres .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }

/* La liste des comptes trouvés — deux lignes, jamais une carte : un compte
   n'est pas une recette, et lui donner la même vignette ferait chercher une
   photo qui n'existe pas. */
.liste-comptes { list-style: none; margin: 0 0 18px; padding: 0; }
.liste-comptes li { border-bottom: 1px solid var(--line); }
.liste-comptes li:last-child { border-bottom: none; }
.liste-comptes button {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: none; border: none; padding: 10px 0; cursor: pointer;
  color: inherit; font: inherit; text-align: left; min-height: 44px;
}
.liste-comptes .pp-portrait { width: 36px; height: 36px; font-size: 22.5px; }
.liste-comptes strong { display: block; font-size: 22px; }
.liste-comptes small { display: block; color: var(--muted); font-size: 19px; }


/* ── La page publique d'une recette — § 42, lot 10 du 07/09 ─────────────────

   ⛔ **Le pied n'est pas `position: fixed`.** Il n'en a pas besoin : l'écran
   est déjà un `flex: 1; min-height: 0` dans `#app`, donc un enfant en colonne
   tient le bas tout seul. Une couche fixe ici aurait rouvert le § 2 en entier —
   recadrage sur `--app-w`, `--rail-w`, partage de bord avec la barre
   d'onglets — pour un pied qui vit sur un écran où la barre est masquée. */
#screen-publique {
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* ⛔ **Aucune des trois valeurs de `.screen`**, et les trois pour une raison
     différente — mesuré au banc à 390 × 844, pas déduit :

     | | |
     |---|---|
     | les 28 px du haut | l'image doit toucher le bord ; encadrée, elle se lit comme une illustration posée sur une page, exactement le § du frigo |
     | les 20 px des côtés | c'est le corps qui les porte, sinon l'image les porte aussi |
     | `--tabbar-h + 32` en bas | **la barre est masquée ici** : cette réserve mettait le pied 100 px sous le pli, donc le seul geste de la page hors de vue — le § 2 dans sa version la plus coûteuse |

     ⚠️ **Et c'est une réserve pour une barre absente**, pas une marge de
     confort : elle a l'air juste dans la feuille et ne l'est que sur les écrans
     qui ont des onglets. */
  padding: 0;
}

/* ⚠️ **C'est l'enveloppe qui défile, pas le corps** — P37, 18/09 : l'en-tête
   d'accès est dedans, donc il s'en va quand on lit. */
#publique-defile {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

#publique-corps {
  /* Le corps porte la gouttière ; l'image, qui est son premier enfant, la
     déborde volontairement — voir `.publique-media`. */
  padding: 0 20px 12px;
}

/* L'image est la page, pas une carte dedans — même arbitrage que le frigo :
   bord à bord, sans cadre ni coins arrondis, parce que c'est ce qui la fait
   lire comme une photo de plat plutôt que comme une illustration posée.

   ⚠️ **Elle déborde la gouttière du corps par des marges négatives.** Un
   `width: 100vw` aurait ignoré le recadrage de `#app` au-delà du palier de
   820 px du § 10 — la photo serait sortie de la colonne. Des marges négatives
   suivent la colonne, quelle que soit sa largeur. */
.publique-media {
  margin: 0 -20px 16px;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--card);
}
/* ⚠️ **Sur un écran court, c'est le rapport qui cède, jamais la largeur** —
   mesuré à 320 × 568 : en 4/3 l'image prend 240 px, et entre l'en-tête et le
   pied il ne restait qu'une ligne de recette.

   ⛔ **Et surtout pas un `max-height` avec `aspect-ratio`** : le navigateur
   tient alors le rapport en rétrécissant la **largeur**, et la photo s'est
   retrouvée avec une bande vide à sa droite — vu au banc. */
@media (max-height: 640px) {
  .publique-media { aspect-ratio: 16 / 9; }
  .publique-pied { padding: 8px 16px calc(8px + env(safe-area-inset-bottom)); }
}

.publique-media img,
.publique-media .thumb-fallback {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* ⚠️ **Le troisième cran se centre** — vu au banc sur une fiche importée sans
   couverture ni dessin : `object-fit` ne s'applique pas à un `div`, et la
   casserole se retrouvait minuscule dans le coin d'un rectangle blanc de
   600 px. Même traitement que la carte de recette, à sa taille. */
.publique-media .thumb-fallback {
  display: grid; place-items: center; color: var(--sand);
  /* Sable pâle sur blanc se lisait comme une image qui n'a pas chargé ; sur un
     fond teinté, le dessin se lit comme un dessin. */
  background: color-mix(in srgb, var(--sand) 22%, var(--card));
}
.publique-media .thumb-fallback .icon { width: 72px; height: 72px; }

/* ── L'en-tête d'accès — P37, 18/09 ─────────────────────────────────────────
   Le logo et les deux portes, en tête de page. ⛔ **Pas de barre fixe** : une
   couche de plus à recomposer pendant un défilement (§ 6), pour deux boutons
   que le pied porte déjà sous le pouce. */
#screen-publique .publique-tete {
  flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 20px 10px;
}
#screen-publique .publique-marque { height: 34px; width: auto; }
#screen-publique .publique-acces { display: flex; gap: 8px; flex: none; }
/* ⚠️ **44 px, même en `btn-mini`** (§ 1) : ce sont les deux commandes que la
   page offre avant qu'on ait lu quoi que ce soit. */
#screen-publique .publique-acces button { min-height: 44px; }

#screen-publique .publique-tete-recette .page-title {
  font-size: clamp(29px, 7.32vw, 34.5px); line-height: 1.15; margin: 0 0 6px;
}
#screen-publique .publique-auteur { margin: 0 0 4px; font-size: 22.5px; color: var(--muted); }
#screen-publique .publique-auteur b { color: var(--espresso); font-weight: 600; }
#screen-publique .publique-origine a { color: var(--cinnamon); font-weight: 600; }

#screen-publique .publique-tags {
  display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0;
}
#screen-publique .publique-tag {
  padding: 5px 12px; border-radius: 999px; font-size: 19.5px;
  background: color-mix(in srgb, var(--sand) 45%, var(--card));
  color: var(--espresso);
}

/* La rangée de faits : elle se replie plutôt que de se tronquer, et chaque
   tuile n'existe que si elle porte un nombre. */
#screen-publique .publique-faits {
  display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 14px;
}
#screen-publique .publique-fait { display: flex; flex-direction: column; gap: 1px; }
#screen-publique .publique-fait small { font-size: 19px; color: var(--muted); }
#screen-publique .publique-fait b { font-size: 24px; font-weight: 600; color: var(--espresso); }
#screen-publique .publique-signaux { margin: 12px 0 0; }

#screen-publique .publique-partager {
  margin-top: 14px; gap: 8px; align-self: flex-start;
}
#screen-publique .publique-partager .icon { width: 18px; height: 18px; }

/* Le verrou des derniers ingrédients — 18/09. Une carte, pas un dégradé posé
   sur des lignes : le serveur ne les sert pas, il n'y a rien à estomper. */
#screen-publique .publique-verrou {
  display: grid; justify-items: center; gap: 8px; text-align: center;
  background: color-mix(in srgb, var(--terra) 8%, var(--card));
  border: 1px solid color-mix(in srgb, var(--terra) 22%, var(--line));
}
#screen-publique .publique-verrou-icone {
  width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center;
  color: var(--cinnamon); background: color-mix(in srgb, var(--terra) 14%, var(--card));
}
#screen-publique .publique-verrou-icone .icon { width: 24px; height: 24px; }
#screen-publique .publique-verrou .card-title { margin: 0; }
#screen-publique .publique-verrou-liste {
  margin: 0; padding: 0; list-style: none; display: grid; gap: 4px;
  font-size: 22.5px; color: var(--espresso);
}
#screen-publique .publique-verrou-liste li::before { content: "✓ "; color: var(--cinnamon); font-weight: 700; }
#screen-publique .publique-verrou .hint { margin: 0; }
#screen-publique .publique-verrou .btn-primary { margin-top: 4px; }

#screen-publique .publique-facultatif {
  font-size: 19px; color: var(--muted); font-style: italic;
}
#screen-publique .publique-etapes li { margin-bottom: 10px; }
#screen-publique .publique-impasse { text-align: center; display: grid; gap: 12px; justify-items: center; }
#screen-publique .publique-impasse .page-title { font-size: 27.5px; }

.publique-liste {
  margin: 8px 0 0;
  padding-left: 20px;
  line-height: 1.6;
}
.publique-liste li { margin-bottom: 4px; }

/* ⚠️ **Opaque, jamais translucide** — § 6. Le contenu défile dessous, et un
   fond translucide sans flou laisse voir la recette glisser sous les boutons ;
   un `backdrop-filter` coûterait une recomposition par image de défilement. */
.publique-pied {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  background: var(--bg);
  border-top: 1px solid var(--line);
}


/* ── Les réalisations — § 10, lot 11 du 07/09 ───────────────────────────────

   ⚠️ **Une hauteur fixe pour la photo**, comme partout ailleurs : une image
   sans taille prend toute la place qu'on lui laisse, et une photo de plat en
   plein écran au milieu d'une fiche est le défaut que le registre des vignettes
   garde depuis le 24/08. */
.realisation {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 0;
  border-top: 1px solid var(--line);
}
.realisation:first-child { border-top: none; }
.realisation img {
  width: 84px;
  height: 84px;
  flex: none;
  object-fit: cover;
  border-radius: var(--radius);
}
.realisation-texte { min-width: 0; }
.realisation-texte p { margin: 0 0 4px; }

#realisation-moi { margin-top: 12px; }
#realisation-moi textarea { width: 100%; }


/* ── Un nom d'auteur qui mène à son auteur — lot B, 07/09 ─────────────────

   ⚠️ **Un souligné, et pas une couleur d'accent.** Le nom vit dans une ligne
   de note (`.hint`, `small`) ou en gras dans un bandeau : le repeindre en bleu
   en ferait le plus voyant de la carte, devant le titre. Le souligné dit
   « ça se tape » sans changer la hiérarchie de ce qu'on lit.

   ⛔ **Et l'accent ne désigne pas deux choses** — `--bleu` porte déjà la
   sélection et les commandes ; s'en servir ici rejouerait le § *un accent qui
   désigne deux choses n'en désigne plus aucune*.

   ⚠️ **Le survol se pose sur `(hover: hover)`**, jamais sur une largeur : un
   iPad en paysage et un portable font tous deux 1 194 px, l'un se touche et
   l'autre se survole. */
/* ⚠️ **Un lien en ligne ne tient pas les 44 px, et il n'a pas à les tenir.**
   Mesuré au banc : 27 px de haut après rembourrage, sur une ligne de note de
   18. C'est l'exception « inline » du critère 2.5.8 — une cible dont la taille
   est **contrainte par la hauteur de ligne du texte qui l'entoure**.

   ⛔ **Et l'agrandir jusqu'à 44 serait pire que de ne rien faire** : le nom vit
   dans une carte entièrement cliquable, donc une zone sensible plus haute que
   sa ligne **volerait** les taps destinés à la carte, sur toute la largeur du
   nom. On gagne les 9 px qui ne chevauchent rien, et on s'arrête là.

   ⛔ Un second bloc `.lien-auteur` aurait été deux endroits qui décident du
   même dessin. */
.lien-auteur {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
  cursor: pointer;
  padding: 6px 2px;
  margin: -6px -2px;
}

@media (hover: hover) and (pointer: fine) {
  .lien-auteur:hover { text-decoration-style: solid; }
}

.lien-auteur:focus-visible {
  outline: 2px solid var(--bleu);
  outline-offset: 2px;
  border-radius: 4px;
}


/* ── L'en-tête du créateur d'une publication — lot D, 07/09 ───────────────

   ⚠️ **Une carte, pas un bandeau.** Les bandeaux de cette fiche disent tous un
   **état** — hors ligne, en lecture seule, publiée : ce sont des avertissements.
   Celui-ci nomme une **personne**, et c'est un contenu. Lui donner l'habit d'un
   avertissement ferait lire « attention, cette recette est de quelqu'un »
   là où on veut dire « voici de qui elle est ».

   ⚠️ **Le portrait réemploie `.pp-portrait` du profil**, dessin compris : deux
   façons de dessiner une absence de photo donneraient deux ronds différents
   pour le même fait, sur deux écrans qu'on enchaîne. */
.publi-createur {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.publi-createur-qui {
  display: flex;
  align-items: center;
  gap: 10px;
  /* ⚠️ **`min-width: 0`, sinon un nom long pousse le bouton hors de la carte.**
     Un enfant de flex refuse de rétrécir sous son contenu par défaut — c'est le
     même piège que la ligne d'ingrédient qui débordait de sa carte le 06/09. */
  min-width: 0;
}

/* ⚠️ **36 px, comme dans la liste de comptes**, et pas les 64 du profil : là-bas
   le portrait *est* le sujet de l'écran, ici c'est une ligne au-dessus d'une
   recette. La règle existe déjà pour `.liste-comptes` — on la rejoint plutôt
   que d'inventer une troisième taille. */
.publi-createur .pp-portrait { width: 36px; height: 36px; font-size: 22.5px; }

.publi-createur-qui strong { display: block; }
.publi-createur-qui .hint { margin: 2px 0 0; }

/* ⚠️ **Le bouton ne rétrécit pas** : « Voir son profil » coupé en deux lignes
   au milieu d'une carte de trois lignes de haut est ce qu'on obtient sans
   cette ligne. */
.publi-createur .btn-mini { flex: none; }


/* ── Les deux gestes d'une fiche qu'on lit — lot E, 07/09 ─────────────────

   ⚠️ **Côte à côte tant qu'ils tiennent, l'un sous l'autre sinon.**
   « Enregistrer chez moi » et « En faire ma version » font 320 px de texte à
   eux deux : sur un iPhone SE ils ne tiennent pas sur une ligne, et un libellé
   coupé en deux au milieu d'un bandeau se lit comme un défaut. `flex-wrap`
   décide, il n'y a pas de palier à écrire (§ *un nombre de colonnes se déduit
   d'une largeur*). */
.partage-gestes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* ⚠️ **Chacun prend sa part et pas plus** : sans `flex: 1`, le bouton plein se
   dimensionne sur son texte et l'autre reste collé à lui, ce qui donne deux
   commandes de largeurs différentes pour deux gestes de même rang. */
.partage-gestes > button { flex: 1 1 auto; min-width: 150px; }


/* ── « Basée sur la recette de X » — lot F, 07/09 ─────────────────────────

   ⚠️ **Une note, pas une alerte.** Elle nomme une provenance, ce qui est un
   contenu : lui donner l'habit d'un bandeau ferait lire « attention » là où on
   veut dire « voici d'où ça vient ». Elle reprend donc `.hint`, et n'ajoute
   qu'une marge. */
.recipe-basee-sur { margin: 6px 0 0; }


/* ── Les avis — AVIS_RECETTES.md, 12/09 ───────────────────────────────────

   **Deux blocs, un dessin d'étoile.** Le résumé près du titre, le bloc privé
   après les informations du plat, la liste publique après les étapes, et le
   formulaire commun dans une feuille. Tout ce qui suit est scopé sur son
   contexte plutôt que sur sa place dans le fichier : le § *une règle qui ne
   gagne que par sa place se perd* a coûté un champ d'import le 08/09, et une
   feuille de huit mille lignes se réorganise. */

/* Le résumé : un chiffre qui mène quelque part, donc un bouton — et il se lit
   comme une donnée, pas comme une commande. */
.recipe-identite .avis-resume {
  /* ⚠️ **`inline-flex`, pas `align-self`.** La carte d'identité est un bloc :
     un alignement de conteneur flex n'y ferait rien, et la règle serait un
     souvenir plutôt qu'un réglage. */
  display: inline-flex; align-items: center;
  margin: 2px 0 8px;
  padding: 4px 10px;
  min-height: 32px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--cream); color: var(--espresso);
  font-size: 19.5px; font-weight: 600; cursor: pointer;
}

/* ⚠️ **La forme dit la note, pas la couleur seule.** Une étoile pleine et une
   étoile vide se distinguent sans voir les couleurs — c'est la règle du § des
   graphiques, transposée à cinq dessins. */
.avis-etoiles-lues { display: inline-flex; align-items: center; gap: 2px; }
.avis-etoiles-lues .icon { width: 16px; height: 16px; color: var(--line); }
.avis-etoiles-lues .avis-etoile.on { color: var(--terra); }

.avis-perso .avis-portee { margin: -4px 0 8px; }
.avis-perso .avis-texte {
  margin: 6px 0 0; font-size: 22px; line-height: 1.5; white-space: pre-wrap;
}

.avis-moyenne { margin: 0 0 4px; font-weight: 600; }
.avis-ligne { padding: 12px 0; border-bottom: 1px solid var(--line); }
.avis-ligne:last-child { border-bottom: none; }
.avis-tete { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.avis-tete strong { font-size: 21px; }
.avis-tete time { color: var(--muted); font-size: 18px; }
.avis-modifie { color: var(--muted); font-size: 18px; font-style: italic; }
/* Un nom qui ouvre un profil se comporte comme un lien, et le dit au doigt. */
.avis-auteur-lien { cursor: pointer; text-decoration: underline; }

/* ── Le formulaire ─────────────────────────────────────────────────────────
   ⚠️ **La portée se lit avant la saisie**, et les deux phrases ne se ressemblent
   pas : celle qui annonce un texte public porte l'accent, celle qui promet le
   silence reste discrète. */
.avis-form .avis-form-portee {
  margin: 2px 0 10px; padding: 8px 10px;
  border-radius: 10px; background: var(--cream);
  color: var(--muted); font-size: 19.5px;
}
.avis-form .avis-form-portee.publique {
  /* ⚠️ **`color-mix` sur le jeton, pas un `rgba` en dur.** L'accent change au
     thème sombre (`#C65A2E` → `#D9743F`) : une couleur recopiée resterait celle
     du jour, et l'écart ne se verrait que sur une capture. */
  background: color-mix(in srgb, var(--terra) 12%, var(--card));
  color: var(--terra); font-weight: 600;
}

/* ⛔ **44 px de cible, et le § 1 passe avant la place.** Cinq étoiles à 28 px
   tiennent mieux sur un petit écran et se ratent une fois sur trois : c'est la
   contrainte AA, et c'est elle qui décide de la taille du dessin. */
.avis-form .avis-etoiles { display: flex; gap: 4px; margin: 4px 0 12px; }
.avis-form .avis-etoile-btn {
  width: 46px; min-height: 46px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; padding: 0; cursor: pointer;
  color: var(--line);
}
.avis-form .avis-etoile-btn .icon { width: 30px; height: 30px; }
.avis-form .avis-etoile-btn.on { color: var(--terra); }
/* Le focus se voit : un groupe de choix qu'on parcourt au clavier sans savoir
   où l'on est ne se remplit pas. */
.avis-form .avis-etoile-btn:focus-visible {
  outline: 2px solid var(--terra); outline-offset: 2px; border-radius: 8px;
}

.avis-form .avis-champ { display: block; font-size: 19.5px; color: var(--muted); }
/* ⛔ **16 px, jamais moins** (§ 1) : iOS agrandit la page à la mise au point
   d'un champ plus petit, puis la laisse zoomée et décalée. */
.avis-form .avis-champ textarea {
  width: 100%; margin-top: 6px; padding: 10px;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--espresso);
  font: inherit; font-size: 24px; line-height: 1.45; resize: vertical;
}
.avis-form .avis-compteur { margin: 4px 0 0; text-align: right; }
.avis-form .avis-erreur { color: var(--terra); font-weight: 600; }
/* Le dessin du plat, quand il n'y a pas de photo : posé entier dans la vignette,
   pas rogné comme une photo. */
.ip-recent-vignette .thumb-illus { object-fit: contain; padding: 6px; }

/* L'alerte des compilations, dans une carte de lot : une ligne en tête des
   lignes, pas un encadré — l'encadré de l'ancien écran passait sur deux lignes
   à 390 et poussait l'aperçu sous le pli. */
.il-lot .batch-compilations {
  margin: 0; padding: 10px 2px 8px 32px; background: none; border: none; border-bottom: 1px solid var(--line);
  border-radius: 0; font-size: 19.5px;
}

/* ── Vérifier et corriger — P20 de la refonte, 14/09 ─────────────────────────
   La maquette : un grand titre serif, la couverture à droite, le nom dans un
   champ à côté de sa vignette, trois tuiles, catégorie et étiquettes, deux
   listes en cartes, et « Valider la recette » en terre cuite.

   ⚠️ **Tout est scopé sur les deux écrans d'édition** (§ *une règle qui ne
   gagne que par sa place se perd*) : P21 (14/09) reprend le même habillage,
   et `:is()` garde la spécificité d'un identifiant. */

:is(#screen-review, #screen-edit) .vc-head {
  position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "retour . source" "texte texte texte";
  align-items: start; margin-bottom: 12px; min-height: 176px;
}
:is(#screen-review, #screen-edit) .vc-head > .icon-btn { grid-area: retour; position: relative; z-index: 1; }
:is(#screen-review, #screen-edit) .vc-source {
  grid-area: source; position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 16px;
  border-radius: 999px; background: var(--card); border: 1px solid var(--line);
  color: var(--cinnamon); font-size: 22px; text-decoration: none;
  box-shadow: 0 4px 14px rgb(46 36 29 / .08);
}
:is(#screen-review, #screen-edit) .vc-source .icon { width: 18px; height: 18px; }
:is(#screen-review, #screen-edit) .vc-head-texte {
  grid-area: texte; position: relative; z-index: 1; padding-top: 8px;
  padding-right: 138px;
}
:is(#screen-review, #screen-edit) .vc-titre {
  margin: 0 0 6px; font-family: var(--ecriture); font-weight: 700;
  font-size: clamp(30.5px, 8.59vw, 36.5px); line-height: 1.08; letter-spacing: -.01em;
}
:is(#screen-review, #screen-edit) .vc-accroche { margin: 0 0 4px; color: var(--muted); font-size: 24.5px; }
:is(#screen-review, #screen-edit) .vc-intro { margin: 0; color: var(--muted); font-size: 22px; line-height: 1.35; }
/* La couverture réelle : un disque posé au bord droit, sous le bouton de source.
   ⚠️ Décorative (`aria-hidden`) — la même image est la porte de la photo, à
   côté du nom. */
:is(#screen-review, #screen-edit) .vc-couverture {
  position: absolute; right: 0; top: 54px; width: 128px; height: 128px;
  border-radius: 50%; overflow: hidden; background: var(--card);
  box-shadow: 0 10px 28px rgb(46 36 29 / .16);
}
:is(#screen-review, #screen-edit) .vc-couverture[hidden] { display: none; }
:is(#screen-review, #screen-edit) .vc-couverture > img,
:is(#screen-review, #screen-edit) .vc-couverture > .thumb-fallback {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
:is(#screen-review, #screen-edit) .vc-couverture > .thumb-fallback { display: grid; place-items: center; color: var(--sand); }
:is(#screen-review, #screen-edit) .vc-head:has(.vc-couverture[hidden]) { min-height: 0; }
:is(#screen-review, #screen-edit) .vc-head:has(.vc-couverture[hidden]) .vc-head-texte { padding-right: 0; }
@media (max-width: 374px) {
  :is(#screen-review, #screen-edit) .vc-couverture { width: 100px; height: 100px; top: 56px; }
  :is(#screen-review, #screen-edit) .vc-head { min-height: 150px; }
  :is(#screen-review, #screen-edit) .vc-head-texte { padding-right: 106px; }
}

:is(#screen-review, #screen-edit) .review-lead { margin: 18px 0 0; }
:is(#screen-review, #screen-edit) .review-lead + .vc-bloc { margin-top: 14px; }

/* Le nom, et sa vignette qui ouvre la photo. */
:is(#screen-review, #screen-edit) .vc-nom { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 10px; align-items: center; margin-bottom: 10px; }
:is(#screen-review, #screen-edit) .vc-mini {
  position: relative; width: 64px; height: 64px; padding: 0; border: 1px solid var(--line);
  border-radius: 16px; background: var(--card); cursor: pointer; overflow: visible;
}
:is(#screen-review, #screen-edit) .vc-mini-image { display: block; width: 100%; height: 100%; border-radius: 15px; overflow: hidden; }
:is(#screen-review, #screen-edit) .vc-mini-image > img,
:is(#screen-review, #screen-edit) .vc-mini-image > .thumb-fallback { display: grid; place-items: center; width: 100%; height: 100%; object-fit: cover; color: var(--sand); }
:is(#screen-review, #screen-edit) .vc-mini-badge {
  position: absolute; right: -5px; bottom: -5px; width: 26px; height: 26px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--terra); color: #fff; border: 2px solid var(--cream);
}
:is(#screen-review, #screen-edit) .vc-mini-badge .icon { width: 14px; height: 14px; }
:is(#screen-review, #screen-edit) .vc-champ-nom { min-height: 60px; }
:is(#screen-review, #screen-edit) .vc-input {
  flex: 1; min-width: 0; min-height: 56px; border: none; background: none; color: inherit; outline: none;
  font-family: var(--ecriture); font-weight: 700; font-size: 26px;
}
:is(#screen-review, #screen-edit) .vc-crayon { color: var(--muted); }
:is(#screen-review, #screen-edit) .vc-photo-panneau { margin-bottom: 10px; }

/* Les trois tuiles. */
:is(#screen-review, #screen-edit) .vc-tuiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
:is(#screen-review, #screen-edit) .vc-tuile { min-height: 60px; padding: 0 22px 0 10px; }
:is(#screen-review, #screen-edit) .vc-tuile::after,
:is(#screen-review, #screen-edit) .vc-categorie::after {
  content: ""; position: absolute; right: 10px; top: 50%; width: 7px; height: 7px;
  border-right: 1.8px solid var(--muted); border-bottom: 1.8px solid var(--muted);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
:is(#screen-review, #screen-edit) .vc-tuile-texte { position: relative; display: block; min-width: 0; flex: 1; }
:is(#screen-review, #screen-edit) .vc-tuiles select,
:is(#screen-review, #screen-edit) .vc-categorie select {
  appearance: none; -webkit-appearance: none; width: 100%; min-width: 0;
  border: none; background: none; color: inherit; font: inherit; font-size: 24px;
  padding: 0; cursor: pointer; outline: none; text-overflow: ellipsis;
}
/* ⚠️ **Le menu occupe toute la tuile** : 26 px de texte seraient une cible
   sous les 44 px. Le libellé se pose dessous, sans capter le doigt. */
:is(#screen-review, #screen-edit) .vc-tuiles select { display: block; min-height: 58px; padding: 0 0 20px; }
:is(#screen-review, #screen-edit) .vc-tuile small {
  position: absolute; left: 0; right: 0; bottom: 10px; pointer-events: none;
  color: var(--muted); font-size: 19px; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Sous 400 px, l'icône cède sa place au texte : « préparation » doit tenir. */
@media (max-width: 399px) {
  :is(#screen-review, #screen-edit) .vc-tuile > .icon { display: none; }
  :is(#screen-review, #screen-edit) .vc-tuile { padding: 0 18px 0 11px; }
  :is(#screen-review, #screen-edit) .vc-tuile::after { right: 8px; }
  :is(#screen-review, #screen-edit) .vc-tuile small { font-size: 18px; }
}

/* Catégorie et étiquettes. Côte à côte quand elles tiennent, l'une sous l'autre
   sinon — des étiquettes coupées ne se retirent plus. */
:is(#screen-review, #screen-edit) .vc-classement { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 8px; margin-top: 8px; }
:is(#screen-review, #screen-edit) .vc-classement:has(.vc-categorie[hidden]) { grid-template-columns: 1fr; }
@media (max-width: 599px) { :is(#screen-review, #screen-edit) .vc-classement { grid-template-columns: 1fr; } }
:is(#screen-review, #screen-edit) .vc-categorie { min-height: 56px; padding-right: 26px; }
:is(#screen-review, #screen-edit) .vc-categorie[hidden] { display: none; }
:is(#screen-review, #screen-edit) .vc-categorie select {
  min-height: 44px; width: auto; max-width: 100%; padding: 0 14px; border-radius: 999px;
  background: color-mix(in srgb, var(--sand) 38%, var(--card));
}
:is(#screen-review, #screen-edit) .vc-etiquette { color: var(--espresso); font-size: 22.5px; white-space: nowrap; }
:is(#screen-review, #screen-edit) .vc-tags { flex-wrap: wrap; min-height: 56px; padding: 6px 4px 6px 10px; }
:is(#screen-review, #screen-edit) .vc-tags-puces { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }
:is(#screen-review, #screen-edit) .vc-tag {
  display: inline-flex; align-items: center; gap: 2px; min-height: 34px; padding: 0 0 0 12px;
  border-radius: 999px; font-size: 22.5px;
  background: color-mix(in srgb, var(--sage) 22%, var(--card)); color: var(--espresso);
}
:is(#screen-review, #screen-edit) .vc-tag.inconnue { background: color-mix(in srgb, var(--amber) 18%, var(--card)); }
/* 44 px de cible, sur une puce qui en paraît 34 : la zone déborde, pas le dessin. */
:is(#screen-review, #screen-edit) .vc-tag-x {
  display: grid; place-items: center; width: 36px; height: 44px; margin: -5px 0;
  border: none; background: none; color: var(--muted); cursor: pointer; padding: 0;
}
:is(#screen-review, #screen-edit) .vc-tag-x .icon { width: 14px; height: 14px; }
:is(#screen-review, #screen-edit) .vc-tags-plus {
  display: grid; place-items: center; width: 44px; height: 44px; flex: none;
  border: none; border-radius: 50%; background: none; color: var(--espresso); cursor: pointer;
}
:is(#screen-review, #screen-edit) .vc-tags-plus .icon { width: 20px; height: 20px; }
:is(#screen-review, #screen-edit) .vc-tags-catalogue { margin-top: 8px; flex-wrap: wrap; }
:is(#screen-review, #screen-edit) .vc-tags-catalogue .chip { min-height: 44px; }
:is(#screen-review, #screen-edit) .vc-note { margin: 8px 2px 0; }

/* Les deux listes. */
:is(#screen-review, #screen-edit) .vc-bloc { margin-top: 22px; }
:is(#screen-review, #screen-edit) .vc-bloc-tete { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 10px; margin-bottom: 10px; }
:is(#screen-review, #screen-edit) .vc-section { margin: 0; font-family: var(--ecriture); font-weight: 700; font-size: 28px; line-height: 1.15; }
:is(#screen-review, #screen-edit) .vc-ajouter {
  display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 16px; flex: none;
  border: 1px solid color-mix(in srgb, var(--terra) 28%, transparent); border-radius: 999px;
  background: color-mix(in srgb, var(--terra) 9%, var(--card)); color: var(--cinnamon);
  font: inherit; font-size: 22.5px; cursor: pointer;
}
:is(#screen-review, #screen-edit) .vc-ajouter .icon { width: 18px; height: 18px; color: var(--terra); }
@media (max-width: 374px) {
  :is(#screen-review, #screen-edit) .vc-ajouter { padding: 0 12px; font-size: 21px; }
  :is(#screen-review, #screen-edit) .vc-section { font-size: 27px; }
}
:is(#screen-review, #screen-edit) .vc-liste {
  background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 2px 12px;
}
:is(#screen-review, #screen-edit) .vc-liste .edit-empty { padding: 14px 0; }
:is(#screen-review, #screen-edit) #review-order,
:is(#screen-review, #screen-edit) #review-order-step { margin-top: 6px; min-height: 44px; }
:is(#screen-review, #screen-edit) .vc-groupe {
  margin: 0; padding: 12px 0 4px; border-bottom: 1px solid var(--line);
  color: var(--muted); font-size: 19.5px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
}

/* La rangée d'ingrédient. ⚠️ **Six pistes, comme partout** — le test des
   variantes les compte. Sous 600 px, la quantité passe sous le nom : la
   maquette tient sur une ligne parce qu'elle écrit en 13 px, et un champ ne
   descend pas sous 16 (§ 1). */
:is(#screen-review, #screen-edit) .rrow {
  grid-template-columns: 14px 8px 38px 1fr auto 36px;
  gap: 4px 8px; padding: 10px 0;
}
:is(#screen-review, #screen-edit) .rrow .diary-thumb { width: 38px; height: 38px; border-radius: 10px; }
:is(#screen-review, #screen-edit) .rrow input.name { font-size: 24px; min-height: 44px; }
:is(#screen-review, #screen-edit) .rrow .rdel { min-height: 44px; min-width: 36px; font-size: 25px; }
:is(#screen-review, #screen-edit) .rrow .rqty {
  display: flex; gap: 6px; border: none; background: none; padding: 0; min-height: 44px;
  overflow: visible; text-overflow: clip;
}
:is(#screen-review, #screen-edit) .rrow .rqty-n,
:is(#screen-review, #screen-edit) .rrow .rqty-u {
  display: flex; align-items: center; min-height: 44px; padding: 0 10px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
  font-size: 24px; white-space: nowrap;
}
:is(#screen-review, #screen-edit) .rrow .rqty-n { min-width: 54px; justify-content: center; }
:is(#screen-review, #screen-edit) .rrow .rqty-u { position: relative; min-width: 108px; padding-right: 28px; }
:is(#screen-review, #screen-edit) .rrow .rqty-u:empty::before { content: "unité"; color: var(--muted); font-style: italic; }
:is(#screen-review, #screen-edit) .rrow .rqty-u::after {
  content: ""; position: absolute; right: 11px; top: 50%; width: 6px; height: 6px;
  border-right: 1.6px solid var(--muted); border-bottom: 1.6px solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
}
:is(#screen-review, #screen-edit) .rrow.check .rqty { border: none; }
:is(#screen-review, #screen-edit) .rrow.check .rqty-n,
:is(#screen-review, #screen-edit) .rrow.check .rqty-u { border-color: var(--amber); }
:is(#screen-review, #screen-edit) .rrow .rsug { left: 64px; }
:is(#screen-review, #screen-edit) .rfacultatif { color: var(--muted); }
@media (max-width: 599px) {
  :is(#screen-review, #screen-edit) .rrow .name { grid-column: 4 / 6; grid-row: 1; }
  :is(#screen-review, #screen-edit) .rrow .rdel { grid-column: 6; grid-row: 1; }
  :is(#screen-review, #screen-edit) .rrow .rqty { grid-column: 4 / 6; grid-row: 2; justify-self: start; }
  :is(#screen-review, #screen-edit) .rrow .rgrip,
  :is(#screen-review, #screen-edit) .rrow .dot,
  :is(#screen-review, #screen-edit) .rrow > .diary-thumb,
  :is(#screen-review, #screen-edit) .rrow > .rvig { grid-row: 1; }
}
@media (max-width: 430px) {
  :is(#screen-review, #screen-edit) .rrow { grid-template-columns: 14px 8px 34px 1fr auto 36px; gap: 4px 6px; }
  :is(#screen-review, #screen-edit) .rrow .diary-thumb { width: 34px; height: 34px; }
  :is(#screen-review, #screen-edit) .rrow .rsug { left: 0; }
}

/* Les étapes, repliées : le numéro, la phrase, le crayon, la corbeille. */
:is(#screen-review, #screen-edit) .vc-etape {
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  margin: 0; padding: 8px 0; background: none;
}
:is(#screen-review, #screen-edit) .vc-etape:last-child { border-bottom: none; }
:is(#screen-review, #screen-edit) .vc-etape-ligne { display: grid; grid-template-columns: 14px 32px minmax(0, 1fr) 40px 40px; gap: 8px; align-items: center; }
:is(#screen-review, #screen-edit) .vc-etape-num {
  display: grid; place-items: center; width: 32px; height: 32px; border-radius: 50%;
  background: color-mix(in srgb, var(--terra) 12%, var(--card)); color: var(--cinnamon); font-size: 22.5px;
}
:is(#screen-review, #screen-edit) .vc-etape-texte { margin: 0; font-size: 22.5px; line-height: 1.4; color: var(--espresso); overflow-wrap: anywhere; }
:is(#screen-review, #screen-edit) .vc-etape-texte small { color: var(--muted); font-size: 19.5px; white-space: nowrap; }
:is(#screen-review, #screen-edit) .vc-etape-crayon,
:is(#screen-review, #screen-edit) .vc-etape-suppr {
  display: grid; place-items: center; width: 40px; height: 44px; min-height: 44px; padding: 0;
  border: none; background: none; cursor: pointer; color: var(--espresso);
}
:is(#screen-review, #screen-edit) .vc-etape-suppr { color: var(--terra); }
:is(#screen-review, #screen-edit) .vc-etape-crayon .icon,
:is(#screen-review, #screen-edit) .vc-etape-suppr .icon { width: 20px; height: 20px; }
:is(#screen-review, #screen-edit) .vc-etape-champs { padding: 6px 0 4px 46px; }
:is(#screen-review, #screen-edit) .vc-etape-champs[hidden] { display: none; }
:is(#screen-review, #screen-edit) .vc-etape-champs input.etitle { background: var(--cream); border-color: var(--line); font-weight: 600; padding-left: 10px; }
@media (max-width: 374px) {
  :is(#screen-review, #screen-edit) .vc-etape-champs { padding-left: 0; }
  :is(#screen-review, #screen-edit) .vc-etape-ligne { grid-template-columns: 14px 28px minmax(0, 1fr) 36px 36px; gap: 6px; }
  :is(#screen-review, #screen-edit) .vc-etape-num { width: 28px; height: 28px; }
}

/* « Valider la recette ». */
:is(#screen-review, #screen-edit) .vc-actions { margin-top: 22px; }
:is(#screen-review, #screen-edit) .vc-valider {
  display: flex; align-items: center; justify-content: center; gap: 12px; min-height: 56px;
  background: var(--bouton-fond); color: var(--bouton-texte); font-size: 24.5px; border-radius: 14px;
}
:is(#screen-review, #screen-edit) .vc-valider .icon { width: 22px; height: 22px; }
:is(#screen-review, #screen-edit) .vc-actions .icon-btn.heart { width: 56px; height: 56px; min-height: 56px; }
:is(#screen-review, #screen-edit) .vc-pied { text-align: center; margin-top: 6px; }


/* ── Modifier une fiche — P21 de la refonte, 14/09 ───────────────────────────
   Ce que l'éditeur ajoute à l'habillage de P20 : le décor d'en-tête, les
   raccourcis, « Photo et titre » côte à côte, les cartes à pas (−/+) et les
   titres de section à icône. */
#screen-edit .me-head { min-height: 0; grid-template-areas: "retour . ." "texte texte texte"; }
#screen-edit .me-head .vc-head-texte { padding-right: 120px; }
#screen-edit .me-decor { position: absolute; right: 0; top: 0; width: 132px; height: 120px; pointer-events: none; }
#screen-edit .me-decor img { position: absolute; display: block; }
#screen-edit .me-decor-basilic { right: 30px; top: 0; width: 64px; transform: rotate(-14deg); }
#screen-edit .me-decor-ail { right: 0; top: 62px; width: 48px; }
#screen-edit .me-decor-tomate { right: 56px; top: 70px; width: 46px; }
@media (max-width: 374px) {
  #screen-edit .me-decor { width: 96px; height: 96px; }
  #screen-edit .me-decor-basilic { width: 48px; right: 22px; }
  #screen-edit .me-decor-ail { width: 36px; top: 50px; }
  #screen-edit .me-decor-tomate { width: 34px; right: 42px; top: 56px; }
  #screen-edit .me-head .vc-head-texte { padding-right: 84px; }
}

/* Les raccourcis : une rangée qui défile au doigt, jamais des onglets. */
/* ⚠️ Une rangée qui défile retient son geste (§ 6) : sans ça, pousser
   « Étiquettes » ferait partir le balayage de retour. */
.me-ancres { overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; }
#screen-edit .me-ancres { display: flex; gap: 8px; margin: 14px -20px 4px; padding: 2px 20px 6px; }
#screen-edit .me-ancres::-webkit-scrollbar { display: none; }
#screen-edit .me-ancre {
  display: inline-flex; align-items: center; gap: 8px; flex: none; min-height: 44px; padding: 0 16px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--card);
  color: var(--espresso); font-size: 22.5px; text-decoration: none;
}
#screen-edit .me-ancre .icon { width: 18px; height: 18px; color: var(--terra); }
#screen-edit .me-section { scroll-margin-top: 12px; }
#screen-edit .me-titre-section { display: flex; align-items: center; gap: 10px; }
#screen-edit .me-titre-section .icon { width: 24px; height: 24px; color: var(--terra); flex: none; }
#screen-edit .me-titre-section:focus { outline: none; }
#screen-edit #edit-sec-general > .me-titre-section { margin-bottom: 10px; }

/* Photo et titre, côte à côte, l'un sous l'autre sous 480 px. */
#screen-edit .me-photo-titre { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); gap: 12px; align-items: start; }
@media (max-width: 359px) { #screen-edit .me-photo-titre { grid-template-columns: 1fr; } }
#screen-edit .me-photo { position: relative; }
#screen-edit .me-photo-apercu {
  width: 100%; height: auto; aspect-ratio: 1 / 1; max-height: 240px; border-radius: 18px;
  background-size: cover; background-position: center;
}
#screen-edit .me-photo-changer {
  position: absolute; left: 8px; right: 8px; bottom: 8px; display: flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 44px; padding: 0 10px; border: none; border-radius: 999px; white-space: nowrap;
  background: var(--card); color: var(--espresso); font: inherit; font-size: 22.5px; cursor: pointer;
  box-shadow: 0 4px 14px rgb(46 36 29 / .16);
}
#screen-edit .me-photo-changer .icon { width: 18px; height: 18px; }
#screen-edit .me-photo-retirer {
  position: absolute; right: 8px; top: 8px; display: grid; place-items: center; width: 44px; height: 44px;
  border: none; border-radius: 50%; background: var(--card); color: var(--espresso); cursor: pointer;
  box-shadow: 0 4px 14px rgb(46 36 29 / .16);
}
#screen-edit .me-photo-retirer[hidden] { display: none; }
#screen-edit .me-photo-retirer .icon { width: 20px; height: 20px; }
#screen-edit .me-champ-titre .edit-field { min-height: 52px; }
#screen-edit .me-champ-titre .hint { margin: 8px 2px 0; }

/* Catégorie, portions, temps : quatre cartes, deux par rangée au-delà de 480 px. */
#screen-edit .me-grille { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 14px; }
/* ⚠️ **Mesuré** : sous 390 px une carte de 155 px laisse 39 px à « 10 min »
   entre − et +, et le nombre se tronque. Une colonne, plutôt qu'un bouton
   sous 44 px. */
@media (max-width: 389px) { #screen-edit .me-grille { grid-template-columns: 1fr; } }
/* Sous 480 px l'icône cède sa place au champ : c'est lui qu'on règle. */
@media (max-width: 479px) {
  #screen-edit .me-carte > .icon { display: none; }
  #screen-edit .me-carte { padding: 10px; }
  #screen-edit .me-photo-changer { font-size: 21px; }
  #screen-edit .me-photo-changer .icon { display: none; }
}
#screen-edit .me-carte {
  display: flex; align-items: center; gap: 12px; padding: 10px 12px;
  background: var(--card); border: 1px solid var(--line); border-radius: 16px;
}
#screen-edit .me-carte[hidden] { display: none; }
#screen-edit .me-carte > .icon { width: 26px; height: 26px; flex: none; color: var(--espresso); }
#screen-edit .me-carte-corps { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
#screen-edit .me-carte-libelle { font-size: 19.5px; color: var(--muted); }
#screen-edit .me-select { position: relative; display: block; }
#screen-edit .me-select::after {
  content: ""; position: absolute; right: 12px; top: 50%; width: 7px; height: 7px;
  border-right: 1.8px solid var(--muted); border-bottom: 1.8px solid var(--muted);
  transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
#screen-edit .me-select select {
  appearance: none; -webkit-appearance: none; width: 100%; min-height: 44px; padding: 0 30px 0 12px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card); color: inherit; font: inherit; font-size: 24px;
}
/* La valeur, − et + dans un seul champ, comme la maquette : trois cases
   séparées ne tiendraient pas deux cartes par rangée à 390 px. */
#screen-edit .me-pas {
  display: flex; align-items: center; gap: 0; min-height: 46px; padding-left: 8px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--card);
}
#screen-edit .me-pas:focus-within { border-color: var(--terra); }
#screen-edit .me-pas output,
#screen-edit .me-minutes {
  display: flex; align-items: center; gap: 2px; flex: 1; min-width: 0; min-height: 44px; font-size: 24px;
}
#screen-edit .me-minutes input {
  flex: none; width: 3ch; min-width: 0; min-height: 42px; border: none; background: none; padding: 0; margin: 0;
  -moz-appearance: textfield;
}
#screen-edit .me-minutes input::-webkit-inner-spin-button,
#screen-edit .me-minutes input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
#screen-edit .me-minutes small { color: var(--muted); font-size: 19.5px; }
#screen-edit .me-pas button {
  display: grid; place-items: center; width: 44px; height: 44px; flex: none; padding: 0;
  border: none; border-radius: 50%; background: none; color: var(--espresso); cursor: pointer;
}
#screen-edit .me-pas button .icon { width: 18px; height: 18px; }

#screen-edit .me-sous-liste { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px; }
#screen-edit .me-sous-liste .link-btn { min-height: 44px; margin-top: 6px; }
#screen-edit .me-dicter { display: inline-flex; align-items: center; gap: 6px; }
#screen-edit .me-dicter .icon { width: 18px; height: 18px; }
#screen-edit .vc-liste .edit-section { margin-top: 10px; }
#screen-edit #edit-order-step { margin-top: 6px; min-height: 44px; }
#screen-edit .me-nutrition { padding: 12px 14px; }
@media (max-width: 430px) {
  #screen-edit .vc-section { font-size: 27px; }
  #screen-edit .me-titre-section .icon { width: 22px; height: 22px; }
  #screen-edit .vc-ajouter { padding: 0 12px; font-size: 21px; }
}
#screen-edit .me-nutrition-chiffres { margin: 0 0 4px; font-size: 24.5px; font-weight: 600; }
#screen-edit .me-nutrition-chiffres.vide { font-size: 22.5px; font-weight: 400; color: var(--muted); }
#screen-edit .vc-valider { padding: 0 14px; font-size: 24px; white-space: nowrap; }
@media (max-width: 359px) { #screen-edit .vc-valider { white-space: normal; line-height: 1.2; font-size: 22.5px; } }
#screen-edit .me-nutrition .hint { margin: 0; }
#screen-edit .me-nutrition .link-btn { min-height: 44px; }
#screen-edit .me-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
#screen-edit .me-tags .vc-tags-puces { flex: 1 1 auto; }
#screen-edit .me-tags .vc-tag { min-height: 40px; }

/* ── Le profil — P30 (moi) et P34 (un membre), un seul gabarit (17/09) ───────
   Scopé sur `.profil-corps`, qui est le point d'estampage des deux écrans.
   Le visuel « profil commu » : portrait à cheval sur le bandeau, Modifier +
   engrenage (ou Suivre), nom en serif, @pseudo, présentation, spécialités,
   trois tuiles, carnet, onglets. */
#screen-profile .accueil-bandeau { padding-bottom: 0; min-height: 120px; }
#screen-profile .accueil-bandeau > .pane-head.tete-marque { justify-content: flex-end; }
/* ⚠️ Le portrait chevauche le bandeau, et le bandeau est `position: relative` :
   sans ce cran de pile, il peint **par-dessus** le haut du rond — mesuré au
   banc, l'initiale coupée à mi-hauteur. */
#screen-profile > .menu-corps.profil-corps { padding-top: 0; position: relative; z-index: 1; }
.profil-corps { --gouttiere: 20px; }
#screen-profil-public .profil-corps { padding: 0 var(--gouttiere) 24px; }
.profil-corps .profil-tete {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  margin-top: -44px; margin-bottom: 10px;
}
#screen-profil-public .profil-corps .profil-tete { margin-top: 6px; }
.profil-corps .profil-portrait {
  display: block; width: 104px; height: 104px; border-radius: 50%; padding: 4px;
  background: var(--card); box-shadow: var(--shadow); flex: none;
}
.profil-corps .profil-portrait-int {
  width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso);
  font-size: 39px; font-weight: 600;
}
.profil-corps .profil-portrait-int img { width: 100%; height: 100%; object-fit: cover; display: block; }
.profil-corps .profil-gestes { display: flex; align-items: center; gap: 8px; padding-bottom: 6px; }
.profil-corps .profil-modifier { min-height: 40px; padding: 0 18px; border-radius: 999px; background: #EFE3D3; border-color: transparent; color: var(--espresso); font-weight: 600; }
.profil-corps .profil-engrenage { background: #EFE3D3; border-color: transparent; }
.profil-corps .profil-suivre { min-height: 40px; padding: 0 20px; border-radius: 999px; width: auto; }
.profil-corps .profil-nom { margin: 0; font-family: var(--ecriture); font-size: 30px; font-weight: 700; line-height: 1.15; }
.profil-corps .profil-pseudo { margin: 2px 0 0; color: var(--muted); font-size: 22px; }
.profil-corps .profil-bio { margin: 8px 0 0; font-size: 22.5px; line-height: 1.45; }
.profil-corps .profil-specialites {
  display: flex; align-items: center; gap: 6px; margin: 8px 0 0;
  font-size: 20px; color: var(--muted);
}
.profil-corps .profil-specialites .icon { width: 16px; height: 16px; color: var(--sage); flex: none; }
.profil-corps .profil-pas-visible {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin: 12px 0 0; padding: 10px 14px; border-radius: 14px;
  background: #E4E9DA; color: #3F4A2E; font-size: 20px; line-height: 1.4;
}
/* ⚠️ **`inherit`, et pas la couleur du bloc recopiée** : le bloc change de
   fond et d'encre en thème sombre (plus bas), le lien restait à l'encre claire
   — 1,17:1 sur le vert foncé, mesuré le 21/09. */
.profil-corps .profil-pas-visible .link-btn { min-height: 32px; font-weight: 600; color: inherit; }
/* R01, 21/09 — l'état de la lecture de mon profil. Les couleurs du thème
   (`--card`, `--muted`) : la ligne vit sur le fond de l'app, pas sur une photo. */
.profil-corps .profil-etat {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  margin: 12px 0 0; padding: 10px 14px; border-radius: 14px;
  background: var(--card); color: var(--muted); box-shadow: var(--shadow);
  font-size: 21px; line-height: 1.4;
}
.profil-corps .profil-etat.warn { color: var(--cinnamon); font-weight: 600; }
.profil-corps .profil-etat .btn-mini { min-height: 44px; font-weight: 600; }
/* ⛔ **Pas de zéros d'avance** : tant qu'aucune réponse n'est arrivée, les
   nombres, le carnet et les onglets n'ont rien à dire — « 0 abonnés » serait
   une affirmation. Les commandes (Modifier, engrenage) restent. */
.profil-corps[data-sans-donnees] .profil-tuiles3,
.profil-corps[data-sans-donnees] .profil-carnet,
.profil-corps[data-sans-donnees] .profil-pas-visible,
.profil-corps[data-sans-donnees] .profil-onglets,
.profil-corps[data-sans-donnees] [data-pp-panneau] { display: none; }
.profil-corps .profil-tuiles3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 16px 0 14px; }
.profil-corps .profil-tuile3 {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 12px 6px; border-radius: 16px; background: #EFE3D3; text-align: center;
}
.profil-corps .profil-tuile3 .icon { width: 22px; height: 22px; color: var(--coeur); margin-bottom: 4px; }
.profil-corps .tuile3-sauge { background: #E4E9DA; }
.profil-corps .tuile3-sauge .icon { color: #5E6E48; }
.profil-corps .tuile3-rose { background: #F3D9C8; }
.profil-corps .profil-tuile3 b { font-size: 26.5px; line-height: 1.1; }
.profil-corps .profil-tuile3 small { font-size: 19px; color: var(--muted); }
.profil-corps .profil-carnet { margin: 0 0 16px; background: #E4E9DA; border: none; }
.profil-corps .profil-carnet .card-title { margin-bottom: 8px; }
.profil-corps .profil-carnet-lignes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.profil-corps .profil-carnet-lignes li { display: flex; align-items: center; gap: 10px; font-size: 22px; }
.profil-corps .profil-carnet-lignes li .icon { width: 20px; height: 20px; color: var(--coeur); flex: none; }
.profil-corps .profil-carnet-lignes li span { flex: 1; min-width: 0; }
.profil-corps .profil-carnet-lignes li b { font-size: 24px; }
.profil-corps .profil-onglets { display: flex; border-bottom: 1px solid var(--line); margin: 4px 0 14px; }
.profil-corps .profil-onglet {
  /* R05, revue du 21/09 : `--muted` rendait 3,35:1 sur le crème — un onglet
     est une commande, il prend l'encre des commandes sans fond. */
  flex: 1 1 0; min-height: 44px; border: none; background: none; color: var(--bouton-second-texte);
  font: inherit; font-size: 22px; font-weight: 600; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.profil-corps .profil-onglet.on { color: var(--coeur); border-bottom-color: var(--coeur); }
.profil-corps .profil-rubrique-tete { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.profil-corps .profil-rubrique-tete .pp-section { margin: 0; }
.profil-corps .profil-rubrique-tete .link-btn { min-height: 40px; color: var(--coeur); font-weight: 600; }
.profil-corps .profil-recettes-rail .recipe-card-wrap { flex: 0 0 158px; }
.profil-corps .profil-collections { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.profil-corps .profil-collection {
  display: flex; align-items: center; gap: 12px; width: 100%; min-height: 64px; padding: 8px 12px;
  border: none; border-radius: 16px; background: var(--card); color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.profil-corps .profil-collection-vignette {
  width: 48px; height: 48px; border-radius: 12px; overflow: hidden; flex: none;
  display: grid; place-items: center; background: #F1E7DA; color: var(--coeur);
}
.profil-corps .profil-collection-vignette img { width: 100%; height: 100%; object-fit: cover; display: block; }
.profil-corps .profil-collection-vignette .icon { width: 22px; height: 22px; }
.profil-corps .profil-collection b { display: block; font-size: 22.5px; }
.profil-corps .profil-collection small { color: var(--muted); font-size: 19.5px; }
.profil-corps .profil-apropos-bio { margin: 0 0 10px; font-size: 22.5px; line-height: 1.5; }
.profil-corps [data-pp-panneau="apropos"] .btn-ghost { margin-top: 12px; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .profil-corps :is(.profil-modifier, .profil-engrenage, .profil-tuile3) { background: #3C3027; }
  :root:not([data-theme="light"]) .profil-corps :is(.tuile3-sauge, .profil-carnet, .profil-pas-visible) { background: #33402A; color: var(--espresso); }
  :root:not([data-theme="light"]) .profil-corps .tuile3-rose { background: #4A3A2E; }
}
:root[data-theme="dark"] .profil-corps :is(.profil-modifier, .profil-engrenage, .profil-tuile3) { background: #3C3027; }
:root[data-theme="dark"] .profil-corps :is(.tuile3-sauge, .profil-carnet, .profil-pas-visible) { background: #33402A; color: var(--espresso); }
:root[data-theme="dark"] .profil-corps .tuile3-rose { background: #4A3A2E; }

/* ── Réglages — la page (C05) ────────────────────────────────────────────────
   Une saisie en ligne (hydratation) à côté d'un interrupteur : même rangée. */
#screen-reglages .reglages-corps { padding-bottom: 24px; }
#screen-reglages .profil-rubrique:first-child { margin-top: 8px; }
#screen-reglages .profil-saisie input {
  width: 72px; min-height: 40px; text-align: center; font: inherit; font-size: 24px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--cream); color: var(--espresso);
}

/* ── La publication — P47, 18/09 ─────────────────────────────────────────────
   Photo en plein bord, boutons ronds dessus, titre serif, auteur + Suivre,
   compteurs, présentation, hashtags, la carte « Voir la recette », puis les
   trois cartes venues de P04. Scopé sur `#screen-publication`. */
#screen-publication { padding: 0; }
/* ⚠️ Le cœur chevauche le bord bas de la photo : c'est l'**image** qui se
   découpe, jamais le cadre — mesuré au banc, le cœur coupé à mi-hauteur. */
#screen-publication .post-hero { position: relative; width: 100%; aspect-ratio: 4 / 3; max-height: 46vh; background: linear-gradient(135deg, #EFE3D8, #E3D3C4); }
#screen-publication .post-hero-image { overflow: hidden; }
#screen-publication .post-hero-image, #screen-publication .post-hero-image img,
#screen-publication .post-hero-image .thumb-fallback, #screen-publication .post-hero-image .thumb-illus {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
#screen-publication .post-hero-image .thumb-illus { object-fit: contain; padding: 12%; background: linear-gradient(135deg, #EFE3D8, #E3D3C4); }
#screen-publication .post-hero-image .thumb-fallback { display: grid; place-items: center; color: var(--sand); }
#screen-publication .post-hero-image .thumb-fallback .icon { width: 48px; height: 48px; }
#screen-publication .post-hero-btn {
  position: absolute; top: calc(env(safe-area-inset-top) + 12px); width: 40px; height: 40px;
  background: rgba(255, 255, 255, .92); border-color: transparent; color: var(--espresso);
}
#screen-publication .post-retour { left: 14px; font-size: 29px; }
#screen-publication .post-menu { right: 14px; }
#screen-publication .post-coeur { top: auto; bottom: -22px; right: 22px; width: 52px; height: 52px; min-height: 52px; }
#screen-publication .post-coeur .icon { width: 24px; height: 24px; }
#screen-publication .post-corps { padding: 20px var(--gouttiere, 20px) 28px; }
#screen-publication .post-titre { margin: 0 64px 10px 0; font-family: var(--ecriture); font-size: 29px; line-height: 1.2; font-weight: 700; }
#screen-publication .post-auteur-ligne { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
#screen-publication .post-auteur { display: flex; align-items: center; gap: 10px; min-width: 0; cursor: pointer; }
#screen-publication .post-portrait {
  width: 36px; height: 36px; border-radius: 50%; overflow: hidden; flex: none;
  display: grid; place-items: center; background: var(--sand); color: var(--espresso); font-size: 22.5px; font-weight: 600;
}
#screen-publication .post-portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
#screen-publication .post-noms { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
#screen-publication .post-noms b { font-size: 22.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#screen-publication .post-noms small { font-size: 19px; color: var(--muted); }
#screen-publication .post-suivre { min-height: 40px; padding: 0 18px; border-radius: 999px; width: auto; flex: none; }
#screen-publication .post-compteurs { display: flex; align-items: center; gap: 18px; margin-bottom: 14px; }
#screen-publication .post-compte {
  display: inline-flex; align-items: center; gap: 5px; font-size: 22.5px; font-weight: 600; color: var(--espresso);
}
#screen-publication .post-compte-btn { border: none; background: none; padding: 0; min-height: 40px; font: inherit; font-weight: 600; color: var(--espresso); cursor: pointer; }
#screen-publication .post-compte .icon { width: 22px; height: 22px; color: var(--coeur); }
#screen-publication .post-marque { position: static; margin-left: auto; width: 40px; height: 40px; min-height: 40px; background: none; color: var(--espresso); }
#screen-publication .post-marque .icon { width: 20px; height: 20px; }
#screen-publication .post-marque.on { background: none; color: var(--coeur); }
#screen-publication .post-presentation { margin: 0 0 12px; font-size: 23px; line-height: 1.5; }
#screen-publication .post-origine { margin: 0 0 12px; font-size: 20px; color: var(--muted); }
#screen-publication .post-origine a { color: var(--coeur); }
#screen-publication .post-hashtags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
#screen-publication .post-hashtag {
  min-height: 30px; padding: 0 12px; border-radius: 999px; border: none;
  background: #F1E7DA; color: var(--espresso); font: inherit; font-size: 19.5px; cursor: pointer;
}
#screen-publication .post-voir-recette {
  display: flex; align-items: center; gap: 14px; width: 100%; min-height: 76px; padding: 10px 14px;
  margin-bottom: 20px; border: none; border-radius: 18px; background: #F3D9C8; color: var(--espresso);
  font: inherit; text-align: left; cursor: pointer;
}
#screen-publication .post-voir-vignette { width: 56px; height: 56px; border-radius: 12px; overflow: hidden; flex: none; background: var(--card); }
#screen-publication .post-voir-vignette img, #screen-publication .post-voir-vignette .thumb-fallback, #screen-publication .post-voir-vignette .thumb-illus {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
#screen-publication .post-voir-vignette .thumb-illus { object-fit: contain; padding: 6px; }
#screen-publication .post-voir-texte { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
#screen-publication .post-voir-texte b { font-size: 24px; color: var(--coeur); }
#screen-publication .post-voir-texte small { font-size: 19.5px; color: var(--muted); }
#screen-publication .post-voir-recette > i { font-style: normal; font-size: 27.5px; color: var(--coeur); }
#screen-publication .card { margin-bottom: 14px; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) #screen-publication .post-hashtag { background: #3C3027; }
  :root:not([data-theme="light"]) #screen-publication .post-voir-recette { background: #4A3A2E; }
}
:root[data-theme="dark"] #screen-publication .post-hashtag { background: #3C3027; }
:root[data-theme="dark"] #screen-publication .post-voir-recette { background: #4A3A2E; }

/* ── La bascule sauge — née avec C30, commune depuis le 20/09 ──────────────
   La case native devient un rail ; même dessin dans toute feuille de réglages.
   Elle vient après `.interrupteur`, dont elle reprend la taille et la marge. */
.bascule {
  appearance: none; -webkit-appearance: none; width: 48px; height: 28px; margin: 0; border-radius: 999px;
  background: #D9D3C8; position: relative; cursor: pointer; transition: background-color .18s ease; flex: none;
}
.bascule::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%;
  background: #FFFFFF; box-shadow: 0 1px 2px rgba(0, 0, 0, .25); transition: transform .18s ease;
}
.bascule:checked { background: #5E6E48; }
.bascule:checked::after { transform: translateX(20px); }
.bascule:disabled { opacity: .45; cursor: default; }
/* ── Paramètres de publication — C30 ──────────────────────────────────────── */
/* ⚠️ **Ces trois règles portent un identifiant, donc elles gagnent contre tout
   le carnet** — et c'est précisément pourquoi elles sont réécrites ici plutôt
   que contredites ailleurs (§ *une règle qui ne gagne que par sa place se
   perd*). La ligne est devenue une **carte** avec le carnet du 21/09 : lui
   laisser `padding-inline: 0` collait son texte au filet. Les retraits de 14 px
   de la phrase et du pied venaient du retrait des feuilles d'avant ; le carnet
   porte le sien sur la feuille. */
#c30-sheet .c30-ligne { padding-inline: 12px; }
#c30-sheet .c30-phrase { text-align: center; margin: 14px 5px 0; }
#c30-sheet .c30-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; padding: 18px 2px 2px; }

/* ── La visite guidée — chantier H, 25/09 ───────────────────────────────────

   **Le projecteur**, pour le tour (H1) et les bulles d'écran (H2) : tout se
   grise, la cible reste nette dans un trou arrondi cerné d'un anneau, et une
   bulle terre cuite la désigne par sa flèche — la forme demandée par Luca le
   25/09 (« comme dans les jeux mobiles »).

   ⛔ **Pas de `backdrop-filter`** (§ 6). Le gris est l'ombre étendue du halo :
   un aplat peint une fois, qui laisse le trou transparent — la technique tient
   sur Safari et dans la WebView, sans masque ni `clip-path`. Rien ne défile
   dessous pendant le tour : le voile prend tous les gestes.

   § 2 — les deux couches fixes sont au registre du plein écran
   (`PLEIN_ECRAN_ASSUME`) : le voile couvre la fenêtre, rail compris, et la
   bulle se place **en coordonnées de fenêtre**, à côté de la boîte de sa cible
   (`getBoundingClientRect`) — recadrée sur la colonne, elle se décalerait de la
   moitié du rail. */
.visite-voile {
  position: fixed; inset: 0; z-index: 50;
  background: transparent;
}
/* Le temps qu'une étape trouve sa cible (l'écran se charge), tout est gris. */
.visite-voile.sans-cible { background: rgba(20, 14, 10, .66); }
.visite-voile[hidden], .visite-halo[hidden], .visite-bulle[hidden] { display: none; }
.visite-halo {
  position: absolute;
  border-radius: 18px;
  box-shadow: 0 0 0 3px var(--terra), 0 0 0 200vmax rgba(20, 14, 10, .66);
  pointer-events: none;
}
.visite-bulle {
  position: fixed; z-index: 51;
  width: min(340px, calc(100vw - 24px));
  box-sizing: border-box;
  padding: 14px 16px 12px;
  background: var(--bouton-fond); color: var(--bouton-texte);
  border-radius: 20px;
  box-shadow: 0 14px 34px rgba(0, 0, 0, .35);
}
/* La cible se cherche encore : la bulle garde le focus, mais ne se montre pas
   là où elle pointait l'étape d'avant. */
.visite-bulle.en-attente { opacity: 0; pointer-events: none; }
/* La flèche : un carré tourné de la couleur de la bulle, posé à cheval sur le
   bord qui fait face à la cible ; l'autre coordonnée vient du script. */
.visite-fleche {
  position: absolute;
  width: 18px; height: 18px;
  background: var(--bouton-fond);
  border-radius: 3px;
  transform: translate(-50%, -50%) rotate(45deg);
}
.visite-bulle[data-cote="dessous"] .visite-fleche { top: 0; }
.visite-bulle[data-cote="dessus"] .visite-fleche { top: 100%; }
.visite-bulle[data-cote="droite"] .visite-fleche { left: 0; }
.visite-bulle[data-cote="gauche"] .visite-fleche { left: 100%; }
.visite-corps { position: relative; max-height: 40vh; overflow-y: auto; }
.visite-compte {
  margin: 0 0 2px;
  font-size: var(--t-micro); letter-spacing: .04em; opacity: .9;
}
.visite-titre {
  margin: 0 0 4px;
  font-size: var(--t-titre-sec); line-height: var(--lh-titre);
}
.visite-texte {
  margin: 0;
  font-size: var(--t-carte-sous); line-height: var(--lh-corps);
}
.visite-actions {
  position: relative;
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  margin-top: 12px;
}
.visite-actions .btn-ghost,
.visite-actions .btn-primary { min-height: 44px; font-size: var(--t-carte-sous); }
.visite-actions .btn-ghost {
  border-color: rgba(255, 255, 255, .55); color: var(--bouton-texte); background: transparent;
}
.visite-actions .btn-primary {
  background: var(--bouton-texte); color: var(--bouton-fond); border-radius: 999px; padding-inline: 22px;
}
.visite-bulle :focus-visible { outline-color: var(--bouton-texte); }
