@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 400;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-sans-400.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 500;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-sans-500.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 600;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-sans-600.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Sans Condensed";
  font-style: normal;
  font-weight: 600;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-condensed-600.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-mono-400.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  /* swap : le texte s'affiche immediatement en police systeme, puis bascule.
     Mieux qu'un texte invisible pendant le chargement. */
  font-display: swap;
  src: url("/assets/polices/plex-mono-500.woff2?v=sne66w") format("woff2");
  /* Plage de caracteres du sous-ensemble latin : le navigateur ne telecharge
     le fichier que si la page en a besoin. */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
                 U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* ============================================================
   NUTTICE — tokens de charte, sprint 1
   Pierre angulaire : aucune valeur brute (couleur, taille, durée)
   ne doit apparaître ailleurs que dans ce fichier.
   Convention de nommage : --nut-<famille>-<rôle>-<variante>
   ============================================================ */

:root {
  color-scheme: dark;

  /* ---------- 1. Couleurs de surface ---------------------------------
     Trois niveaux seulement, très proches en valeur. La hiérarchie est
     portée par l'espacement, pas par le contraste des fonds.
     Le noir est teinté vert (hue ~140) : le feutre du tapis de jeu, et
     le complément chromatique de l'orange accent.               */
  --nut-bg:              #0F1210;  /* fond application            */
  --nut-surface:         #161A17;  /* sidebar, cartes             */
  --nut-surface-raised:  #1E231F;  /* champs, hover, popovers     */
  --nut-surface-sunken:  #0A0C0A;  /* voile de tiroir, code       */

  /* ---------- 2. Lignes ----------------------------------------------
     -line          : séparateur DÉCORATIF uniquement (1,41:1)
     -line-strong   : bordure de composant INTERACTIF (3,26:1 — AA 3:1) */
  --nut-line:            #2B322C;
  --nut-line-strong:     #5D6A5F;

  /* ---------- 3. Texte ----------------------------------------------- */
  --nut-text:            #E8EDE8;  /* principal        15,70:1 / bg */
  --nut-text-muted:      #A0AAA1;  /* secondaire        7,74:1 / bg */
  --nut-text-faint:      #7C8A7E;  /* désactivé, méta   5,12:1 / bg */
  --nut-text-on-accent:  #0F1210;  /* sur aplat orange  7,13:1       */

  /* ---------- 4. Accent ---------------------------------------------
     #FF7A33 : une braise. Trois emplois AUTORISÉS, aucun autre —
     (a) marqueur de page active, (b) action primaire unique de l'écran,
     (c) anneau de focus (variante -hi).
     JAMAIS : icônes de navigation, titres, liens au repos, campagnes,
     états système, badges de compteur.                            */
  --nut-accent:          #FF7A33;  /*  7,13:1 / bg — passe AA en texte */
  --nut-accent-hi:       #FFB067;  /* 10,30:1 / bg — focus             */
  --nut-accent-dim:      #B54E17;  /* pressé, désactivé                */

  /* ---------- 4b. L'ACCENT DE L'ADMINISTRATION -----------------------
     L'ORANGE EST LA COULEUR DU PRODUIT, ce bleu celle du TERRITOIRE
     D'ADMINISTRATION. Arbitrage de Renaud, 19/08/2026 : « lorsque cette
     couleur apparaît, on sait immédiatement où l'on se trouve ». Les
     seuls à la voir sont ceux qui administrent — un joueur n'entre
     jamais dans ces écrans.

     IL NE S'EMPLOIE PAS DIRECTEMENT. Aucune feuille n'écrit
     « var(--nut-admin) » sur un élément : la coquille redéfinit
     « --nut-accent » sur le contenu des écrans d'administration, et tout
     ce qui vit dedans hérite. Une règle, pas une repeinte — et l'écran
     d'administration qu'on écrira dans six mois sera bleu sans que
     personne y pense.

     POURQUOI CE N'EST PAS « --nut-info », DONT IL PARTAGE LA VALEUR.
     Parce que --nut-info sert déjà dans treize endroits vus par un
     joueur ordinaire — la pastille PJ de l'arbre, le bandeau
     d'observation, le badge « jeune » d'une fiche, la jauge du profil.
     Souder les deux, c'est se condamner à repeindre l'arbre
     généalogique le jour où l'on ajuste l'administration. Ils partent
     de la même valeur ; ils doivent pouvoir diverger.

     LA PORTE RESTE ORANGE. L'entrée « Administration » de la barre
     latérale garde le marqueur d'accent produit : la barre est un seul
     vocabulaire, tous ses marqueurs se ressemblent. Le bleu signale le
     territoire, pas le lien qui y mène.                            */
  --nut-admin:           #8FB4C9;  /*  8,56:1 / bg — mesuré, > l'orange */

  /* ---------- 5. États système ---------------------------------------
     Ne portent JAMAIS l'orange : sinon l'accent perd son sens.
     Toujours accompagnés d'une icône et d'un texte.               */
  --nut-ok:              #5FD1A0;  /*  9,80:1 / bg */
  --nut-warn:            #F2637A;  /*  6,04:1 / bg */
  --nut-info:            #8FB4C9;  /*  7,10:1 / bg */

  /* ---------- 6. Teintes de campagne ---------------------------------
     8 teintes désaturées et isoluminantes. Vivent UNIQUEMENT dans le
     jeton, jamais en texte, jamais seules (toujours + monogramme
     + orientation de l'angle coupé).                              */
  --nut-camp-1: #7FA6C4;  /* ardoise        */
  --nut-camp-2: #86B071;  /* mousse         */
  --nut-camp-3: #B08BC4;  /* prune          */
  --nut-camp-4: #C7AE72;  /* sable          */
  --nut-camp-5: #6FB3AD;  /* cyan-gris      */
  --nut-camp-6: #C489A0;  /* rose-poussière */
  --nut-camp-7: #8B93D4;  /* indigo         */
  --nut-camp-8: #A3AE6B;  /* olive          */

  /* ---------- 6b. Couche « habillage » (sprint 6) --------------------
     DEUX jetons, et seulement deux — la contrainte du brief. Reserves a
     la couche theme : filets, blasons, grain. Jamais du texte, jamais
     une couleur de donnee. Un ecran en epure ne les lit pas.          */
  --nut-ornement: #6B5A3E;  /* filets, encadrements, blason */
  --nut-grain:    #1A1D18;  /* grain de fond, 2 % sur --nut-bg */

  /* ---------- 7. Typographie -----------------------------------------
     Une seule superfamille, trois rôles. IBM Plex, OFL 1.1,
     auto-hébergée en production (woff2, sous-ensemble latin + FR).  */
  --nut-font-title: "IBM Plex Sans Condensed", "IBM Plex Sans", system-ui, sans-serif;
  --nut-font-body:  "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --nut-font-data:  "IBM Plex Mono", ui-monospace, monospace;

  --nut-size-1:  12px;  /* méta, compteurs        */
  --nut-size-2:  13px;  /* libellés de champ      */
  --nut-size-3:  14px;  /* base du CHROME         */
  --nut-size-4:  16px;  /* base du CORPS de texte */
  --nut-size-5:  18px;
  --nut-size-6:  22px;
  --nut-size-7:  28px;
  --nut-size-8:  36px;

  --nut-weight-regular: 400;
  --nut-weight-medium:  500;
  --nut-weight-semi:    600;

  --nut-lh-tight: 1.2;   /* titres            */
  --nut-lh-ui:    1.35;  /* interface dense   */
  --nut-lh-text:  1.6;   /* corps de lecture  */

  --nut-track-caps: 0.08em; /* étiquettes de bloc en capitales */

  /* ---------- 8. Espacement (base 4) --------------------------------- */
  --nut-sp-05: 2px;
  --nut-sp-1:  4px;
  --nut-sp-15: 6px;
  --nut-sp-2:  8px;
  --nut-sp-3:  12px;
  --nut-sp-4:  16px;
  --nut-sp-6:  24px;
  --nut-sp-8:  32px;
  --nut-sp-12: 48px;

  /* ---------- 9. Rayons ---------------------------------------------- */
  --nut-radius-1: 3px;    /* champs, entrées de menu */
  --nut-radius-2: 5px;    /* cartes                  */
  --nut-radius-3: 8px;    /* panneaux                */
  --nut-radius-pill: 999px;

  /* ---------- 10. Élévation ------------------------------------------
     Pas d'ombre dans le chrome : l'élévation passe par la surface.
     L'ombre est réservée aux calques qui flottent VRAIMENT
     (popover de campagne repliée, tiroir mobile).                  */
  --nut-elev-0: none;
  --nut-elev-1: 0 2px 8px rgba(0,0,0,.45);
  --nut-elev-2: 0 12px 32px rgba(0,0,0,.55);

  /* ---------- 11. Géométrie de la coquille --------------------------- */
  --nut-sidebar:           240px;
  /* Le jeton « sidebar-collapsed » a ete retire : le repli de la barre laterale
     n'est pas construit, sur decision de Renaud du 07/08/2026 (registre du
     WORKFLOW.md). Un jeton declare et jamais employe se fait reprendre un jour
     par quelqu'un qui croit qu'il sert. */
  --nut-topbar:            48px;
  --nut-read-max:          880px;  /* largeur de lecture plafonnée */
  --nut-target-touch:      44px;
  --nut-target-pointer:    40px;

  /* ---------- 12. Mouvement -----------------------------------------
     Un moment orchestré, pas dix micro-effets.                     */
  --nut-dur-1: 90ms;    /* retour d'appui            */
  --nut-dur-2: 140ms;   /* dépli de section          */
  --nut-dur-3: 220ms;   /* repli de sidebar, tiroir  */
  --nut-ease:  cubic-bezier(.32, .72, 0, 1);

  /* ---------- 13. Focus ---------------------------------------------
     Deux tons : anneau clair + décalage sombre, pour rester
     perceptible sur toutes les surfaces.                           */
  --nut-focus-ring:   2px solid var(--nut-accent-hi);
  --nut-focus-offset: 2px;
}

/* Un thème clair reste possible : seuls les blocs 1 à 5 changent.
   Aucune règle du produit ne référence une valeur brute, donc la
   bascule se fait ici et nulle part ailleurs.
   [data-theme="clair"] { --nut-bg: #FBFCFA; ... } */

@media (prefers-reduced-motion: reduce) {
  :root { --nut-dur-1: 0ms; --nut-dur-2: 0ms; --nut-dur-3: 0ms; }
}

/* ============================================================
   Ajout du sprint 2 — couleurs de rôle.
   Repris tel quel de design/sprint-2/tokens-sprint2.css.
   Absentes du sprint 1 : l'administration n'existait pas encore.
   ============================================================ */
:root {
  --nut-role-admin:  #E5606C;  /* 5,58:1 / bg — role supreme, unique */
  --nut-role-modo:   #A379DA;  /* 5,66:1 / bg */
  --nut-role-membre: #52C285;  /* 8,45:1 / bg */
}
/* Le rouge de l'Admin reste proche de --nut-warn en teinte pure. La
   distinction ne repose donc PAS sur la couleur seule : un badge de rôle porte
   toujours son libellé et une puce carrée (grammaire du jeton de campagne),
   la ou un etat systeme porte une icone et un bandeau. Les deux ne se
   cotoient jamais dans le meme contexte. */


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

/* L'ATTRIBUT « hidden » DOIT GAGNER, TOUJOURS.
   Le navigateur ne lui donne qu'un « display: none » de feuille par defaut :
   la moindre regle qui pose un display — flex, block, grid — l'emporte, et
   l'element reste visible alors que le code dit qu'il est cache. Le piege est
   silencieux : la propriete hidden de l'element renvoie bien true, les tests
   passent,
   et l'ecran affiche autre chose.
   Constate le 07/08/2026 sur trois elements a la fois : l'icone de lecture
   d'une page, la carte des modeles, et le voile d'un modal — celui-ci serait
   reste ouvert en permanence. Une ligne ici ferme la famille entiere. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--nut-sp-12) var(--nut-sp-4);
  background: var(--nut-bg);
  color: var(--nut-text);
  font-family: var(--nut-font-body);
  font-size: var(--nut-size-3);
  line-height: var(--nut-lh-ui);
}

/* Le focus doit rester perceptible sur toutes les surfaces : anneau clair
   (--nut-accent-hi, 10,30:1) plutot que l'accent vif, moins lisible. */
:focus-visible {
  outline: var(--nut-focus-ring);
  outline-offset: var(--nut-focus-offset);
  border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none; }

.colonne { width: 100%; max-width: 392px; }

/* --- Marque --- */
.marque { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.jeton {
  width: 22px; height: 22px; flex: none;
  background: var(--nut-accent);
  /* Angle coupe en haut a gauche : la grammaire du jeton de campagne. */
  clip-path: polygon(6px 0, 100% 0, 100% 100%, 0 100%, 0 6px);
}
h1 {
  margin: 0;
  font-family: var(--nut-font-title);
  font-weight: var(--nut-weight-semi);
  font-size: var(--nut-size-7);
  letter-spacing: .02em;
  line-height: var(--nut-lh-tight);
}
.sous-titre {
  margin: 0 0 28px;
  color: var(--nut-text-muted);
  font-size: var(--nut-size-3);
  line-height: var(--nut-lh-text);
}

/* --- Carte --- */
.carte {
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-3);
  padding: 22px;
}

label {
  display: block;
  margin-bottom: var(--nut-sp-15);
  font-size: var(--nut-size-2);
  font-weight: var(--nut-weight-medium);
  color: var(--nut-text-muted);
}
/* Un label qui suit un champ precedent doit respirer. Trois selecteurs car
   entre deux champs on trouve, selon les cas, rien, un bloc d'erreur, ou un
   texte d'aide. */
.champ + label,
.erreur-champ + label,
.aide + label { margin-top: var(--nut-sp-4); }

input[type="email"], input[type="password"], input[type="text"] {
  display: block; width: 100%; height: 40px;
  padding: 0 var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text);
  font-family: inherit;
  font-size: var(--nut-size-3);
}
input[type="password"] {
  font-family: var(--nut-font-data);
  letter-spacing: .06em;
}
input:disabled { opacity: .55; cursor: not-allowed; }

/* Champ en erreur : bordure + lisere gauche + icone + texte.
   Quatre signaux, dont un seul est la couleur. */
input[aria-invalid="true"] {
  border-color: var(--nut-warn);
  border-left: 3px solid var(--nut-warn);
}
.erreur-champ {
  display: flex; align-items: center; gap: var(--nut-sp-15);
  margin-top: var(--nut-sp-15);
  color: var(--nut-warn);
  font-size: var(--nut-size-2);
}
.erreur-champ[hidden] { display: none; }

/* --- Bandeau d'alerte --- */
.bandeau {
  display: flex; gap: 9px; align-items: flex-start;
  background: var(--nut-surface-raised);
  border-left: 3px solid var(--nut-warn);
  border-radius: var(--nut-radius-1);
  padding: 10px var(--nut-sp-3);
  margin-bottom: var(--nut-sp-4);
  font-size: var(--nut-size-2);
  line-height: var(--nut-lh-text);
}
.bandeau[hidden] { display: none; }
.bandeau.succes { border-left-color: var(--nut-ok); }
.bandeau svg { flex: none; margin-top: 1px; }
.bandeau.alerte svg { color: var(--nut-warn); }
.bandeau.succes svg { color: var(--nut-ok); }

/* --- Bouton primaire : LE seul aplat orange de l'ecran ---
   IL PREND SA LARGEUR NATURELLE. C'etait l'inverse : largeur 100% et marge
   haute de 22 px, dessinees pour le bouton unique d'un formulaire de
   connexion. Partout ailleurs — pied de modal, barre d'outils, rangee
   d'actions — le bouton mangeait la ligne entiere et flottait 22 px sous ses
   voisins. Le defaut s'est produit QUATRE FOIS en trois versions, et chaque
   fois j'ai allonge une liste d'exceptions au lieu de retourner le defaut.
   C'est fait : la pleine largeur est desormais demandee explicitement, par la
   regle ci-dessous, pour les seuls formulaires d'authentification. */
button.primaire {
  display: inline-flex; align-items: center; justify-content: center;
  width: auto; height: 44px;
  background: var(--nut-accent);
  color: var(--nut-text-on-accent);
  border: 0; border-radius: var(--nut-radius-1);
  font-family: inherit;
  font-size: var(--nut-size-3);
  font-weight: var(--nut-weight-semi);
  cursor: pointer;
  transition: background var(--nut-dur-1) linear;
}
/* LE SEUL ENDROIT OU LA PLEINE LARGEUR A UN SENS : le bouton unique d'un
   formulaire d'authentification, dans une carte etroite et centree. Il n'y a
   rien d'autre sur la ligne, et une cible large se vise mieux au pouce. */
.colonne .carte form > button.primaire {
  display: block; width: 100%; margin-top: 22px;
}

button.primaire:hover:not(:disabled) { background: var(--nut-accent-hi); }
button.primaire:disabled {
  background: var(--nut-surface-raised);
  color: var(--nut-text-muted);
  cursor: progress;
}

/* Trois carres qui pulsent, pas un rond qui tourne. */
.points { display: inline-flex; gap: var(--nut-sp-1); margin-right: var(--nut-sp-2); vertical-align: middle; }
.points i { width: 4px; height: 4px; background: currentColor; animation: pulse 1.05s ease-in-out infinite; }
.points i:nth-child(2) { animation-delay: .16s; }
.points i:nth-child(3) { animation-delay: .32s; }
@keyframes pulse { 0%, 100% { opacity: .25 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) {
  .points i { animation: none; opacity: .6; }
}

/* --- Pied de carte --- */
.separateur {
  margin-top: 18px; padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
}
a.discret {
  color: var(--nut-text-muted);
  font-size: var(--nut-size-2);
  text-decoration: none;
  border-bottom: 1px solid var(--nut-line-strong);
}
a.discret:hover { color: var(--nut-text); }

/* Trois elements : ce qu'est le produit, les mentions legales, la version.
   La version est en fin de ligne, en chasse fixe : c'est la premiere chose
   qu'on demande quand quelqu'un signale un defaut. */
.pied {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  margin-top: var(--nut-sp-8);
  padding-top: var(--nut-sp-3);
  border-top: 1px solid var(--nut-line);
  color: var(--nut-text-faint);
  font-size: var(--nut-size-1);
}
.pied .version { margin-left: auto; font-family: var(--nut-font-data); }
.pied a.discret { font-size: var(--nut-size-1); }

/* Texte d'aide sous un champ. Defini ici plutot qu'en attribut style : une
   CSP avec style-src 'self' bloque AUSSI les styles en ligne, y compris les
   attributs. C'est ce qui remontait dans la console du navigateur. */
.aide {
  margin: var(--nut-sp-15) 0 0;
  color: var(--nut-text-muted);
  font-size: var(--nut-size-1);
  line-height: var(--nut-lh-text);
}

/* Rappel de l'adresse a laquelle l'invitation a ete envoyee. Ce n'est pas un
   champ : l'adresse vient du lien, elle n'est pas modifiable. */
.destinataire {
  margin: 0 0 var(--nut-sp-4);
  padding: 10px var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted);
  font-size: var(--nut-size-2);
}
.destinataire strong {
  display: block;
  margin-top: 2px;
  color: var(--nut-text);
  font-family: var(--nut-font-data);
  font-weight: var(--nut-weight-medium);
  word-break: break-all;
}

/* --- LA LIGNE D'INVITATION RECUE — un composant, deux ecrans -------------
 *
 * Elle s'affiche a l'identique sur l'ACCUEIL et dans le PROFIL, parce que
 * c'est le meme objet : « on t'invite, tu acceptes ou tu refuses ». Deux
 * endroits, une seule declaration — je l'avais d'abord ecrite dans chacun des
 * deux fichiers, et le garde-fou de collision l'a refuse a raison : deux
 * copies d'une meme regle divergent au premier ajustement, et c'est celui qui
 * regarde l'un des deux ecrans qui decouvre l'ecart.
 *
 * ELLE EST DONC DANS LE SOCLE, ou ce qui est declare EST FAIT pour etre repris.
 */
.ligne-invitation {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  padding: 9px 0;
  border-bottom: 1px solid var(--nut-line);
}
.ligne-invitation:last-child { border-bottom: 0; }
.ligne-invitation .quoi { flex: 1 1 0; min-width: 0; }
.ligne-invitation .par { color: var(--nut-text-muted); }
.ligne-invitation button {
  flex: none; padding: 3px 10px;
  background: none; border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  color: var(--nut-text-muted); font: inherit; font-size: 12px; cursor: pointer;
}
.ligne-invitation button:hover { color: var(--nut-text); border-color: var(--nut-text-faint); }
.ligne-invitation button.accepter { margin-left: auto; }
.ligne-invitation button.accepter:hover { color: var(--nut-accent); border-color: var(--nut-accent); }
.ligne-invitation button.refuser:hover { color: var(--nut-warn); border-color: var(--nut-warn); }

/* --- LE VOILE, LE MODAL, LE TITRE DE BLOC : le socle des surimpressions ---
 *
 * Ces regles vivaient dans campagne.js — le premier ecran a avoir eu besoin
 * d'un modal — et QUATRE autres ecrans en heritaient : Lore, Configuration,
 * Page et Arbre pour .modal, plus Admin et la Coquille pour .voile. Le modal
 * de l'ecran Lore tenait donc son apparence d'un fichier nomme « campagne ».
 *
 * Rien ne cassait, puisque la feuille est concatenee en une seule ; mais le
 * couplage etait invisible, et il aurait rendu impossible de servir une
 * feuille par ecran — ce qui est au programme du chantier mobile. Deplacees
 * dans le socle le 13/08/2026 : ici, ce qui est declare EST FAIT pour etre
 * repris. Le rendu ne change pas d'un pixel.
 */
/* Le voile assombrit l'ecran de depart sans le faire disparaitre : on comprend
   qu'on est PAR-DESSUS quelque chose, et que fermer y ramene.
   LE VOILE EST AU-DESSUS DE TOUT CE QU'UNE PAGE PEUT DESSINER (15/08/2026).
   A 60, il passait SOUS les boutons de zoom et la legende de l'arbre, qui
   montent a 70 : le modal de configuration de campagne s'ouvrait et l'arbre
   continuait de flotter par-dessus. Une page compose ses propres plans dans
   sa zone ; le chrome de l'application, lui, les coiffe tous — d'ou le saut a
   200, franc, pour qu'aucun futur plan de page ne vienne s'y glisser. */
.voile {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: flex-start; justify-content: center;
  padding: var(--nut-sp-8) var(--nut-sp-4);
  background: rgba(10, 12, 10, .72);
  overflow-y: auto;
}
.modal {
  width: 100%; max-width: 560px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-3);
  box-shadow: var(--nut-elev-2);
}

.modal label, .etiquette-bloc {
  display: block; margin-bottom: 6px;
  font-size: 13px; font-weight: var(--nut-weight-medium);
  color: var(--nut-text-muted);
}
.etiquette-bloc { margin-bottom: 8px; }
.requis { color: var(--nut-warn); }
.modal .champ {
  display: block; width: 100%; height: 40px; padding: 0 12px;
  margin-bottom: 6px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: 14px;
}
.modal .aide { margin: 0 0 20px; font-size: 13px; line-height: 1.5; color: var(--nut-text-faint); }
.modal .aide.serree { margin: 6px 0 0; }

.titre-bloc {
  margin: 0 0 6px; font-size: 11px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}

/* --- L'habillage de campagne : deux primitives de theme -------------------
 *
 * Le grain de fond et le filet d'ornement du sprint 6. Ils vivaient dans
 * maison.js — le premier ecran a en avoir eu besoin — et l'ecran Arbre les a
 * ensuite empruntes (.ab-couche.nut-grain, .ab-couche .nut-filet).
 * L'emprunt marchait parce que la feuille est concatenee en une seule ;
 * il serait tombe en silence le jour ou l'on servira une feuille par ecran,
 * ce qui est au programme du chantier mobile.
 *
 * Ils sont donc ici, dans le socle, ou ce qui est declare EST FAIT pour etre
 * repris. Deplaces le 13/08/2026 ; le rendu ne change pas d'un pixel.
 *
 * Les regles qui composent AVEC ces classes (.mn-couche.nut-grain,
 * .ab-couche.nut-grain) restent chez leur ecran : elles parlent de la
 * maison ou de l'arbre, pas du theme.
 */
/* ==========================================================================
   LES CONVENTIONS TACTILES (16/08/2026, lot 4 du chantier mobile)
   Transcription de design/LISEZMOI-MOBILE-PHASE-1.md, sections 2 a 6.

   ELLES VIVENT ICI, dans la feuille commune, parce qu'elles ne parlent
   d'AUCUN ecran en particulier : un modal est un modal, une ligne de liste
   est une ligne de liste. Une convention posee chez un ecran finit empruntee
   par le suivant — c'est ce qui est arrive au grain et au filet, qui ont
   voyage de maison.js vers l'arbre avant d'atterrir ici.

   CE QUI N'EST PAS ECRIT ICI, ET POURQUOI. Le bouton d'information qui
   remplace le survol et l'indicateur « Enregistre » permanent appartiennent a
   l'annexe eux aussi, mais ils n'ont aujourd'hui aucun utilisateur : leurs
   vrais cas sont la fiche de maison et le profil, qui viennent apres. La
   regle du projet est explicite (voir tokens.js, bloc 11) — un composant
   declare et jamais employe se fait reprendre un jour par quelqu'un qui croit
   qu'il sert. Ils seront livres avec leur premier usage.
   ========================================================================== */

@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {

  /* --- LE MODAL DEVIENT UNE FEUILLE MONTANTE (annexe, section 3) ---------
     Un modal centre a marges suppose de la place autour de lui. A 353 px il
     n'y en a pas : il occupe l'ecran sans en avoir l'air, et son pied finit
     hors de portee du pouce. La feuille s'ancre en bas — la ou est la main —
     et s'arrete a 92 % de la hauteur pour que la page reste visible derriere
     elle. C'est ce reste de page qui dit qu'on est PAR-DESSUS et non AILLEURS. */
  .voile { align-items: flex-end; padding: 0; }
  .modal {
    max-width: none; max-height: 92dvh;
    display: flex; flex-direction: column;
    border: 0; border-top: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-3) var(--nut-radius-3) 0 0;
  }
  /* LA POIGNEE. Elle n'est pas decorative : c'est elle qui annonce que le
     panneau vient du bas, avant meme qu'on ait lu son titre. */
  .modal::before {
    content: ""; flex: none;
    width: 36px; height: 4px; margin: 8px auto 0;
    background: var(--nut-line-strong); border-radius: var(--nut-radius-pill);
  }


  /* --- LE FORMULAIRE (annexe, section 6) --------------------------------
     48 px de haut, et 16 px de corps. Le second n'est pas une preference de
     lisibilite : SOUS 16 px, iOS zoome de force sur le champ actif et ne
     revient jamais tout a fait — la page reste decadree pour le reste de la
     saisie. */
  input[type="email"], input[type="password"], input[type="text"],
  input[type="search"], input[type="number"], select, textarea, .modal .champ {
    height: 48px; font-size: 16px;
  }
  textarea { height: auto; min-height: 96px; }

  /* --- LES LIENS DISCRETS (annexe, section 2) ---------------------------
     « Mot de passe oublié ? », « Saisir un code d'invitation », « Mentions
     légales » : des liens de 18 px de haut, lisibles a la souris et
     inatteignables au pouce. Le harnais les comptait sur QUATRE ecrans.
     La cible se mesure sur la ZONE TAPPABLE, pas sur le dessin : le texte
     garde sa taille et sa discretion, seule la zone grandit. C'est pour ca
     que la regle pose « min-height » sur un inline-flex et non « height » —
     rien ne change a l'oeil. */
  a.discret, .pied a.pied-lien {
    display: inline-flex; align-items: center;
    min-height: var(--nut-target-touch);
  }
  .pied { align-items: center; flex-wrap: wrap; }

  /* --- LA LIGNE DE LISTE (annexe, section 5) ----------------------------
     44 px, ou 56 quand elle porte deux lignes de texte. Le titre prend la
     place et s'arrete en ellipse ; l'action garde ses 44 px au bord droit.
     JAMAIS DE RETOUR A LA LIGNE DANS UNE LISTE : le titre complet se lit
     dans la page qu'elle ouvre. */
  .ligne-invitation {
    min-height: var(--nut-target-touch);
    padding: var(--nut-sp-2) var(--nut-sp-4);
  }
  .ligne-invitation .quoi {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ligne-invitation button {
    min-width: var(--nut-target-touch); min-height: var(--nut-target-touch);
  }
}

.nut-grain {
  background-color: var(--nut-bg);
  background-image:
    repeating-linear-gradient(0deg, var(--nut-grain) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--nut-grain) 0 1px, transparent 1px 3px);
}
.nut-filet {
  border: 1px solid var(--nut-ornement);
  box-shadow: inset 0 0 0 3px var(--nut-bg), inset 0 0 0 4px rgba(107, 90, 62, .45);
}


body.app {
  display: grid;
  grid-template-columns: var(--nut-sidebar) 1fr;
  align-items: stretch;
  min-height: 100vh;
  padding: 0;
}
/* PAS D'ETAT « REPLIE ». Le prototype du sprint 1 en montre un — mode icones a
   60 px, infobulles, popover de sections — et il ne sera pas construit :
   decision de Renaud du 07/08/2026, inscrite au registre du WORKFLOW.md.
   La largeur de la barre est donc fixe. */

/* --- Barre laterale --- */
.barre {
  display: flex; flex-direction: column;
  background: var(--nut-surface);
  border-right: 1px solid var(--nut-line);
  padding: 0 0 10px;
  position: sticky; top: 0; height: 100vh;
  overflow: hidden;
}
/* L'EN-TETE EST UNE RANGEE, PAS UN LOGO POSE.
   Meme hauteur que la barre du haut (48 px) et meme bordure basse : une seule
   ligne traverse toute l'application et pose la composition. Sans elle, la
   marque flotte et l'ecran n'a pas de cadre — c'est ce qui manquait. */
.barre .marque-barre {
  height: var(--nut-topbar); flex: none;
  display: flex; align-items: center; gap: 9px;
  padding: 0 14px;
  border-bottom: 1px solid var(--nut-line);
}
/* Les blocs de navigation ont leur propre gouttiere, puisque la barre n'en a
   plus : c'est la rangee d'en-tete qui doit toucher les bords. */
.barre .bloc { padding: 10px var(--nut-sp-2) 4px; }
.barre .defilant {
  flex: 1 1 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: 6px var(--nut-sp-2) 0;
  scrollbar-width: thin;
}
.barre .defilant .bloc { padding: 0; }
.barre .marque-barre .nom {
  font-family: var(--nut-font-title);
  font-weight: var(--nut-weight-semi);
  font-size: var(--nut-size-4);
  letter-spacing: .02em;
}
.barre .marque-barre .replier { margin-left: auto; }

.bloc { display: flex; flex-direction: column; gap: 2px; }
/* Le bloc du compte tient le bas de la barre, separe par un filet — c'est ce
   qui le detache de la liste des campagnes, qui defile au-dessus. */
.barre .bloc.compte {
  flex: none; margin-top: 0;
  padding: var(--nut-sp-2) var(--nut-sp-2) 0;
  border-top: 1px solid var(--nut-line);
}
/* Les entrees du compte sont plus hautes et plus discretes que celles du haut :
   on y va rarement, elles ne doivent pas concurrencer les campagnes. */
.barre .bloc.compte a, .barre .bloc.compte button.nav {
  height: 34px; color: var(--nut-text-muted); font-weight: var(--nut-weight-medium);
}
.barre .bloc.compte a[aria-current="page"] { color: var(--nut-text); }
/* Pas de compteur : ajoute en 0.25.0, retire le meme jour sur demande de
   Renaud. On ne compte pas ses campagnes, on les reconnait. */
.bloc-titre {
  padding: var(--nut-sp-2) var(--nut-sp-2) var(--nut-sp-15);
  color: var(--nut-text-faint); font-size: 11px;
  font-weight: var(--nut-weight-semi);
  text-transform: uppercase; letter-spacing: var(--nut-track-caps);
}

/* Une entree de navigation. Meme geometrie qu'on soit un lien ou un bouton :
   sinon la barre « saute » d'un pixel d'une ligne a l'autre. */
.barre a, .barre button.nav {
  display: flex; align-items: center; gap: 9px;
  height: 32px;
  padding: 0 var(--nut-sp-2);
  border: 0; border-radius: var(--nut-radius-1);
  background: transparent;
  color: var(--nut-text);
  font: inherit; font-size: 14px;
  text-decoration: none; text-align: left;
  cursor: pointer;
  white-space: nowrap;
}
/* UNE ENTREE INACTIVE EST ATTENUEE, l'active est pleine. Toutes etaient en
   pleine intensite : plus rien ne hierarchisait la colonne, et l'oeil devait
   chercher le fond releve pour savoir ou il etait. Le prototype du sprint 1
   distingue les deux : texte attenue contre texte plein. */
.barre a, .barre button.nav { color: var(--nut-text-muted); }
.barre a:hover, .barre button.nav:hover {
  background: var(--nut-surface-raised); color: var(--nut-text);
}
.barre svg { color: var(--nut-text-faint); flex: none; }
/* PAGE COURANTE : FOND RELEVE ET MARQUEUR ORANGE.
 *
 * Le marqueur avait ete retire, avec ce commentaire :
 *
 *   « La maquette n'en a pas — aucune des 37 regles extraites ne porte de
 *     box-shadow. C'etait une invention. »
 *
 * LE CONSTAT ETAIT FAUX. Le prototype du sprint 1 pose
 * un box-shadow interieur de 2 px a l'accent sur l'entree courante, aux
 * lignes 175, 216, 236 et 255. Et sa charte nomme ce marqueur LE PREMIER des
 * trois emplois autorises de l'accent :
 *
 *   « L'orange a trois emplois, pas quatre. Marqueur de page active, action
 *     primaire unique de l'ecran, anneau de focus. Jamais sur une icone de
 *     navigation, un titre, un badge, un etat systeme. »
 *
 * J'avais regarde les regles EXTRAITES par notre outil, pas le prototype —
 * la meme erreur que brief-contre-prototype, sous une autre forme. Rendu le
 * 07/08/2026. */
.barre a[aria-current="page"] {
  background: var(--nut-surface-raised);
  box-shadow: inset 2px 0 0 0 var(--nut-accent);
  color: var(--nut-text);
  font-weight: var(--nut-weight-semi);
}
.barre a[aria-current="page"] svg { color: var(--nut-text); }

button.replier {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex: none;
  background: none; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
button.replier:hover { background: var(--nut-surface-raised); color: var(--nut-text); }

/* Le filtre de la barre : meme geometrie que les entrees, pour que la colonne
   garde son rythme. */
.filtre-camp { padding: 2px var(--nut-sp-2) 6px; }
.filtre-camp input {
  width: 100%; height: 30px; padding: 0 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: 13px;
}
.filtre-camp input:focus { outline: none; border-color: var(--nut-line-strong); }

.vide-campagnes {
  padding: var(--nut-sp-2);
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
  line-height: var(--nut-lh-text);
}

/* --- Jeton de campagne ---
   Carre teinte portant un monogramme, angle coupe en haut a gauche. Trois
   signaux independants distinguent deux campagnes : les initiales, la teinte,
   et l'orientation de l'angle. La distinction tient donc sans la couleur, et
   tient barre repliee quand le titre a disparu. */
.jeton-camp {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none;
  border-radius: 1px;
  clip-path: polygon(6px 0, 100% 0, 100% 100%, 0 100%, 0 6px);
  color: rgb(16, 19, 15);
  /* EN FONTE DE DONNEES, pas en titre : la charte du sprint 1 reserve le mono
     aux identifiants, et un monogramme en est un. Corrige le 07/08/2026. */
  font-family: var(--nut-font-data);
  font-size: 10px; font-weight: var(--nut-weight-medium);
  letter-spacing: .02em;
}
/* Campagne courante : l'angle bascule en bas a gauche. Un signal de FORME,
   lisible meme quand la couleur ne l'est pas. */
.jeton-camp.courante { clip-path: polygon(0 0, 100% 0, 100% 100%, 6px 100%, 0 calc(100% - 6px)); }
.jeton-camp.t1 { background: var(--nut-camp-1); }
.jeton-camp.t2 { background: var(--nut-camp-2); }
.jeton-camp.t3 { background: var(--nut-camp-3); }
.jeton-camp.t4 { background: var(--nut-camp-4); }
.jeton-camp.t5 { background: var(--nut-camp-5); }
.jeton-camp.t6 { background: var(--nut-camp-6); }
.jeton-camp.t7 { background: var(--nut-camp-7); }
.jeton-camp.t8 { background: var(--nut-camp-8); }

/* --- Une campagne est un DOSSIER, pas un lien ----------------------------
   Releve du prototype du sprint 3. La campagne porte une ligne de tete de
   34 px sur fond releve ; ses categories et ses pages vivent dessous. C'est
   la piece dont depend toute la navigation : sans elle, on ne voit jamais le
   contenu d'une partie depuis la barre. */
.camp-bloc { margin-bottom: 10px; }

.camp-tete {
  position: relative;
  display: flex; align-items: center; gap: var(--nut-sp-2);
  height: 34px; padding: 0 var(--nut-sp-1) 0 var(--nut-sp-2);
  border-radius: var(--nut-radius-1);
  background: transparent;
}
/* SEULE la campagne ouverte porte le fond releve. Je l'avais mis sur toutes :
   les trois campagnes paraissaient actives en meme temps. */
.camp-bloc.courante .camp-tete { background: var(--nut-surface-raised); }
.camp-tete:hover { background: var(--nut-surface-raised); }
/* Une campagne repliee montre sa tete, et rien d'autre. */
.camp-bloc.repliee > div[id^="camp-"] { display: none; }
/* La tete de campagne deroge a la geometrie commune : elle n'est pas une
   entree de navigation parmi d'autres, elle coiffe un groupe. */
.barre .camp-tete button.camp {
  flex: 1 1 0; min-width: 0; height: auto; padding: 0;
  display: flex; align-items: center; gap: 9px;
  border: 0; background: none; color: var(--nut-text);
  font: inherit; font-size: 14px; text-align: left; cursor: pointer;
}
.barre .camp-tete button.camp:hover { background: none; }
.barre .camp .etiquette {
  min-width: 0; font-weight: var(--nut-weight-medium);
  /* Deux lignes maximum, comme l'exige le sprint 1 : un titre long ne doit ni
     casser la barre, ni disparaitre entierement derriere des points. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.25; white-space: normal;
}

/* LA VUE D'ENSEMBLE : discrete au repos, franche au survol et au focus. Elle
   ne dispute pas le titre — c'est lui qu'on lit — mais elle ne se cache pas
   non plus : un controle revele au survol n'existe pas au doigt. */
/* ⚠ 24 px ET PAS 28. A 28, « Les Cendres de Valmyr » — un titre de longueur
   ordinaire — passait a DEUX lignes, et la barre s'allongeait d'un cran pour
   chaque campagne. Deux lignes ne sont pas un defaut (la regle du sprint 1 les
   autorise plutot que des points de suspension), mais les payer pour quatre
   pixels de bouton, si.

   CE QUI RESTE, ET QU'AUCUNE TAILLE NE SAUVE : quand une demande d'acces
   attend, la pastille orange occupe la meme rangee, et ce titre passe a deux
   lignes quelle que soit la taille du bouton — mesure a 28, 24, 22, 20 et 18.
   Ce n'est donc pas le bouton qu'il faudrait retailler mais la rangee entiere,
   et cet etat est transitoire : la demande se traite. */
.camp-tete .camp-vue {
  flex: none; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint);
}
.camp-tete .camp-vue:hover { background: var(--nut-surface); color: var(--nut-text); }
.camp-tete .camp-vue[aria-current="page"] { color: var(--nut-accent); }
.camp-tete .camp-vue svg { width: 14px; height: 14px; }
/* ⚠ AU DOIGT, LA LARGEUR AUSSI. La regle commune de « .camp-tete > * » ne pose
   qu'un « min-height » : la cible sortait a 28 x 44, c'est-a-dire conforme sur
   un axe et fausse sur l'autre. Une regle qui ne vise qu'une dimension ne
   garantit pas une cible. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  .camp-tete .camp-vue { min-width: var(--nut-target-touch); }
}

/* Demandes d'acces en attente. Orange : c'est un appel a agir, l'un des trois
   emplois autorises de l'accent. Visible du seul MJ — c'est lui qui tranche. */
.camp-tete .demandes {
  flex: none; min-width: 18px; height: 18px; padding: 0 5px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--nut-radius-pill);
  background: var(--nut-accent); color: var(--nut-text-on-accent);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  font-weight: var(--nut-weight-semi);
  text-decoration: none;
}



/* LE « + » A DISPARU DE CETTE BARRE (13/08/2026), ET SON HISTOIRE VAUT D'ETRE
   GARDEE — c'est la meme question tranchee deux fois, dans deux sens.

   Le 07/08, il portait deux entrees : « Nouvelle page » et « Nouvelle
   categorie ». La seconde est partie dans le modal de configuration — creer
   une categorie ne mene nulle part, ca n'a de sens qu'en train de ranger. La
   regle posee alors : LE « + » SERT A ECRIRE, LE MODAL SERT A RANGER.

   Le 13/08, le « + » part a son tour, et pour la suite de la meme raison :
   « ecrire » et « ranger » n'etaient pas deux activites, c'etait le meme
   objet administre a deux endroits. On creait ici, on renommait, publiait,
   deplacait et supprimait la-bas. Deux endroits pour un objet, c'est deux
   endroits ou chercher — et celui qui cherche ne sait pas qu'il y en a deux.

   « L'administration des pages se trouve UNIQUEMENT dans la roue dentee.
   Meme leur ajout. » Le modal a son bouton « Nouvelle page » depuis le
   sprint 3 ; il devient le seul. */

/* --- Lignes de l'arborescence ---
   Une gouttiere de 18 px a gauche : elle porte le chevron d'une categorie, et
   reste vide pour une page. Sans elle, les libelles ne s'alignent pas et la
   liste devient illisible des la premiere categorie. */
.arbo { display: flex; align-items: stretch; padding-left: var(--nut-sp-2); }
.arbo.enfant { padding-left: 22px; }
.gouttiere { width: 18px; flex: none; }

.barre button.chevron {
  width: 18px; flex: none; height: auto; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; color: var(--nut-text-faint); cursor: pointer;
}
.barre button.chevron svg { transition: transform var(--nut-dur-2) var(--nut-ease); }

.barre button.chevron[aria-expanded="true"] svg { transform: rotate(90deg); }

.barre .arbo a.page, .barre .arbo button.cat {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; gap: var(--nut-sp-2);
  height: auto; min-height: 30px;
  padding: 5px var(--nut-sp-15) 5px 2px;
  border: 0; border-radius: var(--nut-radius-1);
  background: transparent; color: var(--nut-text);
  font: inherit; font-size: var(--nut-size-3); text-align: left;
  text-decoration: none; cursor: pointer;
}
.barre .arbo a.page .etiquette, .barre .arbo button.cat .etiquette {
  min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.25; white-space: normal;
}
.barre .arbo button.cat { color: var(--nut-text-muted); }
.barre .arbo a.page:hover, .barre .arbo button.cat:hover { background: var(--nut-surface-raised); }
/* LA PAGE COURANTE DE L ARBORESCENCE PORTE LE MEME MARQUEUR que les entrees du
   haut. Le « box-shadow: none » qui etait ici annulait l'heritage — c'est la
   seconde moitie du meme retrait, et le prototype le pose aussi sur les entrees
   de section (sprint 1, lignes 236 et 255). */
.barre .arbo a.page[aria-current="page"] {
  background: var(--nut-surface-raised);
  box-shadow: inset 2px 0 0 0 var(--nut-accent);
}
.barre .arbo a.page[aria-current="page"] svg { color: var(--nut-text); }
.barre .arbo a.page svg { color: var(--nut-text-muted); }

/* Un brouillon : libelle attenue ET pastille creuse. Deux signaux, dont un de
   FORME — la seule nuance de gris ne suffirait pas a qui la percoit mal, et ne
   survivrait pas a un ecran mal regle. */
.barre .arbo a.page.brouillon .etiquette { color: var(--nut-text-faint); }
.pastille-brouillon {
  flex: none; width: 7px; height: 7px; margin-left: auto;
  border-radius: 50%; border: 1.4px solid var(--nut-text-faint);
}

/* Une campagne observee, pas jouee : le mot est ecrit, pas seulement suggere
   par une nuance. Une lecture declaree doit se lire. */
.barre .camp .oeil {
  margin-left: auto; flex: none;
  color: var(--nut-text-faint);
  font-size: var(--nut-size-1);
  font-style: italic;
}

/* --- Zone de contenu ---
   Colonne flexible : le contenu occupe la place disponible, le pied de page
   est POUSSE en bas de la zone. Il appartient au cadre, pas a l'ecran — il
   reste donc a la meme hauteur qu'il y ait trois lignes ou trois ecrans de
   contenu, au lieu de flotter juste sous le dernier paragraphe. */
.contenu {
  display: flex; flex-direction: column;
  min-width: 0;
  padding: 0;
}

/* --- La barre du haut ---
   Presente sur TOUS les ecrans. Elle porte le fil d'Ariane : sans elle, le
   contenu commence au bord de la fenetre et rien ne dit ou l'on se trouve. Sa
   bordure basse prolonge celle de l'en-tete de la barre laterale. */
.barre-haut {
  height: var(--nut-topbar); flex: none;
  display: flex; align-items: center; gap: 10px;
  padding: 0 20px;
  border-bottom: 1px solid var(--nut-line);
}
.fil {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; gap: var(--nut-sp-2);
  font-size: var(--nut-size-2); color: var(--nut-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Le jeton de la campagne se retrouve ici : le meme repere qu'a gauche, au
   meme endroit du regard. */
.fil > a, .fil > .ici { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.fil a { color: inherit; text-decoration: none; }
.fil a:hover { color: var(--nut-text); text-decoration: underline; }
/* NE PAS APPELER CETTE CLASSE « separateur » : ce nom est deja pris dans
   styles.js par le filet de pied de carte, qui ajoute 18 px de marge et une
   bordure. Le chevron heritait des deux et tombait sous la barre. Diagnostique
   au rendu, invisible a la lecture — un nom de classe generique finit toujours
   par etre repris ailleurs. */
.fil-sep { flex: none; color: var(--nut-text-faint); line-height: 1; }
/* Le dernier element du fil est la position courante : elle se lit en clair,
   les precedents restent en retrait. */
.fil .ici { color: var(--nut-text); }
/* LA PASTILLE DIT CE QUE VOIENT LES AUTRES, et rien de plus.
   Elle ne s'affiche que sur une page PUBLIEE : le prototype ne montre qu'elle,
   et une page privee se signale deja dans la barre laterale par un titre
   attenue (sprint 3). Deux marques pour la meme information encombreraient le
   fil sans rien apprendre.

   ELLE S'APPELLE « fil-etat » ET NON « pastille », pour la MEME raison que le
   chevron s'appelle « fil-sep » et non « separateur » : la feuille de style est
   UNE SEULE feuille, sans cloisonnement. « pastille » est deja pris par la
   pastille de teinte de campagne (campagne.js) — un bouton rond de 30 px. Ma
   pastille en heritait la largeur fixe et affichait « e pub ».
   Trouve au rendu le 07/08/2026, invisible a la lecture. */
.fil-etat {
  flex: none; margin-left: 6px; padding: 1px 7px;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
}
/* --- LA RECHERCHE --------------------------------------------------------
   Le bouton porte son raccourci en clair. Une icone seule oblige a survoler
   pour savoir, et le raccourci ne s'apprend alors jamais. */
.barre-haut .chercher {
  flex: none; height: 32px; padding: 0 10px;
  display: flex; align-items: center; gap: 7px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer;
}
.barre-haut .chercher:hover { color: var(--nut-text); border-color: var(--nut-line-strong); }
.barre-haut .chercher .raccourci {
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  color: var(--nut-text-faint);
}
/* Sur petit ecran, le raccourci n'existe pas — il n'y a pas de clavier. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) { .barre-haut .chercher .raccourci { display: none; } }

/* LE PANNEAU S'ANCRE EN HAUT, pas au centre. Il grandit vers le bas au fil des
   resultats : centre, il sauterait a chaque frappe. */
/* LA RECHERCHE RESTE AU-DESSUS DU RESTE, y compris d'un modal ouvert : elle
   etait a 80 quand le voile etait a 60, elle monte avec lui (15/08/2026).
   L'ordre entre les deux est une decision, pas un reste d'arithmetique. */
.voile-recherche {
  position: fixed; inset: 0; z-index: 210;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 12vh 20px 20px;
  background: rgba(10, 12, 10, .72);
}
.panneau-recherche {
  width: 100%; max-width: 620px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-3);
  box-shadow: var(--nut-elev-2);
  overflow: hidden;
}
.panneau-recherche .saisie {
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px; height: 52px;
  border-bottom: 1px solid var(--nut-line);
}
.panneau-recherche .saisie > svg { flex: none; color: var(--nut-text-faint); }
.panneau-recherche input {
  flex: 1 1 0; min-width: 0; height: 100%;
  background: none; border: 0; color: var(--nut-text);
  font: inherit; font-size: var(--nut-size-4);
}
.panneau-recherche input:focus { outline: none; }
/* DEUX CROIX COTE A COTE, C'EST UNE DE TROP. Un champ de type « search » porte
   une croix d'effacement dessinee par le navigateur ; la notre ferme le
   panneau. Vues ensemble, on ne sait plus laquelle fait quoi — et celle du
   navigateur ne se style pas. On la retire. */
.panneau-recherche input::-webkit-search-cancel-button { display: none; }
.panneau-recherche .fermer {
  flex: none; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
.panneau-recherche .fermer:hover { color: var(--nut-text); background: var(--nut-surface-raised); }

.panneau-recherche .resultats { max-height: 52vh; overflow-y: auto; padding: 6px; }
.panneau-recherche .resultat {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border: 0; border-radius: var(--nut-radius-1);
  background: transparent; color: var(--nut-text);
  font: inherit; text-align: left; cursor: pointer;
}
.panneau-recherche .resultat[aria-selected="true"] { background: var(--nut-surface-raised); }
.panneau-recherche .resultat .titre {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* LA CAMPAGNE EST LE POINT DE TOUTE CETTE FONCTIONNALITE : sans elle, une
   liste de titres venus de trois univers differents ne veut rien dire. Elle est
   a droite, en retrait — on la lit quand on hesite, pas avant. */
.panneau-recherche .resultat .ou {
  flex: none; display: flex; align-items: center; gap: 6px;
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
}
.panneau-recherche .resultat .jeton-camp {
  width: 18px; height: 18px; font-size: 9px;
  clip-path: polygon(5px 0, 100% 0, 100% 100%, 0 100%, 0 5px);
}
.panneau-recherche .resultat .genre {
  flex: none; font-size: var(--nut-size-1); color: var(--nut-text-faint);
  font-family: var(--nut-font-data);
}
.panneau-recherche .pied-recherche {
  margin: 0; padding: 8px 12px;
  border-top: 1px solid var(--nut-line); background: var(--nut-surface);
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
}

.barre-haut .outil {
  flex: none; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--nut-radius-1);
  background: transparent; color: var(--nut-text-muted); cursor: pointer;
}
.barre-haut .outil:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
/* Un outil NOMME : meme hauteur que la roue, mais avec son libelle. Une icone
   seule aurait demande d'en inventer une pour « modele », et une icone qu'on
   doit expliquer ne vaut pas mieux qu'un mot. */
.barre-haut .outil-nomme {
  flex: none; height: 30px; padding: 0 11px;
  display: inline-flex; align-items: center; gap: 7px;
  background: transparent;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer; white-space: nowrap;
}
.barre-haut .outil-nomme:hover { color: var(--nut-text); border-color: var(--nut-text-faint); }
.barre-haut .outil svg { color: inherit; }

/* La zone qui defile sous la barre du haut. C'est elle qui porte la gouttiere
   de lecture, plus la zone de contenu elle-meme : sinon la barre du haut
   serait en retrait des bords, et la ligne ne traverserait plus. */
.zone {
  flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0;
  padding: var(--nut-sp-8) var(--nut-sp-6) var(--nut-sp-6);
}
/* LA COLONNE DE LECTURE EST CENTREE. Sans les marges automatiques, elle reste
   collee a gauche : sur un ecran de 2560 px, le texte occupe un tiers de la
   fenetre et laisse un desert a droite. Le prototype du sprint 1 pose des
   marges automatiques ; elles manquaient. */
.zone > .dedans {
  flex: 1 0 auto; width: 100%; max-width: var(--nut-read-max);
  margin-inline: auto;
}
.contenu.large .zone > .dedans { max-width: 940px; }
/* Le pied s'etend sur toute la largeur de la zone, pas seulement sur la
   colonne de lecture : c'est ce qui le rattache visuellement au cadre. */
.zone > .pied { flex: none; margin-top: var(--nut-sp-8); }

/* --- LE TERRITOIRE D'ADMINISTRATION EST BLEU ---
   UNE SEULE REGLE, ET C'EST LE POINT. Rien n'est repeint element par
   element : on redefinit l'accent sur la colonne de lecture, et tout ce qui
   vit dedans herite — l'onglet actif, la case a cocher, le bouton primaire,
   l'anneau de focus, et les ecrans d'administration qu'on ecrira plus tard.
   Voir « --nut-admin » dans tokens.js pour le pourquoi.

   POURQUOI « .zone > .dedans » ET NON « .contenu ». Parce que « .contenu »
   englobe aussi la barre haute mobile — le burger, la loupe — et le pied, qui
   appartiennent a la coquille et non a l'administration.

   HONNETEMENT : aujourd'hui, les deux perimetres RENDENT PAREIL. Ni la barre
   haute ni le pied ne peignent avec l'accent, donc rien ne se verrait. Le
   perimetre etroit n'est pas choisi pour ce qu'il repare, mais pour ce qu'il
   empeche : le jour ou l'un de ces deux endroits prendra l'accent, la faute
   serait deja posee, et invisible.

   C'est pourquoi « outils/essai-admin-bleu.mjs » ne mesure pas seulement des
   couleurs peintes — il lit « --nut-accent » SUR LA FRONTIERE, barre haute et
   pied. Une variable qui deborde se voit meme quand sa consequence ne se voit
   pas encore. (Premiere version de ce gardien : six cibles peintes, et elle
   restait VERTE quand on elargissait le perimetre expres. Un gardien vert sur
   la faute qu'il pretend garder est pire qu'un gardien rouge.) */
body.voie-admin .zone > .dedans { --nut-accent: var(--nut-admin); }

/* --- Le mode PLEINE HAUTEUR ---
   Introduit par l'ecran Lore (0.12.0), le premier a ne pas tenir dans la
   colonne de lecture : ses deux panneaux defilent SEPAREMENT, ce qui exige une
   hauteur fixe et non une page qui s'allonge.
   LE PIED A LONGTEMPS DISPARU ICI, et c'etait le seul ecran ou il disparaissait.
   La raison etait bonne : pousse en bas d'une zone qui ne defile pas, il se
   retrouvait a mi-hauteur, coince entre les deux panneaux.
   Renaud l'a redemande le 09/08/2026, et il a raison : le numero de version est
   la premiere chose qu'on lit quand on signale un defaut, et l'ecran Lore est
   celui ou l'on passe le plus de temps. On lui construit donc une place au lieu
   de le masquer — la zone devient une colonne de deux etages, les panneaux
   prennent ce qui reste, le pied tient le bas. Il coute une trentaine de pixels
   de hauteur utile ; c'est le prix d'une information qu'on cherchait ailleurs. */
.contenu.pleine { height: 100vh; overflow: hidden; }
.contenu.pleine .zone { padding: 0; min-height: 0; overflow: hidden; }
.contenu.pleine .zone > .dedans {
  flex: 1 1 auto; min-height: 0; max-width: none;
  display: flex; flex-direction: column;
}
.contenu.pleine .zone {
  display: flex; flex-direction: column;
}
/* SANS MARGE HAUTE ICI. Ailleurs le pied s'ecarte du contenu qui s'arrete au
   dessus de lui ; dans une zone pleine, le contenu touche deja le bas, et la
   marge ne ferait que voler de la hauteur aux panneaux. */
.contenu.pleine .zone > .pied {
  flex: none; margin-top: 0;
  padding: var(--nut-sp-2) var(--nut-sp-6);
  border-top: 1px solid var(--nut-line);
  background: var(--nut-bg);
}

/* LE BOUTON DU TIROIR ET LE VOILE SE DECLARENT ICI, AVANT la media query qui
   les montre — et c'est tout l'objet de cette note.
   
   Jusqu'au 13/08/2026, la regle « .barre-haut .ouvrir-tiroir { display: none } »
   etait ecrite APRES le bloc @media qui la passait a « flex ». Meme selecteur,
   meme specificite : a specificite egale, c'est la DERNIERE ecrite qui gagne,
   et une media query n'ajoute aucune specificite. Le bouton etait donc masque
   EN PERMANENCE — la navigation laterale devenait inatteignable sous 860 px,
   depuis toujours. Trouve par le harnais outils/rendu-tiroir.mjs, qui ouvre
   une vraie fenetre de 390 px : aucun test d'assertion ne pouvait le voir.
   
   La regle : ce qui est le DEFAUT se declare avant, ce qui le surcharge
   apres. */
.barre-haut .ouvrir-tiroir { display: none; }
.voile-tiroir { display: none; }
/* CE QUI N'EXISTE QUE SOUS LA FRONTIERE se declare eteint ICI, avant la media
   query qui l'allume — la lecon du 13/08 : a specificite egale, c'est la
   derniere regle ecrite qui gagne, et une @media n'ajoute aucune specificite.
   Le bouton du tiroir avait ete masque en permanence pendant des semaines
   pour avoir ete declare dans le mauvais ordre. */
.barre-haut .retour { display: none; }
.barre-haut .ouvrir-tiroir .au-doigt { display: none; }
.barre .bloc-titre.au-doigt { display: none; }
.fil-mobile { display: none; }
.marque-barre .fermer-tiroir { display: none; }
.barre-basse { display: none; }
/* Ce qui n'existe QUE sous la frontiere, eteint ici — meme raison que
   ci-dessus : une @media n'ajoute aucune specificite. */
.barre-haut .ouvrir-tiroir .au-doigt-ferme { display: none; }
.marque-haut { display: none; }
.pied-tiroir { display: none; }

/* --- Petit ecran : la barre devient un tiroir --- */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  body.app, body.app.repliee { grid-template-columns: 1fr; }
  .barre {
    position: fixed; z-index: 40; left: 0; top: 0; width: 300px;
    transform: translateX(-100%);
    transition: transform var(--nut-dur-3) var(--nut-ease);
    box-shadow: var(--nut-elev-2);
  }
  body.app.tiroir-ouvert .barre { transform: translateX(0); }

  /* LE VOILE (13/08/2026, point C7 de design/archives/RESTE-A-FAIRE.md). Il fait deux choses que
     l'absence de voile rendait impossibles : dire qu'on est PAR-DESSUS la
     page — le tiroir flottait au-dessus d'un contenu parfaitement net, sans
     rien qui indique lequel des deux repondait au clic — et offrir une cible
     de fermeture, qui est le geste naturel au pouce. */
  .voile-tiroir {
    display: block;
    position: fixed; inset: 0; z-index: 39;
    background: rgba(10, 12, 10, .55);
    opacity: 0; pointer-events: none;
    transition: opacity var(--nut-dur-3) var(--nut-ease);
  }
  body.app.tiroir-ouvert .voile-tiroir { opacity: 1; pointer-events: auto; }
  .zone { padding: var(--nut-sp-4); }
  .barre-haut .ouvrir-tiroir { display: flex; }

  /* 44 px EN TACTILE. La regle d'accessibilite du sprint 1 le demande, et elle
     ne se discute pas : un doigt n'a pas la precision d'un curseur. Les 32 px
     du pointeur fin restent sur grand ecran.

     LA LISTE A ETE ELARGIE LE 16/08 apres mesure : le harnais rendu-mobile
     trouvait des entrees de campagne a 22 px de haut, parce que « .camp-tete »
     portait un min-height mais que le lien qu'elle contient, lui, n'en avait
     pas. Une regle qui vise le contenant ne garantit rien sur la cible. */
  .barre a, .barre button.nav, .barre button.replier,
  .barre .bloc.compte a, .barre .bloc.compte button.nav,
  .barre .arbo a.page, .barre .arbo button.cat,
  .barre .camp-tete, .barre .camp-tete > *,
  .barre .filtre-camp input {
    min-height: var(--nut-target-touch);
  }
  /* LE CHEVRON QUI REPLIE UNE CATEGORIE fait 18 px de large. Une cible se
     mesure dans sa dimension la PLUS COURTE : haute de 44 et large de 18, elle
     reste un fil a viser au pouce. Le harnais compte les deux, et c'est bien
     ce qu'il fallait — l'ancienne regle ne posait qu'un « min-height ». */
  .barre .arbo .chevron {
    min-width: var(--nut-target-touch);
    min-height: var(--nut-target-touch);
  }

  /* --- LA BARRE HAUTE ---------------------------------------------------
     Trois pieces seulement : ce qui ouvre ou remonte a gauche, le lieu au
     milieu, la recherche a droite. */
  /* 56 px DE HAUT ET 6 px DE GOUTTIERE, releves sur la maquette. Les 48 px du
     bureau y sont serres pour un doigt, et une gouttiere de 16 px poussait la
     croix et le retour 10 px trop a droite : les boutons font 44 px, ils
     portent leur propre marge. Le CONTENU de la page, lui, garde ses 16 px —
     c'est la barre qui touche les bords, pas le texte. */
  .barre-haut { gap: var(--nut-sp-2); height: 56px; padding: 0 6px; }
  .barre-haut .ouvrir-tiroir,
  .barre-haut .retour,
  .barre-haut .chercher {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    min-width: var(--nut-target-touch);
    padding: 0; justify-content: center;
  }
  /* LA LOUPE REMPLACE Ctrl+K, elle ne l'accompagne pas : un telephone n'a pas
     de clavier, et le harnais trouvait « Ctrl K » affiche en 39x32 sur
     mobile — un raccourci proposé a un appareil qui ne peut pas le taper. */
  .barre-haut .chercher { background: none; border: 0; margin-left: auto; }

  /* LA BARRE HAUTE COLLE (17/08/2026, lot 1 du chantier mobile de la maison).
     L'annexe de la phase 1 l'ecrit noir sur blanc : « le grand titre vit dans
     le flux du contenu et la barre haute compacte est position: sticky ».
     Elle ne l'etait pas. Sur les ecrans a page qui defile — la fiche de
     maison, une page de texte —, elle partait au premier defilement : le
     retour, le tiroir et la loupe devenaient inatteignables sans remonter
     toute la page. On ne s'en apercevait pas sur l'arbre ni sur le Lore,
     qui sont en « pleine » hauteur et ne defilent pas la page.
     TROUVE EN MESURANT le collant du rail d'onglets de la maison : le rail
     tenait a 56 px du haut, mais il n'y avait plus rien au-dessus.
     La regle ne touche pas les ecrans « pleine » : leur zone ne defile pas,
     et un sticky y serait sans effet. */
  .contenu:not(.pleine) .barre-haut {
    position: sticky; top: 0; z-index: 30;
    background: var(--nut-bg);
  }
  /* LA ROUE AUSSI FAIT 44 px (17/08/2026). Le bloc ci-dessus donnait leurs
     44 px au burger, au retour et a la loupe, et OUBLIAIT la roue dentee :
     elle restait a 32 x 32, seule cible sous la borne sur l'ecran de l'arbre.
     Le harnais la signalait a chacun de ses douze etats, et elle passait pour
     un defaut connu plutot que pour un defaut a corriger. La regle de la
     phase 1 est « 44 px, partout, sans exception » — une exception qu'on
     tolere douze fois cesse d'etre une exception. */
  .barre-haut .outil {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    min-width: var(--nut-target-touch);
  }
  .barre-haut .ouvrir-tiroir .au-pointeur { display: none; }
  .barre-haut .ouvrir-tiroir .au-doigt { display: flex; }

  /* LE RETOUR PREND LA PLACE DU BURGER des qu'il existe. */
  .barre-haut .retour { display: flex; text-decoration: none; }
  /* LE BURGER NE DISPARAIT PLUS QUAND LE RETOUR PARAIT (corrige le 16/08).
     La v0.72.0 appliquait la lettre de l'annexe — « dans une page, le burger
     cede sa place au retour » — et le resultat, teste sur un vrai telephone,
     etait que LE TIROIR DEVENAIT INATTEIGNABLE depuis une page : plus aucun
     bouton pour l'ouvrir, seulement un geste de balayage que rien n'annonce.
     Une regle qui se lit bien et qui ferme une porte.
     Les deux cohabitent donc : retour a gauche, burger a sa droite, tous deux
     a 44 px. Ca coute 44 px de barre haute, et ca rend la navigation. */
  .barre-haut .retour { order: -1; }

  /* LE FIL D'ARIANE MEURT ICI, remplace par surligne + titre. */
  .fil { display: none; }
  .fil-mobile {
    display: flex; flex-direction: column; justify-content: center;
    flex: 1 1 0; min-width: 0; gap: 1px;
  }
  .fil-m-sur, .fil-m-titre {
    display: block; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .fil-m-sur {
    font-size: var(--nut-size-1); color: var(--nut-text-faint);
    text-transform: uppercase; letter-spacing: var(--nut-track-caps);
  }
  .fil-m-titre {
    font-size: var(--nut-size-3); font-weight: var(--nut-weight-semi);
    color: var(--nut-text);
  }
  /* Le titre seul du profil : meme graisse, sans la surligne au-dessus. */
  .fil-mobile.seul { justify-content: center; }

  /* --- LE TIROIR --------------------------------------------------------- */
  .marque-barre .fermer-tiroir {
    display: flex; align-items: center; justify-content: center;
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    margin-left: auto; flex: none;
    background: none; border: 0; border-radius: var(--nut-radius-1);
    color: var(--nut-text-muted); cursor: pointer;
  }

  /* LA CAMPAGNE COURANTE MONTE EN TETE, et seulement sur mobile : au doigt on
     ne fait pas defiler une liste de campagnes pour retrouver celle qu'on est
     en train de lire. Le rangement du bureau, lui, ne bouge pas — c'est un
     « order » de flex, pas une reecriture du HTML. */
  .barre .defilant .bloc { display: flex; flex-direction: column; }
  .barre .camp-bloc.courante { order: -1; }
  .barre .bloc-titre.au-pointeur { display: none; }
  .barre .bloc-titre.au-doigt { display: block; }
  .barre .bloc-titre.courante-titre { order: -2; }

  /* --- LA BARRE BASSE ---------------------------------------------------- */
  .barre-basse {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
    height: 64px;
    background: var(--nut-surface); border-top: 1px solid var(--nut-line);
  }
  .barre-basse > * {
    flex: 1 1 0;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 3px;
    background: none; border: 0; cursor: pointer;
    font: inherit; font-size: var(--nut-size-1);
    color: var(--nut-text-faint); text-decoration: none;
  }
  .barre-basse .ici { color: var(--nut-text); }
  /* LE FILET SOUS L'ICONE marque la destination courante — meme vocabulaire
     que le marqueur d'accent de la barre laterale. */
  .barre-basse .ici svg {
    border-bottom: 2px solid var(--nut-accent); padding-bottom: 3px;
  }
  /* LA BARRE BASSE NE MANGE PAS LA FIN DE LA PAGE : sans cette reserve, le
     dernier bouton d'un ecran se retrouve sous elle et devient intappable.

     ⚠ NE PAS ECRIRE « .barre-basse ~ * { display: none } ». C'etait ma
     premiere version, et elle masquait le PANNEAU DE RECHERCHE : dans le
     document, la barre basse precede le voile de recherche et le modal de
     configuration. Le selecteur frere aurait eteint la recherche sur le seul
     ecran qui porte un bouton pour l'ouvrir. */
  body.app:has(.barre-basse) .zone { padding-bottom: calc(64px + var(--nut-sp-4)); }

  /* LE PIED DE PAGE. Son unique lien vivait en 18 px de haut : lisible a la
     souris, invisible au pouce. On lui rend ses 44 px sans le rendre voyant —
     la hauteur est une zone tappable, pas une taille de texte. */
  /* SCOPE SOUS « .zone » A DESSEIN. « .pied » appartient a styles.js, la
     feuille commune ; le garde-fou des collisions refuse qu'une seconde
     feuille declare la meme classe NUE, et il a raison — c'est ainsi qu'une
     regle de coquille se met a repeindre le pied du lore et celui de maison
     sans que personne ne l'ait demande. On qualifie donc, et la regle ne sort
     pas de la coquille. */
  .zone .pied { align-items: center; }
  .zone .pied a.pied-lien {
    display: inline-flex; align-items: center;
    min-height: var(--nut-target-touch);
  }

  /* --- LA RECHERCHE, EN PLEIN ECRAN -------------------------------------- */
  .voile-recherche { padding: 0; }
  .panneau-recherche {
    width: 100%; max-width: none; height: 100dvh; max-height: none;
    border: 0; border-radius: 0;
  }
  .panneau-recherche .saisie { height: 56px; }
  .panneau-recherche .saisie input {
    height: 48px;
    /* 16 px AU MINIMUM : en dessous, iOS zoome de force sur le champ actif et
       la page se retrouve decadree apres la frappe. */
    font-size: 16px;
  }
  .panneau-recherche .fermer {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
  }
  .panneau-recherche .resultats { max-height: none; flex: 1 1 auto; }
  .panneau-recherche .resultats > * { min-height: var(--nut-target-touch); }

  /* ======================================================================
     LA COQUILLE DE LA MAQUETTE — barre haute, tiroir, pied
     ======================================================================

     Ecrit le 16/08/2026 apres avoir rendu les quatre onglets de tiroir et de
     barre de la maquette a cote des ecrans reels. Les six ecarts releves par
     Renaud etaient tous ici, et aucun n'avait ete compare : le releve d'ecarts
     du matin annoncait le tiroir « conforme » sur la foi d'une lecture.

     CE BLOC EST LE DERNIER DE LA MEDIA QUERY, et rien ne doit s'ecrire apres
     lui : il surcharge des regles de meme specificite declarees plus haut
     (.barre, .barre-haut, .zone .pied). A specificite egale, c'est la derniere
     ecrite qui gagne. C'est la troisieme fois que ce depot se fait prendre.

     --- 1. LA MARQUE DANS LA BARRE HAUTE --------------------------------- */
  .marque-haut {
    display: flex; align-items: center; gap: 8px;
    flex: 1 1 auto; min-width: 0; padding-left: 4px;
  }
  /* « carre » ET NON « jeton » : le garde-fou des emprunts de classes refuse
     qu'une feuille peigne une classe declaree par une autre — « jeton »
     appartient au socle. La marque de la barre haute a donc son propre nom,
     et la regle ne sort pas de la coquille. */
  .marque-haut .carre {
    width: 14px; height: 14px; flex: none;
    background: var(--nut-accent);
    clip-path: polygon(4px 0, 100% 0, 100% 100%, 0 100%, 0 4px);
  }
  .marque-haut .nom {
    font-family: var(--nut-font-title);
    font-weight: var(--nut-weight-semi);
    font-size: 15px; letter-spacing: .04em;
    white-space: nowrap;
  }
  /* ELLE CEDE LA PLACE DES QU'UN LIEU EST NOMME. La maquette ne montre jamais
     la marque ET un titre de page : dans une page c'est surligne + titre, dans
     le profil c'est « Mon profil ». Le fil mobile est present dans le document
     meme vide — c'est donc sa presence VISIBLE qui decide, d'ou le voisin
     general plutot qu'un test sur le contenu. */
  .fil-mobile ~ .marque-haut { display: none; }
  /* SAUF LE TIROIR OUVERT, ou la maquette la remet dans les deux cas : le
     titre appartient a la page qu'on vient de quitter du regard. */
  body.app.tiroir-ouvert .marque-haut { display: flex; }
  body.app.tiroir-ouvert .fil-mobile,
  body.app.tiroir-ouvert .barre-haut .retour { display: none; }

  /* --- 2. LE BURGER DEVIENT UNE CROIX, AU MEME ENDROIT ------------------
     La croix vivait DANS le panneau, alignee a droite. La maquette la met a
     gauche, dans la barre haute, exactement la ou etait le burger — et garde
     la barre haute visible, loupe comprise. Le panneau descend donc sous
     elle au lieu de la recouvrir. */
  body.app.tiroir-ouvert .ouvrir-tiroir .au-doigt { display: none; }
  body.app.tiroir-ouvert .ouvrir-tiroir .au-doigt-ferme { display: flex; }
  .barre-haut .ouvrir-tiroir .au-doigt-ferme { align-items: center; justify-content: center; }
  /* La croix du panneau n'a plus lieu d'etre : deux sorties a 30 px l'une de
     l'autre se disputent le meme geste. Et la marque non plus : elle est
     desormais dans la barre haute, juste au-dessus.
     ⚠ « .barre .marque-barre », pas « .marque-barre » : la regle qui l'allume
     est qualifiee par .barre, et un selecteur plus court n'aurait pas gagne —
     la media query n'ajoute rien. Ecrit d'abord trop court, vu sur la
     capture : le panneau gardait sa propre barre NUTTICE sous celle du haut. */
  .barre .marque-barre { display: none; }

  /* LE PANNEAU COMMENCE SOUS LA BARRE HAUTE. */
  .barre {
    top: 56px; width: 296px;
    height: calc(100dvh - 56px);
    border-right: 1px solid var(--nut-line);
  }
  .voile-tiroir { top: 56px; }
  /* La barre haute passe DEVANT le panneau : c'est elle qui porte la sortie. */
  .barre-haut { position: relative; z-index: 41; background: var(--nut-surface); }

  /* --- 3. « ACCUEIL » MANQUE LA OU IL SERT, ET S'AFFICHE LA OU IL NE SERT
     A RIEN (corrige le 17/08/2026, arbitrage de Renaud). La regle d'avant
     retirait l'entree DANS une campagne, en s'appuyant sur le retour de la
     barre haute pour ramener a l'accueil. Ce retour a depuis disparu de la
     fiche de maison, puis de l'arbre : le tiroir est devenu la seule porte,
     et elle ne s'ouvrait plus. On inverse — l'entree se retire seulement
     quand on EST sur l'accueil, ou elle ne mene nulle part. */
  body.app.sur-accueil .barre .bloc-accueil-nav { display: none; }
  /* --- 4. HORS CAMPAGNE, LA LISTE EST PLATE — MAIS ELLE SE DEPLIE -------
     La maquette hors campagne ne montre que les NOMS des campagnes, et c'est
     juste AU REPOS : on n'est dans aucune, en deplier une promettrait une
     campagne courante qui n'existe pas.
     MAIS « plate » ne veut pas dire « close ». La premiere version masquait
     l'arborescence en dur, et le tiroir devenait une liste de noms sans
     issue : depuis l'accueil, plus aucune page n'etait atteignable. Le repli
     existe deja dans ce fichier — cliquer le nom d'une campagne ouvre ou
     ferme son dossier — il suffisait de changer le DEFAUT sous la frontiere,
     ce que fait le script. Il n'y a donc rien a masquer ici. */

  /* --- 5. « NOUVELLE CAMPAGNE » SORT DU TIROIR -------------------------- */
  .barre .bloc.compte .nav-creer { display: none; }
  /* --- 6. « COMPTE » PREND SON TITRE ------------------------------------ */
  .barre .bloc.compte { border-top: 0; padding-top: 0; }
  .barre .bloc-titre.compte-titre { display: block; }

  /* --- 7. LE PIED PASSE DE LA PAGE AU TIROIR ---------------------------- */
  .zone > .pied { display: none; }
  .pied-tiroir {
    display: flex; align-items: center; gap: var(--nut-sp-3);
    flex: none; min-height: var(--nut-target-touch);
    padding: 0 var(--nut-sp-4);
    border-top: 1px solid var(--nut-line);
    color: var(--nut-text-faint); font-size: var(--nut-size-1);
  }
  .pied-tiroir .version { margin-left: auto; font-family: var(--nut-font-data); }
  .pied-tiroir .pied-lien {
    display: inline-flex; align-items: center;
    min-height: var(--nut-target-touch);
    color: var(--nut-text-faint); text-decoration: none;
    border-bottom: 1px solid var(--nut-line-strong);
  }
}



/* L'ecran vit desormais DANS la coquille : la largeur et les marges viennent
   d'elle (.contenu.large), plus de conteneur propre a cette page. */
.entete { margin-bottom: var(--nut-sp-6); }
.entete h1 { font-size: var(--nut-size-6); }

/* Le titre porte le role de qui regarde : deux personnes n'y voient pas les
   memes actions, et savoir a quel titre on est la evite de chercher pourquoi
   un bouton manque. */
.rangee-titre { display: flex; align-items: center; gap: var(--nut-sp-3); flex-wrap: wrap; }
.jeton-role {
  padding: 4px 9px;
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-pill);
  color: var(--nut-text-faint);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
}
.jeton-role[hidden] { display: none; }

/* --- Onglets --- */
.onglets {
  display: flex; gap: var(--nut-sp-6);
  border-bottom: 1px solid var(--nut-line);
  margin-bottom: var(--nut-sp-6);
}
.onglets button {
  appearance: none; background: none; border: 0;
  padding: 0 0 10px; margin-bottom: -1px;
  border-bottom: 2px solid transparent;
  color: var(--nut-text-faint);
  font: inherit; font-size: var(--nut-size-3);
  cursor: pointer;
}
.onglets button[aria-selected="true"] {
  color: var(--nut-text);
  font-weight: var(--nut-weight-semi);
  /* L'un des trois emplois autorises de l'accent : marqueur de page active. */
  border-bottom-color: var(--nut-accent);
}
[role="tabpanel"][hidden] { display: none; }

/* --- Barre d'outils de la liste --- */
/* Le filtre prend TOUTE la place restante, le bouton garde la sienne. C'est
   la disposition du prototype, et elle dit ce qui est frequent : on filtre
   tous les jours, on invite une fois par trimestre. */
.outils { display: flex; gap: var(--nut-sp-3); align-items: center; margin-bottom: var(--nut-sp-4); }
.outils .primaire {
  flex: none; display: inline-flex; align-items: center; gap: var(--nut-sp-2);
  height: 38px; padding: 0 var(--nut-sp-4);
  font-size: var(--nut-size-3);
}
.outils .primaire svg { color: currentColor; }
.outils input[type="search"] {
  flex: 1 1 0; min-width: 0; height: 38px; padding: 0 var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
}
button.secondaire {
  height: 36px; padding: 0 var(--nut-sp-4);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer; white-space: nowrap;
}
button.secondaire:hover:not(:disabled) { border-color: var(--nut-text-faint); }
button.secondaire:disabled { opacity: .45; cursor: not-allowed; }

/* --- Liste des membres --- */
.liste { border: 1px solid var(--nut-line); border-radius: var(--nut-radius-3); overflow: hidden; }
/* CINQ COLONNES, comme le prototype : NOM · MEMBRE DEPUIS · COURRIEL · ROLE ·
   ACTIONS. Le courriel etait jusqu'ici tasse sous le nom, en petit ; il a sa
   colonne. Les proportions sont celles de la maquette (1,4 / 1 / 1,6 / 1 /
   88 px) : le courriel est la valeur la plus longue, il a la part la plus
   large. */
.ligne {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1.6fr 1fr 88px;
  gap: var(--nut-sp-3);
  align-items: center;
  padding: 10px var(--nut-sp-4);
  border-bottom: 1px solid var(--nut-line);
  background: var(--nut-surface);
}
.ligne:last-child { border-bottom: 0; }
.ligne.tete {
  background: var(--nut-surface-sunken);
  color: var(--nut-text-faint);
  font-size: var(--nut-size-1);
  text-transform: uppercase;
  letter-spacing: var(--nut-track-caps);
}
/* UNE INVITATION EST EN POINTILLE, PAS EN ORANGE.
   J'avais mis un lisere d'accent plein, en le comptant parmi les trois emplois
   autorises. Ce n'en est aucun — la charte dit « marqueur de page active,
   action primaire unique, anneau de focus », et rien d'autre — et le prototype
   du sprint 2 pose exactement l'inverse :
     pendingBorder: '3px dashed var(--nut-line-strong)'
   Le pointille dit ce qu'il faut dire : la ligne existe mais n'est pas encore
   pleine. Un orange dirait « agis », alors qu'il n'y a rien a faire ici — on
   attend quelqu'un d'autre. Corrige le 07/08/2026. */
.ligne.attente {
  border-left: 3px dashed var(--nut-line-strong);
  padding-left: calc(var(--nut-sp-4) - 3px);
}
/* TOUTES les cellules de texte tronquent, et le min-width de zero n'est pas
   decoratif : sans lui, une cellule de grille refuse de retrecir sous la
   largeur de son contenu, et « Jean-Baptiste de La Tour du Pin » pousse les
   colonnes suivantes hors du tableau au lieu de se couper. */
.ligne .identite, .ligne .courriel, .ligne .meta { min-width: 0; }
.ligne .nom, .ligne .courriel {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ligne .courriel {
  color: var(--nut-text-muted);
  font-family: var(--nut-font-data); font-size: var(--nut-size-2);
}
/* Sous la pastille de role d'une invitation : « En attente d'acceptation ». */
.ligne .sous-role {
  display: block; margin-top: 4px;
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
}
.ligne .meta { color: var(--nut-text-faint); font-size: var(--nut-size-1); }
.ligne .actes { display: flex; gap: var(--nut-sp-1); justify-content: flex-end; }
.ligne .a-droite { text-align: right; }

/* --- Badge de role --- */
/* LA PASTILLE PORTE LA COULEUR DU ROLE, bordure et texte compris — c'est ce
   que montre le prototype, et c'est ce qui rend la colonne lisible d'un coup
   d'oeil. Fond transparent : trois aplats colores par ligne feraient un
   tableau de confettis. Le carre reste, parce que la couleur seule ne suffit
   pas — deux signaux, dont un de forme. */
.badge {
  display: inline-flex; align-items: center; gap: var(--nut-sp-15);
  height: 26px; padding: 0 10px 0 8px; max-width: 100%;
  background: transparent;
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-pill);
  color: var(--nut-text);
  font: inherit; font-size: var(--nut-size-2);
  font-weight: var(--nut-weight-semi);
}
.badge span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.badge.role-admin  { border-color: var(--nut-role-admin);  color: var(--nut-role-admin); }
.badge.role-modo   { border-color: var(--nut-role-modo);   color: var(--nut-role-modo); }
.badge.role-membre { border-color: var(--nut-role-membre); color: var(--nut-role-membre); }
button.badge { cursor: pointer; }
button.badge:disabled { cursor: default; opacity: .55; }
button.badge:hover:not(:disabled) { background: var(--nut-surface-raised); }
/* Puce CARREE : la grammaire du jeton de campagne, jamais le rond d'un etat
   systeme. C'est ce qui distingue un badge d'identite d'un avertissement. */
.badge .puce { width: 9px; height: 9px; flex: none; border-radius: 1px; }
.puce.admin      { background: var(--nut-role-admin); }
.puce.modo       { background: var(--nut-role-modo); }
.puce.membre     { background: var(--nut-role-membre); }
.puce.attente    { background: var(--nut-text-faint); }

/* --- Bouton d'action en icone --- */
button.icone {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  background: none; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); cursor: pointer;
}
button.icone:hover:not(:disabled) { background: var(--nut-surface-raised); color: var(--nut-text); }
button.icone:disabled { color: var(--nut-text-faint); opacity: .4; cursor: default; }

/* La maintenance : discrete, en bas, et elle ne s'affiche que s'il y a quelque
   chose a faire. Un bouton toujours la se fait cliquer par curiosite. */
.maintenance {
  margin-top: var(--nut-sp-6); padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
}
.titre-maintenance {
  margin: 0 0 var(--nut-sp-2);
  font-size: 11px; font-weight: var(--nut-weight-semi);
  text-transform: uppercase; letter-spacing: var(--nut-track-caps);
  color: var(--nut-text-faint);
}
.maintenance .aide { margin: 0 0 var(--nut-sp-3); }

/* --- Menu de choix de role --- */
.menu {
  position: absolute; z-index: 20;
  min-width: 190px; padding: var(--nut-sp-1);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2);
  box-shadow: var(--nut-elev-1);
}
.menu[hidden] { display: none; }
.menu button {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  width: 100%; padding: 8px 10px;
  background: none; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
  text-align: left; cursor: pointer;
}
.menu button:hover:not(:disabled) { background: var(--nut-surface); }
.menu button:disabled { opacity: .42; cursor: default; }
.menu .titre {
  padding: var(--nut-sp-2) 10px var(--nut-sp-1);
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
  text-transform: uppercase; letter-spacing: var(--nut-track-caps);
}
/* Meme liste d'options, mais posee dans le flux d'un dialogue au lieu de
   flotter. Une classe plutot qu'un style en ligne : la CSP interdit les
   seconds, et une classe se relit. */
.choix { position: static; box-shadow: none; margin-bottom: var(--nut-sp-3); }
.choix button[aria-pressed="true"] { background: var(--nut-surface); }
.choix button[aria-pressed="true"] span:last-child { font-weight: var(--nut-weight-semi); }

/* --- Boite de dialogue ---
   « VOILE-ADMIN » ET NON « VOILE ». Le nom court etait deja pris : campagne.js
   declare une classe « voile » pour les modals, avec d'autres valeurs (contenu ancre en
   haut, autre teinte de fond). Les deux feuilles etant concatenees en une, la
   derniere gagnait — et les dialogues d'administration s'affichaient collés au
   haut de l'ecran au lieu d'etre centres. Personne ne l'avait vu : le
   dialogue restait utilisable.
   C'est le meme piege que la classe « separateur » de coquille.js. Un nom de classe
   generique finit toujours par etre repris ailleurs ; un test le verifie
   desormais (voir scripts.test.mjs). */
.voile-admin {
  position: fixed; inset: 0; z-index: 30;
  display: flex; align-items: center; justify-content: center;
  padding: var(--nut-sp-4);
  background: color-mix(in srgb, var(--nut-surface-sunken) 82%, transparent);
}
.voile-admin[hidden] { display: none; }
.boite {
  width: 100%; max-width: 440px; padding: var(--nut-sp-6);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-3);
  box-shadow: var(--nut-elev-2);
}
.boite h2 {
  margin: 0 0 var(--nut-sp-2);
  font-family: var(--nut-font-title); font-size: var(--nut-size-5);
  font-weight: var(--nut-weight-semi);
}
.boite p { margin: 0 0 var(--nut-sp-4); color: var(--nut-text-muted); font-size: var(--nut-size-2); line-height: var(--nut-lh-text); }
.boite .rangee { display: flex; gap: var(--nut-sp-2); justify-content: flex-end; margin-top: var(--nut-sp-6); }
.boite .rangee button { width: auto; margin: 0; height: 38px; padding: 0 var(--nut-sp-4); }
/* Une suppression ne porte pas l'accent orange : l'accent designe l'action
   principale attendue, pas une action destructrice. */
button.danger {
  height: 38px; padding: 0 var(--nut-sp-4);
  background: none; border: 1px solid var(--nut-warn); border-radius: var(--nut-radius-1);
  color: var(--nut-warn); font: inherit; font-size: var(--nut-size-2); cursor: pointer;
}
button.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--nut-warn) 12%, transparent); }

/* --- Cartes de role --- */
.cartes-roles { display: grid; gap: var(--nut-sp-3); margin-bottom: var(--nut-sp-8); }
.carte-role {
  display: grid; grid-template-columns: auto 1fr auto; gap: var(--nut-sp-3);
  align-items: start;
  padding: var(--nut-sp-4);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
}
.carte-role .puce { width: 12px; height: 12px; margin-top: 4px; border-radius: 1px; }
.carte-role h3 { margin: 0; font-size: var(--nut-size-3); font-weight: var(--nut-weight-semi); }
.carte-role p { margin: var(--nut-sp-1) 0 0; color: var(--nut-text-muted); font-size: var(--nut-size-2); line-height: var(--nut-lh-text); }
.carte-role .compte { color: var(--nut-text-faint); font-family: var(--nut-font-data); font-size: var(--nut-size-1); white-space: nowrap; }

/* --- Matrice des droits --- */
.matrice { width: 100%; border-collapse: collapse; font-size: var(--nut-size-2); }
.matrice caption {
  margin-bottom: var(--nut-sp-3);
  color: var(--nut-text-muted); font-size: var(--nut-size-2);
  line-height: var(--nut-lh-text); text-align: left;
}
.matrice th, .matrice td { padding: 8px 10px; border-bottom: 1px solid var(--nut-line); text-align: left; }
.matrice thead th { color: var(--nut-text-faint); font-size: var(--nut-size-1); text-transform: uppercase; letter-spacing: var(--nut-track-caps); font-weight: var(--nut-weight-medium); }
.matrice td.oui, .matrice td.non { text-align: center; width: 92px; font-family: var(--nut-font-data); }
.matrice td.oui { color: var(--nut-ok); }
.matrice td.non { color: var(--nut-text-faint); }

/* --- Etats de la liste --- */
.etat { padding: var(--nut-sp-8) var(--nut-sp-4); text-align: center; color: var(--nut-text-muted); font-size: var(--nut-size-2); }
.squelette { height: 14px; margin: 10px 0; border-radius: 2px; background: var(--nut-surface-raised); }

/* La liste des campagnes a ses propres colonnes : le nom du MJ et
   l'observateur remplacent la date et le role. */
.ligne.camp { grid-template-columns: 1fr 150px 190px 76px; }
/* L'onglet Systemes : SYSTEME · AUTEUR · OU IL VIT · ACTIONS. DEUX icones a
   droite depuis le 19/08/2026 — renommer et supprimer ; « changer le
   proprietaire » est parti avec la notion de proprietaire. La colonne passe
   donc de 108 a 76 px, la meme que celle des campagnes. */
.ligne.sys { grid-template-columns: 1fr 150px 190px 76px; }

/* --- LES MOTEURS DE LA PLATEFORME (19/08/2026, lot 6) -------------------
   Une carte par systeme, ses modeles en cases a cocher. */
.liste.moteur { margin-bottom: var(--nut-sp-4); }
/* TROIS ENFANTS DEPUIS LE 20/08/2026, ET « space-between » NE CONVENAIT PLUS.
   Avec deux, il collait le nom a gauche et le compte a droite. Avec la porte
   vers le modele racine en troisieme, il aurait pose le compte AU MILIEU,
   detache du nom qu'il qualifie. On aligne donc a gauche, et c'est l'element
   de droite qui se pousse lui-meme. */
.liste.moteur .ligne.tete {
  display: flex; align-items: baseline; justify-content: flex-start; gap: 12px;
}
.liste.moteur .ligne.tete .moteur-droite { margin-left: auto; flex: none; }
/* LA PORTE VERS LE MODELE RACINE, a hauteur du nom du systeme : c'est la
   demande de Renaud, mot pour mot — « a cote du nom du systeme, un bouton
   orange Modifier ». 38 px, comme tous les boutons du produit. */
/* LA PORTE PORTE SON DESSIN ELLE-MEME, plutot que d'emprunter « primaire » :
   celle-la vise « button.primaire », et ce lien-ci est un <a>. On ecrit donc
   les quatre lignes qui font un bouton, au lieu d'une classe qui ne
   s'applique pas. La couleur vient de l'accent — dans l'administration, il
   est bleu depuis la 0.83.1, et cet ecran est de l'administration. */
.liste.moteur .ligne.tete .moteur-racine {
  display: inline-flex; align-items: center; justify-content: center;
  align-self: center;
  height: 38px; padding: 0 16px;
  background: var(--nut-accent);
  color: var(--nut-text-on-accent);
  border: 0; border-radius: var(--nut-radius-1);
  text-decoration: none; font-size: 14px;
  font-weight: var(--nut-weight-semi);
  transition: background var(--nut-dur-1) linear;
}
.liste.moteur .ligne.tete .moteur-racine:hover { background: var(--nut-accent-hi); }
/* LE NOM DU SYSTEME NE SE LAISSE PAS RABOTER. Sans « flex: none », les trois
   enfants de la tete se partagent la place et le nom tombe a « CH… » a 353 px
   — mesure du lot 5, sur l'ecran que le lot precedent venait de livrer. */
.liste.moteur .ligne.tete .nom { flex: none; }
.moteur-racine-court { display: none; }
.liste.moteur .ligne.tete .nom {
  font-family: var(--nut-font-title); font-size: var(--nut-size-4);
  font-weight: var(--nut-weight-semi); color: var(--nut-text);
}
.liste.moteur .aide.serree { margin: 0 0 10px; padding: 0 var(--nut-sp-3); }
.modeles-systeme {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
  gap: 2px; padding: 0 var(--nut-sp-3) var(--nut-sp-3);
}
/* LA CIBLE FAIT 44 PX DE HAUT : c'est une case a cocher, le geste le plus
   facile a rater de tout cet ecran, et le seul dont la consequence porte sur
   toutes les campagnes du club a la fois. */
.case-modele {
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 0 10px;
  border-radius: var(--nut-radius-1); cursor: pointer;
}
.case-modele:hover { background: var(--nut-surface-raised); }
.case-modele input { flex: none; width: 17px; height: 17px; accent-color: var(--nut-accent); }
.case-modele input:disabled { cursor: default; }
.case-modele .quoi { flex: 1 1 0; min-width: 0; font-size: var(--nut-size-2); }
/* LE COMPTE EST A DROITE, EN CHIFFRES : c'est lui qui fait decider, il se lit
   avant le clic et pas dans une infobulle. */
.case-modele .combien {
  flex: none; font-family: var(--nut-font-data); font-size: 12px;
  color: var(--nut-text-faint);
}
#vue-systemes .section-titre { margin: var(--nut-sp-6) 0 var(--nut-sp-2); }
#vue-systemes .liste { margin-bottom: var(--nut-sp-4); }
.ligne.camp .jeton-liste, .ligne.sys .jeton-liste {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none;
  clip-path: polygon(6px 0, 100% 0, 100% 100%, 0 100%, 0 6px);
  color: var(--nut-bg);
  font-family: var(--nut-font-data); font-size: 10px;
  font-weight: var(--nut-weight-medium);
}
.ligne.camp .identite, .ligne.sys .identite {
  display: flex; align-items: center; gap: 10px; }
/* Aucun observateur : on l'ecrit, on ne laisse pas une case vide. Une case
   vide se lit comme une donnee manquante, pas comme une absence voulue. */
.sans-observateur { color: var(--nut-text-faint); font-style: italic; font-size: var(--nut-size-2); }

/* --- La zone sensible d'une campagne ---
   Ce qui touche a l'EXISTENCE de la partie, separe par un filet de ce qui
   touche a sa composition. Ajouter un joueur et detruire une campagne n'ont
   pas le meme poids ; deux gestes de poids different ne partagent pas une
   ligne. */
.zone-sensible {
  margin-top: var(--nut-sp-4);
  padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--nut-sp-3);
}
.zone-sensible .titre-sensible {
  flex: 100%; margin: 0;
  color: var(--nut-warn); font-size: var(--nut-size-1);
  font-weight: var(--nut-weight-semi);
  text-transform: uppercase; letter-spacing: var(--nut-track-caps);
}
.zone-sensible .note-sensible {
  flex: 1 1 260px; margin: 0;
  color: var(--nut-text-muted); font-size: var(--nut-size-2);
  line-height: var(--nut-lh-text);
}
.zone-sensible button { flex: none; }

/* Une campagne archivee : attenuee, et le mot est ecrit. Une nuance de gris
   seule ne se lit pas — surtout sur une ligne qu'on parcourt vite. */
.ligne.camp.archivee .nom, .ligne.camp.archivee .meta { color: var(--nut-text-faint); }
.ligne.camp.archivee .jeton-liste { opacity: .45; }
.etiquette-archivee {
  display: inline-flex; align-items: center;
  margin-left: var(--nut-sp-2); padding: 1px 7px;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  color: var(--nut-text-faint); font-size: 11px;
  font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
}

/* --- Composition depliee sous une campagne ---
   Un panneau, pas une boite de dialogue : on regarde la composition en gardant
   la liste sous les yeux, et on enchaine d'une campagne a l'autre. */
.composition {
  padding: var(--nut-sp-4) var(--nut-sp-4) var(--nut-sp-4) 56px;
  background: var(--nut-surface-sunken);
  border-bottom: 1px solid var(--nut-line);
}
.composition[hidden] { display: none; }
.composition h3 {
  margin: 0 0 var(--nut-sp-2);
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
  text-transform: uppercase; letter-spacing: var(--nut-track-caps);
  font-weight: var(--nut-weight-medium);
}
.composition + .composition h3 { margin-top: var(--nut-sp-4); }
.participant {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  padding: var(--nut-sp-15) 0;
  font-size: var(--nut-size-2);
}
.participant .role-camp {
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
}
.participant .actes { margin-left: auto; display: flex; gap: var(--nut-sp-1); }
.rangee-outils { display: flex; gap: var(--nut-sp-2); margin-top: var(--nut-sp-3); flex-wrap: wrap; }

.section-titre {
  margin: var(--nut-sp-8) 0 var(--nut-sp-3);
  font-family: var(--nut-font-title); font-size: var(--nut-size-5);
  font-weight: var(--nut-weight-semi);
}
.ancre { position: relative; }

/* --- LA CARTE D'UN MOTEUR AU DOIGT (lot 5, 20/08/2026) ------------------
   TROIS ENFANTS SUR UNE LIGNE DE 353 px NE TIENNENT PAS, et le lot precedent
   les y avait laisses : le nom tombait a « CH… », le compte passait SOUS le
   bouton, et la phrase « pas de racine — aucun moteur de regles » recouvrait
   le mot « campagnes ». Rien de tout cela n'etait visible a 1280.
   La tete se replie donc : le nom et son compte sur une ligne, la porte sur
   la suivante, pleine largeur et 44 px de haut. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  .liste.moteur .ligne.tete { flex-wrap: wrap; row-gap: 10px; }
  .liste.moteur .ligne.tete .moteur-droite {
    margin-left: 0; width: 100%; flex: 1 0 100%;
  }
  .liste.moteur .ligne.tete .moteur-racine {
    height: var(--nut-target-touch); width: 100%;
    justify-content: center; align-self: stretch;
  }
  /* « Pas de racine » est une phrase, pas une etiquette : elle se lit sur
     plusieurs lignes plutot que de recouvrir sa voisine. */
  .liste.moteur .ligne.tete span.moteur-droite {
    white-space: normal; line-height: 1.35;
  }
  .moteur-racine-large { display: none; }
  .moteur-racine-court { display: inline; }

  .ligne { grid-template-columns: 1fr auto; }
  .ligne .meta { display: none; }
  .ligne.tete { display: none; }

  /* LA LIGNE DE LISTE AU DOIGT (16/08/2026, annexe section 5). 44 px de haut,
     le titre en ellipse — JAMAIS de retour a la ligne dans une liste, le
     titre complet se lit dans la page qu'elle ouvre — et l'action garde ses
     44 px au bord droit. */
  .ligne { min-height: var(--nut-target-touch); align-items: center; }
  .ligne > * { min-width: 0; }
  .ligne .qui, .ligne .quoi {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ligne button, .ligne a {
    min-width: var(--nut-target-touch); min-height: var(--nut-target-touch);
  }
}


/* UNE COLONNE DE 520 px, pas toute la largeur de lecture.
   La zone de contenu fait 880 px : les phrases d'explication de cet ecran y
   couraient sur toute la ligne, et les champs aussi. Un formulaire de reglages
   ne se lit pas comme un texte — la maquette le cadre a 520 px, et c'est cette
   largeur qui rend les paires « champ + bouton » comprehensibles d'un coup. */
.colonne-profil { max-width: 520px; }

.section {
  margin-bottom: var(--nut-sp-6);
  padding: var(--nut-sp-6);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-3);
}
.section h2 {
  margin: 0 0 var(--nut-sp-2);
  font-family: var(--nut-font-title);
  font-size: var(--nut-size-5);
  font-weight: var(--nut-weight-semi);
}
.section > p.intro {
  margin: 0 0 var(--nut-sp-4);
  color: var(--nut-text-muted);
  font-size: var(--nut-size-2);
  line-height: var(--nut-lh-text);
}
.section button.primaire { width: auto; padding: 0 var(--nut-sp-6); height: 40px; margin-top: var(--nut-sp-4); }

/* CHAMP ET BOUTON SUR LA MEME LIGNE, quand le bouton ne fait qu'enregistrer ce
   champ-la. Le placer dessous laisse croire qu'il valide tout le bloc — or le
   bloc de l'adresse en contient deux. La maquette fait ainsi pour le nom et
   pour l'adresse. */
.rangee-champ { display: flex; gap: var(--nut-sp-3); align-items: flex-start; }
.rangee-champ .champ { flex: 1 1 0; min-width: 0; margin-bottom: 0; }
.rangee-champ button.primaire { flex: none; margin-top: 0; }
/* LE PROFIL AU DOIGT (16/08/2026, annexe section 6 — c'est le cas d'ecole du
   formulaire, et le troisieme ecran de demonstration de la phase 1).

   DEUX CHOSES CHANGENT, ET ELLES VONT ENSEMBLE. Le bouton quitte la ligne du
   champ : a 353 px, « Changer le mot de passe » et son champ cote a cote ne
   laissent au champ que de quoi montrer quelques caracteres. Et une fois
   dessous, il prend toute la largeur — sinon il flotte, orphelin, sur une
   ligne vide.

   Le commentaire de « .rangee-champ » dit que placer le bouton dessous ferait
   croire qu'il valide tout le bloc. C'est vrai au bureau, ou deux champs
   peuvent se lire d'un coup d'oeil. A 353 px les champs sont deja empiles :
   le bouton suit CELUI qu'il enregistre, et l'ambiguite disparait avec la
   colonne qui la creait. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  .rangee-champ { flex-direction: column; align-items: stretch; }
  /* « flex: none » N'EST PAS DE LA PRECAUTION, c'est la correction d'un defaut
     que ce bloc venait de creer. En rangee, « flex: 1 1 0 » gouverne la
     LARGEUR du champ ; en colonne, il gouverne sa HAUTEUR — et une base de
     zero l'a ecrasee a 22 px, sous la moitie de la cible. Le harnais l'a dit
     tout de suite ; a l'oeil, un champ un peu plat ne se remarque pas. */
  .rangee-champ .champ { flex: none; margin-bottom: var(--nut-sp-2); }
  .section button.primaire {
    width: 100%; height: var(--nut-target-touch); margin-top: var(--nut-sp-3);
  }
  .rangee-champ button.primaire { margin-top: 0; }
}

.section .apres-champ {
  margin: var(--nut-sp-2) 0 0;
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
  line-height: var(--nut-lh-ui);
}

/* --- La force du mot de passe ---
   UNE MESURE, PAS UNE REGLE. On n'impose aucune composition — la maquette le
   dit en toutes lettres : « une phrase longue vaut mieux qu'un mot court
   truffe de symboles ». La jauge informe, elle n'interdit rien, et elle est
   DOUBLEE D'UN MOT : une barre coloree seule ne dit rien a qui percoit mal les
   couleurs. */
.jauge {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  margin-top: var(--nut-sp-2);
}
.jauge .rail {
  flex: 1 1 0; height: 4px;
  background: var(--nut-line);
  border-radius: var(--nut-radius-pill);
  overflow: hidden;
}
.jauge .rail span {
  display: block; height: 100%; width: 0;
  transition: width var(--nut-dur-2) var(--nut-ease);
}
.jauge .mot { flex: none; font-size: var(--nut-size-1); color: var(--nut-text-faint); }
.jauge.f1 .rail span { width: 25%;  background: var(--nut-warn); }
/* PALIER 2 EN BLEU D'INFORMATION, PAS EN ORANGE.
   L'orange a trois emplois — marqueur de page active, action primaire unique,
   anneau de focus — et un etat systeme n'en est pas un. Le prototype du
   sprint 2 le peint au jeton d'information (« correcte »). Corrige le
   07/08/2026.
   L'echelle elle-meme differe encore du prototype, qui n'a que TROIS paliers
   indexes sur la seule longueur : voir design/archives/RESTE-A-FAIRE.md,
   point D14. */
.jauge.f2 .rail span { width: 50%;  background: var(--nut-info); }
.jauge.f3 .rail span { width: 75%;  background: var(--nut-role-membre); }
.jauge.f4 .rail span { width: 100%; background: var(--nut-ok); }

/* Adresse actuelle : une information, pas un champ. */
.valeur {
  padding: 10px var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border-radius: var(--nut-radius-1);
  font-family: var(--nut-font-data);
  font-size: var(--nut-size-2);
  word-break: break-all;
}
/* « BLOC-ATTENTE » ET NON « ATTENTE ». Le nom court etait beaucoup trop
   generique : admin.js pose la classe « attente » sur les lignes d'invitation
   et sur les pastilles de role correspondantes. Les feuilles etant
   concatenees en une seule, ces regles-ci s'appliquaient aussi la-bas — la
   pastille de role d'une invitation heritait d'un fond, d'un rembourrage de
   10 px et d'un liseré bleu, et s'affichait comme un rectangle gris a la
   place d'un jeton. Trouve au rendu, invisible dans les deux fichiers pris
   separement. Un test verifie desormais qu'aucune classe « nue » n'est
   declaree dans deux feuilles. */
.bloc-attente {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  margin: var(--nut-sp-3) 0 var(--nut-sp-4);
  padding: 10px var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border-left: 3px solid var(--nut-info);
  border-radius: var(--nut-radius-1);
  font-size: var(--nut-size-2);
  line-height: var(--nut-lh-text);
}
.bloc-attente[hidden] { display: none; }
.bloc-attente .adresse { font-family: var(--nut-font-data); color: var(--nut-text); }
.bloc-attente button { margin-left: auto; }


/* ==========================================================================
   LE MODAL — valeurs extraites de design/sprint-3, pas transcrites a la main.
   L'extracteur et ses extraits (outils/extraire-maquette.mjs, outils/extrait/)
   ont ete retires le 17/10/2026, voir l'historique git.
   ========================================================================== */

.modal-tete {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 22px;
  border-bottom: 1px solid var(--nut-line);
}
.modal-tete h2 {
  flex: 1 1 0; margin: 0;
  font-family: var(--nut-font-title); font-size: 18px; font-weight: 600;
}
.modal-tete .fermer {
  flex: none; width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); text-decoration: none;
}
.modal-tete .fermer:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
.modal-corps { padding: 22px; }
.modal-pied {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 16px 22px;
  background: var(--nut-surface-sunken);
  border-top: 1px solid var(--nut-line);
  border-radius: 0 0 var(--nut-radius-3) var(--nut-radius-3);
}
.modal-pied .primaire, .modal-pied .bouton-secondaire {
  height: 38px; font-size: 14px; border-radius: var(--nut-radius-1);
  display: inline-flex; align-items: center;
}
.modal-pied .primaire { padding: 0 18px; font-weight: var(--nut-weight-semi); }
.bouton-secondaire {
  padding: 0 16px; border: 1px solid var(--nut-line-strong);
  background: transparent; color: var(--nut-text-muted);
  text-decoration: none; cursor: pointer; font: inherit; font-size: 14px;
}
.bouton-secondaire:hover { color: var(--nut-text); }

/* L'apercu : ce que le MJ verra tous les jours, pendant qu'il le choisit. */
.apercu {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 22px; padding: 14px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
}
.apercu-texte { min-width: 0; }
.apercu-titre {
  margin: 0 0 2px; font-size: 14px; font-weight: var(--nut-weight-medium);
  color: var(--nut-text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.apercu-aide { margin: 0; font-size: 13px; color: var(--nut-text-faint); }


.deux-colonnes { display: flex; flex-wrap: wrap; gap: 22px; margin-bottom: 22px; }
.colonne-fixe { flex: none; }
.colonne-souple { flex: 1 1 0; min-width: 200px; }
.deux-colonnes .champ { margin-bottom: 0; }

.mono-ligne { display: flex; align-items: center; gap: 6px; }
.mono-ligne .champ-mono {
  flex: none; width: 58px; height: 40px; padding: 0 10px; text-align: center;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text);
  font-family: var(--nut-font-title); font-size: 16px;
  font-weight: var(--nut-weight-semi); text-transform: uppercase;
}
.icone-seule {
  width: 30px; height: 30px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--nut-radius-1);
  background: transparent; color: var(--nut-text-faint); cursor: pointer;
}
.icone-seule:hover { background: var(--nut-surface-raised); color: var(--nut-text); }

/* Le grand jeton de l'apercu : meme dessin que dans la barre, en 38 px. */
.grand-jeton {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none;
  clip-path: polygon(10px 0, 100% 0, 100% 100%, 0 100%, 0 10px);
  color: rgb(16, 19, 15);
  font-family: var(--nut-font-title); font-size: 16px;
  font-weight: var(--nut-weight-semi);
}

.teintes { display: flex; flex-wrap: wrap; gap: 7px; }
/* --- LE CHOIX DU SYSTEME (18/08/2026) --------------------------------------
   Deux cartes, et chacune porte sa phrase. C'est la meme forme que le choix
   de modele d'une page : on choisit un moteur, pas une etiquette. */
.bloc-systeme { border: 0; padding: 0; margin: 0 0 var(--nut-sp-4); }
.bloc-systeme legend { padding: 0; }
.cartes-systeme { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.carte-systeme {
  display: flex; flex-direction: column; gap: 5px; align-items: flex-start;
  min-height: 44px; padding: 12px 14px; text-align: left; cursor: pointer;
  background: var(--nut-surface); color: var(--nut-text);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-2);
}
.carte-systeme:hover { border-color: var(--nut-accent-hi); }
.carte-systeme[aria-checked="true"] {
  border-color: var(--nut-accent); background: var(--nut-surface-raised); }
.carte-systeme .nom { font-size: 15px; font-weight: 600; }
.carte-systeme .dit { font-size: 12.5px; line-height: var(--nut-lh-text);
  color: var(--nut-text-muted); }
.aide.accent { color: var(--nut-accent-hi); }
.pastille {
  /* « padding: 0 » n'est pas decoratif : une regle plus haut donne du padding
     a tous les boutons, les pastilles faisaient 40 px et le huitieme passait
     a la ligne. Constate au rendu, invisible a la lecture. */
  width: 30px; height: 30px; flex: none; padding: 0; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  border: 0; cursor: pointer;
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 0 100%, 0 8px);
}
/* La teinte retenue porte un anneau orange — l'un des trois emplois autorises
   de l'accent : le marqueur de selection. Un anneau, pas un remplissage : la
   couleur choisie doit rester visible. */
.pastille[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--nut-bg), 0 0 0 4px var(--nut-accent-hi);
}

/* --- LA TEINTE RETENUE PORTE UNE COCHE ------------------------------------
   Trouve au rendu, et le defaut existait deja dans le modal de creation : la
   pastille annonce un anneau orange (box-shadow) qu'on ne voit jamais.
   Raison — la pastille est decoupee au clip-path pour son angle coupe, et un
   clip-path rogne TOUT ce que l'element peint, ombre portee comprise.
   L'anneau etait donc dessine, puis efface, a chaque fois.

   Le prototype, lui, pose une coche a l'interieur. Elle est peinte DANS la
   zone conservee, donc elle survit — et c'est un signal de forme, qui tient
   sans la couleur. L'anneau reste declare : il servira le jour ou la pastille
   n'aura plus d'angle coupe, et il ne coute rien. */
.pastille .coche { display: none; }
.pastille[aria-pressed="true"] .coche { display: flex; color: rgb(16, 19, 15); }

.pastille.t1 { background: var(--nut-camp-1); }
.pastille.t2 { background: var(--nut-camp-2); }
.pastille.t3 { background: var(--nut-camp-3); }
.pastille.t4 { background: var(--nut-camp-4); }
.pastille.t5 { background: var(--nut-camp-5); }
.pastille.t6 { background: var(--nut-camp-6); }
.pastille.t7 { background: var(--nut-camp-7); }
.pastille.t8 { background: var(--nut-camp-8); }
.grand-jeton.t1 { background: var(--nut-camp-1); }
.grand-jeton.t2 { background: var(--nut-camp-2); }
.grand-jeton.t3 { background: var(--nut-camp-3); }
.grand-jeton.t4 { background: var(--nut-camp-4); }
.grand-jeton.t5 { background: var(--nut-camp-5); }
.grand-jeton.t6 { background: var(--nut-camp-6); }
.grand-jeton.t7 { background: var(--nut-camp-7); }
.grand-jeton.t8 { background: var(--nut-camp-8); }

/* --- Choix de la teinte ---
   Huit pastilles. L'orange n'y figure pas : il a trois emplois reserves, et
   une campagne orange les rendrait illisibles. Ce n'est pas une omission,
   c'est la charte. */
/* La teinte choisie porte un anneau ET un decalage : deux signaux, dont un
   seul est la couleur. */
.t1 { background: var(--nut-camp-1); }
.t2 { background: var(--nut-camp-2); }
.t3 { background: var(--nut-camp-3); }
.t4 { background: var(--nut-camp-4); }
.t5 { background: var(--nut-camp-5); }
.t6 { background: var(--nut-camp-6); }
.t7 { background: var(--nut-camp-7); }
.t8 { background: var(--nut-camp-8); }

/* --- En-tete d'une campagne --- */
.tete-campagne {
  display: flex; align-items: flex-start; gap: var(--nut-sp-4);
  margin-bottom: var(--nut-sp-6);
}
.tete-campagne .grand-jeton {
  display: flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; flex: none;
  clip-path: polygon(12px 0, 100% 0, 100% 100%, 0 100%, 0 12px);
  color: var(--nut-bg);
  font-family: var(--nut-font-data);
  font-size: var(--nut-size-5); font-weight: var(--nut-weight-medium);
}
.tete-campagne h1 { margin: 0; font-size: var(--nut-size-6); }
.tete-campagne .meta {
  margin-top: var(--nut-sp-1);
  color: var(--nut-text-muted); font-size: var(--nut-size-2);
}
.tete-campagne .meta .ref { font-family: var(--nut-font-data); color: var(--nut-text-faint); }

/* Bandeau d'une campagne observee : la personne doit se souvenir a chaque
   instant qu'elle regarde, et qu'on le sait. */
.bandeau.observation { border-left-color: var(--nut-info); }
.bandeau.observation svg { color: var(--nut-info); }

.grille { display: grid; gap: var(--nut-sp-4); grid-template-columns: 1fr 300px; align-items: start; }
@media (max-width: 900px) { .grille { grid-template-columns: 1fr; } }

/* LE PIED DE MODAL AU DOIGT (16/08/2026, annexe section 4).
   Deux boutons cote a cote font 150 px chacun a 353 px de large ; empiles ils
   font toute la largeur. Et l'ordre S'INVERSE : le geste reflexe au pouce est
   de taper EN BAS, donc ce que l'on tape sans reflechir doit etre celui qui
   ANNULE. Dans le document l'annulation vient en premier et l'action en
   second — l'ordre du bureau, ou l'on lit de gauche a droite ; « column-reverse »
   remet le destructeur en haut de la pile sans toucher au HTML. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  .modal-corps { overflow-y: auto; }
  .modal-pied {
    flex: none; flex-direction: column-reverse; gap: var(--nut-sp-2);
    border-radius: 0;
  }
  .modal-pied > * { width: 100%; }
  .modal-pied .primaire, .modal-pied .bouton-secondaire {
    height: var(--nut-target-touch); justify-content: center;
  }
  .modal-tete .fermer {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
  }
}

/* Le code se saisit en chasse fixe : on le recopie depuis un message, et une
   fonte a chasse fixe rend evidente la difference entre O et 0. */
.code-saisie {
  font-family: var(--nut-font-data);
  font-size: var(--nut-size-5);
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* Pousse l'element suivant a droite. Une classe plutot qu'un style pose en
   JavaScript : elle se relit dans la feuille, et se retrouve. */
.pousse { margin-left: auto; }

.entete-pages { display: flex; align-items: baseline; gap: var(--nut-sp-3); }
.entete-pages .section-titre { margin-top: 0; }

.code-invitation {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  padding: 10px var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border-radius: var(--nut-radius-1);
  font-family: var(--nut-font-data);
  font-size: var(--nut-size-4);
  letter-spacing: .06em;
}

/* ==========================================================================
   L'ACCUEIL DE CAMPAGNE (sprint 13, 29/08/2026)
   ==========================================================================
   Renaud : « cliquer sur une campagne mène à l'accueil de la campagne ».
   Cet ecran remplace la redirection qui sautait dans la premiere page.

   TOUTES LES CLASSES SONT PREFIXEES « ac- ». La feuille du produit est UNE
   feuille : « .section », « .ligne », « .attente » y appartiennent deja a
   trois ecrans differents. La regle du fichier — « toute classe nouvelle
   porte son prefixe, qu'elle paraisse prise ou non » — a ete payee deux fois
   dans la journee sur « .etat » et « .mn-l ». Elle est tenue ici d'avance.

   ⚠ L'ANCIEN ECRAN S'APPUYAIT SUR « .tete-campagne » ET « .code-invitation »,
   qui n'ont JAMAIS ETE DEFINIES nulle part. Personne ne l'a vu parce que la
   redirection rendait l'ecran inatteignable. Elles disparaissent avec lui. */

/* LA LARGEUR S'OCCUPE, ELLE NE S'ETIRE PAS : plafond a 1120 px. Au-dela, le
   rail droit prend la difference plutot que les lignes de texte. */
.ac { max-width: 1120px; }

.ac-tete { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 18px; }
.ac-tete .ac-titres { flex: 1 1 0; min-width: 0; }
.ac-tete h1 { margin: 0; font-family: var(--nut-font-title);
  font-size: var(--nut-size-6); font-weight: 600; }
.ac-tete .ac-meta { margin: 4px 0 0; font-size: 13.5px; color: var(--nut-text-muted); }
.ac-tete .ac-gestes { flex: none; display: flex; align-items: center; gap: 10px; }

/* LA POSITION SE DIT, MEME AU JOUEUR. Savoir a quel titre on entre fait
   partie de ce que l'accueil doit apprendre — la maquette la pose en pastille
   a cote du titre, jamais en couleur : ce n'est pas un etat, c'est un fait. */
.ac-position {
  padding: 5px 11px; border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font-family: var(--nut-font-data); font-size: 11.5px;
  color: var(--nut-text-muted); white-space: nowrap;
}
.ac-coulisses {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 38px; padding: 0 14px;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-2);
  background: var(--nut-surface); color: var(--nut-text);
  font-size: 13px; font-weight: 600; text-decoration: none;
}
.ac-coulisses:hover { border-color: var(--nut-accent); color: var(--nut-accent); }

/* LE BANDEAU « A FAIRE » — MJ SEUL, ET IL LE DIT. Un bloc qui n'apparait
   qu'a une personne doit annoncer qu'il ne s'affiche pas chez les autres :
   sans cette mention, le MJ croit que sa table voit la meme chose que lui. */
.ac-faire {
  margin-bottom: 18px; padding: 0 0 4px;
  border: 1px solid var(--nut-accent-dim); border-radius: var(--nut-radius-2);
  background: var(--nut-surface-sunken);
}
.ac-faire .ac-faire-tete {
  padding: 9px 14px; font-family: var(--nut-font-data); font-size: 11px;
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-accent);
}
.ac-faire .ac-l { border-top: 1px solid var(--nut-line); }
.ac-faire .ac-l .ac-puce { flex: none; width: 6px; height: 6px;
  border-radius: var(--nut-radius-pill); background: var(--nut-accent); }

/* LA GRILLE : contenu a gauche, rail de 340 px a droite (variation retenue
   par Renaud). Sous 900, une colonne — et l'ordre du DOM est deja celui du
   doigt : quoi de neuf, les pages, la table. Rien a reordonner. */
.ac-grille { display: flex; align-items: flex-start; gap: 20px; }
.ac-grille .ac-corps { flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 18px; }
.ac-grille .ac-rail { flex: none; width: 340px;
  display: flex; flex-direction: column; gap: 18px; }
@media (max-width: 900px) {
  .ac-grille { flex-direction: column; gap: 18px; }
  .ac-grille .ac-rail { width: 100%; }
}

.ac-carte {
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-2);
  background: var(--nut-surface); overflow: hidden;
}
.ac-carte .ac-carte-tete {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 12px 14px; border-bottom: 1px solid var(--nut-line);
  background: var(--nut-surface-sunken);
}
.ac-carte .ac-carte-tete h2 { margin: 0; font-family: var(--nut-font-title);
  font-size: 15px; font-weight: 600; }
.ac-carte .ac-carte-tete .ac-aide { font-size: 12px; color: var(--nut-text-faint); }

/* LA LIGNE : une hauteur, partout. Les quatre listes de cet ecran — quoi de
   neuf, les pages, la table, a faire — sont la meme ligne. */
.ac-l {
  display: flex; align-items: center; gap: 10px;
  min-height: 38px; padding: 8px 14px;
  color: inherit; text-decoration: none;
}
a.ac-l:hover { background: var(--nut-surface-raised); }
.ac-carte .ac-l + .ac-l { border-top: 1px solid var(--nut-line); }
.ac-l .ac-nom { font-size: 13.5px; }
.ac-l .ac-ou { font-size: 12px; color: var(--nut-text-faint); }
.ac-l .ac-quand { margin-left: auto; font-family: var(--nut-font-data);
  font-size: 11.5px; color: var(--nut-text-faint); white-space: nowrap; }
.ac-l .ac-role { margin-left: auto; font-size: 12px; color: var(--nut-text-muted); }
.ac-l .ac-role.mj { color: var(--nut-accent); }
.ac-l .ac-fleche { margin-left: auto; color: var(--nut-text-faint); font-size: 12px; }
.ac-l .ac-nom + .ac-fleche { margin-left: auto; }
.ac-l .ac-icone { flex: none; color: var(--nut-text-faint); display: flex; }
.ac-l .ac-icone svg { width: 14px; height: 14px; }
.ac-vide { padding: 14px; font-size: 13px; color: var(--nut-text-muted); }

/* LE BROUILLON SE DIT EN MOT, PAS EN COULEUR SEULE. */
.ac-brouillon {
  padding: 1px 8px; border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font-family: var(--nut-font-data); font-size: 10.5px;
  color: var(--nut-text-muted);
}

/* L'ARBORESCENCE : deux niveaux, comme la barre laterale. La categorie est un
   intitule, pas un lien — elle ne mene nulle part, elle range. */
.ac-cat {
  padding: 10px 14px 4px; font-family: var(--nut-font-data); font-size: 10.5px;
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.ac-carte .ac-cat + .ac-l { border-top: 0; }
.ac-l.ac-sous { padding-left: 30px; }

/* LE CODE : le meme dessin que dans les reglages, pour qu'on le reconnaisse. */
.ac-code {
  display: flex; align-items: center; gap: 10px; padding: 12px 14px;
}
.ac-code .ac-code-val { font-family: var(--nut-font-data); font-size: 16px;
  letter-spacing: .12em; color: var(--nut-text); }
.ac-code-note { padding: 0 14px 12px; font-size: 12px; color: var(--nut-text-faint); }

/* LA DEMANDE EN ATTENTE, DANS LA TABLE : c'est la qu'on la traite, parce que
   c'est la qu'on regarde qui joue. */
.ac-demande {
  margin: 10px 14px 12px; padding: 10px 12px;
  border: 1px solid var(--nut-accent-dim); border-radius: var(--nut-radius-2);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
/* LE NOM PREND SA LIGNE, LES DEUX GESTES LA LEUR. Dans le rail de 340 px,
   « Refuser » retombait seul sous « Accepter » : deux boutons de meme rang
   qui ne sont pas sur le meme rang se lisent comme deux importances. */
.ac-demande .ac-nom { flex: 1 1 100%; font-size: 13px; }
.ac-demande button {
  min-height: 36px; padding: 0 12px; border-radius: var(--nut-radius-1);
  border: 1px solid var(--nut-line-strong); background: none;
  color: var(--nut-text); font-size: 12.5px; cursor: pointer;
}
.ac-demande button.ac-oui:hover { border-color: var(--nut-accent); color: var(--nut-accent); }
.ac-demande button.ac-non:hover { border-color: var(--nut-warn); color: var(--nut-warn); }

@media (max-width: 720px) {
  /* AU DOIGT, TOUTES LES CIBLES FONT 44 PX. */
  .ac-l { min-height: 44px; }
  .ac-coulisses { min-height: 44px; }
  .ac-demande button { min-height: 44px; padding: 0 14px; }
  .ac-tete { flex-wrap: wrap; }
  .ac-tete .ac-gestes { width: 100%; }
}


/* ==========================================================================
   LES REGLAGES DU MAITRE DU JEU
   Une seule ligne desormais : la visibilite. Elle precede le titre parce
   qu'elle dit DANS QUEL ETAT on lit ce qui suit — l'apprendre apres avoir lu
   serait tard.

   LE RANGEMENT N'EST PLUS ICI. Il vivait a deux endroits : ce menu et le modal
   de configuration. Deux endroits pour un meme reglage, c'est un endroit de
   trop — et c'est celui-la qu'on oublie de tenir a jour. Ranger, c'est
   organiser une campagne entiere ; ca se fait la ou on la voit en entier.
   ========================================================================== */
.reglages { margin-bottom: var(--nut-sp-6); }
/* Le changement de modele est un lien, pas un bouton : c'est un geste rare,
   sans risque immediat, et il ne doit pas concurrencer l'interrupteur de
   visibilite qui vit sur la meme ligne. Pousse a droite. */
/* L etat d'enregistrement des notes : discret, en chasse fixe, et il ne bouge
   pas de place quand son texte change de longueur. */
.etat-notes {
  margin-left: auto;
  color: var(--nut-text-faint);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
}
.ligne-reglage {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--nut-sp-4);
}
.ligne-reglage:last-child { margin-bottom: 0; }
.ligne-reglage .note {
  margin: 0; color: var(--nut-text-faint);
  font-size: var(--nut-size-2); line-height: var(--nut-lh-ui);
}

/* --- L'interrupteur de visibilite ---
   DEUX BOUTONS, PAS UNE CASE A COCHER. Une case dit « publié : oui/non » ;
   deux boutons nommes disent les deux etats possibles, et lequel est en cours.
   L'ecart de la 0.3.0 tenait a ca : une pastille verte annonce un etat, elle
   n'offre pas de le changer. */
.segmente {
  display: flex; gap: 2px; padding: 2px; flex: none;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
}
.segmente button {
  height: 28px; padding: 0 12px;
  border: 0; border-radius: 2px;
  background: transparent; color: var(--nut-text-muted);
  font: inherit; font-size: var(--nut-size-2);
  font-weight: var(--nut-weight-medium);
  cursor: pointer; white-space: nowrap;
}
.segmente button:hover { color: var(--nut-text); }
/* LE SEGMENT ACTIF PORTE L'ACCENT, et c'est la maquette qui a raison.
   Un simple fond gris disait « celui-ci est un peu different » ; l'accent dit
   « c'est celui-la ». Sur un interrupteur a deux positions, la difference
   entre les deux etats doit se voir de loin, sans les lire. Corrige le
   07/08/2026 sur signalement de Renaud, qui comparait au prototype. */
.segmente button[aria-pressed="true"] {
  background: var(--nut-accent); color: var(--nut-text-on-accent);
  font-weight: var(--nut-weight-semi);
}
.segmente button[aria-pressed="true"]:hover { color: var(--nut-text-on-accent); }
.segmente button:disabled { cursor: progress; }

/* La phrase qui accompagne l'interrupteur. Elle n'est PAS decorative : « Publié »
   seul ne dit pas a qui. Le vert est reserve a l'etat publie — c'est le seul
   endroit de l'ecran ou une couleur porte une information. */
.phrase-statut {
  display: flex; align-items: center; gap: var(--nut-sp-15);
  margin: 0; font-size: var(--nut-size-2);
  color: var(--nut-text-muted);
}
.phrase-statut svg { flex: none; }
.phrase-statut.publiee { color: var(--nut-ok); }

/* ==========================================================================
   LE TITRE
   ========================================================================== */
/* 14 px et non un jeton d'espacement : 46 (l'icone) + 14 place le titre a
   exactement 60 px du bord, ou l'attend la phrase d'aide juste dessous. Les
   deux doivent s'aligner au pixel — c'est ce qui fait lire le bloc comme un
   seul objet plutot que comme deux elements voisins. */
.tete-page {
  display: flex; align-items: flex-start; gap: 14px;
  margin-bottom: var(--nut-sp-8);
}
.tete-page h1 { margin: 0; padding: 4px 0; font-size: var(--nut-size-7); flex: 1 1 0; min-width: 0; }

/* Le bouton d'icone : 46 px, bordure POINTILLEE tant qu'aucune icone n'est
   choisie. Le pointille dit « il manque quelque chose ici, et c'est
   facultatif » — un trait plein aurait l'air d'un champ vide, donc d'un
   oubli. */
.bouton-icone {
  width: 46px; height: 46px; flex: none; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px dashed var(--nut-line);
  border-radius: var(--nut-radius-2);
  color: var(--nut-text-faint);
  cursor: pointer;
}
/* L'icone est SERIALISEE en 20 px pour la grille du selecteur ; ici elle en
   fait 26. Le viewBox la met a l'echelle sans la redessiner — une seule
   source, deux tailles. */
.bouton-icone svg, .icone-lecture svg { width: 26px; height: 26px; }
.bouton-icone:hover { border-color: var(--nut-line-strong); color: var(--nut-text); }
.bouton-icone.choisie { border-style: solid; color: var(--nut-text-muted); }
/* Le lecteur voit l'icone, sans bouton : elle l'informe, il ne la change pas. */
.icone-lecture {
  width: 46px; height: 46px; flex: none;
  display: flex; align-items: center; justify-content: center;
  color: var(--nut-text-muted);
}

/* Le titre se modifie SUR PLACE, dans la typographie ou il sera lu. Un champ
   de saisie separe, plus haut dans un formulaire, oblige a imaginer le
   resultat. Le decalage de -8 px compense le rembourrage pour que le texte
   reste exactement a la meme place, champ ou pas. */
/* SELECTEUR « input.champ-titre » ET NON « .champ-titre », et ce n'est pas du
   zele. styles.js declare une regle sur input[type="text"] : hauteur 40 px,
   rembourrage 12 px, taille 14 px, fond releve. Deux selecteurs, donc plus
   specifique qu'une simple classe. Ma regle etait ecrasee en silence : le titre
   s'affichait en 14 px dans un cadre de formulaire, au lieu des 28 px de la
   maquette. Constate en MESURANT la taille calculee ; a l'oeil, on voit juste
   un ecran un peu terne sans savoir pourquoi. */
input.champ-titre {
  width: 100%; height: auto; padding: 4px 8px; margin-left: -8px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text);
  font-family: var(--nut-font-title);
  font-size: var(--nut-size-7);
  font-weight: var(--nut-weight-semi);
  line-height: 1.2;
}
input.champ-titre:hover { border-color: var(--nut-line); background: var(--nut-surface); }
input.champ-titre:focus { border-color: var(--nut-line-strong); background: var(--nut-surface); }
.zone-titre { flex: 1 1 0; min-width: 0; }
/* Le titre ne porte plus de phrase d'aide. Elle expliquait ce que le champ
   montre deja — un titre dans lequel on peut ecrire — et annoncait une
   promesse que l'interface tient sans avoir a la dire. Renaud, 07/08 : la
   retirer. La marge basse qu'elle portait revient au bloc du titre. */

/* ==========================================================================
   LE CORPS
   ========================================================================== */
/* LE FILET. C'est lui qui separe les reglages du texte, et il est POINTILLE :
   il marque une limite sans peser comme une bordure de bloc. Sans lui, le
   texte de la page commencait dans le prolongement du titre et l'ecran n'avait
   plus d'articulation. */
.corps-page {
  max-width: var(--nut-read-max);
  padding-top: var(--nut-sp-6);
  border-top: 1px dashed var(--nut-line);
  font-size: var(--nut-size-4); line-height: var(--nut-lh-text);
}
/* Le premier element du corps colle au filet. Sans cette regle, un texte qui
   commence par un titre ajoutait ses 32 px de marge haute a la gouttiere du
   filet : le corps paraissait detache de sa page. */
.corps-page > *:first-child { margin-top: 0; }
/* « .corps-page h2/h3 » ont disparu le 09/08/2026 : le vocabulaire clos n'a
   que p / ul / ol, et plus rien ne fabrique de titre interne. Les regles sont
   retirees plutot que gardees « au cas ou » — une feuille pleine de selecteurs
   sans element est une feuille qu'on n'ose plus toucher. */
.corps-page p { margin: 0 0 var(--nut-sp-4); }
.corps-page ul { margin: 0 0 var(--nut-sp-4); padding-left: var(--nut-sp-6); }
.corps-page li { margin-bottom: var(--nut-sp-1); }
.corps-page b { font-weight: var(--nut-weight-semi); color: var(--nut-text); }
.corps-page i { font-style: italic; }
.corps-page u { text-decoration: underline; text-underline-offset: 2px; }
.corps-page ol { margin: 0 0 var(--nut-sp-4); padding-left: var(--nut-sp-6); }
.corps-page .vide { color: var(--nut-text-faint); font-size: var(--nut-size-4); }

.actions-corps { display: flex; gap: var(--nut-sp-2); margin-top: var(--nut-sp-4); }

/* --- Edition du corps --- */
/* --- L EDITEUR RICHE DE LA PAGE ---
   Meme moteur que les fiches (« editeur.js »), habillage propre a cet ecran.
   Les classes ne sont PAS partagees : le module n'en pose aucune, et deux
   feuilles qui declareraient « .lore-editeur » se marcheraient dessus. */
.page-outils {
  display: flex; align-items: center; gap: var(--nut-sp-1);
  margin-bottom: var(--nut-sp-2);
  padding-bottom: var(--nut-sp-2);
  border-bottom: 1px solid var(--nut-line);
}
.page-outils button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  background: none; border: 1px solid transparent;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); cursor: pointer;
}
.page-outils button:hover {
  background: var(--nut-surface-raised); color: var(--nut-text);
}
.page-outils button:focus-visible {
  outline: 2px solid var(--nut-accent); outline-offset: 1px;
}
.page-outils .filet {
  width: 1px; height: 18px; margin: 0 var(--nut-sp-2);
  background: var(--nut-line);
}
/* Le compteur pousse a droite : il informe, il ne commande rien. */
.page-compteur {
  margin-left: auto;
  color: var(--nut-text-faint);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
}
.page-compteur.pres { color: var(--nut-accent); }

.page-editeur {
  min-height: 320px; padding: var(--nut-sp-3) 0;
  color: var(--nut-text);
  font-size: var(--nut-size-4); line-height: var(--nut-lh-text);
  outline: none;
}
/* LES BOUTONS DE TABLEAU SONT MASQUES HORS D UN TABLEAU. Voir
   « surveillerTableau » dans editeur.js : l'attribut suit le curseur. */
.page-outils .si-tableau { display: none; gap: var(--nut-sp-1); }
.page-outils[data-tableau="oui"] .si-tableau { display: inline-flex; }

/* Le tableau, dans l'editeur comme en lecture. Les bordures sont FUSIONNEES :
   sans cela chaque cellule porte la sienne et la grille double d'epaisseur. */
.page-editeur table, .corps-page table {
  width: 100%; margin: 0 0 var(--nut-sp-4);
  border-collapse: collapse;
  font-size: var(--nut-size-3);
}
.page-editeur th, .page-editeur td,
.corps-page th, .corps-page td {
  padding: var(--nut-sp-2) var(--nut-sp-3);
  border: 1px solid var(--nut-line);
  text-align: left; vertical-align: top;
}
.page-editeur th, .corps-page th {
  background: var(--nut-surface);
  color: var(--nut-text); font-weight: var(--nut-weight-semi);
}
/* La cellule ou l'on ecrit se signale : dans une grille, savoir ou l'on est
   vaut mieux qu'un curseur clignotant de deux pixels. */
.page-editeur th:focus, .page-editeur td:focus { outline: none; }
.page-editeur table:focus-within th, .page-editeur table:focus-within td {
  border-color: var(--nut-line-strong);
}
.page-editeur p { margin: 0 0 var(--nut-sp-4); }
.page-editeur ul, .page-editeur ol { margin: 0 0 var(--nut-sp-4); padding-left: var(--nut-sp-6); }
.page-editeur li { margin-bottom: var(--nut-sp-1); }
/* L INVITE N EST PAS UN CONTENU : c'est un « ::before », donc rien a effacer
   avant d'ecrire, et rien que la relecture puisse prendre pour du texte. */
.page-editeur[data-vide="oui"]::before {
  content: 'Écris ici.';
  color: var(--nut-text-faint);
  pointer-events: none;
}

.editeur {
  display: block; width: 100%; min-height: 55vh;
  padding: var(--nut-sp-4);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2);
  color: var(--nut-text);
  font-family: var(--nut-font-body);
  font-size: var(--nut-size-4);
  line-height: var(--nut-lh-text);
  resize: vertical;
}
.barre-edition {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  margin-top: var(--nut-sp-3);
}
.barre-edition .etat {
  margin-left: auto; padding: 0;
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
}
/* « .aide-format » a disparu avec la notice qu'elle habillait : la mise en
   forme se fait a la barre d'outils, plus a la syntaxe. */

/* --- La carte « Nouveau modèle » ---
   Elle annonce ou va le produit. Elle est ANNONCEE COMME INERTE : la mention
   « à venir » et la phrase sous le titre disent que cliquer ne produira rien.
   Une carte qui promet et ne repond pas coute plus cher qu'une absence. */
/* La phrase qui dit ou s'administrent les pages. Elle est posee AU-DESSUS de
   la carte des modeles, entre le titre et elle : c'est une indication sur la
   campagne, pas une consigne sur le choix en cours. Discrete par nature — on
   la lit une fois, on ne la relit jamais. */
.ou-administrer {
  width: 100%; max-width: 820px;
  margin: var(--nut-sp-6) 0 0;
  font-size: 13px; color: var(--nut-text-muted);
}
.carte-modeles {
  display: block; width: 100%; max-width: 820px;
  margin-top: var(--nut-sp-4);
  padding: var(--nut-sp-4) var(--nut-sp-6);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
  text-align: left;
}
.carte-modeles .tete {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  margin-bottom: var(--nut-sp-2);
  font-family: var(--nut-font-title); font-size: var(--nut-size-4);
  font-weight: var(--nut-weight-semi);
}
/* La pastille « À VENIR » a disparu en 0.12.0 avec la carte inerte : le modele
   Lore repond, la carte n'annonce donc plus une absence globale. Ce qui reste a
   construire est dit en toutes lettres dans la phrase du bas. */
.carte-modeles .bouton-secondaire {
  display: inline-flex; align-items: center; height: 34px;
  margin-top: var(--nut-sp-3);
  border-radius: var(--nut-radius-1);
  font-size: var(--nut-size-2);
}
.carte-modeles .quoi strong {
  color: var(--nut-text); font-weight: var(--nut-weight-semi);
}
.carte-modeles .quoi {
  display: block; color: var(--nut-text-muted);
  font-size: var(--nut-size-3); line-height: var(--nut-lh-text);
}
/* LE CHOIX EN DEUX TEMPS (sprint 6). Trois tuiles de categorie sur une ligne ;
   la tuile choisie ouvre une grille de modeles SOUS elle, sur la meme carte.
   AUCUNE categorie n'est mise en avant : l'encart ne s'affiche que lorsqu'aucun
   modele n'a ete choisi, et suggerer une reponse a une question qu'on vient de
   poser revient a ne pas la poser. */
.tete-choix {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--nut-sp-4);
}
.cats {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--nut-sp-3); margin-top: var(--nut-sp-4);
}
.cats .cat {
  display: block; text-align: left; cursor: pointer;
  padding: var(--nut-sp-3) var(--nut-sp-4);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
  color: inherit; font: inherit;
}
.cats .cat[aria-selected="true"] { border-color: var(--nut-accent); }
.cats .cat:hover { border-color: var(--nut-text-faint); }
.cats .cat[aria-selected="true"]:hover { border-color: var(--nut-accent); }
/* La vignette carree reprend la grammaire des monogrammes de la barre
   laterale : deux lettres, coin coupe, teintee par la CATEGORIE — les jetons
   de campagne existants, aucun nouveau. */
.carte-modeles .vignette {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; margin-bottom: var(--nut-sp-2);
  border-radius: var(--nut-radius-1);
  background: var(--nut-surface-sunken); color: var(--nut-text-muted);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  font-weight: var(--nut-weight-semi);
  clip-path: polygon(6px 0, 100% 0, 100% 100%, 0 100%, 0 6px);
}
.carte-modeles .v-ecrire     { background: color-mix(in srgb, var(--nut-camp-1) 28%, var(--nut-surface-sunken)); color: var(--nut-camp-1); }
.carte-modeles .v-recueils   { background: color-mix(in srgb, var(--nut-camp-3) 28%, var(--nut-surface-sunken)); color: var(--nut-camp-3); }
.carte-modeles .v-mecaniques { background: color-mix(in srgb, var(--nut-camp-4) 28%, var(--nut-surface-sunken)); color: var(--nut-camp-4); }
.cats .nom {
  display: block; color: var(--nut-text);
  font-family: var(--nut-font-title);
  font-size: var(--nut-size-3); font-weight: var(--nut-weight-semi);
}
.cats .dit {
  display: block; margin-top: var(--nut-sp-1);
  color: var(--nut-text-muted);
  font-size: var(--nut-size-2); line-height: var(--nut-lh-text);
}
.cats .compte {
  display: block; margin-top: var(--nut-sp-2);
  color: var(--nut-text-faint);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
}
.cats .compte.accent { color: var(--nut-accent); }
.grille-modeles {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: var(--nut-sp-3); margin-top: var(--nut-sp-4);
  padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
}
.choix-modele {
  display: flex; align-items: flex-start; gap: var(--nut-sp-3);
  text-align: left; padding: var(--nut-sp-3) var(--nut-sp-4);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
  color: inherit; font: inherit;
}
.choix-modele .vignette { margin-bottom: 0; flex: none; }
button.choix-modele { cursor: pointer; }
button.choix-modele:hover { border-color: var(--nut-accent); }
.choix-modele .nom {
  display: block; color: var(--nut-text);
  font-size: var(--nut-size-3); font-weight: var(--nut-weight-semi);
}
.choix-modele .etiquette {
  margin-left: var(--nut-sp-1);
  color: var(--nut-text-faint);
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  font-weight: var(--nut-weight-regular);
  letter-spacing: 0.08em; text-transform: uppercase;
}
.choix-modele .dit {
  display: block; margin-top: var(--nut-sp-1);
  color: var(--nut-text-muted);
  font-size: var(--nut-size-2); line-height: var(--nut-lh-text);
}
/* L'A-VENIR est grise ET libelle : l'absence n'est pas signalee par la couleur
   seule, l'etiquette la porte. */
.choix-modele.a-venir { opacity: 0.55; }
.choix-modele.a-venir .vignette { background: var(--nut-surface-sunken); color: var(--nut-text-faint); }
.carte-modeles.repli {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--nut-sp-4);
}
@media (max-width: 900px) {
  .cats, .grille-modeles { grid-template-columns: 1fr; }
}
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  /* Les 40 px du pointeur fin ne suffisent pas au doigt. */
  .grille-icones button { height: var(--nut-target-touch); }
}
.carte-modeles .pourquoi {
  display: block; margin-top: var(--nut-sp-15);
  color: var(--nut-text-faint);
  font-size: var(--nut-size-2); line-height: var(--nut-lh-ui);
}

/* ==========================================================================
   LE SELECTEUR D'ICONES
   ========================================================================== */
.modal.icones { max-width: 520px; }
.champ-recherche {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  height: 38px; padding: 0 var(--nut-sp-3);
  margin-bottom: var(--nut-sp-3);
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
}
/* LE HALO DE FOCUS APPARTIENT AU CADRE, PAS AU CHAMP. Pose sur le champ, il
   se dessinait A L'INTERIEUR du cadre arrondi et debordait par-dessus : deux
   boites imbriquees, dont une orange. Ici c'est le cadre entier qui s'allume
   quand le champ prend le focus — un seul objet a l'ecran, comme il n'y a
   qu'un seul objet dans l'intention. */
.champ-recherche:focus-within { border-color: var(--nut-accent-hi); }
.champ-recherche input:focus { outline: none; }
.champ-recherche svg { flex: none; color: var(--nut-text-faint); }
.champ-recherche input[type="text"] {
  flex: 1 1 0; min-width: 0; width: auto; height: 100%;
  padding: 0;
  background: transparent; border: 0;
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
}
.puces-categories { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--nut-sp-4); }
.puces-categories button {
  height: 28px; padding: 0 10px;
  background: transparent;
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-pill);
  color: var(--nut-text-muted);
  font: inherit; font-size: var(--nut-size-2); cursor: pointer;
}
.puces-categories button:hover { color: var(--nut-text); border-color: var(--nut-text-faint); }
.puces-categories button[aria-pressed="true"] {
  background: var(--nut-surface-raised);
  border-color: var(--nut-line-strong);
  color: var(--nut-text);
}
.grille-icones { max-height: 300px; overflow: auto; }
.grille-icones .groupe { margin-bottom: var(--nut-sp-4); }
.grille-icones .groupe-titre {
  margin: 0 0 var(--nut-sp-2);
  color: var(--nut-text-faint); font-size: 11px;
  font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
}
/* LA GRILLE D'ICONES SE COMPTE TOUTE SEULE (16/08/2026).
   Elle etait figee a huit colonnes, avec une exception a 560 px qui la
   ramenait a cinq. L'exception est morte avec la frontiere unique de la
   v0.71.0, et la grille s'est retrouvee a huit colonnes sur un ecran de
   353 px : des cases de 38 px, sous le minimum tactile. « auto-fill » avec un
   plancher a la taille de la cible resout les deux d'un coup — le nombre de
   colonnes devient une CONSEQUENCE de la largeur disponible, plus une valeur
   a maintenir en deux endroits. */
.grille-icones .cases {
  display: grid; gap: 6px;
  grid-template-columns: repeat(auto-fill, minmax(var(--nut-target-touch), 1fr));
}
.grille-icones button {
  height: 40px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); cursor: pointer;
}
.grille-icones button:hover { border-color: var(--nut-line-strong); color: var(--nut-text); }
.grille-icones button[aria-pressed="true"] {
  background: var(--nut-surface-raised);
  border-color: var(--nut-accent-hi);
  color: var(--nut-text);
}
.pied-icones {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  padding: 14px 20px;
  background: var(--nut-surface-sunken);
  border-top: 1px solid var(--nut-line);
  border-radius: 0 0 var(--nut-radius-3) var(--nut-radius-3);
}
.pied-icones .note {
  flex: 1 1 0; min-width: 120px; margin: 0;
  color: var(--nut-text-faint); font-size: var(--nut-size-2);
  line-height: var(--nut-lh-ui);
}
/* Les deux boutons gardent leur largeur naturelle et ne se cassent pas en
   deux lignes : c'est la phrase, a gauche, qui absorbe ce qui reste. */
.pied-icones .primaire, .pied-icones .bouton-secondaire {
  flex: none; height: 34px; padding: 0 14px;
  display: inline-flex; align-items: center;
  font-size: var(--nut-size-2); white-space: nowrap;
  border-radius: var(--nut-radius-1);
}

/* Etiquette lue par les seuls lecteurs d'ecran. */
.invisible {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}


/* ------------------------------------------------------------------------
   L'ECRAN

   LARGEUR BORNEE A 940 px, la meme que le modal qu'il remplace. Les deux
   colonnes sont devenues deux onglets : plus rien n'oblige a tenir cote a
   cote, et une ligne de formulaire qui court sur 1200 px se lit mal — l'oeil
   perd l'etiquette avant d'atteindre le champ.
   ------------------------------------------------------------------------ */
#parametres {
  max-width: 940px;
  /* LE REMBOURRAGE DU PANNEAU EST UNE VARIABLE, ET PAS UN NOMBRE REPETE.
     Il vaut 22 px, 16 sous le palier etroit — et le builder, qui remplit le
     panneau de bord a bord, doit l'ANNULER exactement. Ecrit deux fois, il
     s'est desynchronise au premier changement de palier : le builder
     debordait de 7 px a 900, un defaut invisible partout ailleurs et que
     seule la mesure a trouve. Une valeur, un endroit. */
  --param-pad: 22px;
}

.param-tete { margin-bottom: 18px; }
.param-tete h1 { margin: 0 0 2px; }
.param-tete .aide { margin: 0; }

/* --- LES ONGLETS ---------------------------------------------------------
   Un filet continu sous la rangee, et l'onglet courant le rompt : c'est le
   dessin qui dit « ce panneau est la suite de cet onglet » sans qu'on ait a
   suivre une couleur. Il tient donc aussi pour qui ne distingue pas l'accent.

   AUCUN DEFILEMENT HORIZONTAL ICI. Trois onglets courts tiennent a 353 px ;
   une barre qui defile cacherait le troisieme sans le dire. (Le chiffre exact
   est mesure plus bas, au harnais — pas devine ici.) */
.param-onglets {
  display: flex; gap: 2px;
  border-bottom: 1px solid var(--nut-line);
  margin-bottom: 0;
}
.param-onglet {
  flex: none;
  /* 44 px : la cible tactile, et pas une valeur choisie a l'oeil. */
  height: 44px; padding: 0 16px;
  background: transparent; border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--nut-text-muted);
  font: inherit; font-size: var(--nut-size-3);
  cursor: pointer;
}
.param-onglet:hover { color: var(--nut-text); }
.param-onglet[aria-selected="true"] {
  color: var(--nut-text);
  font-weight: var(--nut-weight-semi);
  border-bottom-color: var(--nut-accent);
}

/* --- LE PANNEAU ---------------------------------------------------------- */
.param-panneau {
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-top: 0;
  border-radius: 0 0 var(--nut-radius-3) var(--nut-radius-3);
}
.param-dedans { padding: var(--param-pad); }

/* LE PIED NE COLLE PAS AU FOND DE LA FENETRE, contrairement a celui du modal.
   Le modal bornait sa hauteur a 66vh et gardait ses boutons visibles ; un
   ecran defile normalement, et un pied fixe y mangerait une bande de hauteur
   sur toutes les tailles pour un geste qu'on fait une fois. On descend pour
   enregistrer, comme dans tout formulaire. */
.param-pied {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  padding: 14px var(--param-pad);
  background: var(--nut-surface-sunken);
  border-top: 1px solid var(--nut-line);
  border-radius: 0 0 var(--nut-radius-3) var(--nut-radius-3);
}
.param-etat { flex: 1 1 0; min-width: 0; font-size: 13px; color: var(--nut-text-faint); }

/* LA HAUTEUR DES BOUTONS DU PIED, QUI NE VIENT PAS TOUTE SEULE.
   Le modal la tenait par « .modal-pied .primaire », une regle que ce pied-ci
   n'herite pas. Au premier rendu, « Annuler » mesurait 83 × 21 — un bouton de
   la moitie de la taille de son voisin, a cote du geste le plus important de
   l'ecran. Trouve par le harnais, pas a la relecture : les cinq criteres
   etaient verts, seule la mesure des cibles l'a dit.
   38 px comme partout ailleurs dans le produit. C'est sous les 44 px de la
   cible tactile — comme les boutons de modal, les fleches de l'arborescence
   et les pastilles de teinte : c'est une convention du produit entier, pas
   une decision prise ici, et elle se corrigera partout ou nulle part. */
.param-pied .primaire, .param-pied .bouton-secondaire,
.param-carte .bouton-secondaire {
  height: 38px; padding: 0 18px;
  display: inline-flex; align-items: center;
  font-size: 14px; border-radius: var(--nut-radius-1);
}
.param-pied .primaire { font-weight: var(--nut-weight-semi); }

/* La carte d'identite du systeme : un encadre creuse, comme les blocs de code
   d'invitation, pour dire « ceci se lit, ne se remplit pas ». */
.param-carte {
  padding: 14px; margin-top: 12px;
  background: var(--nut-surface-sunken);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
}


/* --- LES REGLAGES EN CARTES, DEUX COLONNES A 1280 (sprint 13, 29/08/2026) --
 *
 * L'onglet etait une colonne unique de treize sections empilees : il fallait
 * defiler pour savoir ce qu'il contenait, et rien ne disait ou finissait une
 * question et ou commencait la suivante. Six cartes, deux familles : ce qui
 * decrit la campagne a gauche, ce qui decide qui y entre a droite.
 *
 * LE SEUIL EST A 1000 px DE FENETRE. Une media query mesure la FENETRE, pas
 * la colonne : a 1280 de fenetre, la zone de contenu ne fait qu'environ
 * 1040 px une fois la barre laterale deduite, mais c'est bien 1280 que la
 * regle compare. 1000 laisse donc les deux colonnes aux ecrans larges et les
 * empile des 900 — la largeur ou la barre laterale se retire. */
.param-cartes { display: flex; flex-direction: column; gap: 14px; }
.param-cartes .param-col { display: flex; flex-direction: column; gap: 14px; }
@media (min-width: 1000px) {
  .param-cartes { flex-direction: row; align-items: flex-start; }
  .param-cartes .param-col { flex: 1 1 0; min-width: 0; }
}
.param-bloc {
  padding: 16px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2); background: var(--nut-surface);
}
.param-bloc > .titre-bloc:first-child { margin-top: 0; }

/* LA CARTE DU SYSTEME : le dessin de la creation, filet accent compris.
   C'est le seul encadre accent de cet onglet — il designe la seule decision
   qu'on ne peut pas reprendre. */
.param-syst {
  padding: 14px; border: 1px solid var(--nut-accent);
  border-radius: var(--nut-radius-2); background: var(--nut-surface-sunken);
}
.param-syst-tete { display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; margin: 0; }
.param-syst-nom { font-family: var(--nut-font-title);
  font-size: var(--nut-size-4); font-weight: var(--nut-weight-semi); }
.param-syst-pastille {
  padding: 2px 9px; border: 1px solid var(--nut-accent);
  border-radius: var(--nut-radius-pill);
  font-family: var(--nut-font-data); font-size: 11px; color: var(--nut-accent);
}
.param-syst-dit { margin: 8px 0 0; font-size: 13.5px; color: var(--nut-text-muted); }
/* LA PHRASE QUI DIT « C'EST DEFINITIF » EST EN ACCENT, et c'est le seul
   endroit de l'ecran ou une aide l'est : elle porte un fait irreversible. */
.param-syst-fixe { margin: 10px 0 14px; font-size: 13px; color: var(--nut-accent); }

/* LE DECROCHAGE DES REGLES (0.95.0). Dans la carte du systeme, sous sa
   description : c'est un fait sur les regles de CETTE campagne, il se lit ou
   on lit le systeme. Le liseré le distingue de la description sans en faire
   une alarme — la campagne fonctionne, elle ne recoit simplement plus les
   mises a jour. */
.param-syst-derive { margin-top: 12px; padding: 10px 12px;
  border-left: 2px solid var(--nut-warn); background: var(--nut-surface); }
.param-syst-derive .dit { margin: 0 0 10px; font-size: 13px;
  color: var(--nut-text-muted); }
.param-syst-derive .rangee { display: flex; align-items: center;
  gap: var(--nut-sp-3); flex-wrap: wrap; }
/* LA PHRASE QUI DIT CE QU'ON PERD EST A COTE DU BOUTON, pas dans son
   infobulle : un geste qui ecrase doit annoncer ce qu'il ecrase avant qu'on
   s'approche, pas quand on est deja dessus. */
.param-syst-derive .prevenir { font-size: 12px; color: var(--nut-warn); }

/* PAS DE RETOUCHE AUX PASTILLES DE TEINTE ICI, ET C'EST LA GARDE QUI L'A
   DECIDE. Dans la colonne de gauche, les huit pastilles se replient a sept
   plus une. J'ai voulu resserrer leur ecart depuis cette feuille — le test
   des emprunts de classes l'a refuse : « .teintes » est declaree par
   « campagne.js », et une feuille qui retouche la classe d'une autre cree un
   endroit ou le meme composant n'a pas la meme forme selon l'ecran.
   Le repli n'est pas un defaut : le nom de la couleur s'affiche sous le
   groupe entier, pas sous la derniere pastille. Une exception dans la liste
   des emprunts connus aurait achete une coquetterie au prix d'une regle. */

@media (max-width: 900px) {
  #parametres { --param-pad: 16px; }
  .param-onglet { padding: 0 11px; }
}

/* Une valeur qu'on lit sans pouvoir la changer. Elle n'est pas grisee comme un
   champ desactive : ce n'est pas un champ, c'est un fait. */
.valeur-fixe {
  margin: 0 0 6px;
  font-family: var(--nut-font-title);
  font-size: var(--nut-size-4); font-weight: var(--nut-weight-semi);
}
.ligne-valeur {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 8px;
}
.valeur-data { font-family: var(--nut-font-data); font-size: 15px; }
.valeur-code {
  font-family: var(--nut-font-data); font-size: 19px;
  letter-spacing: .08em; color: var(--nut-text);
}
.aide-en-ligne { font-size: 13px; color: var(--nut-text-faint); }

.bouton-copier {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 11px;
  background: transparent;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted);
  font: inherit; font-size: 13px; cursor: pointer;
}
.bouton-copier:hover { color: var(--nut-text); border-color: var(--nut-text-faint); }
.bouton-copier svg { color: inherit; }

/* --- L'arborescence ------------------------------------------------------
   Une ligne par element, en deux etages : le nom au-dessus, ce qui agit
   dessous. La maquette fait ainsi, et c'est ce qui permet a un titre long de
   rester lisible sans ecraser le menu de categorie. */
/* --- LA SAISIE D'UNE LIGNE (sprint 13, v3b — 29/08/2026) -------------------
 *
 * La poignee au bord gauche, les cibles en pointille pendant la saisie. Le
 * pointille dit « ceci n'est pas une ligne, c'est une place » sans un mot de
 * plus — et il ne ressemble a rien d'autre dans cette feuille.
 */
.arbo-poignee {
  flex: none; width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 1px solid transparent;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: grab;
}
.arbo-poignee:hover { border-color: var(--nut-line); color: var(--nut-text-muted); }
.arbo-poignee.saisie {
  border-color: var(--nut-accent); color: var(--nut-accent); cursor: grabbing;
}

/* LE BANDEAU DE SAISIE dit QUOI on deplace et offre la sortie. Sans le nom,
   une liste entiere en pointille ne dit pas ce qu'on est en train de poser. */
.arbo-saisie-tete {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 8px; padding: 9px 12px;
  border: 1px solid var(--nut-accent); border-radius: var(--nut-radius-1);
  background: var(--nut-surface-sunken);
}
.arbo-saisie-tete .quoi { flex: 1 1 0; min-width: 0; font-size: 13px; color: var(--nut-text); }
.arbo-annuler {
  flex: none; min-height: 32px; padding: 0 12px;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  background: none; color: var(--nut-text-muted); font: inherit; font-size: 12.5px;
  cursor: pointer;
}
.arbo-annuler:hover { border-color: var(--nut-text-faint); color: var(--nut-text); }

/* LA CIBLE : pleine largeur, pointillee, et elle DIT ou elle depose. */
.arbo-cible {
  display: block; width: 100%; min-height: 34px; margin-bottom: 4px;
  padding: 0 12px;
  border: 1px dashed var(--nut-line-strong); border-radius: var(--nut-radius-1);
  background: none; color: var(--nut-text-faint);
  font: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
}
.arbo-cible:hover, .arbo-cible:focus-visible {
  border-color: var(--nut-accent); color: var(--nut-accent);
  background: var(--nut-surface-sunken);
}

/* LES LIGNES RESTENT VISIBLES PENDANT LA SAISIE, et deviennent inertes : les
   cacher ferait perdre le repere qu'on est venu chercher — on deplace par
   rapport a ce qu'on voit. */
.arbo-item.inerte { opacity: .5; }
.arbo-item.inerte.saisie {
  opacity: 1; border-color: var(--nut-accent);
  box-shadow: inset 2px 0 0 0 var(--nut-accent);
}

@media (max-width: 900px) {
  .arbo-poignee { width: var(--nut-target-touch); height: var(--nut-target-touch); }
  .arbo-cible { min-height: var(--nut-target-touch); }
  .arbo-annuler { min-height: var(--nut-target-touch); }
}

/* --- LA LIGNE D'ARBORESCENCE, D'UNE SEULE HAUTEUR (sprint 13, 29/08/2026) --
 *
 * Elle tenait sur DEUX rangs — nom et statut au-dessus, categorie et gestes
 * en dessous — soit environ 76 px par entree. Une arborescence de douze pages
 * demandait de defiler pour lire un ordre, alors que l'ordre est precisement
 * ce qu'on vient y regarder.
 *
 * ⚠ LE SCRIPT N'A PAS BOUGE, ET C'EST VOULU. Les deux moities existaient deja
 * comme conteneurs (« .arbo-tete » et « .arbo-pied ») ; il suffit que la ligne
 * les pose cote a cote au lieu de les empiler. Reecrire « lignePage » aurait
 * remis en jeu quatre gestes qui marchent — le renommage, la bascule de
 * publication, le rangement en categorie, la suppression — pour une question
 * de mise en page.
 *
 * LA POIGNEE DE SAISIE VIENT ENSUITE, dans sa propre version : arbitrage de
 * Renaud, « on livre d'abord la structure avec les flèches ↑↓ seules ». */
.arbo-item {
  display: flex; align-items: center; gap: 8px;
  min-height: 38px; padding: 5px 9px; margin-bottom: 4px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
}
/* Une page rangee dans une categorie est DECALEE : c'est le seul signal de
   hierarchie de cette colonne, et il doit se voir sans lire. */
.arbo-item.dedans { margin-left: 16px; }
/* SOUS 720, LA LIGNE SE REPLIE PLUTOT QUE DE SE COMPRIMER. Six controles sur
   353 px ne tiennent pas ; les tasser rendrait chaque cible plus petite que le
   doigt, ce qui est pire que deux rangs. */
@media (max-width: 900px) {
  .arbo-item { flex-wrap: wrap; }
  .arbo-item .arbo-tete { flex: 1 1 100%; }
}
.arbo-tete { flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; gap: 7px; }
.arbo-tete .cfg-marque { flex: none; color: var(--nut-text-faint); display: flex; }
.arbo-tete .nom {
  flex: 1 1 0; min-width: 0; font-size: 13px; color: var(--nut-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Le nom d'une categorie se modifie SUR PLACE. Sans cela, une categorie creee
   depuis la barre laterale resterait « Nouvelle categorie » a vie : rien
   d'autre, dans l'application, ne permet de la renommer. */
.arbo-tete input.nom {
  height: 26px; padding: 0 6px;
  background: var(--nut-surface);
  border: 1px solid transparent;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: 13px;
}
.arbo-tete input.nom:hover { border-color: var(--nut-line); }
.arbo-tete input.nom:focus { border-color: var(--nut-line-strong); }
.etiquette-cat {
  flex: none; padding: 1px 7px;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font-size: 11px; color: var(--nut-text-muted);
}
/* --- L'interrupteur de publication ---
   Il a la FORME de l'etiquette voisine, parce qu'il occupe la meme place et
   dit la meme chose — mais il est un bouton, et doit s'annoncer comme tel :
   d'ou le survol qui reagit et l'anneau de focus. Une pastille qui se clique
   sans jamais le montrer est une commande cachee.

   L'accent est reserve a l'etat PUBLIE. C'est le seul des deux qui engage
   quelqu'un d'autre que le MJ : le brouillon est l'etat de repos, il se lit
   en gris comme le reste. */
.arbo-statut {
  flex: none; padding: 1px 7px;
  background: transparent;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font: inherit; font-size: 11px; color: var(--nut-text-muted);
  cursor: pointer;
}
.arbo-statut:hover:not(:disabled) { border-color: var(--nut-text-muted); color: var(--nut-text); }
.arbo-statut:focus-visible { outline: var(--nut-focus-ring); outline-offset: 2px; }
.arbo-statut.publiee { border-color: var(--nut-accent); color: var(--nut-accent); }
/* Une page sans modele ne se publie pas : le bouton reste, desactive, pour
   que la raison se lise a l'endroit meme ou le geste manque. */
.arbo-statut.vide { cursor: not-allowed; opacity: .55; }
/* LE PIED N'EST PLUS UN PIED : c'est la moitie droite de la ligne. Il garde
   son nom, parce que le renommer obligerait a toucher au script — et que le
   script, ici, est ce qu'on ne veut surtout pas remuer. */
.arbo-pied { flex: none; display: flex; align-items: center; gap: 6px; }
.arbo-pied select {
  flex: none; width: 150px; min-width: 0; height: 28px; padding: 0 6px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: 13px;
}
.arbo-pied .meta {
  flex: none; min-width: 0; font-size: 13px; color: var(--nut-text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.arbo-pied .fleche, .arbo-pied .retirer {
  flex: none; width: 28px; height: 28px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: transparent;
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); cursor: pointer;
}
.arbo-pied .fleche:hover, .arbo-pied .retirer:hover {
  border-color: var(--nut-text-faint); color: var(--nut-text);
}
/* Une fleche inutilisable reste VISIBLE, pas cachee : sa disparition ferait
   sauter la ligne d'un element a l'autre. */
.arbo-pied .fleche[disabled] {
  color: var(--nut-line-strong); border-color: var(--nut-line); cursor: default;
}
.arbo-pied .retirer:hover { color: var(--nut-warn); border-color: var(--nut-warn); }
.arbo-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.arbo-actions .bouton-secondaire {
  height: 32px; padding: 0 12px;
  display: inline-flex; align-items: center; gap: 7px; font-size: 13px;
}
.arbo-vide {
  margin: 0 0 10px; padding: 12px 0;
  color: var(--nut-text-faint); font-size: 13px; line-height: 1.6;
}

/* --- Participants et demandes --- */
/* Les resultats de la recherche de membres. Une liste courte, sous le champ,
   qui n'existe que pendant la frappe — elle ne prend donc pas de place quand
   elle est vide, sinon le bloc respirerait en permanence pour rien. */
#config-inviter-resultats:empty { display: none; }
#config-inviter-resultats {
  margin-top: var(--nut-sp-2);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1);
  background: var(--nut-surface);
  overflow: hidden;
}
.ligne-trouve {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  width: 100%; padding: 7px var(--nut-sp-3);
  background: none; border: 0; border-bottom: 1px solid var(--nut-line);
  color: var(--nut-text); font: inherit; font-size: 13px;
  text-align: left; cursor: pointer;
}
.ligne-trouve:last-child { border-bottom: 0; }
.ligne-trouve:hover { background: var(--nut-surface-raised); }
.ligne-trouve .geste { margin-left: auto; color: var(--nut-text-faint); font-size: 11.5px; }
.ligne-trouve:hover .geste { color: var(--nut-accent); }
/* Ni resultat ni erreur : une phrase, pas une ligne cliquable. */
.aucun-trouve { padding: 7px var(--nut-sp-3); color: var(--nut-text-faint); font-size: 12.5px; }
.ligne-membre {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--nut-line);
  font-size: 13px;
}
.ligne-membre:last-child { border-bottom: 0; }
.ligne-membre .position { margin-left: auto; color: var(--nut-text-faint); }
.ligne-membre .mini {
  flex: none; height: 28px; padding: 0 10px;
  background: transparent;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: 13px; cursor: pointer;
}
.ligne-membre .mini:hover { color: var(--nut-text); border-color: var(--nut-text-faint); }
.ligne-membre .mini.refuser:hover { color: var(--nut-warn); border-color: var(--nut-warn); }
.ligne-membre .mini.accepter { margin-left: auto; }
.titre-bloc.demandes { margin-top: 18px; color: var(--nut-accent); }

/* --- LE BUILDER DANS L'ONGLET SYSTEME (corrige le 19/08/2026) -----------
 *
 * COQUILLE SIGNALEE PAR RENAUD, capture a l'appui : en defilant dans l'onglet
 * Systeme, la barre du builder — « Abandonner les retouches / Exporter /
 * Enregistrer les regles » — recouvrait la carte « Les regles de cette
 * campagne » posee au-dessus. « Une fiche de maison lit ces regles » se lisait
 * a moitie.
 *
 * LA CAUSE, ET ELLE EST ANCIENNE : « .mn-builder.pleine » porte une marge
 * NEGATIVE de 24 px sur les QUATRE cotes. Elle avait toutes les raisons d'exister quand le builder REMPLACAIT
 * la fiche de maison : il occupait l'ecran entier, et cette marge lui faisait
 * franchir le rembourrage de la page pour toucher les bords.
 *
 * DANS L'ONGLET, IL A UN VOISIN AU-DESSUS. Les 24 px negatifs du haut le
 * tiraient donc PAR-DESSUS la carte — 24 px mesures, exactement la valeur du
 * jeton. Les trois autres cotes restent justes : le builder doit toujours
 * toucher les bords du panneau.
 *
 * (Aucun accent grave dans ce commentaire : on est dans un gabarit de chaine,
 * et il refermerait la feuille. C'est la faute que ce fichier annonce en
 * tete, et que j'ai refaite en l'ecrivant — pour la seconde fois.)
 *
 * ON NE TOUCHE PAS A LA REGLE D'ORIGINE : elle reste vraie la ou elle a ete
 * ecrite. On annule la seule moitie qui ne l'est plus, et seulement ici.
 */
#parametres .mn-builder.pleine {
  /* LE HAUT : zero, il a un voisin. LES TROIS AUTRES COTES : exactement le
     rembourrage du panneau, pour le remplir de bord a bord sans le depasser. */
  margin: 0 calc(-1 * var(--param-pad)) calc(-1 * var(--param-pad));
}

/* LE SEUL MODAL QUI RESTE DANS CET ECRAN : la question posee quand on change
   d'onglet avec des modifications en cours. Il reprend .voile et .modal de
   STYLES_CAMPAGNE, et ne sert qu'a une question a laquelle on repond par oui
   ou par non. */
.param-question { max-width: 460px; }
/* AUCUNE REGLE SUR .modal-pied ICI : celui de STYLES_CAMPAGNE fait deja
   exactement ce qu'il faut (flex, boutons a droite, fond creuse). Le
   redeclarer aurait ete un emprunt de classe pour ne rien changer — et le
   garde-fou de scripts.test.mjs l'a dit avant moi. */
.param-question .corps-question { padding: 20px; }
.param-question .corps-question p { margin: 0; }

/* Modal ouvert : la page derriere ne defile plus. Sans cela, la molette
   traverse le voile et l'ecran de depart bouge dessous — on croit avoir perdu
   sa place. */
body.modal-ouvert { overflow: hidden; }


.bloc-accueil { margin-bottom: var(--nut-sp-8); }

/* --- Les invitations recues ---
   Meme accent que les demandes d'acces : c'est l'un des trois emplois
   autorises de l'orange — un appel a agir. La LIGNE elle-meme est declaree
   dans le socle (styles.js) : elle est identique dans le profil. */
.bloc-accueil.invitations > .titre-bloc { color: var(--nut-accent); }

/* --- Rejoindre une campagne ---
   Une carte, pas une section nue : ce bloc DEMANDE quelque chose, contrairement
   a ses deux voisins qui racontent. Le cadre le distingue sans crier — l'orange
   reste reserve aux demandes d'acces, qui, elles, attendent une reponse.
   Les classes .champ, .code-saisie, .erreur-champ et .primaire sont
   EMPRUNTEES : ce sont les memes champs qu'ailleurs, une seule declaration. */
.carte-rejoindre {
  max-width: 560px;
  padding: var(--nut-sp-4) var(--nut-sp-6);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
}
.carte-rejoindre .aide { margin: 0 0 var(--nut-sp-4); }
.carte-rejoindre label { display: block; margin-bottom: 6px; }
/* Le champ et son bouton sur une ligne, et le champ ne se laisse pas ecraser :
   un code de huit caracteres en chasse fixe a besoin de sa largeur. */
.rangee-code { display: flex; gap: var(--nut-sp-3); align-items: stretch; }
.rangee-code .champ { flex: 1 1 auto; min-width: 0; }
.rangee-code button { flex: none; }
/* LE BOUTON NE PARTAGE JAMAIS LA LIGNE DU CHAMP (16/08/2026, annexe
   section 6). A 353 px, un code de huit caracteres en chasse fixe et son
   bouton cote a cote laissent au champ de quoi en montrer six.
   Cette regle remplace celle qui vivait a 560 px : la frontiere est unique
   depuis la v0.71.0, et une largeur intermediaire n'a plus le droit de cite. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {
  .rangee-code { flex-direction: column; }
  .rangee-code button { width: 100%; min-height: var(--nut-target-touch); }
}
/* La reponse du serveur, volontairement identique qu'un code existe ou non :
   sinon ce champ permettrait d'en essayer jusqu'a en trouver un. */
.etat-rejoindre {
  margin: var(--nut-sp-3) 0 0; font-size: 13px; color: var(--nut-text-muted);
}
.etat-rejoindre[hidden] { display: none; }
.bloc-accueil > .titre-bloc { margin: 0 0 var(--nut-sp-3); }

/* --- Les demandes d'acces ---
   ORANGE, et c'est l'un des trois emplois autorises de l'accent : un appel a
   agir. C'est le seul bloc de cet ecran qui attend quelque chose de la
   personne qui le lit. */
.bloc-accueil.demandes > .titre-bloc { color: var(--nut-accent); }
.ligne-demande {
  display: flex; align-items: center; gap: var(--nut-sp-3); flex-wrap: wrap;
  padding: var(--nut-sp-3) var(--nut-sp-4);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-left: 3px solid var(--nut-accent);
  border-radius: var(--nut-radius-2);
  margin-bottom: var(--nut-sp-2);
}
.ligne-demande .qui { font-size: var(--nut-size-3); }
.ligne-demande .ou {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  color: var(--nut-text-muted); font-size: var(--nut-size-2);
}
.ligne-demande .boutons { margin-left: auto; display: flex; gap: var(--nut-sp-2); }
.ligne-demande button {
  height: 32px; padding: 0 var(--nut-sp-3);
  background: transparent;
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer; white-space: nowrap;
}
.ligne-demande button:hover:not(:disabled) { color: var(--nut-text); border-color: var(--nut-text-faint); }
.ligne-demande button.refuser:hover:not(:disabled) { color: var(--nut-warn); border-color: var(--nut-warn); }
.ligne-demande button:disabled { opacity: .5; cursor: progress; }

/* Le chevron des lignes de campagne : dessine par le script, invisible sur
   large ou la carte n'en a pas besoin. */
.carte-campagne .chevron-camp { display: none; }

/* Le lien qui remplace le formulaire de code quand on a deja des campagnes.
   Invisible sur large : la carte y est toujours depliee. */
#bloc-rejoindre .lien-code { display: none; }

/* Le sous-titre a deux textes — l'un pour large, l'autre sous la frontiere.
   DEUX SPANS PLUTOT QU'UN BRANCHEMENT DANS LE SCRIPT : la largeur est une
   affaire de feuille de style, et le script n'a pas a savoir sur quel
   telephone il tourne. */
/* Le titre d'etat de la carte et le signe + du bouton n'existent que sous la
   frontiere : sur large, l'etiquette de section et le libelle suffisent. */
.carte-rejoindre .titre-vide { display: none; }
.chemin .signe-plus { display: none; }

/* Sur large, l'etat vide a son propre sous-titre dans le bloc #vide : celui-ci
   se tait pour ne pas les afficher tous les deux. */
#sous-titre.etat-vide { display: none; }

/* --- Mes campagnes --- */
.grille-campagnes {
  display: grid; gap: var(--nut-sp-3);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.carte-campagne {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  padding: var(--nut-sp-4);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-2);
  color: var(--nut-text); text-decoration: none;
}
.carte-campagne:hover { border-color: var(--nut-line-strong); }
.carte-campagne .nom-camp {
  display: block; font-size: var(--nut-size-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.carte-campagne .role-camp {
  display: block; margin-top: 2px;
  color: var(--nut-text-faint); font-size: var(--nut-size-1);
}
.carte-campagne .texte { min-width: 0; }

/* --- L'etat vide : DEUX CHEMINS, COTE A COTE ---
   Le prototype les met sur la meme ligne, et c'est juste : on ne cree pas une
   campagne « faute de mieux ». Rejoindre est le cas le plus frequent dans un
   club — la carte a donc le meme poids visuel, pas moins. */
.deux-chemins {
  display: grid; gap: var(--nut-sp-4);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.chemin {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--nut-sp-2);
  padding: var(--nut-sp-6);
  background: var(--nut-surface);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-3);
}
.chemin h2 {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  margin: 0; font-family: var(--nut-font-title);
  font-size: var(--nut-size-4); font-weight: var(--nut-weight-semi);
}
.chemin h2 svg { color: var(--nut-text-faint); }
.chemin p {
  margin: 0; color: var(--nut-text-muted);
  font-size: var(--nut-size-2); line-height: var(--nut-lh-text);
}
.chemin .action { margin-top: var(--nut-sp-2); }

/* Le jeton d'une campagne, a la taille de l'accueil. Meme dessin que dans la
   barre laterale : l'angle coupe en haut a gauche. */
.jeton-accueil {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 0 100%, 0 8px);
  color: rgb(16, 19, 15);
  font-family: var(--nut-font-title);
  font-size: var(--nut-size-2); font-weight: var(--nut-weight-semi);
}
.jeton-mini {
  width: 18px; height: 18px; flex: none;
  clip-path: polygon(5px 0, 100% 0, 100% 100%, 0 100%, 0 5px);
  color: rgb(16, 19, 15);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--nut-font-title); font-size: 9px;
  font-weight: var(--nut-weight-semi);
}
.jeton-accueil.t1, .jeton-mini.t1 { background: var(--nut-camp-1); }
.jeton-accueil.t2, .jeton-mini.t2 { background: var(--nut-camp-2); }
.jeton-accueil.t3, .jeton-mini.t3 { background: var(--nut-camp-3); }
.jeton-accueil.t4, .jeton-mini.t4 { background: var(--nut-camp-4); }
.jeton-accueil.t5, .jeton-mini.t5 { background: var(--nut-camp-5); }
.jeton-accueil.t6, .jeton-mini.t6 { background: var(--nut-camp-6); }
.jeton-accueil.t7, .jeton-mini.t7 { background: var(--nut-camp-7); }
.jeton-accueil.t8, .jeton-mini.t8 { background: var(--nut-camp-8); }

/* ==========================================================================
   SOUS LA FRONTIERE — L'ACCUEIL DE LA MAQUETTE MOBILE
   ==========================================================================

   CE BLOC EST EN FIN DE FEUILLE, ET CE N'EST PAS UN DETAIL DE RANGEMENT.
   Une media query n'ajoute AUCUNE specificite : a specificite egale, c'est la
   derniere regle ecrite qui gagne. Une correction mobile posee au-dessus de
   une regle de base ne s'applique tout simplement pas. L'exemple d'origine
   citait une regle des mises a jour recentes, retirees le 30/09/2026 ; la
   lecon, elle, vaut toujours. C'est arrive deux fois dans ce depot — le bouton du tiroir
   le 13/08, l'accueil le 16/08. Regle : ce qui est le defaut se declare
   avant, ce qui le surcharge apres. Ne rien ajouter sous ce bloc.

   Ce qu'il obtient, releve d'ecarts en main (design/archives/ECARTS-MOBILE-PHASE-1.md
   section 1, et design/REPRISE-MOBILE.md, retire le 17/10/2026, voir
   l'historique git) :

     1. LES CAMPAGNES D'ABORD. Sur la maquette elles suivent le titre ; le
        bloc « Rejoindre » vient apres, et les mises a jour apres lui. L'ordre
        du HTML sert le grand ecran et ne bouge pas : c'est « order » qui
        replace les blocs, sans toucher au DOM ni au clavier — l'ordre de
        tabulation suit le DOM, pas la peinture, et les blocs sont
        independants les uns des autres.
     2. LE FORMULAIRE DE CODE SEULEMENT DANS L'ETAT VIDE. Des qu'il existe une
        campagne, il n'en reste qu'un lien sous la liste. Le formulaire n'a
        pas disparu : le lien le deplie.
     3. LA LIGNE DE MISE A JOUR PLIEE EN DEUX. Titre complet sur sa ligne,
        campagne · etat · date en meta dessous. Avant ce pli, le titre tombait
        a 42 px pour 257 px de texte — « Le d… », « Sa » : ce qui survivait
        etait l'information la moins utile de la ligne.

   Les campagnes passent de la carte a la LIGNE DE LISTE de la maquette :
   56 px, monogramme 28, titre en ellipse, role en meta, chevron. */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {

  /* --- 1. L'ORDRE DES BLOCS ---
     #corps devient une colonne flex pour que « order » ait prise. Les valeurs
     laissent de la place entre elles : #vide et #bloc-campagnes ne sont jamais
     visibles ensemble, d'ou l'entrelacement volontaire (campagnes 30,
     rejoindre 40, vide 50) — avec des campagnes on lit liste puis lien ; sans,
     on lit formulaire puis « creer une campagne ». */
  #corps { display: flex; flex-direction: column; }
  /* Les blocs se serrent : la maquette n'a pas de respiration de 32 px entre
     une liste et le lien qui la suit — ils se lisent d'un trait. */
  .bloc-accueil { margin-bottom: var(--nut-sp-4); }
  .bloc-accueil > .titre-bloc { margin: 10px 0 6px; }
  #bloc-campagnes { margin-bottom: 0; }
  #bloc-demandes { order: 10; }
  #bloc-invitations { order: 20; }
  #bloc-campagnes { order: 30; }
  #bloc-rejoindre { order: 40; }
  #vide { order: 50; }

  #sous-titre.etat-vide { display: block; }
  /* 14 px sous le titre, comme la maquette (en-tete 20/16/14). Les 28 px du
     socle sont dessines pour une page de texte, pas pour une liste. */
  #sous-titre { margin-bottom: 14px; }

  /* --- 2. LES CAMPAGNES EN LIGNES DE LISTE ---
     Plus de grille, plus de cartes : une pile de lignes separees d'un filet,
     exactement le dessin de la maquette. */
  /* PLEINE LARGEUR, FILETS COMPRIS. Sur la maquette les separateurs vont d'un
     bord a l'autre de l'ecran ; la liste ressort donc des 16 px de .zone par
     une marge negative, et se les redonne en padding. Le texte reste aligne
     sur le reste de la page, le filet non — c'est exactement le dessin. */
  .grille-campagnes, #bloc-rejoindre.replie .lien-code {
    margin-left: calc(-1 * var(--nut-sp-4));
    margin-right: calc(-1 * var(--nut-sp-4));
  }
  .grille-campagnes { display: block; gap: 0; }
  .carte-campagne {
    gap: 12px; min-height: 56px; padding: 0 var(--nut-sp-4);
    background: none; border: 0;
    border-bottom: 1px solid var(--nut-line);
    border-radius: 0;
  }
  .carte-campagne:hover { border-color: var(--nut-line); background: none; }
  .carte-campagne .jeton-accueil {
    width: 28px; height: 28px;
    clip-path: polygon(7px 0, 100% 0, 100% 100%, 0 100%, 0 7px);
    font-size: 12px;
  }
  /* .texte prend la place restante ET accepte de retrecir : sans min-width 0,
     un titre long refuse de se mettre en ellipse et pousse le chevron dehors. */
  .carte-campagne .texte { flex: 1 1 0; min-width: 0; }
  .carte-campagne .nom-camp { font-size: 15px; font-weight: 500; }
  .carte-campagne .role-camp { margin-top: 1px; font-size: 12px; }
  .carte-campagne .chevron-camp {
    display: block; flex: none; color: var(--nut-text-faint);
  }

  /* --- 3. LE CODE D'INVITATION : UN LIEN, PUIS LA CARTE ---
     « replie » est pose par le script des qu'il y a au moins une campagne.
     Le lien se dessine comme celui de la maquette : 52 px de haut, souligne
     d'un filet, sans aplat. */
  #bloc-rejoindre.replie > .titre-bloc,
  #bloc-rejoindre.replie .carte-rejoindre { display: none; }
  /* ET SEULEMENT QUAND C'EST REPLIE. Sans le « .replie » ici, le lien
     s'affichait AUSSI dans l'etat vide, juste au-dessus du formulaire qu'il
     est cense deplier — deux fois la meme porte, dont une qui ne mene nulle
     part. Vu sur la capture, pas dans le code. */
  #bloc-rejoindre.replie .lien-code {
    display: flex; align-items: center;
    width: auto; min-height: 52px; padding: 0 var(--nut-sp-4);
    background: none; border: 0;
    color: var(--nut-text-muted); font: inherit; font-size: 14px;
    text-align: left; cursor: pointer;
  }
  #bloc-rejoindre .lien-code span { border-bottom: 1px solid var(--nut-line-strong); }
  .carte-rejoindre { padding: 20px 16px; border-radius: var(--nut-radius-3); }
  /* Quatre lignes d'explication en gris pale tiennent sur un ecran de bureau ;
     a 353 px, en pleine lumiere, elles se lisent mal. La maquette les met en
     « muted », pas en « faint » — un cran de contraste au-dessus. */
  .carte-rejoindre .aide { color: var(--nut-text-muted); line-height: 1.5; }
  /* La carte dit l'etat avant d'expliquer : l'etiquette de section lui cede
     la place, elle disait deja la meme chose en plus petit. */
  #bloc-rejoindre:not(.replie) > .titre-bloc { display: none; }
  #bloc-rejoindre:not(.replie) .carte-rejoindre .titre-vide {
    display: block; margin: 0 0 12px;
    font-size: 16px; font-weight: var(--nut-weight-semi); color: var(--nut-text);
  }

  /* --- 5. L'ETAT VIDE ---
     La maquette y montre UN chemin ecrit et un bouton : la carte de code
     juste au-dessus EST le chemin « rejoindre », le repeter en vis-a-vis
     n'apprend rien. Reste le bouton de creation, en second, sans aplat
     orange — l'orange de cet ecran appartient a « Envoyer ma demande ». */
  #vide > .sous-titre { display: none; }
  .deux-chemins { display: block; }
  .chemin-rejoindre { display: none; }
  .chemin-creer {
    padding: 0; background: none; border: 0; border-radius: 0;
  }
  .chemin-creer h2, .chemin-creer p { display: none; }
  .chemin-creer .action {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; min-height: 48px; margin: 0;
    background: transparent; color: var(--nut-text);
    border: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-1);
    font-size: 15px; font-weight: 500; text-decoration: none;
  }
  .chemin-creer .signe-plus { display: flex; align-items: center; }
}


/* --- L'ecran pleine hauteur ---
   L'ecran Lore est le premier a ne pas tenir dans la colonne de lecture : ses
   deux panneaux defilent SEPAREMENT, ce qui exige une hauteur fixe. La coquille
   reçoit donc un mode « pleine », qui retire la gouttiere et le pied. */
.lore { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.lore > .aide { padding: var(--nut-sp-6); }
.lore > .bandeau { margin: var(--nut-sp-4); }

.lore-corps { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }

/* --- L'avertissement « page publiee, fiches en brouillon » ---
   Il ne decrit pas une erreur mais une CONSEQUENCE : les joueurs voient une
   page vide. Le dire a l'ecran vaut mieux que laisser le MJ le decouvrir en
   demandant a quelqu'un ce qu'il voit. */
.lore-alerte {
  flex: none; display: flex; align-items: center; gap: var(--nut-sp-2);
  padding: 10px 20px;
  background: var(--nut-surface);
  border-bottom: 1px solid var(--nut-line);
}
.lore-alerte svg { flex: none; color: var(--nut-info); }
.lore-alerte p {
  flex: 1 1 0; min-width: 0; margin: 0;
  font-size: var(--nut-size-2); line-height: var(--nut-lh-text);
  color: var(--nut-text-muted);
}
.lore-alerte strong { color: var(--nut-text); font-weight: var(--nut-weight-medium); }

.lore-colonnes { flex: 1 1 auto; min-height: 0; display: flex; align-items: stretch; }

/* ==========================================================================
   COLONNE DE GAUCHE — LA LISTE
   ========================================================================== */
.lore-liste {
  width: 264px; flex: none;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--nut-surface);
  border-right: 1px solid var(--nut-line);
}
.lore-filtre {
  flex: none; padding: 12px 12px 10px;
  border-bottom: 1px solid var(--nut-line);
}
.lore-filtre .champ-recherche { margin-bottom: 0; height: 34px; }
.lore-filtre .champ-recherche .vider {
  flex: none; display: flex; align-items: center;
  padding: 0; background: transparent; border: 0;
  color: var(--nut-text-faint); cursor: pointer;
}
.lore-filtre .champ-recherche .vider:hover { color: var(--nut-text); }
.lore-compte {
  margin: 8px 0 0;
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
}
.lore-fiches {
  flex: 1 1 0; min-height: 0; overflow-y: auto; overflow-x: hidden;
  padding: 8px; scrollbar-width: thin;
}
/* --- Un groupe de la liste --- */
.lore-groupe { margin-bottom: 10px; }
/* L'EN-TETE D'UN GROUPE : TOUT SUR LA MEME LIGNE.
   Le titre, le compte et les deux outils flottaient a des hauteurs
   differentes — le titre est en majuscules a 11 px, le compte dans la police de
   donnees, les icones dans des boutons de 24 px. Trois hauteurs de ligne
   naturelles differentes, donc trois lignes de base. On impose donc une hauteur
   COMMUNE de 28 px a chaque partie, et l'alignement cesse de dependre de la
   police. Signale par Renaud le 07/08. */
.lore-groupe-tete {
  display: flex; align-items: center; gap: 4px;
  min-height: 28px; padding: 0 2px 0 4px;
}
.lore-groupe-tete .titre {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; gap: 7px;
  height: 28px; padding: 0;
  line-height: 1;
  background: transparent; border: 0;
  color: var(--nut-text-muted); font: inherit; font-size: 11px;
  font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  text-align: left; cursor: pointer;
}
.lore-groupe-tete .titre:hover { color: var(--nut-text); }
.lore-groupe-tete .titre .nom {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lore-groupe-tete .titre .n {
  flex: none; margin-left: auto;
  display: flex; align-items: center; height: 28px;
  font-family: var(--nut-font-data); font-size: 11px; line-height: 1;
  letter-spacing: 0; color: var(--nut-text-faint);
}
.lore-groupe-tete .chevron {
  flex: none; display: flex; align-items: center; height: 28px;
  color: var(--nut-text-faint);
  transition: transform var(--nut-dur-1) var(--nut-ease);
}
.lore-groupe-tete .titre[aria-expanded="false"] .chevron { transform: rotate(0deg); }
.lore-groupe-tete .titre[aria-expanded="true"] .chevron { transform: rotate(90deg); }
.lore-groupe-tete .outil {
  flex: none; width: 24px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
.lore-groupe-tete .outil:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
.lore-groupe-tete .outil.suppr:hover { color: var(--nut-warn); }
.lore-groupe .renommage { padding: 4px 2px 6px; }
.lore-groupe .renommage input[type="text"] {
  width: 100%; height: 30px; padding: 0 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-accent-hi); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
}
/* Les fiches d'un groupe sont EN RETRAIT : c'est le retrait qui dit
   l'appartenance, pas un filet ni une couleur de fond. */
.lore-groupe .lore-entree { padding-left: 22px; }
.lore-groupe .rien {
  margin: 2px 0 0; padding: 3px 8px 3px 22px;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
  line-height: var(--nut-lh-text);
}

.lore-fiches .vide {
  margin: 6px 4px;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
  line-height: var(--nut-lh-text);
}
.lore-entree {
  width: 100%; display: flex; align-items: flex-start; gap: 7px;
  min-height: 30px; padding: 5px 8px;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-3);
  text-align: left; cursor: pointer;
}
.lore-entree:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
.lore-entree[aria-current="true"] {
  background: var(--nut-surface-raised); color: var(--nut-text);
  font-weight: var(--nut-weight-medium);
}
.lore-entree .nom {
  flex: 1 1 0; min-width: 0; line-height: var(--nut-lh-tight);
  overflow-wrap: anywhere;
}
/* LE BROUILLON SE SIGNALE PAR UNE FORME, PAS PAR UNE COULEUR : un anneau creux
   contre un disque plein. Lisible sans distinguer les couleurs, et lisible en
   noir et blanc — c'est la meme regle que pour l'oeil barre de page.js. */
.lore-puce {
  flex: none; margin-top: 6px; width: 7px; height: 7px;
  border-radius: 50%; border: 1.4px solid var(--nut-text-faint);
}
.lore-pied {
  flex: none; padding: 8px;
  display: flex; flex-direction: column; gap: 6px;
  border-top: 1px solid var(--nut-line);
}
/* « X fiches en brouillon · Publier tout » a ete retire le 07/08/2026 : une
   fiche en brouillon est une fiche PRIVEE, pas une fiche inachevee. */
.lore-bouton {
  height: 32px; padding: 0 11px;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  background: transparent;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
  font-weight: var(--nut-weight-medium); cursor: pointer; white-space: nowrap;
}
.lore-bouton:hover { border-color: var(--nut-text-faint); }
.lore-bouton.discrete {
  border-color: var(--nut-line); color: var(--nut-text-muted);
  font-weight: var(--nut-weight-regular);
}
.lore-bouton.discrete:hover { color: var(--nut-text); border-color: var(--nut-line-strong); }
/* LA SUPPRESSION EST NEUTRE AU REPOS, et ne se teinte qu'au survol.
   C'est ce que montre le prototype : au bas du recapitulatif, elle porte le
   meme cadre que « Ajouter un champ » juste au-dessus. Un bouton cercle de
   rouge en permanence n'avertit de rien — il attire l'oeil avant le texte de la
   fiche, et c'est le seul geste de l'ecran qui ne merite pas d'etre vu en
   premier. */
.lore-bouton.discrete.danger:hover { color: var(--nut-warn); border-color: var(--nut-warn); }
.lore-bouton.petit { height: 26px; padding: 0 9px; font-size: var(--nut-size-1); }
.lore-bouton.risque { border-color: var(--nut-warn); color: var(--nut-warn); }
/* LA SUPPRESSION NE DOIT PAS ETRE L'ELEMENT LE PLUS VOYANT DE L'ECRAN.
   Cerclee de rouge en permanence, elle l'etait : le regard tombait dessus avant
   le texte de la fiche. Un bouton rouge en attente n'avertit de rien — il
   n'avertit que quand on le vise. Elle reste donc effacee, et ne prend sa
   couleur qu'au survol, au moment ou elle devient une intention. */
.lore-bouton.efface {
  border-color: transparent; color: var(--nut-text-faint);
  font-weight: var(--nut-weight-regular);
}
.lore-bouton.efface:hover { border-color: var(--nut-warn); color: var(--nut-warn); }
.lore-bouton:disabled { opacity: .55; cursor: progress; }
.lore-bouton.ecarte { margin-top: 16px; }

.lore-creation {
  padding: 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
}
.lore-creation input[type="text"] {
  width: 100%; height: 32px; margin-bottom: 8px; padding: 0 8px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
}
.lore-creation .actions { display: flex; gap: 6px; justify-content: flex-end; }
.lore-creation .lore-select-cat {
  width: 100%; height: 30px; margin-bottom: 8px; padding: 0 6px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-2);
}
/* Les deux boutons de creation partagent la largeur : « Fiche » est le geste
   courant, « Categorie » celui qu'on fait trois fois par campagne. Le second
   est donc plus discret, pas plus petit — un bouton plus petit se rate. */
.lore-deux { display: flex; gap: 6px; }
.lore-deux .lore-bouton { flex: 1 1 0; min-width: 0; }

/* ==========================================================================
   COLONNE DE DROITE — LA FICHE
   ========================================================================== */
.lore-fiche { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }

.lore-vide {
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center;
  padding: var(--nut-sp-8);
}
.lore-vide .dedans { max-width: 400px; text-align: center; }
.lore-vide svg { color: var(--nut-text-faint); margin-bottom: 12px; }
.lore-vide h2 {
  margin: 0 0 10px;
  font-family: var(--nut-font-title); font-size: var(--nut-size-6);
  font-weight: var(--nut-weight-semi);
}
.lore-vide p {
  margin: 0; color: var(--nut-text-muted);
  font-size: var(--nut-size-3); line-height: var(--nut-lh-text);
}

.lore-ouverte { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.lore-entete {
  flex: none; display: flex; align-items: center; gap: var(--nut-sp-3);
  flex-wrap: wrap; padding: 14px 26px 12px;
  border-bottom: 1px solid var(--nut-line);
}
/* L'etat de l'enregistrement est POUSSE a droite : c'est le dernier endroit ou
   le regard va, et on n'y va que pour verifier. */
/* MEME TRAITEMENT DANS L'EN-TETE DE LA FICHE. L'interrupteur fait 34 px de
   haut (deux segments de 28 plus la gouttiere), la phrase et l'etat sont du
   texte : sans hauteur commune, ils se posaient sur trois lignes de base. */
.lore-entete > * { min-height: 34px; display: flex; align-items: center; }
.lore-sauve {
  margin-left: auto; display: flex; align-items: center; gap: 7px;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
}
.lore-sauve svg { flex: none; }
.lore-sauve.enregistre { color: var(--nut-ok); }
.lore-sauve.erreur { color: var(--nut-warn); }
.lore-lecture-seule {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
}

/* --- Le conflit ---
   Il n'annonce pas un echec mais un REFUS PROTECTEUR : rien n'a ete ecrase.
   C'est la phrase du prototype, et c'est la bonne : le premier reflexe devant
   un message rouge est de croire qu'on a perdu son texte. */
.lore-conflit {
  flex: none; display: flex; align-items: flex-start; gap: var(--nut-sp-2);
  padding: 12px 26px;
  background: var(--nut-surface);
  border-bottom: 1px solid var(--nut-warn);
}
.lore-conflit > svg { flex: none; margin-top: 1px; color: var(--nut-warn); }
.lore-conflit .dit { flex: 1 1 0; min-width: 0; }
.lore-conflit .dit p { margin: 0; }
.lore-conflit .quoi {
  font-size: var(--nut-size-3); font-weight: var(--nut-weight-medium);
  margin-bottom: 3px;
}
.lore-conflit .pourquoi {
  font-size: var(--nut-size-2); color: var(--nut-text-muted);
  line-height: var(--nut-lh-text);
}
.lore-conflit .actions { flex: none; display: flex; gap: 8px; }

.lore-defilant {
  flex: 1 1 0; min-height: 0; overflow-y: auto; overflow-x: hidden;
  padding: 0 26px 60px;
}
.lore-colonne { max-width: var(--nut-read-max); }

/* Le titre de la fiche se modifie SUR PLACE, comme celui de la page. Un champ
   qui ne se distingue d'un titre qu'au survol : on le lit comme un titre, on
   s'apercoit qu'il s'ecrit quand on va dessus. */
input.lore-titre {
  width: 100%; height: auto; margin: 18px 0 18px -8px; padding: 4px 8px;
  background: transparent;
  border: 1px solid transparent; border-radius: var(--nut-radius-1);
  color: var(--nut-text);
  font-family: var(--nut-font-title); font-size: var(--nut-size-7);
  font-weight: var(--nut-weight-semi); line-height: var(--nut-lh-tight);
}
input.lore-titre:hover { border-color: var(--nut-line); background: var(--nut-surface); }
input.lore-titre:focus {
  outline: none;
  border-color: var(--nut-line-strong); background: var(--nut-surface);
}
/* « Retirée après publication » se teinte de l'avertissement, ni du vert de la
   publication ni du gris du brouillon : c'est le seul des trois états qui
   raconte une PERTE — des joueurs avaient accès à cette fiche, ils ne l'ont
   plus. Le prototype lui donne exactement cette couleur.
   La classe est qualifiée par la fiche ouverte : « retiree » est un mot trop
   ordinaire pour rester nu dans une feuille qui n'est pas cloisonnée. */
.lore-ouverte .phrase-statut.retiree { color: var(--nut-warn); }

/* La phrase « Renommer met a jour toutes les mentions… » a ete retiree le
   07/08/2026, sur demande de Renaud. Elle disait quelque chose de vrai et
   d'utile UNE FOIS ; elle le redisait a chaque fiche ouverte, pour un doute
   qu'on n'a qu'au premier renommage. La regle, elle, ne change pas : une
   mention reference un identifiant, jamais un nom. */
.lore-titre-lu {
  margin: 18px 0 20px;
  font-family: var(--nut-font-title); font-size: var(--nut-size-7);
  font-weight: var(--nut-weight-semi); line-height: var(--nut-lh-tight);
}

/* La barre d'outils COLLE EN HAUT du defilement : sur une fiche longue, mettre
   un mot en gras ne doit pas obliger a remonter. */
.lore-outils {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  padding: 8px 0; margin-bottom: 18px;
  background: var(--nut-bg);
  border-bottom: 1px solid var(--nut-line);
}
.lore-outils button {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); cursor: pointer;
}
.lore-outils button:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
/* Les quatre boutons de manipulation de tableau n'apparaissent que dans un
   tableau. L'attribut est pose par « surveillerTableau », qui suit le curseur
   sans passer par l'etat de Preact — sinon l'ecran se redessinerait a chaque
   deplacement du curseur, soit plusieurs fois par seconde. */
.lore-outils .si-tableau { display: none; gap: 2px; }
.lore-outils[data-tableau="oui"] .si-tableau { display: inline-flex; }

/* Le tableau, dans l'editeur comme en lecture. Bordures FUSIONNEES : sans
   cela chaque cellule porte la sienne et la grille double d'epaisseur. */
.lore-editeur table, .lore-leur-texte table {
  width: 100%; margin: 0 0 12px;
  border-collapse: collapse;
  font-size: var(--nut-size-3);
}
.lore-editeur th, .lore-editeur td,
.lore-leur-texte th, .lore-leur-texte td {
  padding: 6px 10px;
  border: 1px solid var(--nut-line);
  text-align: left; vertical-align: top;
}
.lore-editeur th, .lore-leur-texte th {
  background: var(--nut-surface);
  color: var(--nut-text); font-weight: var(--nut-weight-semi);
}
.lore-editeur table:focus-within th, .lore-editeur table:focus-within td {
  border-color: var(--nut-line-strong);
}
/* Le seul bouton nomme de la barre : il n'est pas carre, il a la largeur de
   son mot. */
.lore-outils button.nomme {
  width: auto; height: 30px; padding: 0 10px; gap: 7px;
  font: inherit; font-size: var(--nut-size-2);
}
.lore-outils button.nomme .signe {
  font-family: var(--nut-font-data); font-size: 15px;
}
.lore-outils .filet {
  width: 1px; height: 20px; margin: 0 4px;
  background: var(--nut-line);
}
.lore-compteur {
  margin-left: auto;
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  color: var(--nut-text-faint);
}
.lore-compteur.pres { color: var(--nut-warn); }

/* --- La zone de saisie --- */
.lore-editeur {
  min-height: 220px; padding: 2px 0 20px;
  outline: none;
  font-size: var(--nut-size-4); line-height: var(--nut-lh-text);
  color: var(--nut-text);
}
.lore-editeur p { margin: 0 0 var(--nut-sp-3); }
.lore-editeur ul, .lore-editeur ol { margin: 0 0 var(--nut-sp-3); padding-left: 22px; }
.lore-editeur li { margin-bottom: var(--nut-sp-1); }
/* Le repere de zone vide n'est PAS un placeholder de champ : c'est du CSS pose
   sur un contenteditable, qui disparait des la premiere lettre. */
.lore-editeur[data-vide="oui"]::before {
  content: attr(data-invite);
  color: var(--nut-text-faint);
  pointer-events: none;
}

/* ==========================================================================
   LES MENTIONS
   ==========================================================================
   TROIS ETATS, ET AUCUN NE DEPEND D'UNE COULEUR.

     vivante  maillon + soulignement POINTILLE — la fiche existe et se lit
     cachee   cadenas + texte nu, sans lien — elle existe mais est en brouillon
     morte    maillon barre + texte barre — elle a ete supprimee

   Le souligne de MISE EN FORME, lui, est plein et sans icone. Les deux ne se
   confondent donc jamais, meme pour qui ne distingue pas le pointille du plein :
   il reste l'icone. */
.lore-editeur [data-mention], .lore-leur-texte [data-mention] {
  text-decoration: underline dotted 1px;
  text-underline-offset: 3px;
  border-radius: 2px;
  cursor: pointer;
}
.lore-editeur [data-mention]:hover {
  text-decoration: underline solid 1px;
  background: var(--nut-surface-raised);
}
.lore-editeur [data-mention] svg, .lore-leur-texte [data-mention] svg {
  display: inline-block; vertical-align: -2px; margin-right: 3px; opacity: .65;
}
.lore-editeur [data-etat="morte"], .lore-leur-texte [data-etat="morte"] {
  color: var(--nut-text-faint);
  text-decoration: line-through 1px;
  cursor: default;
}
.lore-editeur [data-etat="cachee"], .lore-leur-texte [data-etat="cachee"] {
  text-decoration: none;
  cursor: default;
}

/* --- La liste qui s'ouvre apres un « @ » --- */
/* La liste flotte dans la FENETRE, pas dans une colonne : elle sert desormais
   deux endroits — le texte, au milieu, et les champs, dans une colonne de
   320 px ou une boite de 288 px ne tiendrait pas sans deborder. */
.lore-at {
  position: fixed; z-index: 30; width: 288px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2);
  box-shadow: var(--nut-elev-2);
  overflow: hidden;
}
.lore-at .choix { max-height: 236px; overflow-y: auto; padding: 4px; }
.lore-at button {
  width: 100%; display: flex; align-items: center; gap: var(--nut-sp-2);
  padding: 7px 8px;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
  text-align: left; cursor: pointer;
}
.lore-at button .nom {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lore-at button[aria-selected="true"] { background: var(--nut-surface-sunken); }
.lore-at .groupe { margin-bottom: 4px; }
.lore-at .groupe .tete {
  margin: 0; padding: 6px 8px 4px;
  font-size: 10px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.lore-at button .entree {
  flex: none;
  font-family: var(--nut-font-data); font-size: var(--nut-size-1);
  color: var(--nut-text-faint);
}
/* La ligne de creation tient sur deux lignes : le nom, puis ce qu'il va
   devenir. Elle s'aligne donc en HAUT, pas au milieu. */
.lore-at button.creer { align-items: flex-start; gap: 9px; padding: 9px 8px; }
.lore-at button.creer .signe { flex: none; margin-top: 1px; color: var(--nut-text-muted); }
.lore-at button.creer .deux-lignes { min-width: 0; }
.lore-at button.creer .nom { display: block; }
.lore-at button.creer .portee {
  display: block; margin-top: 2px;
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
  line-height: var(--nut-lh-ui);
}
.lore-at .compte {
  margin: 0; padding: 6px 10px;
  background: var(--nut-surface);
  border-top: 1px solid var(--nut-line);
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
}

/* ==========================================================================
   LA COLONNE RECAPITULATIVE
   ==========================================================================
   Elle repond a « qu'est-ce que cette fiche EST » — modele, rangement, champs,
   qui la cite — quand la colonne du milieu repond a « qu'est-ce qu'elle
   raconte ». Deux questions, deux endroits.

   Elle se REPLIE, parce qu'on ecrit plus souvent qu'on ne classe : sur une
   fenetre etroite, 320 px pris a l'ecriture se sentent tout de suite. */
.lore-recap {
  width: 320px; flex: none;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--nut-surface);
  border-left: 1px solid var(--nut-line);
}
.lore-recap-tete {
  flex: none; display: flex; align-items: center; gap: var(--nut-sp-2);
  padding: 12px 14px;
  border-bottom: 1px solid var(--nut-line);
}
.lore-recap-tete .etiquette {
  flex: 1 1 0;
  font-size: 11px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.lore-recap-tete .outil {
  flex: none; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
.lore-recap-tete .outil:hover { background: var(--nut-surface-raised); color: var(--nut-text); }
.lore-recap-corps {
  flex: 1 1 0; min-height: 0; overflow-y: auto; overflow-x: hidden;
  padding: 14px; scrollbar-width: thin;
}

/* Le bouton qui la fait revenir. Il tient sur le bord droit, sans occuper de
   colonne : un panneau replie ne doit pas laisser un trou a sa place. */
.lore-deplier {
  flex: none; width: 28px;
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 14px;
  background: var(--nut-surface);
  border: 0; border-left: 1px solid var(--nut-line);
  color: var(--nut-text-faint); cursor: pointer;
}
.lore-deplier:hover { color: var(--nut-text); }
.lore-deplier svg { transform: rotate(180deg); }

.lore-champ-bloc { margin-bottom: 12px; }
.lore-champ-bloc label {
  display: block; margin-bottom: 6px;
  font-size: 11px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.lore-champ-bloc select {
  width: 100%; height: 32px; padding: 0 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer;
}
.lore-libre {
  margin: 0 0 12px; padding: 10px 12px;
  background: var(--nut-surface-raised); border-radius: var(--nut-radius-1);
  font-size: var(--nut-size-2); color: var(--nut-text-muted);
  line-height: var(--nut-lh-text);
}

/* --- Un champ du modele ---
   Le libelle est en petit et en retrait, la valeur en grand : c'est la valeur
   qu'on lit, le libelle ne sert qu'a savoir ce qu'on lit. */
.lore-valeur { margin-bottom: 12px; }
.lore-valeur .libelle {
  margin: 0 0 4px;
  font-size: var(--nut-size-1); font-weight: var(--nut-weight-medium);
  color: var(--nut-text-faint);
}
/* --- LE CHAMP A JETONS ---
   Un champ contient une LISTE : « Parents » porte deux personnes, pas une
   phrase qui en nomme deux. Chaque element est un jeton — du texte, ou une
   fiche. Le cadre entier se comporte comme un champ de saisie : c'est LUI qui
   s'allume au focus, pas la petite zone de frappe a l'interieur, sinon on voit
   deux boites imbriquees dont une seule reagit. */
.lore-jetons {
  display: flex; flex-wrap: wrap; align-items: center; gap: 5px;
  min-height: 32px; margin-left: -8px; padding: 4px 6px;
  background: transparent;
  border: 1px solid transparent; border-radius: var(--nut-radius-1);
  cursor: text;
}
.lore-jetons:hover { border-color: var(--nut-line); background: var(--nut-surface-raised); }
.lore-jetons:focus-within {
  border-color: var(--nut-line-strong); background: var(--nut-surface-raised);
}
.lore-jetons input[type="text"] {
  flex: 1 1 60px; min-width: 60px; width: auto; height: 24px; padding: 0 2px;
  background: transparent; border: 0;
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
}
.lore-jetons input[type="text"]:focus { outline: none; }

.lore-jeton {
  display: inline-flex; align-items: center; gap: 5px;
  max-width: 100%; height: 24px; padding: 0 4px 0 7px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-pill);
  color: var(--nut-text); font-size: var(--nut-size-2);
}
.lore-jeton .nom {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* UN JETON QUI EST UNE FICHE SE DISTINGUE PAR SON MAILLON, pas par sa couleur —
   meme regle que dans le texte. Il est cliquable ; un jeton de texte ne l'est
   pas, et son curseur le dit. */
.lore-jeton.fiche { cursor: pointer; }
.lore-jeton.fiche:hover { border-color: var(--nut-text-faint); }
.lore-jeton.fiche svg { flex: none; opacity: .65; }
.lore-jeton.morte { color: var(--nut-text-faint); text-decoration: line-through; cursor: default; }
.lore-jeton .retirer {
  flex: none; width: 16px; height: 16px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: 50%;
  color: var(--nut-text-faint); font: inherit; font-size: 14px;
  line-height: 1; cursor: pointer;
}
.lore-jeton .retirer:hover { background: var(--nut-line); color: var(--nut-text); }

.lore-valeur .lu {
  margin: 0; display: flex; flex-wrap: wrap; gap: 5px;
  font-size: var(--nut-size-3); color: var(--nut-text);
  line-height: var(--nut-lh-text); overflow-wrap: anywhere;
}
.lore-aucun-champ {
  margin: 0 0 12px;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
  line-height: var(--nut-lh-text);
}

/* ==========================================================================
   L ECRAN DES MODELES
   ==========================================================================
   Un ECRAN, pas un modal — c'est ce que montre le prototype, et c'est juste :
   on y vient pour poser la forme d'un univers entier, pas pour repondre a une
   question. Un modal se ferme ; un ecran se parcourt.
   Corrige le 07/08/2026 : j'en avais fait un modal sans le demander. */
.lore-ecran-modeles {
  flex: 1 1 auto; min-height: 0; overflow: auto;
  padding: 26px 34px 70px;
}
.lore-ecran-modeles .dedans { max-width: 760px; }
.lore-retour {
  display: flex; align-items: center; gap: 7px;
  margin-bottom: 18px; padding: 0;
  background: transparent; border: 0;
  color: var(--nut-text-muted); font: inherit; font-size: var(--nut-size-2);
  cursor: pointer;
}
.lore-retour:hover { color: var(--nut-text); }
.lore-retour svg { transform: rotate(180deg); }
.lore-ecran-modeles h1 {
  margin: 0 0 6px;
  font-family: var(--nut-font-title); font-size: var(--nut-size-7);
  font-weight: var(--nut-weight-semi); line-height: var(--nut-lh-tight);
}
.lore-ecran-modeles .chapeau {
  margin: 0 0 26px;
  color: var(--nut-text-muted); font-size: var(--nut-size-3);
  line-height: var(--nut-lh-text);
}
.lore-ecran-modeles .chapeau strong {
  color: var(--nut-text); font-weight: var(--nut-weight-regular);
}

/* --- L etat vide, avec ses points de depart ---
   Trois modeles tout faits, modifiables et supprimables ensuite. Devant une
   page blanche, on ne sait pas quels champs inventer ; devant « Personnage :
   Naissance, Mort, Maison, Enfants », on sait quoi corriger. */
.lore-depart {
  background: var(--nut-surface);
  border: 1px dashed var(--nut-line-strong); border-radius: var(--nut-radius-2);
  padding: 22px;
}
.lore-depart .quoi { margin: 0 0 6px; font-size: var(--nut-size-4); color: var(--nut-text); }
.lore-depart .pourquoi {
  margin: 0 0 16px; color: var(--nut-text-muted);
  font-size: var(--nut-size-3); line-height: var(--nut-lh-text);
}
.lore-depart .choix { display: flex; gap: 8px; flex-wrap: wrap; }

/* --- La corbeille des champs --- */
.lore-corbeille {
  margin-top: 16px; padding: 16px 20px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-2);
}
.lore-corbeille .tete {
  margin: 0 0 10px;
  font-size: 11px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.lore-corbeille .ligne {
  display: flex; align-items: center; gap: var(--nut-sp-3);
  padding: 7px 0; border-top: 1px solid var(--nut-line);
}
.lore-corbeille .ligne .nom {
  flex: 1 1 0; min-width: 0;
  font-size: var(--nut-size-3); color: var(--nut-text-muted);
}
.lore-corbeille .ligne .meta {
  flex: none; font-size: var(--nut-size-1); color: var(--nut-text-faint);
  white-space: nowrap;
}
.lore-corbeille .pied {
  margin: 10px 0 0;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
  line-height: var(--nut-lh-ui);
}

/* --- Le gestionnaire de modeles --- */
.lore-modele-carte {
  margin-bottom: 12px; padding: 18px 20px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-2);
}
.lore-modele-carte .entete {
  display: flex; align-items: center; gap: var(--nut-sp-2);
  margin-bottom: var(--nut-sp-2);
}
.lore-modele-carte .entete input[type="text"] {
  flex: 1 1 0; min-width: 0; height: 30px; padding: 0 8px;
  background: transparent;
  border: 1px solid transparent; border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit;
  font-family: var(--nut-font-title); font-size: var(--nut-size-4);
  font-weight: var(--nut-weight-semi);
}
.lore-modele-carte .entete input[type="text"]:hover,
.lore-modele-carte .entete input[type="text"]:focus {
  outline: none; border-color: var(--nut-line-strong); background: var(--nut-surface);
}
.lore-modele-carte .usage {
  flex: none; font-size: var(--nut-size-1); color: var(--nut-text-faint);
  white-space: nowrap;
}
.lore-champ-ligne {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--nut-line);
}
/* La corbeille d'un champ est une ICONE, pas un mot : la ligne porte deja un
   libelle modifiable et un compte, un troisieme mot la rendrait illisible. */
.lore-champ-ligne .jeter {
  flex: none; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
.lore-champ-ligne .jeter:hover { background: var(--nut-surface-raised); color: var(--nut-warn); }
.lore-champ-ligne input[type="text"] {
  flex: 1 1 0; min-width: 0; height: 30px;
  margin-left: -8px; padding: 0 8px;
  background: transparent;
  border: 1px solid transparent; border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
}
.lore-champ-ligne input[type="text"]:hover {
  border-color: var(--nut-line); background: var(--nut-surface-raised);
}
.lore-champ-ligne input[type="text"]:focus {
  outline: none;
  border-color: var(--nut-line-strong); background: var(--nut-surface-raised);
}
.lore-champ-ligne .compte {
  flex: none; font-family: var(--nut-font-data);
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
  white-space: nowrap;
}
.lore-champ-ligne.efface .nom {
  flex: 1 1 0; min-width: 0;
  font-size: var(--nut-size-2); color: var(--nut-text-faint);
  text-decoration: line-through;
}
.lore-champ-ligne .delai {
  flex: none; font-size: var(--nut-size-1); color: var(--nut-warn);
  white-space: nowrap;
}
.lore-modele-carte .ajout {
  display: flex; gap: 8px; align-items: center;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--nut-line);
}
.lore-modele-carte .ajout input[type="text"] {
  flex: 1 1 0; min-width: 0; height: 30px; padding: 0 8px;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
}
.modal.modeles { max-width: 620px; }
.modal.modeles .modal-corps { max-height: 62vh; overflow: auto; }

/* UN MODAL COURT SE CENTRE, un modal long reste en haut.
   Le voile commun ancre tout en haut, ce qui convient au modal de
   configuration et au selecteur d'icones — hauts tous les deux, et qui
   sauteraient a chaque changement de contenu. Une question de trois lignes,
   elle, flotte alors dans le tiers superieur de l'ecran.
   Les marges automatiques font les deux : elles centrent quand ca tient, et se
   reduisent a zero en haut quand ca deborde. */
.voile-centre > .modal { margin: auto 0; }

/* --- L IMAGE, EN TETE DU RECAPITULATIF ---
   Le prototype la pose AVANT le modele et la categorie : c'est ce qu'on voit en
   premier d'une fiche quand on l'ouvre, et ce qu'on cherche du regard quand on
   la reconnait dans un souvenir. */
.lore-image { margin-bottom: var(--nut-sp-4); }

/* LE CADRE GARDE SA PLACE AVANT QUE L'IMAGE ARRIVE. Le rapport 16/10 est fixe,
   donc la hauteur est connue des le premier rendu : le recapitulatif ne se
   reorganise pas sous les yeux au moment ou l'image se pose. C'est aussi
   pourquoi le serveur envoie les dimensions avec l'adresse. */
.lore-image .cadre {
  position: relative; aspect-ratio: 16 / 10;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-2);
  overflow: hidden;
}
.lore-image .cadre img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
.lore-image .meta {
  margin: var(--nut-sp-2) 0 0;
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
  line-height: var(--nut-lh-ui);
}
.lore-image .gestes { display: flex; gap: 6px; margin-top: var(--nut-sp-2); }

/* LA ZONE DE DEPOT EST EN POINTILLES, et c'est la seule bordure pointillee de
   l'application : elle dit « rien ici pour l'instant, mais quelque chose peut y
   tomber ». Une bordure pleine dessinerait un objet vide plutot qu'une place
   libre. */
.lore-depot {
  margin-bottom: var(--nut-sp-4); padding: 16px 14px;
  border: 1px dashed var(--nut-line); border-radius: var(--nut-radius-2);
  text-align: center;
}
.lore-depot.survol {
  border-color: var(--nut-accent-hi); background: var(--nut-surface-raised);
}
.lore-depot .dessin {
  display: inline-flex; margin-bottom: var(--nut-sp-2); color: var(--nut-text-faint);
}
.lore-depot .invite { margin: 0 0 4px; font-size: var(--nut-size-2); color: var(--nut-text); }
.lore-depot .regles {
  margin: 0 0 var(--nut-sp-3);
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
  line-height: var(--nut-lh-ui);
}
.lore-depot .gestes { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; }
/* LE CHAMP DE FICHIER EST CACHE, ET LA REGLE N'EST PAS QUALIFIEE PAR LA ZONE.
   Il vit VOLONTAIREMENT en dehors d'elle — « Remplacer » doit pouvoir l'ouvrir
   quand la zone n'existe plus. Ecrite « .lore-depot .fichier », elle ne
   l'atteignait donc jamais, et « Choose File » s'affichait en clair au milieu du
   recapitulatif. Trouve au rendu, le 07/08/2026. */
.lore-fichier { display: none; }

/* LA VIGNETTE FLOUE PENDANT L ENVOI. Ce n'est pas une decoration : c'est la
   seule preuve que le fichier qui part est bien celui qu'on a choisi. Une barre
   de progression seule laisse ce doute entier jusqu'a la fin.

   ELLE NE S APPELLE NI « apercu » NI « jauge ». Les deux existent deja — le
   premier dans campagne.js (une rangee avec 14 px de rembourrage), le second
   dans profil.js (une rangee en ligne). Ma vignette en aurait herite une
   disposition en ligne, et ma barre de 5 px serait devenue une rangee flexible.
   Trouve par le garde ajoute en 0.20.0, avant tout rendu. */
.lore-depot .vignette {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden;
  margin-bottom: 10px; border-radius: var(--nut-radius-1);
  background: var(--nut-surface-raised);
}
.lore-depot .vignette img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; filter: blur(6px); opacity: .7;
}
.lore-depot .progression {
  height: 5px; margin-bottom: var(--nut-sp-2);
  border-radius: 99px; background: var(--nut-line); overflow: hidden;
}
.lore-depot .progression > span { display: block; height: 100%; background: var(--nut-accent); }
.lore-depot .etape { margin: 0; font-size: var(--nut-size-2); color: var(--nut-text-muted); }

/* L ERREUR S ALIGNE A GAUCHE alors que tout le reste de la zone est centre :
   c'est une phrase a LIRE, parfois longue, et un texte centre sur trois lignes
   se relit mal. */
.lore-depot .erreur {
  display: flex; gap: 7px; align-items: flex-start;
  margin: var(--nut-sp-3) 0 0; text-align: left;
  font-size: var(--nut-size-2); color: var(--nut-warn);
  line-height: var(--nut-lh-ui);
}
.lore-depot .erreur > span:first-child { flex: none; margin-top: 1px; }

/* --- « Ajouter un champ », dans le recapitulatif ---
   Le prototype le pose ICI, a cote des champs qu'il complete. Y aller par
   l'ecran des modeles obligerait a quitter la fiche pour ajouter une ligne a
   son propre resume. */
.lore-ajout-champ { margin-top: var(--nut-sp-3); }
.lore-ajout-champ form { margin-bottom: 10px; }
.lore-ajout-champ input[type="text"] {
  width: 100%; height: 32px; padding: 0 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-accent-hi); border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-2);
}
.lore-ajout-champ .portee {
  margin: 6px 0 0;
  font-size: var(--nut-size-1); color: var(--nut-text-faint);
  line-height: var(--nut-lh-ui);
}

/* --- La zone de suppression, en bas du recapitulatif --- */
.lore-zone-suppression {
  margin-top: var(--nut-sp-6); padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
}
/* Le bouton ne s'etale PAS sur toute la colonne : le prototype lui donne la
   meme taille qu'a « Ajouter un champ », juste au-dessus. Un bouton pleine
   largeur en bas d'une colonne se lit comme l'action principale de l'ecran —
   c'est exactement ce que la suppression ne doit pas etre. */
.lore-zone-suppression .lore-bouton { align-self: flex-start; }

/* Le bouton qui confirme une suppression est PLEIN et rouge : c'est le seul
   endroit de l'application ou l'accent cede la place a l'avertissement, et
   c'est justement parce qu'il ne doit ressembler a rien d'autre. */
.modal-pied .primaire.danger {
  background: var(--nut-warn); color: rgb(16, 19, 15);
  border-color: var(--nut-warn);
}

/* Un bouton desactive doit se VOIR desactive : sinon on clique, rien ne se
   passe, et on croit a une panne. */
.modal-pied .primaire.danger:disabled {
  opacity: .45; cursor: not-allowed;
}

/* La consequence, dans le modal de suppression. Encadree a gauche par un filet
   d'avertissement : elle n'est pas une phrase de plus, c'est LA raison
   d'hesiter. */
.lore-consequence {
  display: flex; align-items: flex-start; gap: var(--nut-sp-2);
  margin-bottom: var(--nut-sp-3); padding: 10px 12px;
  background: var(--nut-surface-raised);
  border-left: 2px solid var(--nut-warn);
  border-radius: 0 var(--nut-radius-1) var(--nut-radius-1) 0;
}
.lore-consequence > svg { flex: none; margin-top: 2px; color: var(--nut-warn); }
.lore-consequence p { margin: 0; }
.lore-consequence .quoi {
  font-size: var(--nut-size-3); color: var(--nut-text);
  margin-bottom: 3px;
}
.lore-consequence .qui {
  font-size: var(--nut-size-2); color: var(--nut-text-muted);
  line-height: var(--nut-lh-text);
}

/* --- « Citee par » --- */
.lore-citee {
  margin-top: var(--nut-sp-4); padding-top: var(--nut-sp-4);
  border-top: 1px solid var(--nut-line);
}
.lore-citee .tete {
  margin: 0 0 8px;
  font-size: 11px; font-weight: var(--nut-weight-semi);
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.lore-citee .liens { display: flex; flex-direction: column; gap: 2px; }
/* DES RANGEES, PAS DES PASTILLES. Dans une colonne de 320 px, des pastilles se
   cassent en deux lignes des le second nom un peu long, et la liste cesse de se
   lire comme une liste. */
.lore-citee button {
  width: 100%; display: flex; align-items: center; gap: var(--nut-sp-2);
  padding: 6px 8px; margin-left: -8px;
  background: transparent; border: 0; border-radius: var(--nut-radius-1);
  color: var(--nut-text); font: inherit; font-size: var(--nut-size-3);
  text-align: left; cursor: pointer;
}
.lore-citee button:hover { background: var(--nut-surface-raised); }
.lore-citee button svg { flex: none; opacity: .65; }
.lore-citee button .nom {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lore-citee .aucune {
  margin: 0; font-size: var(--nut-size-2); color: var(--nut-text-faint);
  line-height: var(--nut-lh-text);
}

/* La version de l'autre, en lecture seule, dans le modal de conflit. */
.lore-leur-texte {
  max-height: 46vh; overflow: auto;
  padding: var(--nut-sp-3);
  background: var(--nut-surface-sunken);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: var(--nut-size-3); line-height: var(--nut-lh-text);
}
.lore-leur-texte p { margin: 0 0 var(--nut-sp-2); }
.lore-leur-texte ul, .lore-leur-texte ol { margin: 0 0 var(--nut-sp-2); padding-left: 22px; }
.lore-leur-texte .rien { color: var(--nut-text-faint); }

@media (max-width: 1100px) {
  /* Sous 1200 px, l'ecriture passe avant le classement : la colonne
     recapitulative s'efface, et le bouton la fait revenir a la demande. */
  .lore-recap { position: absolute; right: 0; top: 0; bottom: 0; z-index: 15;
    box-shadow: var(--nut-elev-2); }
  .lore-colonnes { position: relative; }
}
@media (max-width: 900px) {
  .lore-colonnes { flex-direction: column; }
  .lore-recap { position: static; width: auto; box-shadow: none;
    border-left: 0; border-top: 1px solid var(--nut-line); }
  .lore-deplier { display: none; }
  .lore-liste { width: auto; max-height: 40vh; border-right: 0; border-bottom: 1px solid var(--nut-line); }
  .lore-defilant { padding: 0 var(--nut-sp-4) 40px; }
  .lore-entete { padding: 12px var(--nut-sp-4); }
}


/* --- La coquille de l'ecran : une colonne qui occupe tout. --------------- */
.ab {
  height: 100%;
  display: flex; flex-direction: column;
  background: var(--nut-bg);
}
.ab .aide { color: var(--nut-text-muted); padding: var(--nut-sp-4); }
.ab-bandeau-erreur {
  margin: var(--nut-sp-3) var(--nut-sp-4); padding: 10px 14px;
  border: 1px solid var(--nut-warn); border-radius: var(--nut-radius-1);
  color: var(--nut-warn); font-size: var(--nut-size-2);
}

/* --- Le bandeau de tete : blason, titre, annee, etat. --------------------- */
.ab-tete {
  flex: none; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 12px 20px; border-bottom: 1px solid var(--nut-line);
}
.ab-blason {
  width: 38px; height: 46px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--nut-surface-raised);
  border-radius: var(--nut-radius-1) var(--nut-radius-1) 18px 18px;
  font-family: var(--nut-font-title); font-weight: 600; font-size: 16px;
}
/* Le blason IMAGE (12/08/2026) : l'ecusson garde sa forme, l'image la
   remplit — object-fit cover, jamais deformee. Meme regle que la fiche
   de maison. */
.ab-blason.avec-image { overflow: hidden; padding: 0; }
.ab-blason.avec-image img { width: 100%; height: 100%; object-fit: cover;
  display: block; }
.ab-tete h1 {
  margin: 0; font-family: var(--nut-font-title);
  font-size: var(--nut-size-7); font-weight: 600; line-height: 1.05;
}
/* UNE MAISON PAS ENCORE NOMMEE. Le titre garde sa place et sa taille — c'est
   bien un titre, il attend son texte — mais en gris, pour qu'on ne le lise
   pas comme un nom. Le repli sur le titre de la page a ete ecarte le
   13/08/2026 : il rendrait les deux valeurs indistinguables, ce qu'on vient
   precisement de corriger. */
.ab-tete h1.sans-nom { color: var(--nut-text-faint); font-weight: 500; }
.ab-tete .sous-titre {
  margin: 2px 0 0; color: var(--nut-text-muted); font-size: 12.5px;
}
/* Le crayon discret a cote du titre — renommer, blason. Copie de la fiche
   de maison, mais ici il appartient aux JOUEURS aussi (12/08/2026). */
.ab-editer-maison {
  flex: none; align-self: center;
  width: 28px; height: 28px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--nut-radius-1);
  color: var(--nut-text-faint); cursor: pointer;
}
.ab-editer-maison:hover { color: var(--nut-accent);
  background: var(--nut-surface-raised); }
/* L'encart de l'annee en cours — comme sur la fiche de maison. */
.ab-annee {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--nut-text-muted);
}
.ab-annee input { width: 76px; padding: 6px 8px; font-size: 12.5px;
  background: var(--nut-surface-sunken); color: var(--nut-text);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1); }
.ab-annee .suivante {
  padding: 6px 10px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1); background: none; cursor: pointer;
  font-size: 11.5px; color: var(--nut-text-muted);
}
.ab-annee .suivante:hover { border-color: var(--nut-accent);
  color: var(--nut-accent); }
/* Le modal « renommer / blason » — copie de la fiche de maison. */
.ab-maison-edit {
  width: min(480px, calc(100vw - 48px));
  background: var(--nut-surface); border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-3, 8px); padding: 18px 20px;
}
.ab-maison-edit h2 { margin: 0 0 14px; font-family: var(--nut-font-title);
  font-size: var(--nut-size-5); font-weight: 600; }
.ab-maison-edit h3 { margin: 18px 0 8px; font-size: 11px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--nut-text-faint); }
.ab-maison-edit .rang-nom { display: flex; gap: 8px; margin-top: 4px;
  flex-wrap: wrap; }
.ab-maison-edit .rang-nom input { flex: 1; min-width: 180px; padding: 8px 10px;
  background: var(--nut-surface-sunken); color: var(--nut-text);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: 13px; }
.ab-maison-edit .rang-blason { display: flex; gap: 14px;
  align-items: flex-start; }
.ab-maison-edit .apercu-blason {
  width: 64px; height: 76px; flex: none; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--nut-surface-sunken); border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1) var(--nut-radius-1) 30px 30px;
}
.ab-maison-edit .apercu-blason img { width: 100%; height: 100%; object-fit: cover; }
.ab-maison-edit .apercu-blason .vide { font-size: 11px; color: var(--nut-text-faint); }
.ab-maison-edit .gestes-blason { display: flex; flex-direction: column;
  gap: 8px; align-items: flex-start; }
.ab-maison-edit .gestes-blason .aide { font-size: 12px;
  color: var(--nut-text-faint); max-width: 34ch; }
.ab-maison-edit .erreur { color: var(--nut-warn); margin-top: 10px; }
.ab-maison-edit .pied-edit { margin-top: 18px; display: flex;
  justify-content: flex-end; }
/* L'etat d'enregistrement : discret, en chasse fixe. */
.ab-tete .etat-arbre {
  flex: none; font-family: var(--nut-font-data); font-size: 11px;
  color: var(--nut-text-faint);
}
.ab-tete .etat-arbre.souci { color: var(--nut-warn); }

/* --- Le corps : toile + panneau. ----------------------------------------- */
.ab-corps { flex: 1; min-height: 0; display: flex; align-items: stretch; }

/* --- La toile ------------------------------------------------------------- */
.ab-toile {
  flex: 1; min-width: 0; position: relative; overflow: hidden; cursor: grab;
  /* SANS CECI, LE NAVIGATEUR GARDE LE GESTE POUR LUI : il valait « auto », donc
     un glissement partait en defilement de page et un pincement zoomait le
     document entier, par-dessus nos ecouteurs. Second defaut signale au releve
     de lecture du 16/08. « none » rend les deux gestes a la toile. */
  touch-action: none;
}
.ab-toile:active { cursor: grabbing; }
.ab-monde {
  position: absolute; left: 0; top: 0;
  /* Largeur et hauteur INLINE : elles viennent de la mise en page. */
  transform-origin: 0 0;
}
.ab-monde > svg {
  position: absolute; left: 0; top: 0; pointer-events: none;
}

/* La pastille d'une union — posee sur le lien, centree. */
.ab-union {
  position: absolute; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 4px; padding: 2px 8px;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-pill);
  font-family: var(--nut-font-data); font-size: 10px; white-space: nowrap;
  color: var(--nut-text-muted);
}
.ab-union.officieuse { color: var(--nut-text-faint); }
.ab-union.ouverte { border-color: var(--nut-accent); }
.ab-union.cliquable { cursor: pointer; }
.ab-union.cliquable:hover { border-color: var(--nut-accent-hi); }

/* --- Une card de personnage ---------------------------------------------- */
/* LA TOILE NE SE SELECTIONNE PAS (23/08/2026).
   Renaud : « quand je fais defiler l'arbre a la souris, une partie du texte
   est selectionne comme si j'avais fait une selection ». C'est le
   comportement par defaut d'un glissement souris, et rien ne l'en empechait :
   « pointerdown » ne previent pas la selection, et la toile n'avait aucune
   regle. Deplacer un plan n'est pas surligner un texte.
   LE DETAIL D'UNE CARD FAIT EXCEPTION : c'est la que vit la prose — une bio,
   une note — et on doit pouvoir la copier. */
.ab-toile { -webkit-user-select: none; user-select: none; }
.ab-noeud .detail { -webkit-user-select: text; user-select: text; }

.ab-noeud {
  /* Largeur INLINE : derivee de la longueur du nom (190-340 px) — plus
     jamais de nom tronque (sprint 10). */
  position: absolute; z-index: 5;
  background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2);
  overflow: hidden; cursor: pointer;
  transition: width var(--nut-dur-2) var(--nut-ease);
}
.ab-noeud:hover { border-color: var(--nut-accent-hi); }
/* Decede : card assombrie, trait fin — le canal « vivant/mort » du brief. */
.ab-noeud.mort { background: var(--nut-surface-sunken); border-color: var(--nut-line); }
.ab-noeud.ouvert {
  z-index: 40;
  border-color: var(--nut-accent);
  box-shadow: var(--nut-elev-2);
  cursor: default;
}
/* Le liseret d'appartenance : 6 px en tete de card — passe de 3 a 6 px le
   12/08/2026, « il est difficilement visible ». Toujours present : le
   reglage qui permettait de le couper a ete retire le meme jour. */
.ab-noeud .liseret { display: block; height: 6px; }
.ab-noeud .corps-noeud {
  padding: 9px 12px 10px; display: flex; flex-direction: column; gap: 3px;
}
.ab-noeud .ligne-nom { display: flex; align-items: baseline; gap: 8px; }
.ab-noeud .nom {
  font-weight: 600; font-size: 13px; color: var(--nut-text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ab-noeud.mort .nom { color: var(--nut-text-muted); }
.ab-noeud .annees {
  margin-left: auto; font-family: var(--nut-font-data);
  font-size: 10.5px; color: var(--nut-text-faint); white-space: nowrap;
}
.ab-noeud .sub {
  font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--nut-text-faint);
}
.ab-noeud .chips { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 2px; }
.ab-noeud .chip {
  font-family: var(--nut-font-data); font-size: 9.5px; text-transform: uppercase;
  padding: 1px 6px; border-radius: var(--nut-radius-pill);
  border: 1px solid var(--nut-line-strong); color: var(--nut-text-muted);
}
.ab-noeud .chip.pj { border-color: var(--nut-info); color: var(--nut-info); }
/* LE CHIP D'AGE (16/08/2026) reste dans le gris des chips : PJ garde l'accent
   parce qu'il dit QUI joue, une information d'un autre ordre. Deux accents
   cote a cote ne hierarchisent plus rien. */
.ab-noeud .chip.age { border-color: var(--nut-line-strong); color: var(--nut-text-muted); }

/* La vue complexe — depliee sur place, sous un filet. */
.ab-noeud .detail {
  display: flex; flex-direction: column; gap: 7px;
  margin-top: 6px; padding-top: 9px; border-top: 1px solid var(--nut-line);
  cursor: auto;
}
.ab-noeud .detail p.bio {
  margin: 0; font-size: 12px; line-height: 1.55; color: var(--nut-text-muted);
}
.ab-noeud .detail .donnees {
  display: flex; flex-direction: column; gap: 3px;
  font-family: var(--nut-font-data); font-size: 11px; color: var(--nut-text-faint);
}
.ab-noeud .detail .donnees .vivant { color: var(--nut-ok); }
.ab-noeud .detail .donnees .mort { color: var(--nut-text-faint); }
.ab-noeud .actions-mj { display: flex; flex-wrap: wrap; gap: 5px; }
.ab-noeud .actions-mj button, .ab-union-pop .secondaire, .ab-conjoint .segment {
  padding: 5px 9px; border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1); font-size: 11.5px;
  color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-noeud .actions-mj button:hover { border-color: var(--nut-accent); color: var(--nut-accent); }
.ab-noeud .actions-mj button.risque:hover { border-color: var(--nut-warn); color: var(--nut-warn); }

/* L'encart « Nouvelle union » dans la card. */
.ab-conjoint {
  display: flex; flex-direction: column; gap: 7px; padding: 9px 10px;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  background: var(--nut-surface-raised);
}
.ab-conjoint .titre-encart {
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--nut-text-faint);
}
.ab-conjoint .segments { display: flex; gap: 4px; }
.ab-conjoint .segment { padding: 4px 9px; border-color: var(--nut-line); }
.ab-conjoint .segment[aria-pressed="true"] {
  border-color: var(--nut-accent); color: var(--nut-accent);
}
.ab-conjoint label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11.5px; color: var(--nut-text-muted);
}
.ab-conjoint input { width: 110px; }
.ab-conjoint select { width: 100%; }
.ab-conjoint .boutons { display: flex; gap: 5px; }

/* L'encart de confirmation de suppression : meme famille que « Nouvelle
   union », mais borde d'avertissement — il precede un geste destructeur. */
.ab-suppr {
  display: flex; flex-direction: column; gap: 7px; padding: 9px 10px;
  border: 1px solid var(--nut-warn); border-radius: var(--nut-radius-1);
  background: var(--nut-surface-raised);
}
.ab-suppr .titre-encart {
  font-size: 10.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--nut-warn);
}
.ab-suppr .dit-suppr {
  margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--nut-text-muted);
}
.ab-suppr .boutons { display: flex; gap: 5px; }
.ab-suppr .confirmer-suppr {
  padding: 5px 10px; border: 0; border-radius: var(--nut-radius-1);
  background: var(--nut-warn); color: var(--nut-text-on-accent);
  font-weight: 600; font-size: 11.5px; cursor: pointer;
}
.ab-suppr .segment {
  padding: 5px 9px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1); font-size: 11.5px;
  color: var(--nut-text-muted); background: none; cursor: pointer;
}

/* Les champs de saisie de l'ecran — un seul style. */
.ab input[type="text"], .ab input[type="number"], .ab textarea, .ab select {
  padding: 6px 8px; background: var(--nut-surface-sunken);
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: 12.5px; color: var(--nut-text); font-family: var(--nut-font-body);
}
.ab input[type="number"] { font-family: var(--nut-font-data); font-size: 12px; }
.ab textarea { font-size: 12px; line-height: 1.5; resize: vertical; }
.ab input:focus, .ab textarea:focus, .ab select:focus {
  outline: none; border-color: var(--nut-accent);
}

/* Le bouton plein — accent. */
.ab .primaire {
  padding: 5px 10px; border: 0; border-radius: var(--nut-radius-1);
  background: var(--nut-accent); color: var(--nut-text-on-accent);
  font-weight: 600; font-size: 11.5px; cursor: pointer;
}
.ab .primaire:hover { background: var(--nut-accent-hi); }

/* Le formulaire d'edition de la fiche (vue depliee, mode edition). */
.ab-noeud .edition-fiche { display: flex; flex-direction: column; gap: 7px; }
.ab-noeud .edition-fiche label {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 11.5px; color: var(--nut-text-muted);
}
.ab-noeud .edition-fiche .paire { display: flex; gap: 8px; flex-wrap: wrap; }
.ab-noeud .edition-fiche .paire .courte input { width: 90px; }
.ab-noeud .edition-fiche .segments { display: flex; gap: 4px; }
.ab-noeud .edition-fiche .segment {
  padding: 4px 9px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1); font-size: 11.5px;
  color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-noeud .edition-fiche .segment[aria-pressed="true"] {
  border-color: var(--nut-accent); color: var(--nut-accent);
}
.ab-noeud .edition-fiche .primaire { align-self: flex-start; padding: 6px 12px; font-size: 12px; }

/* --- Le popover d'une union ---------------------------------------------- */
.ab-union-pop {
  position: absolute; width: 230px; z-index: 60;
  background: var(--nut-surface); border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2); box-shadow: var(--nut-elev-2);
  padding: 12px 13px; display: flex; flex-direction: column; gap: 9px;
  cursor: auto;
}
.ab-union-pop .tete-pop { display: flex; align-items: center; gap: 8px; }
.ab-union-pop .titre-pop {
  font-size: 11px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--nut-text-faint);
}
.ab-union-pop .fermer-pop {
  margin-left: auto; width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font-size: 11px; background: none; cursor: pointer;
}
.ab-union-pop .fermer-pop:hover { color: var(--nut-text); }
.ab-union-pop .conjoints { font-size: 12px; color: var(--nut-text-muted); }
.ab-union-pop label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11.5px; color: var(--nut-text-muted);
}
.ab-union-pop input { width: 110px; }
.ab-union-pop .primaire, .ab-union-pop .secondaire { align-self: flex-start; }
.ab-union-pop .secondaire:hover { border-color: var(--nut-accent); color: var(--nut-accent); }
.ab-union-pop .note {
  margin: 0; font-size: 10.5px; line-height: 1.5; color: var(--nut-text-faint);
}

/* --- L'etat vide : la porte d'entree d'un arbre neuf ---------------------- */
.ab-vide {
  position: absolute; left: 50%; top: 42%;
  transform: translate(-50%, -50%); z-index: 65;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 26px 32px; text-align: center;
  background: var(--nut-surface); border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-2); box-shadow: var(--nut-elev-1);
  max-width: 380px;
}
.ab-vide .grand {
  margin: 0; font-family: var(--nut-font-title);
  font-size: var(--nut-size-5); font-weight: 600; color: var(--nut-text);
}
.ab-vide .petit {
  margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--nut-text-muted);
}
.ab-vide .primaire { padding: 8px 14px; font-size: 12.5px; }
.ab-couche.nut-grain .ab-vide { border-color: var(--nut-ornement); }

/* --- Les commandes de zoom ------------------------------------------------ */
.ab-zoom {
  position: absolute; top: 12px; right: 12px; z-index: 70;
  display: flex; flex-direction: column; gap: 4px;
}
.ab-zoom button {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: var(--nut-surface); border: 1px solid var(--nut-line-strong);
  border-radius: var(--nut-radius-1); font-size: 15px;
  color: var(--nut-text-muted); cursor: pointer;
}
.ab-zoom button:hover { color: var(--nut-text); }
.ab-zoom .recadrer { font-size: 12px; }

/* --- La legende ----------------------------------------------------------- */
/* LA LEGENDE VIT DANS LE PANNEAU (15/08/2026) : elle ne flotte plus sur le
   plan, donc plus de position absolue, plus de z-index, plus de coin de vue
   mange. Elle prend la forme des autres sections d'« Autour de l'arbre ». */
.ab-legende .entrees {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 10.5px; color: var(--nut-text-muted);
}
.ab-legende .entree { display: flex; align-items: center; gap: 7px; }
.ab-legende .carre-vivant {
  width: 16px; height: 10px; flex: none;
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line-strong); border-radius: 2px;
}
.ab-legende .carre-mort {
  width: 16px; height: 10px; flex: none;
  background: var(--nut-surface-sunken);
  border: 1px solid var(--nut-line); border-radius: 2px;
}
.ab-legende .trait-plein { width: 16px; flex: none; border-top: 1.5px solid var(--nut-line-strong); }
.ab-legende .trait-pointille { width: 16px; flex: none; border-top: 1.5px dashed var(--nut-line-strong); }
.ab-legende .filet-legende { height: 1px; background: var(--nut-line); margin: 2px 0; }
.ab-legende .pastille-liseret { width: 16px; height: 5px; flex: none; }

/* --- La mini-carte -------------------------------------------------------- */
.ab-minicarte {
  /* Hauteur INLINE : elle suit le ratio du monde calcule. */
  position: absolute; right: 12px; bottom: 12px; z-index: 70;
  width: 168px;
  background: var(--nut-surface-sunken);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  overflow: hidden; cursor: crosshair;
}
/* Largeur INLINE (l'echelle reelle de la card), hauteur fixe. */
.ab-minicarte .point { position: absolute; height: 4px; border-radius: 1px; }
.ab-minicarte .fenetre {
  position: absolute; border: 1px solid var(--nut-accent-hi);
  background: rgba(255, 176, 103, .08);
}

/* --- Le panneau « Autour de l'arbre » ------------------------------------ */
.ab-panneau {
  flex: none; background: var(--nut-surface);
  border-left: 1px solid var(--nut-line);
  display: flex; flex-direction: column;
  transition: width var(--nut-dur-2) var(--nut-ease); overflow: hidden;
}
.ab-panneau.ouvert { width: 292px; }
.ab-panneau.replie { width: 44px; }
.ab-panneau .rail {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 14px 0; width: 100%; border: 0; background: none;
  color: var(--nut-text-muted); cursor: pointer;
}
.ab-panneau .rail:hover { color: var(--nut-text); }
.ab-panneau .rail .vertical {
  writing-mode: vertical-rl; font-size: 11px; font-weight: 600;
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
}
.ab-panneau .dedans-panneau {
  display: flex; flex-direction: column; min-height: 0; flex: 1;
}
.ab-panneau .tete-panneau {
  flex: none; display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--nut-line);
}
.ab-panneau .tete-panneau h2 {
  margin: 0; font-family: var(--nut-font-title); font-size: 15px; font-weight: 600;
}
.ab-panneau .replier {
  margin-left: auto; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font-size: 12px; background: none; cursor: pointer;
}
.ab-panneau .replier:hover { border-color: var(--nut-line-strong); color: var(--nut-text); }
.ab-panneau .corps-panneau {
  flex: 1; min-height: 0; overflow-y: auto; padding: 14px;
  display: flex; flex-direction: column; gap: 18px;
}
.ab-panneau h3 {
  margin: 0; font-size: 11px; font-weight: 600;
  letter-spacing: var(--nut-track-caps); text-transform: uppercase;
  color: var(--nut-text-faint);
}
.ab-panneau .tete-section { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.ab-panneau .tete-section .ajouter {
  margin-left: auto; padding: 3px 8px;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  font-size: 11px; color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-panneau .tete-section .ajouter:hover { border-color: var(--nut-accent); color: var(--nut-accent); }

/* Les categories repliables de la maisonnee. */
.ab-cats { display: flex; flex-direction: column; gap: 5px; }
.ab-cat { border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1); overflow: hidden; }
.ab-cat > .toggle {
  width: 100%; display: flex; align-items: center; gap: 7px; padding: 7px 10px;
  background: var(--nut-surface-sunken); border: 0;
  font-size: 11.5px; font-weight: 600; color: var(--nut-text-muted);
  cursor: pointer; text-align: left;
}
.ab-cat > .toggle:hover { color: var(--nut-text); }
.ab-cat .chevron {
  font-size: 9px; color: var(--nut-text-faint);
  transition: transform var(--nut-dur-2) var(--nut-ease);
}
.ab-cat.ouverte .chevron { transform: rotate(90deg); }
.ab-cat .compte {
  margin-left: auto; font-family: var(--nut-font-data);
  font-size: 10px; color: var(--nut-text-faint);
}
.ab-cat .corps-cat { display: flex; flex-direction: column; }
.ab-membre {
  display: flex; align-items: baseline; gap: 8px;
  padding: 7px 10px 7px 26px; border-top: 1px solid var(--nut-line);
}
/* LE LISERET D'UN MEMBRE (16/08/2026). Il n'est jamais « venu d'une autre
   maison » — il EST de la maison, c'est sa lignee qui est autre : on ne lit
   donc que l'axe du sexe. Le retrait gauche passe de 26 a 23 px, le filet
   prend les 3 px : le texte ne bouge pas d'un pixel. */
.ab-membre.liserete { padding-left: 23px; border-left: 3px solid; }
.ab-membre .edite-sexe { display: flex; gap: 4px; align-items: center; }
.ab-membre .edite-sexe button {
  padding: 2px 8px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1); background: none;
  font-size: 11px; color: var(--nut-text-muted); cursor: pointer;
}
.ab-membre .edite-sexe button[aria-pressed="true"] {
  border-color: var(--nut-accent); color: var(--nut-accent);
}
.ab-membre .bloc-nom { flex: 1; min-width: 0; }
.ab-membre .nom-membre { display: block; font-size: 12.5px; font-weight: 500; color: var(--nut-text); }
.ab-membre.mort .nom-membre { color: var(--nut-text-faint); }
.ab-membre .edite-deces {
  display: inline-flex; margin-top: 4px; align-items: center; gap: 5px;
  font-size: 11px; color: var(--nut-text-muted);
}
.ab-membre .edite-deces input { width: 70px; padding: 3px 6px; font-size: 11px; }
/* Le PJ qui joue (ou a joue) ce membre — demande du 12/08/2026. Meme
   pastille que sur les cards de l'arbre. */
.ab-membre .pj-membre {
  display: inline-block; margin-left: 6px; padding: 0 5px;
  border: 1px solid var(--nut-info); border-radius: var(--nut-radius-pill);
  font-family: var(--nut-font-data); font-size: 9px; line-height: 15px;
  color: var(--nut-info); vertical-align: 1px;
}
.ab-membre .edite-joueur {
  display: flex; margin-top: 4px; align-items: center; gap: 5px;
  font-size: 11px; color: var(--nut-text-muted);
}
.ab-membre .edite-joueur input { width: 110px; padding: 3px 6px; font-size: 11px; }
.ab-membre .retirer-membre {
  margin-right: 6px; padding: 3px 7px;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: 10.5px; color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-membre .retirer-membre:hover { border-color: var(--nut-warn); color: var(--nut-warn); }
.ab-membre .annees-membre {
  font-family: var(--nut-font-data); font-size: 10.5px;
  color: var(--nut-text-faint); white-space: nowrap;
}
.ab-membre .crayon {
  width: 20px; height: 20px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: 10px; color: var(--nut-text-faint); background: none; cursor: pointer;
}
.ab-membre .crayon:hover { border-color: var(--nut-line-strong); color: var(--nut-text); }
.ab-cat .vide-cat {
  margin: 0; padding: 7px 10px 8px 26px; border-top: 1px solid var(--nut-line);
  font-size: 11px; color: var(--nut-text-faint);
}

/* Les reglages du MJ. */
.ab-reglages { border-top: 1px solid var(--nut-line); padding-top: 14px; }
.ab-reglages .qui-voit { margin: 0 0 10px; font-size: 11px; color: var(--nut-text-faint); }
/* L'habillage de l'arbre : le meme interrupteur segmente que partout, et
   « .segmente » est EMPRUNTE a page.js, comme le font deja Lore et la
   configuration. Une seule declaration, ailleurs. */
.ab-reglages .bloc-habillage { display: flex; flex-direction: column; gap: 7px; }
.ab-reglages .bloc-habillage > .intitule { font-size: 12px; color: var(--nut-text-muted); }
.ab-reglages .aide-reglage { margin: 0; font-size: 11px; color: var(--nut-text-faint); }
.ab-reglages .bloc-couleurs { margin-top: 12px; display: flex; flex-direction: column; gap: 9px; }
.ab-reglages .bloc-couleurs > .intitule { font-size: 12px; color: var(--nut-text-muted); }
.ab-reglages .rang-couleur { display: flex; align-items: center; gap: 8px; }
.ab-reglages .rang-couleur .qui { flex: 1; min-width: 0; font-size: 11.5px; color: var(--nut-text-faint); }
.ab-reglages .teintes-arbre { display: flex; gap: 4px; flex-wrap: wrap; }
.ab-reglages .teinte {
  width: 18px; height: 18px; flex: none;
  border: 1px solid transparent; border-radius: var(--nut-radius-1);
  cursor: pointer; padding: 0;
}
.ab-reglages .teinte[aria-pressed="true"] {
  border-color: var(--nut-text);
  box-shadow: 0 0 0 1px var(--nut-bg), 0 0 0 2px var(--nut-line-strong);
}
/* Les categories configurables — l'ecart n°2 du prototype, demande par
   Renaud : renommer, ajouter, retirer les categories de la maisonnee. */
.ab-reglages .bloc-categories { margin-top: 14px; display: flex; flex-direction: column; gap: 6px; }
.ab-reglages .bloc-categories > .intitule { font-size: 12px; color: var(--nut-text-muted); }
.ab-reglages .rang-categorie { display: flex; align-items: center; gap: 6px; }
.ab-reglages .rang-categorie input { flex: 1; min-width: 0; font-size: 12px; padding: 5px 8px; }
.ab-reglages .rang-categorie .retirer {
  width: 24px; height: 24px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  font-size: 11px; color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-reglages .rang-categorie .retirer:hover:not(:disabled) {
  border-color: var(--nut-warn); color: var(--nut-warn);
}
.ab-reglages .rang-categorie .retirer:disabled {
  opacity: .4; cursor: not-allowed;
}
.ab-reglages .rang-categorie .habitants {
  flex: none; font-family: var(--nut-font-data); font-size: 10px;
  color: var(--nut-text-faint); min-width: 14px; text-align: right;
}
.ab-reglages .ajouter-categorie {
  align-self: flex-start; padding: 4px 9px;
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
  font-size: 11px; color: var(--nut-text-muted); background: none; cursor: pointer;
}
.ab-reglages .ajouter-categorie:hover { border-color: var(--nut-accent); color: var(--nut-accent); }

.ab-panneau .note-pied {
  margin: 0; padding-top: 12px; border-top: 1px solid var(--nut-line);
  font-size: 11px; line-height: 1.6; color: var(--nut-text-faint);
}

/* --- Le modal « Ajouter a la maisonnee » ---------------------------------- */
.ab-voile {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0, 0, 0, .55);
  display: flex; align-items: center; justify-content: center;
}
.ab-modal {
  width: 340px; background: var(--nut-surface);
  border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-2);
  box-shadow: var(--nut-elev-2); padding: 16px 18px;
  display: flex; flex-direction: column; gap: 12px;
}
.ab-modal .tete-modal { display: flex; align-items: center; gap: 8px; }
.ab-modal h2 { margin: 0; font-family: var(--nut-font-title); font-size: 15px; font-weight: 600; }
.ab-modal .fermer-modal {
  margin-left: auto; width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); font-size: 11px; background: none; cursor: pointer;
}
.ab-modal .fermer-modal:hover { color: var(--nut-text); }
.ab-modal label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--nut-text-muted);
}
.ab-modal input[type="number"] { width: 110px; }
.ab-modal .pied-modal { display: flex; gap: 6px; margin-top: 2px; }
.ab-modal .pied-modal .primaire { padding: 7px 13px; font-size: 12.5px; }
.ab-modal .pied-modal .annuler {
  padding: 7px 13px; border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-1); font-size: 12.5px;
  color: var(--nut-text-muted); background: none; cursor: pointer;
}

/* --- La couche « habillage » : grain, filets, blason. --------------------- */
.ab-couche { display: flex; flex-direction: column; height: 100%; }
.ab-couche.nut-grain {
  background-color: var(--nut-bg);
  background-image:
    repeating-linear-gradient(0deg, var(--nut-grain) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--nut-grain) 0 1px, transparent 1px 3px);
}
.ab-couche .nut-filet {
  border: 1px solid var(--nut-ornement);
  box-shadow: inset 0 0 0 3px var(--nut-bg), inset 0 0 0 4px rgba(107, 90, 62, .45);
}
.ab-couche.nut-grain .ab-panneau { border-left-color: var(--nut-ornement); }
.ab-couche.nut-grain .ab-modal,
.ab-couche.nut-grain .ab-union-pop {
  border-color: var(--nut-ornement);
  box-shadow: inset 0 0 0 3px var(--nut-surface), inset 0 0 0 4px rgba(107, 90, 62, .45),
    var(--nut-elev-2);
}

@media (max-width: 900px) {
  .ab-panneau.ouvert { width: 240px; }
}

/* ========================================================================== */
/*  MOBILE — l'arbre n'est plus un plan, c'est une liste                      */
/* ========================================================================== */
/*
 * CE BLOC SE DECLARE EN FIN DE FEUILLE, et ce n'est pas un detail de style :
 * une media query n'ajoute AUCUNE specificite. A specificite egale, c'est la
 * derniere regle ecrite qui gagne. Le bouton du tiroir de la coquille est
 * reste masque en permanence pendant des semaines pour avoir ete declare dans
 * le mauvais ordre (13/08/2026). Ce qui est le defaut se declare avant, ce qui
 * le surcharge apres.
 *
 * LE PLAN N'EST PAS MASQUE ICI : il n'est PAS RENDU DU TOUT sous la frontiere
 * (voir estMobile dans le script). Le masquer aurait laisse tourner le
 * calcul de mise en page, la toile, la mini-carte et le panneau dans un DOM
 * invisible. La disposition, elle, reste calculee — la liste lui emprunte son
 * rang (la generation) et son posx (l'ordre dans la generation).
 */
@media (max-width: 720px), ((hover: none) and (pointer: coarse) and (max-width: 1180px)) {

  /* --- LE BANDEAU ------------------------------------------------------- */
  /* 22 px en Condensed pour le nom, 12 px pour le sous-titre : releve sur la
     maquette rendue, pas repris de son annexe. */
  .ab-tete-m {
    flex: none; display: flex; align-items: flex-start; gap: 12px;
    padding: 14px 16px 0;
  }
  .ab-tete-m .ab-blason { width: 38px; height: 46px; }
  .ab-tete-m h1 {
    margin: 0; font-family: var(--nut-font-title);
    font-size: 22px; font-weight: 600; line-height: 1.15;
  }
  .ab-tete-m h1.sans-nom { color: var(--nut-text-faint); font-weight: 500; }
  .ab-tete-m .sous-titre {
    margin: 2px 0 0; font-size: 12px; color: var(--nut-text-muted);
  }

  /* --- LA LIGNE D'ANNEE ------------------------------------------------- */
  /* Le pas-a-pas remplace le champ numerique du bureau : au doigt, un champ
     « number » ouvre un clavier pour changer une valeur d'un cran. Les deux
     boutons font 44 px, la valeur est en chasse fixe — elle change souvent et
     ne doit pas faire danser la ligne. */
  .ab-annee-m {
    flex: none; display: flex; align-items: center; gap: 10px;
    padding: 10px 16px 12px; min-height: 48px;
  }
  .ab-annee-m .intitule {
    font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: var(--nut-track-caps); color: var(--nut-text-faint);
  }
  .ab-annee-m .pas-a-pas {
    display: flex; align-items: center;
    border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  }
  .ab-annee-m .pas-a-pas button {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-muted);
    font-size: 17px; cursor: pointer;
  }
  .ab-annee-m .pas-a-pas button:disabled { opacity: .35; cursor: default; }
  .ab-annee-m .nombre {
    min-width: 46px; text-align: center;
    font-family: var(--nut-font-data); font-size: 16px; color: var(--nut-text);
  }
  /* SANS CALENDRIER, on ecrit ce que ca change — pas un champ vide. */
  .ab-annee-m .aucune { font-size: 12.5px; color: var(--nut-text-muted); }
  .ab-annee-m .etat-arbre {
    margin-left: auto; flex: none;
    font-family: var(--nut-font-data); font-size: 12px; color: var(--nut-ok);
  }
  .ab-annee-m .etat-arbre.souci { color: var(--nut-warn); }

  /* --- LES ONGLETS, colles sous la barre haute -------------------------- */
  .ab-onglets {
    position: sticky; top: 56px; z-index: 20;
    flex: none; display: flex; align-items: stretch;
    background: var(--nut-bg); border-bottom: 1px solid var(--nut-line);
  }
  .ab-onglets .onglet {
    flex: 1 1 0; min-width: 0; height: 48px;
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; border-bottom: 2px solid transparent;
    font-size: 14px; font-weight: 600; color: var(--nut-text-faint);
    cursor: pointer;
  }
  .ab-onglets .onglet[aria-selected="true"] {
    color: var(--nut-text); border-bottom-color: var(--nut-accent);
  }
  .ab-onglets .lire {
    flex: none; width: var(--nut-target-touch); height: 48px;
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-faint); cursor: pointer;
  }

  /* --- LA LISTE --------------------------------------------------------- */
  .ab-liste { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .ab-filtre { padding: 12px 16px 0; }
  .ab-filtre input {
    width: 100%; height: 48px; padding: 0 12px;
    background: var(--nut-surface-sunken); color: var(--nut-text);
    border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
    font-size: 16px;
  }
  .ab-filtre .compte {
    display: block; padding: 6px 0 0;
    font-size: 12px; color: var(--nut-text-faint);
  }
  .ab-section {
    display: flex; align-items: center; gap: 8px;
    padding: 14px 16px 6px;
    font-size: 11px; font-weight: 600; text-transform: uppercase;
    letter-spacing: var(--nut-track-caps); color: var(--nut-text-faint);
  }
  .ab-section .effectif { font-weight: 400; }

  /* --- UNE CARD --------------------------------------------------------- */
  /* 56 px MINIMUM ET DEUX LIGNES, releve a 348x56 sur la maquette. Le padding
     gauche vaut 13 px et non 16 : le liseret de 3 px prend la difference, et
     le texte reste aligne sur celui des autres blocs. */
  .ab-carte {
    display: block; width: 100%; min-height: 56px;
    padding: 8px 10px 8px 13px; text-align: left;
    background: none; border: 0; border-top: 1px solid var(--nut-line);
    cursor: pointer;
  }
  .ab-carte + .ab-carte { border-top: 1px solid var(--nut-line); }
  .ab-carte.ouverte { background: var(--nut-surface); }
  .ab-carte .haut { display: flex; align-items: center; gap: 8px; }
  .ab-carte .nom {
    flex: 1 1 auto; min-width: 0;
    font-size: 15px; font-weight: 600; color: var(--nut-text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* LE NOM GRISE DIT LE DECES — c'est la transcription mobile de la card
     assombrie du plan. La liste n'a pas de fond par card pour le porter. */
  .ab-carte.morte .nom { color: var(--nut-text-muted); }
  /* ON MARQUE L'EXCEPTION, PAS LA REGLE : un PNJ ne porte aucune pastille. */
  .ab-carte .pj {
    flex: none; font-family: var(--nut-font-data); font-size: 10px;
    padding: 1px 6px; border-radius: var(--nut-radius-pill);
    border: 1px solid var(--nut-accent-dim); color: var(--nut-accent);
  }
  .ab-carte .chevron { flex: none; color: var(--nut-text-faint); }
  .ab-carte .meta {
    margin: 2px 0 0; font-size: 12.5px; color: var(--nut-text-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* LE BADGE D'AGE VIT SUR LA LIGNE DE META, pas sur celle du nom : il est
     une LECTURE de l'age, et la ligne du nom est deja disputee entre le nom
     (en ellipse des « Ser Duncan de la Porte Haute »), la pastille PJ et le
     chevron. Ici il s'aligne en colonne quand on fait defiler la liste. */
  .ab-carte .meta .age {
    display: inline-block; margin-right: 6px; padding: 0 6px;
    border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-pill);
    font-family: var(--nut-font-data); font-size: 10px; text-transform: uppercase;
    letter-spacing: var(--nut-track-caps); color: var(--nut-text-faint);
    vertical-align: 1px;
  }

  /* --- LA CARD DEPLIEE -------------------------------------------------- */
  /* LE LISERET DE LA PARTIE DEPLIEE PASSE A L'ACCENT (16/08/2026, demande de
     Renaud). La card repliee garde la couleur qui dit l'appartenance ; le
     deplie, lui, n'avait aucun filet, et rien ne disait OU LA FICHE S'ARRETE
     — sur une fiche longue, les blocs de liens ressemblaient a la suite de la
     liste. L'orange ne redit pas l'appartenance, il trace une etendue. */
  .ab-fiche {
    padding: 4px 10px 14px 13px; background: var(--nut-surface);
    border-left: 3px solid var(--nut-accent);
  }
  .ab-fiche .joueur { margin: 0 0 8px; font-size: 12.5px; color: var(--nut-text-muted); }
  .ab-fiche .bio { margin: 0 0 6px; font-size: 13.5px; line-height: 1.55; color: var(--nut-text); }
  .ab-fiche h3 {
    display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
    margin: 14px 0 0; font-size: 11px; font-weight: 600;
    text-transform: uppercase; letter-spacing: var(--nut-track-caps);
    color: var(--nut-text-faint);
  }
  .ab-fiche h3 .note { font-weight: 400; text-transform: none; letter-spacing: normal; }
  /* UNE LIGNE DE LIEN EST UNE PORTE : 44 px, un chevron, et elle ouvre la
     card de la personne nommee. C'est le geste « comprendre une filiation »
     que le plan ne rendait pas possible au doigt. */
  .ab-fiche .lien {
    display: flex; align-items: center; gap: 10px;
    width: 100%; min-height: var(--nut-target-touch);
    padding: 6px 0; text-align: left;
    background: none; border: 0; border-top: 1px solid var(--nut-line);
    cursor: pointer;
  }
  .ab-fiche .lien:first-of-type { border-top: 0; }
  .ab-fiche .lien .dedans { flex: 1 1 auto; min-width: 0; }
  .ab-fiche .lien .qui {
    display: block; font-size: 14px; color: var(--nut-text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ab-fiche .lien.morte .qui { color: var(--nut-text-muted); }
  .ab-fiche .lien .quoi { display: block; font-size: 12px; color: var(--nut-text-faint); }
  .ab-fiche .lien .chevron { flex: none; color: var(--nut-text-faint); }
  .ab-fiche .sans-lien { margin: 12px 0 0; font-size: 12.5px; color: var(--nut-text-muted); }

  /* --- L'ETAT VIDE ------------------------------------------------------ */
  .ab-vide-m {
    margin: 16px; padding: 20px 18px;
    background: var(--nut-surface); border: 1px solid var(--nut-line);
    border-radius: var(--nut-radius-2);
  }
  .ab-vide-m h2 { margin: 0 0 8px; font-family: var(--nut-font-title);
    font-size: var(--nut-size-5); font-weight: 600; }
  .ab-vide-m p { margin: 0; font-size: 13px; line-height: 1.55;
    color: var(--nut-text-muted); }

  /* --- LA PASTILLE DE RETOUR -------------------------------------------- */
  /* Elle ne parait qu'apres un saut de proche en proche, et elle NOMME ou
     elle ramene : « retour » tout court obligerait a se souvenir. Flottante
     en bas, la ou le pouce arrive sans deplacer la main. */
  .ab-retour-m {
    position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
    z-index: 30; max-width: calc(100% - 32px);
    display: flex; align-items: center; gap: 8px;
    height: var(--nut-target-touch); padding: 0 16px;
    background: var(--nut-surface-raised);
    border: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-pill); box-shadow: var(--nut-elev-2);
    color: var(--nut-text); font-size: 13.5px; cursor: pointer;
  }
  .ab-retour-m .qui {
    min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* --- LA FEUILLE MONTANTE ---------------------------------------------- */
  .ab-voile-m {
    position: fixed; inset: 0; z-index: 90;
    background: rgba(10, 12, 10, .62);
    display: flex; align-items: flex-end;
  }
  .ab-feuille {
    width: 100%; max-height: 82%; overflow-y: auto;
    padding: 10px 16px 20px;
    background: var(--nut-surface);
    border-top: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-3) var(--nut-radius-3) 0 0;
  }
  .ab-feuille .poignee {
    display: block; width: 36px; height: 4px; margin: 0 auto 12px;
    background: var(--nut-line-strong); border-radius: var(--nut-radius-pill);
  }
  .ab-feuille h2 {
    margin: 0 0 10px; font-family: var(--nut-font-title);
    font-size: var(--nut-size-5); font-weight: 600;
  }
  .ab-feuille .entree {
    display: flex; align-items: center; gap: 12px;
    min-height: var(--nut-target-touch); padding: 4px 0;
    border-top: 1px solid var(--nut-line);
    font-size: 13px; color: var(--nut-text-muted);
  }
  .ab-feuille .entree:first-of-type { border-top: 0; }
  .ab-feuille .trait { flex: none; width: 22px; height: 8px; border-radius: 2px; }
  .ab-feuille .trait.pointille {
    height: 0; border-top: 3px dashed var(--nut-text-faint);
    background: none; border-radius: 0;
  }
  .ab-feuille .exemple { flex: none; font-size: 13px; }
  .ab-feuille .exemple.grise { color: var(--nut-text-muted); }
  .ab-feuille .fermer {
    width: 100%; min-height: var(--nut-target-touch); margin-top: 14px;
    background: none; border: 1px solid var(--nut-line);
    border-radius: var(--nut-radius-1); color: var(--nut-text);
    font-size: 14px; cursor: pointer;
  }

  /* --- LA MAISONNEE ------------------------------------------------------ */
  /* Ses membres n'ont ni sexe ni biographie dans le modele : pas de liseret
     colore, et une fiche courte. Voir la note du gabarit. */
  .ab-section.avec-plus { padding-right: 6px; }
  /* 44 PX DE HAUT, ET NON 36. La maquette rend ce bouton a 44 x 36 sur ses
     huit sections ; son annexe ecrit pourtant « chaque section avec son + (44
     px) ». Le dessin contredit sa propre regle, et la grammaire de la phase 1
     — cible de 44 px, sans exception — n'est pas negociable. Ecart annonce
     dans design/LECTURE-MAQUETTE-ARBRE-MOBILE.md, section 4. */
  .ab-section .ajouter-cat {
    margin-left: auto; flex: none;
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-faint);
    font-size: 17px; cursor: pointer;
  }
  .ab-section .vide-cat {
    margin-left: auto; font-weight: 400; text-transform: none;
    letter-spacing: normal; font-size: 12px;
  }
  .ab-carte.membre { border-left: 3px solid var(--nut-line-strong); }
  .ab-fiche .categorie { margin: 0; font-size: 12.5px; color: var(--nut-text-muted); }

  /* La liste porte la pastille et la feuille : elles se posent DANS
     l'ecran, pas dans la fenetre — le prototype est un telephone pose dans
     une page, et « fixed » l'aurait collee au navigateur. */

  /* --- LES PLEINS ECRANS D'ECRITURE ------------------------------------- */
  /* « Lire, c'est la liste ; modifier, c'est un plein ecran. » Aucune saisie
     dans une card : sur 353 px, un champ pose dans une liste qui defile perd
     son libelle des que le clavier monte. */
  /* FIXED, ET NON ABSOLUTE. Premiere version : le plein ecran se posait dans
     « ab-m » et laissait la barre haute de la coquille au-dessus de lui — la
     surligne de campagne, le burger et la loupe restaient visibles pendant
     qu'on modifiait une fiche. La maquette, elle, couvre TOUT l'ecran : sa
     barre a elle porte la croix, le titre et l'etat, et rien d'autre.
     Vu sur la capture, pas dans le code. Le z-index passe au-dessus des 41 de
     la barre haute mobile de la coquille. */
  .ab-plein {
    position: fixed; inset: 0; z-index: 90;
    display: flex; flex-direction: column; background: var(--nut-bg);
  }
  .ab-plein .tete {
    flex: none; display: flex; align-items: center; gap: 10px;
    height: 56px; padding: 0 6px; border-bottom: 1px solid var(--nut-line);
  }
  .ab-plein .tete .fermer {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-muted);
    font-size: 17px; cursor: pointer;
  }
  .ab-plein .tete h2 {
    flex: 1 1 auto; min-width: 0; margin: 0;
    font-size: 15px; font-weight: 600; color: var(--nut-text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .ab-plein .tete .etat-arbre {
    flex: none; font-family: var(--nut-font-data); font-size: 12px;
    color: var(--nut-ok); padding-right: 8px;
  }
  .ab-plein .tete .etat-arbre.souci { color: var(--nut-warn); }
  .ab-plein .corps { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 16px 24px; }
  /* LA MARGE PORTE SUR TOUS LES CHAMPS, pas seulement sur ceux qui sont des
     LABEL. Le selecteur disait « label.champ » ; or un champ qui contient
     plusieurs commandes (le liseret, le pas-a-pas d'annee) est un DIV, pas un
     label — un label ne peut en designer qu'une. Ces blocs-la n'avaient donc
     aucun espacement, et l'aide du liseret venait toucher le libelle
     « Année de naissance ». Vu sur la capture. */
  .ab-plein .champ { display: block; margin-top: 16px; }
  .ab-plein .champ:first-child { margin-top: 0; }
  .ab-plein .libelle {
    display: block; margin-bottom: 6px;
    font-size: 12.5px; color: var(--nut-text-muted);
  }
  /* 48 px ET 16 px DE TEXTE : sous 16 px, Safari mobile zoome tout seul sur le
     champ focalise et la mise en page saute. */
  .ab-plein input[type="text"], .ab-plein textarea, .ab-plein select {
    width: 100%; min-height: 48px; padding: 10px 12px;
    background: var(--nut-surface-sunken); color: var(--nut-text);
    border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
    font-size: 16px; font-family: var(--nut-font-body);
  }
  .ab-plein textarea { min-height: 120px; line-height: 1.55; resize: vertical; }
  .ab-plein .segments { display: flex; gap: 8px; }
  .ab-plein .segments button {
    flex: 1 1 0; min-height: 48px; display: flex; align-items: center;
    justify-content: center; gap: 8px;
    background: none; border: 1px solid var(--nut-line);
    border-radius: var(--nut-radius-1); color: var(--nut-text);
    font-size: 14px; cursor: pointer;
  }
  .ab-plein .segments button[aria-pressed="true"] {
    border-color: var(--nut-accent); color: var(--nut-accent);
  }
  .ab-plein .segments .trait { width: 18px; height: 6px; border-radius: 2px; }
  .ab-plein .rang { display: flex; align-items: center; gap: 12px; }
  .ab-plein .pas-a-pas {
    display: flex; align-items: center; flex: none;
    border: 1px solid var(--nut-line); border-radius: var(--nut-radius-1);
  }
  .ab-plein .pas-a-pas button {
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-muted);
    font-size: 17px; cursor: pointer;
  }
  /* LE NOMBRE EST DEVENU UN CHAMP (27/08/2026) — et il doit cesser de
     ressembler a un champ. Sans cette regle il herite du « width: 100% » et
     du filet de tous les champs de l'ecran, et le groupe − / nombre / +
     eclate sur deux lignes. Il garde ses 16 px : sous 16, Safari mobile
     zoome sur le champ focalise et la mise en page saute. */
  .ab-plein .pas-a-pas input.nombre {
    width: 72px; min-width: 0; min-height: var(--nut-target-touch);
    padding: 0; text-align: center;
    background: none; border: 0; border-radius: 0;
    font-family: var(--nut-font-data); font-size: 16px; color: var(--nut-text);
  }
  /* IL EST FOCALISABLE, DONC IL SE VOIT FOCALISE. Un champ sans filet qui ne
     marque pas son focus est invisible au clavier. */
  .ab-plein .pas-a-pas input.nombre:focus-visible {
    outline: 2px solid var(--nut-accent); outline-offset: -3px;
    border-radius: var(--nut-radius-1);
  }
  /* LA CASE SE TAPE SUR TOUTE SA LIGNE : 44 px de haut, temoin de 22 px. La
     regle de la phase 1 porte sur la ZONE TAPPABLE, pas sur le dessin. */
  .ab-plein .case {
    display: flex; align-items: center; gap: 12px;
    width: 100%; min-height: var(--nut-target-touch); margin-top: 12px;
    padding: 0; background: none; border: 0;
    border-top: 1px solid var(--nut-line);
    color: var(--nut-text); font-size: 14px; text-align: left; cursor: pointer;
  }
  .ab-plein .case .temoin {
    flex: none; width: 22px; height: 22px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--nut-line-strong); border-radius: var(--nut-radius-1);
    color: var(--nut-text-on-accent); font-size: 13px;
  }
  .ab-plein .case[aria-pressed="true"] .temoin {
    background: var(--nut-accent); border-color: var(--nut-accent);
  }
  .ab-plein .case .dedans { flex: 1 1 auto; min-width: 0; }
  /* L'AIDE PASSE A LA LIGNE PARTOUT, pas seulement dans une case. La regle
     etait scopee a « .case » ; dans les radios de rattachement, l'aide restait
     donc en ligne et se collait au titre — « Enfant d'une unionChoisis
     l'union… ». Vu sur la capture. */
  .ab-plein .aide-case {
    display: block; margin-top: 2px;
    font-size: 12px; line-height: 1.45; color: var(--nut-text-faint);
  }
  .ab-plein h3 {
    margin: 20px 0 6px; font-size: 11px; font-weight: 600;
    text-transform: uppercase; letter-spacing: var(--nut-track-caps);
    color: var(--nut-text-faint);
  }
  .ab-plein .radio {
    display: flex; align-items: flex-start; gap: 12px;
    width: 100%; min-height: var(--nut-target-touch); padding: 8px 0;
    background: none; border: 0; border-top: 1px solid var(--nut-line);
    color: var(--nut-text); font-size: 14px; text-align: left; cursor: pointer;
  }
  .ab-plein .radio .rond {
    flex: none; width: 20px; height: 20px; margin-top: 2px;
    border: 1px solid var(--nut-line-strong); border-radius: 50%;
  }
  .ab-plein .radio[aria-checked="true"] .rond {
    border-color: var(--nut-accent); border-width: 6px;
  }
  .ab-plein .primaire-large {
    width: 100%; min-height: 52px; margin-top: 22px;
    background: var(--nut-accent); border: 0; border-radius: var(--nut-radius-1);
    color: var(--nut-text-on-accent); font-size: 15px; font-weight: 600;
    cursor: pointer;
  }
  .ab-plein .primaire-large:disabled { opacity: .45; cursor: default; }

  /* --- LA GRILLE D'ACTIONS DE LA FICHE ---------------------------------- */
  .ab-fiche .actions {
    display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px;
  }
  .ab-fiche .actions button {
    min-height: var(--nut-target-touch); padding: 6px 8px;
    background: none; border: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-1); color: var(--nut-text);
    font-size: 13px; text-align: center; cursor: pointer;
  }
  .ab-fiche .actions button.risque {
    border-color: var(--nut-warn); color: var(--nut-warn);
  }
  .ab-fiche .actions button:disabled { opacity: .4; cursor: default; }
  .ab-fiche .pourquoi { margin: 8px 0 0; font-size: 12px; color: var(--nut-text-faint); }

  /* --- LE RANG « AJOUTER UNE PERSONNE » --------------------------------- */
  .ab-ajout-rang {
    display: flex; align-items: center; gap: 10px;
    width: 100%; min-height: 48px; padding: 0 16px;
    background: none; border: 0; border-bottom: 1px solid var(--nut-line);
    color: var(--nut-text); font-size: 14px; text-align: left; cursor: pointer;
  }
  .ab-ajout-rang .plus { flex: none; font-size: 17px; color: var(--nut-text-faint); }

  /* --- LA FEUILLE DESTRUCTRICE ------------------------------------------ */
  .ab-feuille .dit { margin: 0 0 16px; font-size: 13.5px; line-height: 1.55;
    color: var(--nut-text-muted); }
  .ab-feuille .detruire {
    width: 100%; min-height: 52px; background: var(--nut-warn); border: 0;
    border-radius: var(--nut-radius-1); color: var(--nut-text-on-accent);
    font-size: 15px; font-weight: 600; cursor: pointer;
  }


  /* --- LE CRAYON DU BANDEAU --------------------------------------------- */
  .ab-crayon-m {
    flex: none; margin-left: auto;
    width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-faint); cursor: pointer;
  }

  /* --- LES REGLAGES ------------------------------------------------------ */
  .ab-plein .segment-large {
    min-height: var(--nut-target-touch); padding: 0 14px;
    background: none; border: 1px solid var(--nut-line-strong);
    border-radius: var(--nut-radius-1); color: var(--nut-text);
    font-size: 13.5px; cursor: pointer;
  }
  .ab-plein .aide-case.erreur { color: var(--nut-warn); }
  .ab-palette { margin-top: 14px; }
  .ab-palette .intitule-liseret {
    display: flex; align-items: center; gap: 10px; min-height: 24px;
    font-size: 13.5px; color: var(--nut-text);
  }
  .ab-palette .trait { flex: none; width: 20px; height: 7px; border-radius: 2px; }
  /* DEUX RANGEES DE QUATRE, ET NON UNE DE HUIT.
     Huit teintes sur une ligne donnent 38 px de large chacune a 353 px — et
     le harnais les a comptees, toutes les trente-deux. J'avais d'abord ecrit
     ici une justification : « la regle porte sur la zone tappable, et une
     rangee de choix exclusifs se vise a l'horizontale ». C'etait une exception
     inventee pour sauver une mise en page, alors que replier en deux rangees
     donne 84 x 44 sans rien couter. La grammaire dit 44 px sans exception ;
     quand la regle et la mise en page se disputent, c'est la mise en page qui
     cede. La tache dessinee, elle, fait 22 px. */
  .ab-palette .rangee-teintes {
    display: flex; flex-wrap: wrap; gap: 2px; margin-top: 4px;
  }
  .ab-palette .teinte {
    flex: 0 0 calc(25% - 2px); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; cursor: pointer;
  }
  .ab-palette .teinte .tache {
    width: 22px; height: 22px; border-radius: 50%;
    border: 1px solid var(--nut-line);
  }
  .ab-palette .teinte.choisie .tache {
    box-shadow: 0 0 0 2px var(--nut-bg), 0 0 0 4px var(--nut-accent);
  }
  .ab-ligne-cat { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
  .ab-ligne-cat input { flex: 1 1 auto; min-width: 0; }
  .ab-ligne-cat .retirer {
    flex: none; width: var(--nut-target-touch); height: var(--nut-target-touch);
    display: flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--nut-text-faint);
    font-size: 15px; cursor: pointer;
  }
  .ab-ligne-cat .retirer:disabled { opacity: .3; cursor: default; }
  .ab-m { position: relative; }
}


/* ==========================================================================
   L'ECRAN DU MODELE RACINE
   Cotes reprises de design/recu-systemes (LISEZMOI, section « Les valeurs ») :
   badge pill 10/9 px mono, titre 21 px condensed, phrase 13 px.
   ========================================================================== */

#racine {
  max-width: 940px;
  /* LE REMBOURRAGE DU PANNEAU EST UNE VARIABLE, et pour la meme raison qu'aux
     parametres : le builder remplit le panneau de bord a bord et doit
     l'ANNULER exactement. Ecrit deux fois, il se desynchronise au premier
     changement de palier — c'est le defaut de 7 px de la 0.83.0. */
  --racine-pad: 22px;
}

/* LA FLECHE MENE QUELQUE PART, ET C'EST LA CONDITION DE SON EXISTENCE.
   Celle qu'on a enterree le 19/08 appelait une action qui ne produisait rien.
   Celle-ci est un LIEN vers l'onglet Systemes de l'administration — et
   l'administration sait ouvrir cet onglet sur ce fragment, sinon on
   retomberait sur « Membres » et le lien serait mort a moitie. */
.rc-retour {
  display: inline-flex; align-items: center; gap: 6px;
  height: 44px; margin-left: -10px; padding: 0 10px;
  border-radius: var(--nut-radius-1);
  color: var(--nut-text-muted); text-decoration: none; font-size: 13px;
}
.rc-retour:hover { background: var(--nut-surface-raised); color: var(--nut-text); }

.rc-tete { margin: 2px 0 18px; }
.rc-titre-ligne { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.rc-badge {
  flex: none;
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 10px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--nut-accent) 22%, transparent);
  color: var(--nut-accent);
  font-family: var(--nut-font-data); font-size: 10px;
  letter-spacing: .09em; text-transform: uppercase;
}
.rc-titre {
  margin: 0;
  font-family: var(--nut-font-title); font-size: 21px;
  font-weight: var(--nut-weight-semi);
}
/* LA PHRASE DE PORTEE, VERSION LONGUE. Le compte est en evidence : « les 7
   campagnes en cours » sans le 7 ne rassure personne, et c'est le chiffre qui
   fait accepter le geste. */
.rc-portee { margin: 8px 0 0; font-size: 13px; color: var(--nut-text-muted); max-width: 72ch; }
.rc-portee .compte { color: var(--nut-accent); }

.rc-panneau {
  background: var(--nut-surface-raised);
  border: 1px solid var(--nut-line);
  border-radius: var(--nut-radius-3);
}
.rc-dedans { padding: var(--racine-pad); }

/* LE BUILDER REMPLIT LE PANNEAU, MAIS IL A UN VOISIN AU-DESSUS.
   « .mn-builder.pleine » porte une marge negative sur les QUATRE cotes, ecrite
   du temps ou le builder REMPLACAIT la fiche de maison et devait franchir le
   rembourrage de la page. Ici, l'en-tete du modele racine est au-dessus de
   lui : les 24 px du haut le tireraient par-dessus, exactement le defaut de la
   0.82.1. On annule la seule moitie qui n'est plus vraie. */
#racine .mn-builder.pleine {
  margin: 0 calc(-1 * var(--racine-pad)) calc(-1 * var(--racine-pad));
  border-radius: 0 0 var(--nut-radius-3) var(--nut-radius-3);
}

/* LA CONFIRMATION. Elle reprend .voile et .modal de la feuille commune : ce
   n'est pas un dessin de plus, c'est celui du produit. */
.rc-confirme { max-width: 520px; }
.rc-confirme .modal-corps p { margin: 0 0 12px; font-size: 14px; line-height: 1.5; }
.rc-confirme .modal-corps p:last-child { margin-bottom: 0; }
.rc-confirme strong { color: var(--nut-text); font-weight: var(--nut-weight-semi); }

@media (max-width: 900px) {
  #racine { --racine-pad: 16px; }
}
