/* Gilt Rust — styles. Repris du prototype (_source/prototype/index.html) puis
   étendus : file de construction, écrans d'auth, classement, dialog system.
   Univers visuel PROPRE au jeu (nuit/pierre/or/rouille + serif de registre),
   distinct des tokens "--bg-..." et "--accent-..." de l'écosystème Stephan.
   PIEGE VECU : ne JAMAIS écrire « étoile slash » dans un commentaire CSS
   (ex. un glob de token) — ça ferme le commentaire et la récupération
   d'erreur du parseur avale la règle suivante. Ici c'était :root entier :
   toutes les variables mortes, texte noir partout. */

:root {
  /* ── MATIÈRE — ce que c'est. Ne change qu'en refonte. ────────────────────
     Toutes ces valeurs sont EXTRAITES de art.js, aucune inventée.

     Les neutres de l'interface étaient à 212° de teinte — la rampe `slate` de
     Tailwind, le gris par défaut de toutes les interfaces sombres depuis 2021
     — quand ceux des sprites sont à 25°. 187° d'écart, soit l'opposé sur la
     roue : le village était éclairé à la lampe et l'interface à l'écran de
     bureau. C'est ça, et pas un manque de goût, qui la faisait ressembler à
     une application générée.

     --rouille #d4694a est la couleur la PLUS utilisée de tout l'art
     (10 occurrences) et ne servait à rien ici.

     --os-faible ne vaut pas #96856a, premier candidat : 5,3:1 sur --fond mais
     4,48:1 sur --surface-haute, deux centièmes sous le seuil AA. Le banc l'a
     écarté avant qu'on ne le voie. */
  --suie:#13100c;    --suie-claire:#1c1712;  --brique:#27201a;
  --fonte:#362c22;   --nuit-scene:#1a1510;
  --or:#d8a441;      --laiton:#c8912f;
  --rouille:#d4694a; --mousse:#8fae5c;       --verdet:#93ab90;
  --os:#ece3d2;      --os-doux:#b8a488;      --os-faible:#9c8b70;
  /* Le papier sort de la MÊME palette de sprites que la suie — #e8dfc8,
     #c9b89a et #3a2b1e sont tous les trois dans art.js. Ce n'est donc pas un
     thème clair posé à côté d'un thème sombre, c'est la même matière vue de
     l'autre bord. */
  --papier:#ddd2b6;  --encre:#3a2b1e;        --papier-filet:#bda98a;

  /* ── RÔLE — à quoi ça sert. C'est la couche qu'on règle. ─────────────────
     Un composant ne connaît que ça. Régler « le texte secondaire » devient
     une ligne, au lieu de savoir qu'il s'appelle --os-doux et d'espérer qu'il
     ne serve à rien d'autre. Tenu par _tools/tests/test_jetons.php. */
  --fond:var(--suie);
  --surface:var(--suie-claire);
  --surface-haute:var(--brique);
  --filet:var(--fonte);
  --fond-scene:var(--nuit-scene);
  --texte:var(--os);
  --texte-doux:var(--os-doux);
  --texte-faible:var(--os-faible);
  --accent:var(--or);          /* ce qui est actif, et rien d'autre */
  --focus:var(--laiton);       /* le clavier seul, jamais l'état actif */
  --positif:var(--mousse);
  --repos:var(--verdet);       /* liens, et ce qui est stable sans être un succès */
  --alerte:var(--rouille);

  /* ── RELIEF — la lumière vient d'en haut, toujours ───────────────────────
     Une plaque a une épaisseur : arête haute éclairée, arête basse dans
     l'ombre. Ces deux valeurs remplacent le filet de contour uniforme, qui
     était le réflexe tenant lieu de hiérarchie. */
  --lumiere:#46392c;    /* liseré supérieur d'une plaque */
  --ombre:#0a0705;      /* arête inférieure, et fond de gorge */
  --creux:#0b0806;      /* fond de jauge, gravé dans la plaque */

  /* ── FORMES — un rayon par nature, plus un seul pour tout ────────────── */
  --r-plaque:2px;       /* instrument : usiné, presque vif */
  --r-doux:8px;         /* ce qui flotte : dialogues, messages */

  --serif:'Iowan Old Style','Palatino Linotype',Palatino,'Book Antiqua',Georgia,serif;
  --sans:system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;
  --marge:clamp(12px,4vw,24px);
}
*,*::before,*::after { box-sizing:border-box; }
html,body {
  margin:0; background:var(--fond); color:var(--texte);
  font-family:var(--sans); font-size:16px; line-height:1.5;
  overscroll-behavior:none;              /* coupe le pull-to-refresh Android */
  -webkit-text-size-adjust:100%;
}
body.jeu { -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
button {
  font:inherit; color:inherit; cursor:pointer;
  touch-action:manipulation;             /* tue le double-tap zoom et le délai de 300 ms */
  -webkit-tap-highlight-color:transparent;
}
/* LE FOCUS A SA PROPRE COULEUR, et ce n'est pas un caprice : --accent désigne
   « ce qui est actif » — l'onglet courant, la valeur qui monte. Peindre
   l'anneau de focus avec la même couleur rendait les deux indiscernables au
   clavier : on ne savait plus si un onglet était sélectionné ou seulement
   atteint par la tabulation.

   Le laiton donne 6,82:1 sur --fond, très au-dessus des 3:1 exigés pour un
   contour d'élément d'interface. Mesuré par test_contraste.php. */
:focus-visible { outline:2px solid var(--focus); outline-offset:2px; }
a { color:var(--repos); }

.app {
  max-width:720px; margin:0 auto; min-height:100dvh;
  display:flex; flex-direction:column;
  padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* le registre : chiffres en serif à chasse tabulaire, séparés par des filets.
   Grille FLUIDE : le nombre de postes suit les ressources déclarées dans
   regles.json — une grille à 3 colonnes fixes tasserait tout dès la 4e. */
.ledger {
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(84px, 1fr));
  gap:6px 0;
  border-bottom:1px solid var(--filet);
  padding:14px var(--marge) 12px;
}
/* Le filet séparateur suivait l'ordre des éléments : sur plusieurs lignes, il
   tombait au mauvais endroit. Bordure à gauche de chaque poste, retirée sur le
   premier — et sur ceux qui commencent une ligne, ce que la grille fluide ne
   permet pas de cibler en CSS : on l'accepte, le filet reste discret. */
.poste { padding-inline:10px; border-left:1px solid var(--filet); }
.poste:first-child { padding-left:0; border-left:0; }
/* LA GRAVURE. L'intitulé est frappé DANS la plaque : un cheveu de lumière sous
   le texte suffit à creuser, à condition qu'il reste un cheveu.
   Le contraste est porté par la COULEUR du texte, jamais par l'ombre — c'est ce
   qui laisse test_contraste.php seule autorité sur la lisibilité. */
.poste__label {
  display:block; font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--texte-faible); text-shadow:0 1px 0 rgba(255,255,255,.06);
}
.poste__valeur { display:block; font-family:var(--serif); font-size:24px; line-height:1.15; font-variant-numeric:tabular-nums; }
/* La couleur suit la RESSOURCE, plus son rang dans la liste : avec nth-child,
   ajouter une ressource repeignait toutes les suivantes. */
.poste--or .poste__valeur        { color:var(--accent); }
.poste--mousse .poste__valeur    { color:var(--positif); }
.poste--os-doux .poste__valeur   { color:var(--texte-doux); }
.poste--os-faible .poste__valeur { color:var(--texte-faible); }
.poste--verdet .poste__valeur    { color:var(--repos); }
.poste--rouille .poste__valeur   { color:var(--alerte); }
.poste__taux { display:block; font-size:12px; color:var(--texte-doux); font-variant-numeric:tabular-nums; }
.poste__taux--perte { color:var(--alerte); }
/* jauge des ressources plafonnées (l'énergie) : le rapport à la capacité
   se lit d'un coup d'oeil, ce qu'un nombre seul ne permet pas */
/* UNE JAUGE DEVIENT UN CADRAN DÈS QU'ELLE PORTE DES CRANS.
   C'est le signal « instrument » le moins cher du lot : un dégradé répété,
   peint une fois, jamais recalculé. La piste est CREUSÉE dans la plaque —
   ombre interne portée — au lieu d'être posée dessus.
   `position:relative` est obligatoire : sans lui, le `::after` des crans se
   placerait contre un ancêtre positionné quelconque et traverserait la page. */
.poste__jauge {
  display:block; position:relative; height:5px; margin:3px 0; overflow:hidden;
  background:var(--creux);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.9), inset 0 -1px 0 rgba(255,255,255,.05);
}
.poste__jauge::after {
  content:''; position:absolute; inset:0; pointer-events:none;
  background:repeating-linear-gradient(90deg,transparent 0 9px,rgba(0,0,0,.45) 9px 10px);
}
.poste__jauge-fill { display:block; height:100%; width:0; background:linear-gradient(#b4d184,var(--positif)); transition:width 300ms linear; }
.poste__jauge-fill--bas { background:var(--alerte); }
@media (prefers-reduced-motion:reduce) { .poste__jauge-fill { transition:none; } }

.corps { flex:1; padding:var(--marge); display:flex; flex-direction:column; gap:22px; }

/* ── Quartiers : la mise en page suit la place, pas l'appareil ────────────
   Téléphone : un quartier à la fois, les onglets pilotent.
   Tablette  : deux de front.
   Bureau    : les trois, la cité entière — les onglets n'ont plus lieu d'être.
   Aucun test d'appareil en JS : la largeur disponible décide seule. */

/* Dans le panneau, donc plus de marge laterale a reprendre : le corps la pose
   deja. Ecart a 8 px, minimum requis entre deux cibles tactiles. */
.onglets {
  display:flex; gap:8px; padding:0 0 10px; flex-wrap:wrap;
}
/* LE PAPIER EST UNE RE-LIAISON, PAS UNE SECONDE FEUILLE DE STYLES.
   Douze déclarations, et AUCUN composant ne change : listes, boutons, jauges,
   tableaux et l'arbre du Savoir ne lisent que des rôles depuis le lot O, donc
   ils basculent en entrant ici. C'est le rendement direct de la couche à deux
   étages, et c'est ce que la nomenclature par matière rendait impossible.

   Les accents sont PLUS SOMBRES que leurs jumeaux de nuit : #d4694a sur crème
   ne fait que 3,0:1 — il passe pour un trait de courbe, jamais pour du texte.
   Chaque valeur ci-dessous est vérifiée par test_contraste.php, qui joue
   désormais ses contrôles une fois par thème.

   --lumiere et --ombre DOIVENT être redéfinis : les biseaux du lot P les
   lisent, et un liseré #46392c sur du crème dessinerait un trait brun sale au
   lieu d'une arête éclairée. */
.papier {
  --fond:var(--papier);      --surface:#e6dcc2;      --surface-haute:#d6cbaa;
  --texte:var(--encre);      --texte-doux:#5c4d3a;   --texte-faible:#5e5039;
  --filet:var(--papier-filet);
  --lumiere:rgba(255,255,255,.55);  --ombre:rgba(58,43,30,.18);  --creux:#c7b795;
  --accent:#6d4609;          --alerte:#8a3315;       --positif:#3a5619;
  --repos:#3a5619;           --focus:#6d4609;

  color:var(--texte);
  background:linear-gradient(#e4dbc0,#d6cbaa);
  /* Posé SUR la nuit, avec son ombre : le panneau ne remplit pas l'écran, la
     nuit l'encadre. C'est ce qui l'empêche d'éblouir dans une partie jouée le
     soir — un document, pas une lampe. */
  box-shadow:0 10px 30px rgba(0,0,0,.6), 0 0 0 1px #8a7a5e;
  padding:18px 20px 22px;
}

/* LE BISEAU REMPLACE LE FILET.
   Un filet de 1 px uniforme est le réflexe qui tient lieu de hiérarchie : au
   lieu de séparer par le fond, l'espace ou le poids, on trace un trait. Une
   plaque, elle, a une épaisseur — arête haute éclairée, arête basse dans
   l'ombre — et cette épaisseur se lit sans qu'on la regarde.
   Deux ombres internes de 1 px : aucun coût de mise en page, rien à recalculer.

   L'onglet ACTIF n'est plus un texte souligné, c'est une plaque de laiton. La
   fonte tient, le laiton actionne. */
.onglet {
  flex:1 1 auto; min-height:44px; padding:0 12px; font-size:13px;
  color:var(--texte-faible);
  border:0; border-radius:var(--r-plaque);
  background:linear-gradient(var(--surface-haute),var(--surface));
  box-shadow:inset 0 1px 0 var(--lumiere), inset 0 -1px 0 var(--ombre);
  transition:color 120ms ease, box-shadow 120ms ease;
}
.onglet--actif {
  color:var(--fond);
  background:linear-gradient(#e0b458,#c8912f);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35), 0 1px 3px rgba(0,0,0,.5);
}

.quartiers { display:grid; grid-template-columns:1fr; gap:26px; }
/* Un seul quartier visible : celui que l'onglet désigne. */
.quartier { display:none; }
.quartier--actif { display:block; }
/* LE TITRE DE QUARTIER RÉPÈTE L'ONGLET ACTIF, posé juste au-dessus et mis en
   évidence. Deux fois le même mot coûtaient ~28 px de hauteur — et sur cette
   page cette hauteur-là sort directement de la scène, qui est bornée par elle.

   Masqué à l'oeil, JAMAIS retiré : il tient la hiérarchie des titres et sert
   la navigation par en-têtes. `clip-path` et non `display:none`, qui le
   sortirait aussi de l'arbre d'accessibilité. */
.quartier__titre {
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
.quartier .cadre { margin-top:14px; }

/* UN quartier à la fois, à TOUTES les largeurs.
   Ce n'est pas une préférence de mise en page mais une contrainte arithmétique :
   le repère de scène est accordé au sprite (SCENE.w × plot × sprite = largeur du
   sprite), donc l'échelle d'affichage vaut « largeur du village ÷ SCENE.w ».
   Trois colonnes de ~430 px sur un écran de 1440 plafonneraient le sprite à ×1
   en échelle entière — soit MOINS gros qu'avant l'agrandissement de la toile.
   Voir la spec de La Forge, section 6. Ne pas « rétablir » les trois colonnes. */
@media (min-width:1100px) {
  /* Sans cet élargissement, un quartier se serrerait dans une largeur de
     téléphone sur un écran de 27 pouces. C'est aussi ce qui borne l'échelle
     d'affichage : calerVillage() ne peut pas dépasser ce que .app autorise. */
  .app { max-width:1400px; }

  /* LE BANDEAU SORT DE LA HAUTEUR ET ENTRE DANS LA LARGEUR.
   *
   * La scène a un rapport FIXE (331 × 310, soit 1,068) : elle est bornée par la
   * dimension la plus serrée, et sur grand écran c'est TOUJOURS la hauteur. Les
   * 150 px cédés à la colonne ne coûtent donc rien — cette largeur n'était pas
   * utilisée. Les 89 px de hauteur rendus, eux, font grandir la scène dans les
   * DEUX sens : 755 × 707 → 903 × 846, soit +43 % de surface.
   *
   * Vérifiable : `php _tools/chrome.php 4 --bas --colonne`.
   *
   * QUATRE ZONES et non deux : `.app` contient aussi le fil conducteur ET la
   * barre des vues. Une grille qui n'en nomme pas assez ne laisse pas l'enfant
   * oublié dehors — elle l'AUTO-PLACE, et toutes les cellules nommées étant
   * prises, elle lui fabrique une rangée implicite dans la PREMIÈRE colonne,
   * c'est-à-dire dans les 150 px du bandeau. C'est ce qui est arrivé aux vues
   * en v84 : les onglets tronqués par leur propre `overflow-x`, et le menu
   * `···` — poussé à droite par `margin-left:auto` — hors de la zone défilée,
   * donc invisible. Un enfant non nommé d'une grille nommée est un bug en
   * attente ; il n'y a pas d'avertissement.
   *
   * `order:-1` ne rattrape rien : en grille il ne change que l'ORDRE dans
   * lequel l'auto-placement examine les enfants, jamais la cellule libre. */
  .app {
    display:grid;
    grid-template-columns:150px 1fr;
    grid-template-areas:'ledger vues' 'ledger fil' 'ledger corps';
    grid-template-rows:auto auto 1fr;
  }
  .vues { grid-area:vues; }
  .ledger {
    grid-area:ledger; grid-template-columns:1fr;
    position:relative;
    border-bottom:0; border-right:0;
    /* La plaque verticale : éclairée sur son arête gauche, ombrée sur la
       droite où elle rencontre la scène. Remplace le filet de séparation. */
    background:linear-gradient(100deg,var(--surface-haute),var(--surface));
    box-shadow:inset 1px 0 0 var(--lumiere), inset -1px 0 0 var(--ombre),
               1px 0 0 var(--filet);
    align-content:start; gap:14px 0;
  }

  /* LES RIVETS N'EXISTENT QUE SUR LA COLONNE, qui est une vraie plaque
     verticale. Partout ailleurs ce serait du papier peint : une fixation dit
     que la pièce est TENUE, et une pièce qui n'est tenue à rien n'en a pas.
     Quatre coins, en dégradés radiaux — aucune image, aucune requête.

     La règle vit DANS la media query : sous 1100 px le bandeau est une rangée
     horizontale, et river ses quatre coins n'aurait aucun sens mécanique. */
  .ledger::before, .ledger::after {
    content:''; position:absolute; left:7px; right:7px; height:7px;
    pointer-events:none;
    background:
      radial-gradient(circle at 3px 3px, #7a6650, #2b2118 62%, var(--ombre)) left  center / 7px 7px no-repeat,
      radial-gradient(circle at 3px 3px, #7a6650, #2b2118 62%, var(--ombre)) right center / 7px 7px no-repeat;
  }
  .ledger::before { top:9px; }
  .ledger::after  { bottom:9px; }
  .fil { grid-area:fil; }
  .poste { border-left:0; padding-inline:0; }
  /* `min-width:0` : sans lui, une piste de grille refuse de rétrécir sous la
     taille intrinsèque de son contenu, et la scène déborderait au lieu de
     s'adapter. Le piège classique des grilles à `1fr`. */
  .corps { grid-area:corps; min-width:0; }
}

/* ── Navigation de niveau 1 : la grande vue ─────────────────────────────── */
/* Branche fermée par une voie concurrente. Visuellement DISTINCTE d'un nœud
   verrouillé : une branche fermée ne s'ouvre pas en achetant un prérequis, elle
   demande un oubli. Les afficher pareil ferait chercher au joueur un prérequis
   qui n'existe pas. */
/* L'OPACITÉ N'EST PAS UN ÉTAT, c'est un réglage de rendu. À .34 sur du papier,
   un nœud fermé vire au presque-blanc et se confond avec la page ; la croix,
   elle, se lit sur les deux thèmes. On remonte donc l'opacité et on laisse le
   signal à la forme. */
.noeud--fermee { opacity:.55; }
.noeud--fermee rect, .noeud--fermee circle { stroke:var(--alerte); }

/* Le bouton n'héritait d'AUCUN style : la règle générique `button {}` ne pose
   que le curseur, et les styles visibles sont portés par `.barre button`, dont
   celui-ci n'est pas un descendant. Résultat : texte de la couleur du fond,
   illisible. Un bouton hors barre doit porter son propre style. */
/* ── Grand Œuvre : prestige, voies, boutique ─────────────────────────────── */
.oeuvre { padding:0 var(--marge) 20px; }
.oeuvre__bilan { display:flex; flex-wrap:wrap; gap:22px; padding:6px 0 14px; }
.oeuvre__chiffre { display:flex; flex-direction:column; }
.oeuvre__chiffre b { font-size:22px; color:var(--accent); font-variant-numeric:tabular-nums; }
.oeuvre__chiffre small { font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--texte-faible); }
/* Le gain est annoncé EN PERMANENCE, pas seulement dans la confirmation : c'est
   ce qui rend « maintenant ou plus tard ? » jouable au lieu d'être un pari. */
.oeuvre__gain { margin:0 0 18px; padding:10px 14px; font-size:13px; color:var(--texte-doux);
  border:1px solid var(--filet); border-radius:var(--r-plaque); }
.oeuvre__gain--pret { color:var(--texte); border-color:var(--accent); }
.oeuvre__aide { margin:0 0 10px; font-size:12px; color:var(--texte-faible); }
.oeuvre__voies, .oeuvre__boutique {
  display:grid; grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); gap:8px; margin-bottom:22px;
}
.voie, .achat {
  display:flex; flex-direction:column; gap:3px; padding:10px 12px; text-align:left;
  background:transparent; color:var(--texte-doux); border:1px solid var(--filet);
  border-radius:var(--r-plaque); transition:border-color 120ms ease,color 120ms ease;
}
.voie:hover, .achat:not([disabled]):hover { color:var(--texte); border-color:var(--texte-faible); }
.voie--active { border-color:var(--accent); color:var(--texte); }
.voie__nom, .achat__nom { font-size:14px; }
.voie__detail, .achat__prix { font-size:11px; color:var(--texte-faible); }
.achat__prix { color:var(--accent); }
.achat__etat { font-size:11px; color:var(--texte-faible); font-variant-numeric:tabular-nums; }
/* CE QUE FAIT L'ACHAT. Deux lignes de texte courant dans une tuile de bouton :
   d'ou `white-space:normal` — les boutons de cette grille sont en `flex` et
   n'enroulent pas leur texte par defaut, et la phrase sortirait de la tuile.
   `text-align:left` : une description centree se lit deux fois moins vite
   qu'un bloc aligne, et celle-ci decide d'un achat definitif. */
.achat__quoi {
  font-size:11.5px; line-height:1.45; color:var(--texte-doux);
  white-space:normal; text-align:left; margin:2px 0 4px;
}
/* Un achat pas encore câblé reste VISIBLE — il montre la suite — mais ne peut
   pas être payé : vendre un objet sans effet serait un mensonge au joueur. */
.achat[disabled] { opacity:.4; cursor:default; }

.oubli { display:flex; align-items:center; gap:10px; padding:14px var(--marge) 0; margin:0; }
.oubli button {
  min-height:44px; padding:0 16px; font-size:13px;
  background:transparent; color:var(--texte-doux); border:1px solid var(--filet);
  border-radius:var(--r-plaque); transition:color 120ms ease,border-color 120ms ease;
}
.oubli button:hover { color:var(--alerte); border-color:var(--alerte); }
.oubli__aide { font-size:12px; color:var(--texte-faible); }

/* Sur MOBILE la barre des vues est FIXE en bas : c'est la seule zone qu'un
   pouce atteint sans changer de prise, et elle sort ainsi du chrome au-dessus
   du jeu.

   FIXE et non `sticky` : elle est le dernier enfant de `.app`, donc une barre
   collante n'y a AUCUNE plage d'adherence — il n'y a rien apres elle pour la
   retenir, et elle defile comme un element ordinaire. C'est ce qui la rendait
   invisible tant qu'on n'avait pas fait defiler jusqu'en bas.

   Sur grand ecran une barre flottante en bas n'a aucun sens : `order` la
   remonte en tete du conteneur flex, sans dupliquer le markup. Media query et
   non test d'appareil : la largeur disponible decide seule. */
/* `flex-start` et NON `center`. Un conteneur qui defile et dont le contenu est
   CENTRE deborde des deux cotes — et le debut est alors INATTEIGNABLE : le
   defilement ne va pas dans les valeurs negatives. Avec cinq vues sur un
   telephone, « Village » restait coupe a gauche sans aucun moyen d'y revenir,
   pendant qu'il restait du defilement inutile a droite. Le centrage n'etait
   visible que tant que la barre tenait a l'ecran, c'est-a-dire avant que la
   cinquieme vue n'existe. */
.vues {
  display:flex; gap:8px; justify-content:flex-start;
  position:fixed; left:0; right:0; bottom:0; z-index:30;
  padding:6px var(--marge) calc(6px + env(safe-area-inset-bottom));
  background:var(--fond); border-top:1px solid var(--filet);
  overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none;
}
@media (min-width:768px) {
  .vues {
    /* `relative` et non `static` : le chariot est un enfant ABSOLU, il lui faut
       un bloc conteneur. Sur mobile la barre est `fixed`, donc elle en est
       déjà un — c'est pourquoi cette déclaration vit ici et non dans la règle
       de base, où elle écraserait le `fixed` et décollerait la barre du bas
       de l'écran. */
    position:relative; order:-1; justify-content:flex-start;
    border-top:0; border-bottom:1px solid var(--filet);
    padding:0 var(--marge) 6px;
  }
}
/* Reserve pour la barre fixe : sans elle, le pied de page passe dessous — et
   sur iPhone, sous la barre de gestes du systeme. */
@media (max-width:767px) {
  .app { padding-bottom:calc(60px + env(safe-area-inset-bottom)); }
}
/* Les plaques montées sur le rail. Elles ne portent PAS l'état actif : c'est le
   chariot de laiton qui le désigne, en glissant jusqu'à elles. Un indicateur
   qui voyage se suit du regard ; une bordure qui change de couleur, non. */
.vue {
  flex:0 1 auto; min-height:44px; padding:0 18px; font:inherit; font-size:13px;
  cursor:pointer; color:var(--texte-faible);
  border:0; border-radius:var(--r-plaque);
  background:linear-gradient(var(--surface-haute),var(--surface));
  box-shadow:inset 0 1px 0 var(--lumiere), inset 0 -1px 0 var(--ombre);
  transition:color 160ms ease;
}
.vue--actif { color:var(--accent); }

/* ── LE RAIL, LE CHARIOT ET LA ROUE ───────────────────────────────────────
   La barre n'est plus une rangée de boutons, c'est un support : une gorge
   sombre court sous les plaques, et les plaques sont montées dessus.

   CONTRAINTE FONDATRICE DU LOT : le mécanisme ne prend AUCUNE hauteur. La
   gorge est une ombre, le chariot est absolu, et la roue tient dans les 44 px
   déjà occupés par les onglets. Depuis v86 la scène remplit la place qu'on lui
   laisse — le moindre pixel pris ici se verrait immédiatement en bas du
   village. Vérifié par `php _tools/chrome.php 4 --colonne`. */
.vues::after {
  content:''; position:absolute; left:0; right:0; bottom:0; height:4px;
  background:var(--ombre); box-shadow:inset 0 2px 3px rgba(0,0,0,.95);
  pointer-events:none;
}

/* LE CHARIOT. L'indicateur cesse d'être une couleur pour devenir un objet qui
   voyage — et un objet qui voyage se suit du regard, ce qu'une bordure qui
   change de teinte ne permet pas.

   Sa position et sa largeur sont posées en CSSOM par `placerChariot()` : les
   onglets n'ont PAS la même largeur (« Village » contre « Grand Œuvre »), donc
   aucun pourcentage ne peut les atteindre.

   La courbe dépasse puis revient. Sans ce dépassement, un glissement linéaire
   se lit comme une animation d'interface et non comme le déplacement d'une
   pièce ayant une masse. */
.vues__chariot {
  position:absolute; left:0; bottom:0; height:100%; width:0; z-index:0;
  background:linear-gradient(rgba(58,44,24,.9),rgba(36,26,14,.9));
  box-shadow:inset 0 1px 0 #6b5228;
  transition:transform 320ms cubic-bezier(.34,1.42,.5,1), width 320ms ease;
}
.vues__chariot::after {
  content:''; position:absolute; left:8%; right:8%; bottom:0; height:4px;
  background:linear-gradient(#f0d189,#c08e2c);
  box-shadow:0 0 9px rgba(216,164,65,.55);
}
/* Les plaques passent AU-DESSUS du chariot, sinon il les recouvrirait. */
.vue { position:relative; z-index:1; }

/* LA ROUE DE COMMANDE : 26 px centrés dans une barre qui en fait 44. Elle
   avance d'une dent par changement de vue — elle traduit le geste, elle ne
   tourne pas en fond. */
.vues__rouage {
  flex:0 0 auto; align-self:center; margin-right:6px;
  transition:transform 320ms cubic-bezier(.34,1.42,.5,1);
}

/* PAS DE ROUE SUR TÉLÉPHONE. La barre y déborde déjà avec cinq vues et le
   menu ; la roue lui prendrait 32 px de LARGEUR, ressource la plus rare de cet
   écran, pour une pièce qui n'informe de rien. Le chariot, lui, reste : c'est
   lui qui désigne la vue active.
   Le lot P s'interdit de coûter de la hauteur — il ne va pas prendre en
   largeur ce qu'il refuse en hauteur. */
@media (max-width:767px) {
  .vues__rouage { display:none; }
}

@media (prefers-reduced-motion:reduce) {
  .vues__chariot, .vues__rouage { transition:none; }
}
.panneau { display:none; }
.panneau--actif { display:block; position:relative; }   /* reference de l horloge en surimpression */

/* la scène : ratio fixe, tout positionné en %, aucune media query */
.scene {
  position:relative; background:var(--fond-scene); border:1px solid var(--filet);
  border-radius:14px; overflow:hidden;
  /* width:100% est OBLIGATOIRE avec margin-inline:auto : sans largeur définie,
     un élément flex centré se dimensionne sur son contenu, et le contenu de
     .village est entièrement en position:absolute — donc largeur nulle, et
     toute la scène disparaît. C'est ici un REPLI : calerVillage() pose une
     largeur en pixels, multiple entier de SCENE.w. Si le JS ne tourne pas, la
     scène reste visible, simplement à une échelle fractionnaire.

     Il y avait auparavant un `max-width:calc(66vh * 4 / 5)` ici, qu'une règle
     `max-width:none` en media query croyait annuler — mais cette règle la
     PRÉCÉDAIT à spécificité égale, donc elle n'a jamais rien annulé. Invisible
     tant qu'il y avait trois colonnes plus étroites que le plafond ; bloquant
     dès qu'il n'y en a plus qu'une. */
  width:100%; margin-inline:auto;
}

/* Le rapport DOIT être celui de SCENE.w / SCENE.h (game.js) : c'est lui qui
   fait qu'une unité de scène est carrée. Un rapport désaccordé étirerait tout
   le village d'un axe. */
.village { position:relative; width:100%; aspect-ratio:331/310; }
/* La ligne d horizon. Sa hauteur n est PAS un choix esthetique : elle doit
   passer sous les pieds de la rangee la plus haute, sinon la moitie des
   batiments flotte dans le ciel. Mesure : les pieds de la rangee du fond
   tombent entre 29 et 35 %, ceux de la rangee de devant entre 77 et 81 %.
   Un horizon a 28 % plante les deux. A 70 % — la valeur d avant, heritee
   d une scene de 414 de haut — il coupait le vide entre les deux rangees.
   A REVOIR si les positions des batiments changent : les deux vont ensemble. */
/* CIEL et SOL se partagent la scène : leurs hauteurs sont COMPLÉMENTAIRES,
   posées depuis le même `horizon`. C'est ce qui garantit qu'aucun liseré de
   fond ne peut apparaître entre les deux, quelle que soit la valeur — un ciel
   réglé séparément finirait par ne plus toucher l'herbe.
   Les 28/72 % ci-dessous sont le repli : si `scene.json` n'arrive pas, la scène
   reste juste. */
.ciel {
  position:absolute; left:0; right:0; top:0; height:28%; z-index:0;
  /* Le plus CLAIR en bas. C'est la perspective atmosphérique : le ciel
     s'éclaircit vers l'horizon parce qu'on regarde à travers plus d'air. Un
     aplat n'a aucune profondeur, et c'est ce qui faisait plat. */
  /* LE CIEL EST PILOTE PAR LE CYCLE. La valeur ci-dessous n'est plus qu'un
     REPLI : elle sert avant le premier tic, et si le JS ne tourne pas. Le
     degrade vivant arrive par `--ciel-degrade`, posee en CSSOM sur <html> —
     donc HERITEE par les trois quartiers, qui ne peuvent pas diverger.
     Le plus CLAIR reste en bas quelle que soit l'heure : c'est la perspective
     atmospherique, on regarde a travers plus d'air pres de l'horizon. */
  background:var(--ciel-degrade,
    linear-gradient(to bottom, #10151c 0%, #171d26 46%, #212a33 84%, #2b3138 100%));
}
/* La brume chaude posée sur l'horizon, et sur lui seul : elle relie le ciel à
   la terre au lieu de les laisser se toucher par une arête franche. */
.ciel::after {
  content:''; position:absolute; left:0; right:0; bottom:0; height:40%;
  background:linear-gradient(to bottom, rgba(216,164,65,0) 0%, rgba(216,164,65,.11) 100%);
}
.sol {
  position:absolute; left:0; right:0; bottom:0; height:72%; z-index:0;
  /* Inverse du ciel : le plus clair en HAUT, là où l'herbe est loin. Le sol
     s'assombrit en venant vers le joueur.

     LE VERT ETAIT TRISTE PARCE QU'IL NE BOUGEAIT PAS. Trois arrêts, la même
     teinte du matin au soir, sous un ciel qui, lui, traversait quatre moments :
     l'herbe ne répondait à rien. Ce n'est pas la couleur qui manquait, c'est le
     mouvement — et il vient maintenant de `--sol-degrade`, posée par le cycle
     comme `--ciel-degrade`, donc héritée par les trois quartiers.

     La valeur ci-dessous reste le repli, pour l'instant d'avant le premier tic
     et pour le cas où le JS ne tourne pas. */
  background:var(--sol-degrade,
    linear-gradient(to bottom, #2c3a2b 0%, #223024 30%, #1a241d 70%, #141b17 100%));
  border-top:1px solid var(--sol-liseré, #46583f);
}
/* Les touffes, sur la seule bande proche de l'horizon, et fondues vers le bas.
   Les semer partout ferait un damier : de près on ne voit plus les brins, on
   voit la masse.

   DEUX PAS AU LIEU D'UN. Un seul intervalle régulier de 4 px se lit comme une
   trame imprimée dès qu'on la regarde — l'œil trouve la période. Deux pas
   premiers entre eux (4 et 7) ne se répètent qu'au bout de 28 px, et la bande
   cesse d'être un motif pour redevenir de l'herbe.

   Leur clarté suit le cycle : une touffe éclairée à minuit trahirait tout. */
.sol::before {
  content:''; position:absolute; left:0; right:0; top:0; height:19%;
  background:
    repeating-linear-gradient(90deg,
      var(--herbe, rgba(146,176,104,.19)) 0 1px, rgba(0,0,0,0) 1px 4px),
    repeating-linear-gradient(90deg,
      var(--herbe-fond, rgba(120,148,88,.11)) 0 1px, rgba(0,0,0,0) 1px 7px);
  -webkit-mask-image:linear-gradient(to bottom, #000 0%, transparent 100%);
          mask-image:linear-gradient(to bottom, #000 0%, transparent 100%);
}
/* LE VALLONNEMENT. Deux taches très larges et très faibles, l'une claire vers
   la gauche, l'autre sombre vers la droite : elles cassent l'aplat sans jamais
   se lire comme des objets. C'est ce qui manquait le plus — un dégradé
   vertical seul donne un fond de studio, pas un terrain. */
.sol::after {
  content:''; position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(120% 70% at 22% 18%, var(--sol-bosse, rgba(150,178,116,.07)) 0%, rgba(0,0,0,0) 62%),
    radial-gradient(90% 60% at 78% 52%, var(--sol-creux, rgba(10,16,12,.16)) 0%, rgba(0,0,0,0) 66%);
}
.decor { position:absolute; height:auto; image-rendering:pixelated; pointer-events:none; }
/* LES ASTRES. Une couche a part, posee entre le ciel et le sol : le soleil ne
   peut donc pas passer devant une facade, et le sol la recouvre par
   construction plutot que par un z-index a tenir.
   Sa hauteur native (78 px sur 331 de large) vaut EXACTEMENT les 25 % de ciel
   de la scene : `height:auto` la recale toute seule a toute echelle. */
.astres { position:absolute; left:0; top:0; width:100%; height:auto; z-index:0;
          image-rendering:pixelated; pointer-events:none; }
.routes { position:absolute; left:0; top:0; width:100%; height:auto; z-index:0; image-rendering:pixelated; pointer-events:none; }
/* Habitants : AU-DESSUS des bâtiments (z 2), là où les routes restent dessous
   (z 0). Sur un canvas unique, l'un des deux était forcément du mauvais côté. */
/* UNE seule couche de marcheurs, devant tous les bâtiments, et c'est JUSTE.
   Vérifié en 2026-08-05 après avoir tenté le contraire : sur 280 recouvrements
   réels entre un marcheur et une façade, la couche unique en place **zéro** du
   mauvais côté.

   La raison tient à la géométrie du réseau : les voies sont posées SOUS les
   façades qu'elles desservent (`porte.y + DESSERTE`), donc un marcheur est
   toujours réellement plus près que tout bâtiment dont il croise le dessin.

   Une couche par RANGÉE a été essayée puis retirée : elle en plaçait 8 du
   mauvais côté. Deux bâtiments d'une même rangée n'ont pas la même ligne de
   pieds (224 et 246 pour Carrière et Mine), et un marcheur qui passe entre les
   deux doit être devant l'un et derrière l'autre — ce qu'une couche commune à
   la rangée ne peut pas exprimer. Un vrai tri demanderait une couche par
   bâtiment.

   Et si l'on y revient : `.plot` ne doit JAMAIS recevoir de z-index, il
   créerait un contexte d'empilement dont `.plot__etiq` (z-index 3) ne pourrait
   plus sortir, et les étiquettes passeraient sous les marcheurs. */
.gens { position:absolute; left:0; top:0; width:100%; height:auto; z-index:2; image-rendering:pixelated; pointer-events:none; }

/* PAS de z-index ici, délibérément. Un `z-index` sur un élément positionné crée
   un CONTEXTE D'EMPILEMENT dont les enfants ne peuvent plus sortir : l'étiquette
   resterait alors sous la couche des habitants. En `auto`, le plot se peint en
   ordre de DOM (donc au-dessus du sol et des routes, qui le précèdent), et
   `.plot__etiq` peut remonter par-dessus les habitants. */
.plot {
  position:absolute; width:42%;
  display:flex; flex-direction:column; align-items:center; gap:2px;
  padding:0; background:none; border:0; text-align:center;
  transition:transform 90ms ease;
}
.plot:active { transform:scale(.97); }
.plot--vide { opacity:.45; }
.plot__sprite { width:46%; height:auto; display:block; image-rendering:pixelated; }
/* La PARCELLE cesse d etre une surface de pointage, ses deux enfants visibles
   la deviennent. Le bouton faisait 42 % de la largeur de scene pour un batiment
   qui en occupe 19 % : tout le vide autour construisait le batiment, et un
   decor pose dessus (qui porte pointer-events:none) laissait le clic passer au
   travers — le buisson montait la Ferme d un niveau.

   Le <button> garde TOUTE sa semantique : focus clavier, Entree, libelle, role.
   `pointer-events` ne touche qu a l entree de pointage. */
.plot { pointer-events:none; }
.plot__sprite, .plot__etiq { pointer-events:auto; }
/* UN CHANTIER RESTE CLIQUABLE. La regle inverse existait tant qu'un clic
   CONSTRUISAIT : re-cliquer une tuile occupee n'avait aucun sens. Le clic
   OUVRE desormais, et c'est justement pendant un chantier qu'on veut voir le
   temps restant et le detail. Le garde vit sur le bouton « Ameliorer » de la
   carte, qui se desactive. */
.plot__etiq {
  /* Au-dessus des habitants : c'est de l'interface, elle doit rester lisible.
     Un marcheur qui passe par-dessus un chiffre le rendrait illisible. */
  position:relative; z-index:3;
  display:flex; flex-direction:column; gap:1px; padding:4px 9px 5px;
  /* Ce fond était `rgba(20,24,29,.72)` — l'ancien --nuit, ÉCRIT EN DUR. Le lot
     O ne l'a pas vu : le banc des jetons traque les `var(--matiere)`, pas les
     littéraux. Les étiquettes de tuile sont donc restées bleu-gris pendant que
     tout le reste passait au chaud. */
  border:0; border-radius:var(--r-plaque);
  background:linear-gradient(rgba(39,32,26,.88),rgba(19,16,12,.88));
  box-shadow:inset 0 1px 0 var(--lumiere), inset 0 -1px 0 var(--ombre),
             0 2px 5px rgba(0,0,0,.5);
  transition:box-shadow 120ms ease;
}
/* LES ÉTATS PASSENT DE LA BORDURE À L'ANNEAU DE LUMIÈRE. Sans bordure, un
   `border-color` ne s'applique à rien — et ne laisserait aucune trace de son
   inefficacité. `inset 0 0 0 1px` dessine le même trait, sur la même arête,
   sans occuper de place. */
.plot--ok .plot__etiq {
  box-shadow:inset 0 0 0 1px var(--accent), 0 2px 5px rgba(0,0,0,.5);
}
/* hover souris seulement : sur tactile, un hover collant resterait allumé */
@media (hover:hover) {
  .plot:hover .plot__etiq {
    box-shadow:inset 0 0 0 1px var(--texte-faible), 0 2px 5px rgba(0,0,0,.5);
  }
  .plot--ok:hover .plot__etiq {
    box-shadow:inset 0 0 0 1px var(--accent), 0 0 6px rgba(216,164,65,.25);
  }
}
/* flash d'achat : une pulsation dorée brève, puis retour au calme */
@keyframes plot-flash {
  0%   { box-shadow:0 0 0 2px var(--accent), 0 0 14px rgba(216,164,65,.55); }
  100% { box-shadow:0 0 0 0 rgba(216,164,65,0); }
}
.plot--flash .plot__etiq { animation:plot-flash 380ms ease-out; }
/* bloqué par la population : le manque s'affiche en rouille, la tuile reste
   cliquable (le tap explique combien d'habitants il faut) */
.plot--bloque .plot__cout { color:var(--alerte); }
/* raffineur au ralenti : le manque d'intrant se voit sur la tuile, pas
   seulement dans les chiffres du registre */
.plot--ralenti .plot__cout { color:var(--alerte); }
.plot--ralenti .plot__etiq { box-shadow:inset 0 0 0 1px var(--alerte), 0 2px 5px rgba(0,0,0,.5); }
.plot__titre { font-family:var(--serif); font-size:13px; line-height:1.2; }
.plot__niv {
  color:var(--texte-faible); font-family:var(--sans); font-size:11px;
  font-variant-numeric:tabular-nums; text-shadow:0 1px 0 rgba(255,255,255,.06);
}
.plot__prod,.plot__cout { font-size:11px; line-height:1.3; font-variant-numeric:tabular-nums; }
.plot__prod { color:var(--texte-doux); }
.plot__cout { color:var(--texte-faible); }
.plot--ok .plot__cout { color:var(--accent); }

/* ── file de construction : barre sur la tuile en chantier ── */
.plot--chantier { pointer-events:none; }
/* Une tuile en chantier n a PAS le meme aspect qu une tuile normale : elle est
   inerte au clic, et un joueur qui clique sans effet doit comprendre en
   regardant, pas en essayant. La bordure verte ne suffisait pas — on la
   remarque quand on la cherche. Le texte passe au vert avec elle. */
.plot--chantier .plot__etiq { box-shadow:inset 0 0 0 1px var(--repos), 0 2px 5px rgba(0,0,0,.5); }
.plot--chantier .plot__cout { color:var(--repos); }
/* LA JAUGE EST EN FLUX, et c'est un retour en arrière assumé.
   Elle était ABSOLUE en `bottom:-1px` pour ne rien ajouter à la hauteur : à
   l'époque l'étiquette portait six lignes, et celles de la rangée basse
   sortaient du cadre que `.scene` coupe net. Depuis La Carte elle n'en porte
   plus qu'UNE — nom et niveau — donc les 14 px se paient, et seulement pendant
   un chantier. Absolue, la jauge recouvrait le nom au lieu de le suivre. */
.plot__chantier { display:none; position:relative; width:100%; }
.plot--chantier .plot__chantier { display:block; }
/* 14 px et non 3 : elle doit loger son texte. C'est la seule chose qui
   GROSSIT sur la scene ; tout le reste vient d'y maigrir.

   `display:block` OBLIGATOIRE. Ces deux noeuds sont des <span>, et un inline
   IGNORE `height` et `width` : la piste et son remplissage avaient une taille
   nulle, donc invisibles — et `.plot__restant`, cale en `height:100%` sur un
   parent effondre, sortait de l'etiquette. Un seul oubli pour les deux
   symptomes. Le jumeau du bandeau, `.poste__jauge`, le porte bien. */
.plot__jauge {
  display:block; position:relative; height:14px; border-radius:var(--r-plaque);
  background:var(--creux); overflow:hidden;
  box-shadow:inset 0 2px 3px rgba(0,0,0,.95);
}
/* LES CRANS. `pointer-events:none` est OBLIGATOIRE : ce calque couvre toute la
   jauge, qui vit dans une étiquette cliquable. Sans lui, le clic sur une tuile
   en chantier serait avalé par un décor — le bug du buisson de la Ferme,
   réintroduit ailleurs. */
.plot__jauge::after {
  content:''; position:absolute; inset:0; pointer-events:none;
  background:repeating-linear-gradient(90deg,transparent 0 8px,rgba(0,0,0,.4) 8px 9px);
}
/* CE REMPLISSAGE PORTE DU TEXTE, celui du bandeau non — d'où deux verts.
   Le lot P l'avait éclairci à #b4d184 : le temps restant, resté quasi-blanc,
   tombait à 1,33:1. Et aucune couleur de texte unique ne peut s'en sortir, la
   ligne traversant à la fois le vert et le creux noir de la piste vide.
   C'est donc le remplissage qui descend : 4,58:1 au point le plus clair du
   dégradé, 5,9:1 au plus sombre — et il reste trente fois plus lumineux que la
   piste, donc la jauge se lit toujours comme une jauge. */
.plot__jauge-fill {
  display:block; height:100%; width:0;
  background:linear-gradient(#457029,#38571f); transition:width 400ms linear;
}
@media (prefers-reduced-motion:reduce) {
  .plot__jauge-fill { transition:none; }   /* la barre saute par paliers, sans glisser */
}
/* Le temps se pose SUR la piste entiere, jamais dans la partie remplie : a
   95 % d'avancement il tiendrait sinon dans un filet de deux pixels.

   `inset:0` et non `height:100%` : un pourcentage de hauteur contre un parent
   de hauteur AUTO retombe sur `auto`, et le centrage vertical n'aurait alors
   rien a centrer. Les quatre decalages a zero etirent, quelle que soit la
   hauteur du parent. */
/* `z-index:1` rend EXPLICITE ce que l'ordre du DOM donne déjà : ce nœud est un
   frère de `.plot__jauge`, placé après elle, donc il se peint par-dessus ses
   crans. Le rang est posé pour que déplacer le balisage ne hachure pas le
   temps restant sans prévenir. */
.plot__restant {
  position:absolute; inset:0; z-index:1;
  display:flex; align-items:center; justify-content:center;
  font-size:9px; color:var(--texte); font-variant-numeric:tabular-nums;
  /* Un liseré sur les QUATRE côtés, pas seulement dessous : sous le chiffre se
     trouve tantôt le vert du remplissage, tantôt le creux, et une ombre portée
     ne détache que d'un bord. */
  text-shadow:0 1px 1px rgba(0,0,0,.95), 0 -1px 1px rgba(0,0,0,.7),
              1px 0 1px rgba(0,0,0,.7), -1px 0 1px rgba(0,0,0,.7);
}
.plot__alerte { color:var(--alerte); }

/* l'arbre : SVG, donc net à tout niveau de zoom */
.titre { font-family:var(--serif); font-size:15px; font-weight:400; letter-spacing:.08em; text-transform:uppercase; color:var(--texte-doux); margin:0 0 10px; }
.cadre { background:var(--surface); border:1px solid var(--filet); border-radius:14px; padding:8px; }
.arbre { display:block; width:100%; height:auto; touch-action:none; }
.lien { fill:none; stroke:var(--filet); stroke-width:2; transition:stroke 200ms ease; }
.lien--actif { stroke:var(--repos); }
.noeud { cursor:pointer; }
.noeud rect { fill:var(--surface-haute); stroke:var(--filet); stroke-width:1; transition:stroke 140ms ease,fill 140ms ease; }
/* zone tactile invisible, plus large que le rectangle visible (cibles 44 px) */
.noeud .noeud__zone { fill:transparent; stroke:none; }
.n-nom { font-family:var(--serif); font-size:14px; fill:var(--texte); }
/* L'EFFET, toujours lisible — c'est la seule chose qui permette de décider.
   Il reste discret tant que le nœud n'est pas acquis : c'est une promesse, pas
   un acquis, et l'afficher en clair ferait croire qu'il agit déjà. */
.n-effet { font-family:var(--sans); font-size:11.5px; fill:var(--texte-doux); }
.n-etat { font-family:var(--sans); font-size:11px; fill:var(--texte-faible); }
.noeud--acquise .n-effet { fill:var(--repos); }
.noeud--verrouillee .n-effet { fill:var(--texte-faible); }
.noeud--verrouillee rect:not(.noeud__zone) { fill:var(--surface); }
/* Sur papier, un fond « plus clair » ne distingue plus rien : la hachure prend
   le relais, et elle hérite de sa couleur par `currentColor`. */
.papier .noeud--verrouillee rect:not(.noeud__zone) { fill:url(#hachure); }
.noeud--verrouillee .n-nom { fill:var(--texte-faible); }
.noeud--ok rect:not(.noeud__zone) { stroke:var(--accent); }
.noeud--ok .n-etat { fill:var(--accent); }
.noeud--acquise rect:not(.noeud__zone) { fill:rgba(147,171,144,.14); stroke:var(--repos); }
.noeud--acquise .n-etat { fill:var(--repos); }

.barre { display:flex; flex-wrap:wrap; gap:8px; padding:12px var(--marge) calc(12px + env(safe-area-inset-bottom)); border-top:1px solid var(--filet); align-items:center; }
/* LE MENU PARTAGE L'HABILLAGE DE LA BARRE, et il le partage plutot que de le
   recopier : ce sont les memes commandes, deplacees. La regle de base `button`
   ne pose ni fond ni bordure — un bouton hors de ce selecteur garde donc le
   chrome gris par defaut du navigateur, illisible sur fond sombre. Personne ne
   l'avait vu parce que le menu etait ROGNE depuis La Colonne : ses quatre
   boutons n'avaient jamais ete affiches. Un defaut ne se decouvre pas tant que
   ce qui le porte reste invisible. */
.barre button, .barre a.bouton,
.menu__liste button, .menu__liste a.bouton {
  flex:1 1 auto; min-height:44px; padding:0 14px; font-size:13px;
  background:transparent; color:var(--texte-doux); border:1px solid var(--filet);
  border-radius:var(--r-plaque); transition:color 120ms ease,border-color 120ms ease;
  text-decoration:none; display:inline-flex; align-items:center; justify-content:center;
}
.barre button:hover, .barre a.bouton:hover,
.menu__liste button:hover, .menu__liste a.bouton:hover {
  color:var(--texte); border-color:var(--texte-faible);
}
/* Les pastilles de palette gardent LEUR mise en page : elles sont hautes,
   etroites et empilees, la regle des commandes les etirerait sur la largeur. */
.menu__liste .palettes__opt { flex:0 0 auto; min-height:0; padding:5px 6px; font-size:10px; }
.danger:hover { color:var(--alerte); border-color:var(--alerte); }
.mode { flex:1 1 100%; font-size:11px; color:var(--texte-faible); text-align:center; }

.toast {
  position:fixed; left:50%; bottom:calc(84px + env(safe-area-inset-bottom));
  transform:translate(-50%,8px); margin:0; max-width:calc(100vw - 32px);
  padding:9px 16px; font-size:13px; background:var(--surface-haute);
  border:1px solid var(--filet); border-radius:999px;
  opacity:0; pointer-events:none; transition:opacity 180ms ease,transform 180ms ease;
  z-index:40;
}
.toast--on { opacity:1; transform:translate(-50%,0); }
.toast--jalon { border-color:var(--accent); color:var(--accent); font-family:var(--serif); }

/* bandeau hors-ligne */
.horsligne {
  display:none; padding:6px var(--marge); text-align:center; font-size:12px;
  background:var(--surface); color:var(--alerte); border-bottom:1px solid var(--filet);
}
.horsligne--on { display:block; }

/* cinématique d'accueil */
.intro {
  position:fixed; inset:0; z-index:60; background:var(--fond);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:28px; text-align:center;
  padding:calc(28px + env(safe-area-inset-top)) 24px calc(28px + env(safe-area-inset-bottom));
  transition:opacity 500ms ease;
}
.intro[hidden] { display:none; }            /* display:flex l'emporterait sur hidden */
.intro.sortie { opacity:0; }
body.fige { overflow:hidden; }
.intro__passer {
  position:absolute; top:calc(16px + env(safe-area-inset-top)); right:20px;
  background:none; border:0; padding:6px 4px; font-size:11px;
  letter-spacing:.12em; text-transform:uppercase; color:var(--texte-faible);
}
.intro__passer:hover { color:var(--texte-doux); }
.intro__plan {
  position:relative; width:100%; height:clamp(160px,30vh,240px);
  display:flex; align-items:flex-end; justify-content:center;
}
.intro__plan canvas {
  position:absolute; bottom:0; height:auto; image-rendering:pixelated;
  transition:opacity 900ms ease;
}
.intro__sprite { width:clamp(112px,26vw,170px); opacity:.5; }
.intro__visage { width:clamp(96px,23vw,146px); opacity:0; }
.intro.visage .intro__sprite { opacity:0; }
.intro.visage .intro__visage { opacity:1; }
.intro.final .intro__sprite { opacity:1; }
.intro.final .intro__visage { opacity:0; }
.intro__texte {
  max-width:34rem; min-height:5.4em;
  display:flex; flex-direction:column; gap:.45em;
  font-family:var(--serif); font-size:clamp(15px,2.7vw,19px); line-height:1.55;
  color:var(--texte-doux);
  opacity:0; transform:translateY(7px);
  transition:opacity 700ms ease, transform 700ms ease;
}
.intro__texte.on { opacity:1; transform:none; }
.intro__texte span:first-child { color:var(--texte); }
.intro__fin { display:flex; flex-direction:column; align-items:center; gap:20px; }
.intro__fin[hidden] { display:none; }
.intro__titre {
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(28px,7vw,46px); letter-spacing:.16em; text-transform:uppercase;
  color:var(--accent);
}
.intro__go {
  padding:11px 24px; font-size:14px; background:transparent; color:var(--texte);
  border:1px solid var(--accent); border-radius:var(--r-plaque);
}
.intro__go:hover { background:rgba(216,164,65,.12); }

/* ── écrans d'authentification et pages simples ── */
.page {
  max-width:420px; margin:0 auto; min-height:100dvh;
  display:flex; flex-direction:column; justify-content:center; gap:18px;
  padding:32px var(--marge);
}
.page--large { max-width:640px; justify-content:flex-start; padding-top:40px; }
.page__titre {
  margin:0; font-family:var(--serif); font-weight:400; text-align:center;
  font-size:clamp(26px,6vw,38px); letter-spacing:.14em; text-transform:uppercase; color:var(--accent);
}
.page__sous { margin:0; text-align:center; font-size:13px; color:var(--texte-doux); }
.form { display:flex; flex-direction:column; gap:14px; background:var(--surface); border:1px solid var(--filet); border-radius:14px; padding:22px; }
.champ { display:flex; flex-direction:column; gap:5px; }
.champ label { font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--texte-faible); }
.champ input {
  font:inherit; color:var(--texte); background:var(--fond);
  border:1px solid var(--filet); border-radius:var(--r-plaque); padding:11px 12px;
}
.champ input:focus { outline:2px solid var(--accent); outline-offset:1px; border-color:transparent; }
.champ__note { font-size:11px; color:var(--texte-faible); }
.form__erreur { display:none; font-size:13px; color:var(--alerte); }
.form__erreur--on { display:block; }
.bouton--or {
  min-height:46px; padding:0 18px; font-size:14px;
  background:transparent; color:var(--texte); border:1px solid var(--accent); border-radius:var(--r-plaque);
}
.bouton--or:hover { background:rgba(216,164,65,.12); }
.page__liens { display:flex; flex-direction:column; gap:6px; text-align:center; font-size:13px; }
.page__liens a { color:var(--texte-doux); }
.page__liens a:hover { color:var(--texte); }

/* ── classement ── */
.tableau { width:100%; border-collapse:collapse; background:var(--surface); border:1px solid var(--filet); border-radius:14px; overflow:hidden; }
.tableau th, .tableau td { padding:10px 14px; text-align:left; font-size:14px; border-bottom:1px solid var(--filet); }
.tableau th { font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--texte-faible); font-weight:400; }
.tableau td.num { font-family:var(--serif); font-variant-numeric:tabular-nums; text-align:right; }
.tableau tr:last-child td { border-bottom:0; }
.tableau tr.moi td { color:var(--accent); }
.rang-moi { text-align:center; font-size:13px; color:var(--texte-doux); }
/* Le titre precede le pseudo, et doit rester SOUS lui : c'est le joueur qu'on
   cherche dans un classement, pas son rang honorifique. Plus petit, en
   capitales espacees, dans la couleur faible — il se lit sans se disputer. */
.rang__titre {
  display:inline-block; margin-right:7px;
  font-size:10.5px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--texte-faible);
}

/* ── crédit footer (obligatoire écosystème) ── */
.credit {
  padding:14px var(--marge) calc(14px + env(safe-area-inset-bottom));
  text-align:center; font-size:11px; color:var(--texte-faible);
}
.credit a { color:var(--texte-faible); }
.credit a:hover { color:var(--texte-doux); }

/* ── Dialog system ──────────────────────────────────────────────────────
   Les noms de classes sont DICTÉS par assets/js/dialog.js (lib écosystème,
   nommage `dlg-modal`, `is-visible`, `btn`). Ne jamais réinventer un nommage
   ici : la première version de ce bloc utilisait une convention BEM inventée,
   donc RIEN ne stylait — et `.dlg-backdrop`, seule classe qui correspondait
   par hasard, restait à opacity:0 tout en couvrant l'écran en position:fixed.
   Résultat : un voile invisible qui avalait tous les clics du jeu.
   Vérifier avec :  grep -oE "'dlg-[a-z-]+'" assets/js/dialog.js | sort -u  */

/* STRUCTURE RÉELLE de la lib (dialog.js:90 et :180) : le voile et la modale
   sont deux FRÈRES dans <body>, pas un parent et son enfant. La modale doit
   donc se positionner elle-même et passer DEVANT le voile.
   Première version : `.dlg-modal` sans position ni z-index -> elle tombait en
   flux normal (coin bas-gauche, après tout le contenu) et le voile z-index:80
   passait devant, avalant les clics sur « Confirmer ». */

.dlg-backdrop {
  position:fixed; inset:0; z-index:80; background:rgba(19,16,12,.76);
  opacity:0; visibility:hidden; transition:opacity 160ms ease, visibility 160ms;
}
/* visibility en plus de l'opacité : sans elle, un voile à opacity:0 continue
   d'intercepter tous les clics — invisible ET bloquant. */
.dlg-backdrop.is-visible { opacity:1; visibility:visible; }
body.dlg-no-scroll { overflow:hidden; }

.dlg-modal {
  position:fixed; z-index:90;              /* AU-DESSUS du voile (80) */
  left:50%; top:50%;
  transform:translate(-50%, calc(-50% + 8px));
  width:calc(100% - 32px); max-width:420px;
  background:var(--surface); color:var(--texte);
  border:1px solid var(--filet); border-radius:14px; padding:20px;
  max-height:calc(100dvh - 40px); overflow:auto;
  opacity:0; visibility:hidden;
  transition:opacity 160ms ease, transform 160ms ease, visibility 160ms;
}
.dlg-modal.is-visible {
  opacity:1; visibility:visible;
  transform:translate(-50%, -50%);
}
.dlg-modal--large { max-width:640px; }
.dlg-modal--small { max-width:340px; }
/* variante « panneau latéral » de la lib */
.dlg-modal--right {
  left:auto; right:0; top:0; bottom:0;
  transform:translateX(12px); max-height:none; border-radius:14px 0 0 14px;
}
.dlg-modal--right.is-visible { transform:none; }

.dlg-modal-head { display:flex; align-items:center; gap:10px; margin-bottom:12px; }
.dlg-modal-title-wrap { display:flex; align-items:center; gap:10px; flex:1; min-width:0; }
.dlg-modal-icon { width:22px; height:22px; flex:none; color:var(--repos); }
.dlg-modal-icon svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.dlg-modal--success .dlg-modal-icon { color:var(--positif); }
.dlg-modal--warning .dlg-modal-icon { color:var(--accent); }
.dlg-modal--error .dlg-modal-icon,
.dlg-modal--danger .dlg-modal-icon { color:var(--alerte); }
.dlg-modal-title { margin:0; font-family:var(--serif); font-weight:400; font-size:18px; }
.dlg-modal-close {
  margin-left:auto; min-width:36px; min-height:36px; padding:0 8px; font-size:18px;
  background:transparent; color:var(--texte-faible); border:0; border-radius:var(--r-doux);
}
.dlg-modal-close:hover { color:var(--texte); }
.dlg-modal-body { font-size:14px; color:var(--texte-doux); }
.dlg-message { margin:0; }
.dlg-modal-foot { display:flex; gap:8px; justify-content:flex-end; flex-wrap:wrap; margin-top:18px; }

.dlg-prompt-input {
  width:100%; font:inherit; color:var(--texte); background:var(--fond);
  border:1px solid var(--filet); border-radius:var(--r-doux); padding:10px 12px; margin-top:12px;
}
.dlg-prompt-input:focus { outline:2px solid var(--accent); outline-offset:1px; }
.dlg-prompt-error { margin:6px 0 0; font-size:12px; color:var(--alerte); }

/* boutons de la lib : nommage `btn`, `btn--primary`, `btn--danger` */
.dlg-modal-foot .btn, .dlg-modal .btn {
  min-height:44px; padding:0 16px; font-size:13px; background:transparent;
  color:var(--texte-doux); border:1px solid var(--filet); border-radius:var(--r-doux);
}
.dlg-modal .btn:hover { color:var(--texte); border-color:var(--texte-faible); }
.dlg-modal .btn--primary { color:var(--texte); border-color:var(--accent); }
.dlg-modal .btn--primary:hover { background:rgba(216,164,65,.12); }
.dlg-modal .btn--danger { color:var(--alerte); border-color:var(--alerte); }
.dlg-modal .btn--danger:hover { background:rgba(212,105,74,.12); }

/* toasts et flash de la lib (non utilisés par le jeu, qui a son propre toast,
   mais stylés pour qu'un appel futur ne rende pas du texte nu) */
/* haut-droite : c'est la position documentée par la lib (dialog.js §toast).
   Le jeu utilise son propre toast (#toast, en bas) — celui-ci ne sert que si
   du code appelle window.dialog.toast(). */
.dlg-toast-container {
  position:fixed; right:16px; top:calc(16px + env(safe-area-inset-top)); z-index:95;
  display:flex; flex-direction:column; gap:8px; max-width:min(360px, calc(100vw - 32px));
}
.dlg-toast {
  display:flex; gap:10px; padding:12px 14px; background:var(--surface-haute); color:var(--texte);
  border:1px solid var(--filet); border-radius:var(--r-doux); font-size:13px;
  opacity:0; transform:translateY(6px); transition:opacity 160ms ease, transform 160ms ease;
}
.dlg-toast.is-visible { opacity:1; transform:none; }
.dlg-toast--success { border-color:var(--positif); }
.dlg-toast--warning { border-color:var(--accent); }
.dlg-toast--error, .dlg-toast--danger { border-color:var(--alerte); }
.dlg-toast-icon { width:18px; height:18px; flex:none; }
.dlg-toast-icon svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:2; }
.dlg-toast-title { display:block; font-family:var(--serif); }
.dlg-toast-msg { display:block; color:var(--texte-doux); }
.dlg-toast-body { flex:1; min-width:0; }
.dlg-toast-close { margin-left:auto; background:none; border:0; color:var(--texte-faible); min-width:32px; min-height:32px; }
.dlg-flash-slot { display:block; }
.dlg-flash { padding:10px 14px; border:1px solid var(--filet); border-radius:var(--r-doux); font-size:13px; }
.dlg-flash--success { border-color:var(--positif); }
.dlg-flash--error, .dlg-flash--danger { border-color:var(--alerte); }
.dlg-flash-msg { display:block; }
.dlg-flash-detail { display:block; color:var(--texte-faible); font-size:12px; }

@media (prefers-reduced-motion:reduce) {
  *,*::before,*::after { transition-duration:.01ms !important; animation-duration:.01ms !important; }
}

/* ── Vue Habitants (lot F : Aux portes) ───────────────────────────────────
   CSP stricte : `style-src 'self'` bloque tout attribut style inline. Tout
   passe par des classes, sans exception — les rares positions dynamiques
   passent par element.style (CSSOM), que la CSP n'interdit pas. */

/* Le 4e onglet fait déborder la barre sous ~370 px : « Village Habitants
   Savoir Grand Œuvre » ne tient pas. Défilement horizontal discret plutôt
   qu'un retour à la ligne, qui décalerait toute la scène d'une hauteur de
   bouton selon la largeur de l'écran. */
.vues { overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none; }
.vues::-webkit-scrollbar { display:none; }
.vue { position:relative; white-space:nowrap; padding:0 14px; }

/* La pastille doit rester lisible sur le bouton ACTIF comme sur les autres :
   fond or, texte nuit, jamais l'inverse. */
.vue__pastille {
  position:absolute; top:2px; right:4px; min-width:17px; padding:0 4px;
  font-size:10px; font-weight:700; line-height:17px; text-align:center;
  border-radius:9px; background:var(--accent); color:var(--fond);
}

.cite { display:flex; flex-direction:column; gap:14px; padding:0 var(--marge) var(--marge); }

.cite__bilan { display:flex; gap:8px; }
.cite__chiffre {
  flex:1; display:flex; flex-direction:column; align-items:center; gap:2px;
  padding:10px 4px; background:var(--surface); border:1px solid var(--filet);
  border-radius:var(--r-plaque);
}
.cite__chiffre b { font-family:var(--serif); font-size:18px; font-weight:400; color:var(--texte); }
.cite__chiffre small { font-size:11px; color:var(--texte-faible); }
/* Un oisif est une place gâchée : la couleur d'alerte du jeu, pas une nuance. */
.cite__chiffre--alerte b { color:var(--alerte); }
.cite__chiffre--alerte { border-color:var(--alerte); }

.cite__vide { font-size:13px; color:var(--texte-faible); margin:0; }
.cite__alerte { font-size:13px; color:var(--alerte); margin:0 0 8px; }

.cite__portes { display:flex; flex-direction:column; gap:6px; }

/* ════════════════════════ LA RELÈVE — vue Habitants par bâtiment ═══════════
   Le préfixe est `posteb` et NON `poste` : `.poste` désigne déjà une case du
   bandeau de ressources, et une redéfinition l'écraserait silencieusement.
   C'est le défaut `.gens` — un canevas de marcheurs transformé en boîte opaque
   par une classe homonyme — que ce fichier a déjà payé une fois. */

/* ── le vivier des sans-poste ── */
/* En LIGNES qui se replient, pas en colonne : seize personnes tiennent en
   quatre lignes là où seize cartes prenaient quatre écrans. C'est tout l'objet
   de la refonte. */
.vivier {
  display:flex; flex-wrap:wrap; gap:5px;
  padding:8px; background:var(--surface);
  border:1px dashed var(--filet); border-radius:var(--r-plaque);
}
.puce {
  display:inline-flex; align-items:center; gap:6px;
  min-height:32px; padding:3px 9px; cursor:pointer;
  font-size:12px; color:var(--texte);
  background:var(--surface-haute); border:1px solid var(--filet); border-radius:16px;
  transition:border-color 120ms ease, background 120ms ease;
}
.puce:hover { border-color:var(--texte-faible); }
/* La sélection doit se voir en vision PÉRIPHÉRIQUE : c'est elle qui explique
   pourquoi des postes se sont allumés à l'autre bout de l'écran. */
.puce--choisie { border-color:var(--accent); background:rgba(216,164,65,.12); }
.puce__nom { font-family:var(--serif); font-size:13px; }
.puce__metier { font-size:11px; color:var(--texte-doux); }

/* ── un bloc par bâtiment ── */
.posteb {
  display:block; margin-bottom:6px;
  background:var(--surface); border:1px solid var(--filet);
  border-left-width:3px; border-left-color:var(--filet);
  border-radius:var(--r-plaque); overflow:hidden;
}
/* La bordure gauche porte l'état : elle se repère en vision périphérique, là où
   un chiffre demande d'être lu. Même parti que `.candidat--rare`. */
.posteb--manque { border-left-color:var(--alerte); }
.posteb--plein  { border-left-color:var(--positif); }
.posteb__tete {
  display:flex; align-items:baseline; justify-content:space-between; gap:10px;
  width:100%; min-height:40px; padding:8px 11px; cursor:pointer;
  background:transparent; border:0; color:var(--texte); text-align:left;
}
.posteb__nom { font-family:var(--serif); font-size:15px; }
/* Le METIER mene, le QUARTIER suit. Sur cet ecran on decide qui va ou : le
   metier se compare a celui de l habitant (un hors-metier ne vaut que 40 %),
   le quartier ne sert qu a se reperer. Le vert-de-gris passe donc du second
   au premier, et le quartier recule d un ton. */
.posteb__metier {
  margin-left:7px; font-family:var(--sans); font-size:11px; color:var(--repos);
}
.posteb__quartier {
  margin-left:6px; font-family:var(--sans); font-size:10px;
  letter-spacing:.08em; text-transform:uppercase; color:var(--texte-faible);
}
.posteb__etat { font-size:12px; color:var(--texte-doux); font-variant-numeric:tabular-nums; white-space:nowrap; }
.posteb__r      { color:var(--positif); }
.posteb__r--bas { color:var(--alerte); }
.posteb__chevron { margin-left:7px; color:var(--texte-faible); }
.posteb__gens { display:flex; flex-direction:column; gap:4px; padding:0 11px 9px; }
.posteb__ligne { display:flex; align-items:center; gap:8px; font-size:12px; }
.posteb__hors   { font-style:normal; font-size:11px; color:var(--alerte); }
.posteb__etoile { font-style:normal; font-size:11px; color:var(--texte-faible); }
/* Un poste libre SANS sélection n'est pas une cible : il ne doit ni réagir au
   survol ni ressembler à un bouton, sinon on clique dans le vide — et un clic
   sans effet ni message est ce qui a coûté le plus de temps sur ce projet. */
.posteb__vide {
  justify-content:center; min-height:32px;
  color:var(--texte-faible); border:1px dashed var(--filet); border-radius:var(--r-plaque);
}
.posteb__cible {
  justify-content:center; width:100%; min-height:36px; cursor:pointer;
  font-size:12px; color:var(--accent); text-align:center;
  background:rgba(216,164,65,.08); border:1px solid var(--accent); border-radius:var(--r-plaque);
}
.posteb__cible:hover { background:rgba(216,164,65,.18); }
/* La PERMUTATION se distingue de l'affectation : verdet et non or, parce que
   ce n'est pas la même décision — l'une remplit un vide, l'autre déloge
   quelqu'un. Deux couleurs pour deux gestes. */
/* L echange, en trois tons. Un bouton qui ne fait rien gagner ne doit pas
   avoir l air d une bonne idee : le ton par defaut RECULE, et seul un gain
   reel s allume. L etoile marque le meilleur echange du batiment — sortir le
   moins bon, ce qui se calcule et n a donc pas a se deviner. */
.posteb__permuter {
  margin-left:auto; min-height:30px; padding:0 8px; cursor:pointer;
  font-size:11px; color:var(--texte-faible); white-space:nowrap;
  background:transparent; border:1px solid var(--filet); border-radius:var(--r-plaque);
}
.posteb__permuter:hover { border-color:var(--repos); background:rgba(147,171,144,.12); }
.posteb__permuter--gain    { color:var(--repos); }
.posteb__permuter--conseil {
  color:var(--positif); border-color:var(--positif); font-weight:600;
}

/* ── la barre de la personne sélectionnée ── */
/* COLLÉE en bas : la sélection se fait en haut (le vivier) et s'applique en bas
   (les bâtiments) ; une barre qui défilerait avec le contenu disparaîtrait au
   moment précis où l'on en a besoin. */
.barre-sel {
  position:sticky; bottom:0; z-index:4;
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin-top:10px; padding:9px 11px;
  background:var(--surface-haute); border:1px solid var(--accent); border-radius:var(--r-plaque);
}
.barre-sel__qui { display:flex; align-items:center; gap:7px; font-family:var(--serif); font-size:14px; }
.barre-sel__metier { font-family:var(--sans); font-size:11px; color:var(--texte-doux); }
/* Le soulagement, en vert : c'est la seule chose POSITIVE d'un bouton qui ne
   dit sinon que ce qu'on perd. Il n'apparaît qu'en disette — voir game.js. */
.barre-sel__soulage { color:var(--positif); font-variant-numeric:tabular-nums; }
.barre-sel__retirer, .barre-sel__route {
  min-height:34px; padding:0 11px; font-size:12px; cursor:pointer;
  background:transparent; color:var(--texte-doux);
  border:1px solid var(--filet); border-radius:var(--r-plaque);
}
.barre-sel__retirer { margin-left:auto; }
/* Le départ est destructeur : il se signale en rouille, mais seulement au
   survol — une couleur d'alerte permanente sur un bouton rarement utilisé finit
   par ne plus rien dire. */
.barre-sel__route:hover { color:var(--alerte); border-color:var(--alerte); }

/* ── l'échange aux portes ── */
.echange {
  grid-column:1 / -1; margin-top:8px; padding:8px;
  background:var(--fond); border:1px solid var(--filet); border-radius:var(--r-plaque);
}
.echange__lore { margin:0 0 7px; font-size:11.5px; font-style:italic; color:var(--texte-doux); line-height:1.5; }
.echange__ligne {
  display:flex; align-items:center; gap:8px; width:100%;
  min-height:34px; padding:4px 8px; margin-bottom:4px; cursor:pointer;
  font-size:12px; color:var(--texte); text-align:left;
  background:var(--surface-haute); border:1px solid var(--filet); border-radius:5px;
}
.echange__ligne:hover { border-color:var(--accent); }
.echange__ligne em { margin-left:auto; font-style:normal; font-size:11px; color:var(--alerte); }
.echange__note { margin:6px 0 0; font-size:11px; color:var(--texte-faible); }

.candidat {
  display:grid; grid-template-columns:1fr auto; align-items:center; gap:4px 10px;
  padding:10px 12px; background:var(--surface); border:1px solid var(--filet);
  border-left-width:3px; border-left-color:var(--filet); border-radius:var(--r-plaque);
}
/* Le rare se signale par la BORDURE, pas par la couleur du texte : le texte doit
   rester lisible, et une bordure se repère en vision périphérique. */
.candidat--rare { border-left-color:var(--accent); }

.candidat__nom { font-family:var(--serif); font-size:15px; color:var(--texte); }
.candidat__metier { font-size:12px; color:var(--texte-doux); grid-column:1; }
.candidat__delai { font-size:11px; color:var(--accent); grid-column:1; }

/* Les pastilles de talent : pleines en or, vides en trait. Le `title` porte la
   valeur chiffrée pour qui lit au lecteur d'écran. */
.talent { grid-row:1; grid-column:2; letter-spacing:.12em; font-size:11px; color:var(--accent); }

.candidat__gestes { grid-column:1 / -1; display:flex; gap:6px; margin-top:4px; }
.candidat__gestes button {
  flex:1; min-height:40px; font-size:13px; background:transparent;
  border:1px solid var(--filet); border-radius:var(--r-plaque); color:var(--texte-doux);
}
.candidat__oui { border-color:var(--positif); color:var(--positif); }
.candidat__oui:disabled { border-color:var(--filet); color:var(--texte-faible); cursor:default; }
/* Les DEUX gestes reagissent au survol. Seul le refus en avait un : le bouton
   d accueil paraissait mort, alors que c est celui qu on veut cliquer. */
.candidat__oui:hover:not(:disabled) { border-color:var(--positif); box-shadow:0 0 6px rgba(143,174,92,.25); }
.candidat__non:hover { border-color:var(--alerte); color:var(--alerte); }

/* Postes et rendement sous chaque tuile du Village : sans ça, la mécanique est
   invisible là où elle agit. */
.plot__postes { font-size:11px; color:var(--texte-faible); }
.plot__postes--vide { color:var(--alerte); }

/* Une LIGNE, pas une banniere. Cliquable, donc 44 px de haut au minimum. */
/* SA HAUTEUR NE DOIT JAMAIS BOUGER — la scene se cale sur ce qui la surmonte,
   et une aide contextuelle qui redimensionne le jeu en apparaissant est un
   defaut, pas une economie de place. D'ou `nowrap` : sur un telephone etroit,
   un nom long passerait sur deux lignes et deplacerait le village. */
.fil {
  display:flex; align-items:center; gap:8px;
  min-height:44px; margin:0; padding:0 var(--marge);
  font-size:13px; color:var(--accent); cursor:pointer;
  border-bottom:1px solid var(--filet);
  white-space:nowrap; overflow:hidden;
}
/* `visibility` et NON `display:none` : la boite reste, donc la rangee garde sa
   hauteur. La bordure et le pointage disparaissent avec le texte. */
.fil--vide { visibility:hidden; }
.fil[hidden] { display:none; }
.fil:hover { color:var(--texte); }

/* Les jalons : franchis en clair, a venir en sourdine. La bordure doree ne
   porte JAMAIS l'information seule — le texte revele fait le second signal. */
.jalons { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px; }
.jalon {
  display:flex; flex-direction:column; gap:2px; padding:8px 12px;
  background:var(--surface); border:1px solid var(--filet);
  border-left-width:3px; border-left-color:var(--filet); border-radius:var(--r-plaque);
}
.jalon--fait { border-left-color:var(--accent); }
.jalon__quoi { font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--texte-faible); }
.jalon__texte { font-family:var(--serif); font-size:14px; color:var(--texte-faible); }
.jalon--fait .jalon__texte { color:var(--texte); }

/* Le choix de palette, dans le menu. Des PASTILLES et non une liste de mots :
   on choisit une couleur, donc on la montre. Le nom reste, en petit, pour le
   clavier et pour qui ne distingue pas les teintes — une pastille seule serait
   inutilisable au lecteur d'ecran comme au daltonien. */
.palettes { display:flex; flex-direction:column; gap:6px; padding:4px 0 2px; }
.palettes__titre {
  font-size:10.5px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--texte-faible);
}
.palettes__choix { display:flex; gap:6px; flex-wrap:wrap; }
.palettes__opt {
  display:flex; flex-direction:column; align-items:center; gap:4px;
  min-width:44px; min-height:44px; padding:5px 6px; cursor:pointer;
  background:none; border:1px solid var(--filet); border-radius:var(--r-plaque);
  color:var(--texte-faible); font-size:10px;
}
.palettes__opt:hover { border-color:var(--texte-faible); color:var(--texte-doux); }
.palettes__opt[aria-checked="true"] { border-color:var(--accent); color:var(--texte); }
/* La pastille porte TROIS bandes et non un aplat : une palette n'est pas une
   couleur, c'est un rapport entre des couleurs — un carre uni ne dirait rien
   de ce qui change vraiment sur la cite. */
.palettes__vu {
  width:26px; height:13px; border-radius:2px; overflow:hidden;
  display:flex; border:1px solid var(--ombre);
}
.palettes__vu > i { flex:1; }

/* Le menu de la barre des vues : <details> natif, donc fermeture au clic
   ailleurs et au clavier sans une ligne de JS.
   IL VIVAIT DANS UN PIED DE PAGE. La Colonne l'a remonte dans la barre du haut
   (game.js, `barre.appendChild(coin)`) et le CSS n'a pas suivi : la liste
   gardait son `bottom:100%`, donc elle s'ouvrait AU-DESSUS d'une barre deja
   collee en haut — hors ecran. Le `<details>` s'ouvrait, la liste existait,
   elle etait simplement invisible, ce qui se lit comme « le bouton ne marche
   plus » et envoie chercher un ecouteur qui n'a jamais existe. */
.menu { position:relative; flex:0 0 auto; }
.menu__ouvre {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:44px; min-height:44px; padding:0 14px; cursor:pointer;
  color:var(--texte-doux); border:1px solid var(--filet); border-radius:var(--r-plaque);
  list-style:none;
}
.menu__ouvre::-webkit-details-marker { display:none; }
.menu[open] .menu__ouvre { color:var(--texte); border-color:var(--texte-faible); }
.menu__liste {
  /* `fixed` ET NON `absolute`, et ce n'est pas un detail de placement.
     `.vues` porte `overflow-x:auto` pour ses cinq onglets. Or des qu'un axe
     vaut `auto`, l'autre cesse d'etre `visible` : la barre ROGNE AUSSI EN
     HAUTEUR. Une liste en `absolute` y est donc decoupee a la hauteur d'un
     onglet — elle existe, elle a sa taille, et on n'en voit rien.
     Un element `fixed` a le VIEWPORT pour bloc conteneur : aucun `overflow`
     d'ancetre ne le rogne. Ses coordonnees sont posees par game.js a
     l'ouverture, parce qu'elles dependent de l'ecran — la barre est en bas sur
     mobile et en haut sur bureau, donc la liste s'ouvre vers le haut ou vers
     le bas selon la place. Les valeurs ci-dessous ne sont qu'un repli. */
  position:fixed; right:12px; top:auto; bottom:64px; z-index:50;
  display:flex; flex-direction:column; gap:6px; min-width:200px; padding:8px;
  background:var(--surface-haute); border:1px solid var(--filet); border-radius:var(--r-plaque);
}
.menu__liste > * { min-height:44px; }
/* La deconnexion est SEPAREE : un filet et un ecart, pour qu'elle ne soit
   jamais au contact d'une action courante. */
.menu__liste .danger { margin-top:6px; border-top:1px solid var(--filet); padding-top:12px; }

/* ------------------------------------------------ le village froid, annonce */
/* Passe six heures d'absence, la cite se rend eteinte : fenetres au verre nu,
   ni marcheurs, ni vent, ni animations. C'est voulu — mais c'etait MUET, donc
   indiscernable d'un chargement rate. L'invite dit l'etat et le geste qui en
   sort ; elle est aussi le seul moyen de raviver au clavier, le geste d'origine
   etant un `pointerdown` sur un canvas. */
.ranime { display:none; }
.quartiers--froid .ranime {
  display:flex; flex-direction:column; align-items:center; gap:2px;
  width:100%; min-height:56px; padding:10px var(--marge);
  margin:0 0 var(--marge);
  background:var(--surface-haute); border:1px solid var(--accent); border-radius:var(--r-plaque);
  text-align:center;
}
.ranime__titre { color:var(--accent); font-family:var(--serif); font-size:1.02rem; }
.ranime__txt   { color:var(--texte-doux); font-size:.86rem; }
/* La braise : lente, discrete, et sur l'OMBRE seule — animer la bordure ou la
   taille declencherait un recalcul de mise en page a chaque image. */
.quartiers--froid .ranime { animation:braise 3.4s ease-in-out infinite; }
@keyframes braise {
  0%,100% { box-shadow:0 0 0 0 rgba(216,164,65,0); }
  50%     { box-shadow:0 0 14px 1px rgba(216,164,65,.28); }
}
@media (prefers-reduced-motion:reduce) {
  .quartiers--froid .ranime { animation:none; }
}

/* La disette, dite sur la ressource qui en est la cause. Rouille et non
   « perte » : un taux negatif est un fait comptable, une famine est un
   avertissement. Les deux se lisent sur la meme ligne, il faut les distinguer. */
.poste__taux--faim { color:var(--alerte); font-weight:600; }

/* ------------------------------------------------------- l'onglet Stats */
/* Sept verdicts d'une ligne, toujours visibles ; le detail se deplie. Sept
   lignes tiennent sur un ecran de telephone, sept paves en occuperaient
   quatre — et l'ossature « par question » ne tiendrait plus sa promesse. */
.stats { display:flex; flex-direction:column; gap:8px; padding:0 var(--marge) var(--marge); }

/* LES COURBES PEIGNENT EN RÔLES, plus en littéraux.
   Elles portaient `#2f3944`, `#7d8896`, `#8aa663`, `#58a08a` — les couleurs
   FROIDES d'avant le lot O, restées bleu-gris sur un fond devenu brun. Des
   attributs SVG écrits en JS sont hors d'atteinte du banc des jetons comme de
   l'audit des littéraux CSS : ni l'un ni l'autre ne regarde le JS.
   En classes, elles suivent le thème — y compris le renversement du papier.
   Les épaisseurs de trait vivaient dans les attributs : elles doivent vivre ici
   maintenant, sinon tout retombe au 1 px par défaut. */
.cb-axe    { fill:none; stroke:var(--filet); stroke-width:1; }
.cb-repere { stroke:var(--texte-faible); stroke-width:.5; }
.cb-trait  { fill:none; stroke:var(--positif); stroke-width:1.5; }
.cb-or     { stroke:var(--accent); }
.cb-alerte { stroke:var(--alerte); }
.cb-repos  { stroke:var(--repos); }

.stbloc {
  background:linear-gradient(var(--surface-haute),var(--surface));
  border:0; border-radius:var(--r-plaque);
  box-shadow:inset 0 1px 0 var(--lumiere), inset 0 -1px 0 var(--ombre);
}
.stbloc__tete {
  display:flex; align-items:baseline; justify-content:space-between; gap:10px;
  min-height:44px; padding:9px 12px; cursor:pointer; list-style:none;
}
/* Le marqueur natif de <summary> varie d'un navigateur a l'autre et casse
   l'alignement. On le retire des deux facons qui existent. */
.stbloc__tete::-webkit-details-marker { display:none; }
.stbloc__tete::marker { content:''; }
.stbloc__titre {
  font-family:var(--sans); font-size:10px; letter-spacing:.09em;
  text-transform:uppercase; color:var(--texte-faible); white-space:nowrap;
}
.stbloc__verdict { font-family:var(--serif); font-size:13.5px; color:var(--texte); text-align:right; }
.stbloc__corps { padding:0 12px 12px; }
.stbloc[open] .stbloc__tete { border-bottom:1px solid var(--filet); margin-bottom:10px; }

/* Une table de chiffres se lit en colonnes alignees : chiffres tabulaires, et
   defilement horizontal DANS la table — jamais dans la page. */
.sttable { width:100%; border-collapse:collapse; font-size:12px; font-variant-numeric:tabular-nums; }
.stbloc__corps { overflow-x:auto; }
.sttable th {
  font-family:var(--sans); font-size:9.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--texte-faible); text-align:left; font-weight:400; padding:4px 8px 4px 0;
  border-bottom:1px solid var(--filet); white-space:nowrap;
}
.sttable td { padding:5px 8px 5px 0; color:var(--texte-doux); white-space:nowrap; }
.sttable tr:first-child td { color:var(--texte); }
.stnote { font-size:11.5px; color:var(--texte-faible); line-height:1.5; margin:0 0 10px; }
.sttitre { font-family:var(--sans); font-size:10px; letter-spacing:.08em;
           text-transform:uppercase; color:var(--texte-faible); margin:12px 0 4px; }

.stgraph { width:100%; }
/* Le viewBox porte la largeur REELLE du conteneur : `height:auto` rend donc
   exactement les 132 px du viewBox, et non 400 px sur un ecran large. */
.courbe { display:block; width:100%; height:auto; }
.courbe__vide  { fill:var(--texte-faible); font-size:9px; font-family:sans-serif; }
/* Les axes. Sans eux une courbe ne dit ni combien ni quand, et la meme
   image sert pour « x2 » et pour « x1000 ». */
.courbe__axe   { fill:var(--texte-faible); font-size:9px;   font-family:var(--sans); }
.courbe__duree { fill:var(--texte-faible); font-size:8.5px; font-family:var(--sans); }

/* Le poste actuel, accole au prenom. Les prenoms ne sont pas uniques : trois
   Aldric dans une cite, et « Aldric -> Mathis » ne designe personne. */
.stou { color:var(--texte-faible); font-size:10.5px; }

/* ------------------------------------------------------------- la carte */
/* Un seul exemplaire pour toute la scene. `fixed` et non `absolute` : elle doit
   pouvoir sortir du cadre d'un quartier sans etre rognee par son `overflow`,
   et son placement est calcule en coordonnees de fenetre par `carte.js`. */
.carte {
  position:fixed; z-index:40; min-width:220px; max-width:min(340px, 94vw);
  background:linear-gradient(var(--surface-haute),var(--surface));
  border:0; border-radius:var(--r-plaque);
  padding:10px 12px; color:var(--texte);
  /* L'anneau d'or, le liseré de lumière et l'ombre portée dans UNE déclaration :
     un `box-shadow` déjà présent aurait écrasé la bordure si on l'avait laissée
     à part. Trois couches, un seul point de vérité. */
  box-shadow:inset 0 0 0 1px var(--accent), inset 0 1px 0 var(--lumiere),
             0 6px 24px rgba(0,0,0,.55);
}
.carte[hidden] { display:none; }
.carte__tete { display:flex; align-items:baseline; gap:8px; }
.carte__titre { font-family:var(--serif); font-size:15px; }
.carte__niv { color:var(--texte-faible); font-size:11px; font-variant-numeric:tabular-nums; }
.carte__fermer {
  margin-left:auto; min-width:32px; min-height:32px; padding:0;
  background:transparent; border:0; color:var(--texte-faible); font-size:13px;
}
/* `pre-wrap` : le corps est ecrit en `textContent` avec des sauts de ligne, ce
   qui evite de reconstruire du balisage quatre fois par seconde. */
.carte__corps {
  white-space:pre-wrap; font-size:12px; color:var(--texte-doux);
  margin:6px 0 10px; font-variant-numeric:tabular-nums; line-height:1.55;
}
/* 44 px : la cible tactile minimale. C'est le bouton le plus presse du jeu. */
.carte__action {
  width:100%; min-height:44px; padding:8px 10px; cursor:pointer;
  background:transparent; border:1px solid var(--repos); border-radius:var(--r-plaque);
  color:var(--repos); font-size:12px; line-height:1.4;
}
.carte__action:disabled { color:var(--texte-faible); border-color:var(--filet); cursor:default; }

/* MOBILE : collee en bas, pleine largeur, jamais sous le doigt — qui recouvre
   ce qu'il touche. `carte.js` pose deja x=0 et y=bas ; le CSS accorde la
   largeur et arrondit les deux coins hauts seulement. */
@media (max-width:767px) {
  .carte {
    left:0 !important; right:0; width:100%; max-width:none;
    border-radius:var(--r-plaque) var(--r-plaque) 0 0; border-bottom:0;
    padding-bottom:calc(10px + env(safe-area-inset-bottom));
  }
}

/* ------------------------------------------------- le depli des matieres */
/* Il occupe une cellule pleine largeur de la grille du bandeau : c'est ce qui
   le garde aligne avec les compteurs sans regle de position. */
.ledger__plus { grid-column:1 / -1; }
.ledger__ouvre {
  list-style:none; cursor:pointer; padding:6px 0; min-height:32px;
  display:flex; align-items:center;
  font-size:11px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--texte-faible);
}
.ledger__ouvre::-webkit-details-marker { display:none; }
.ledger__ouvre::marker { content:''; }
.ledger__ouvre::before { content:'▾\00a0'; }
.ledger__plus[open] .ledger__ouvre::before { content:'▴\00a0'; }
.ledger__matieres {
  display:grid; grid-template-columns:repeat(auto-fit, minmax(84px, 1fr));
  gap:6px 0; padding-top:8px; border-top:1px solid var(--filet);
}

/* Le menu, pose dans le coin de la barre des vues plutot que dans un pied a
   lui : un bouton qu'on touche trois fois par partie ne merite pas une rangee
   permanente.

   `margin-left:auto` et NON `position:absolute`. La barre est `fixed` sur
   mobile et `static` sur bureau : un `absolute` n aurait pas eu le meme parent
   selon la largeur d ecran, et serait parti dans le coin de la page sur
   bureau. Une marge automatique le pousse a droite dans les deux cas, sans
   dependre de qui est positionne. */
.menu-coin { margin-left:auto; display:flex; align-items:center; flex:0 0 auto; }
