/* ------------------------------------------------------------------ jetons */
/*
 * Trois tapis, un seul jeu de noms.
 *
 * Les couleurs ne sont plus ecrites la ou elles servent : chaque surface, chaque
 * filet, chaque ombre porte un nom, et un theme n'est rien d'autre qu'un jeu de
 * valeurs pour ces noms. C'est ce qui permet d'en ajouter un sans relire la
 * feuille, et c'est ce qui garantit qu'aucun coin de l'ecran ne restera vert
 * dans le theme blanc parce qu'on l'aura oublie.
 *
 * Les trois familles disent leur role plutot que leur teinte :
 *
 *   --raise-*  une surface qui se souleve du tapis (bandeau, jeton, bouton)
 *   --line-*   un filet, une bordure, une separation
 *   --well-*   un creux, une ombre portee sur le tapis
 *
 * Sur un tapis sombre, se soulever c'est s'eclaircir ; sur un tapis clair,
 * c'est l'inverse. Les valeurs changent donc de sens d'un theme a l'autre, mais
 * pas les noms - et c'est tout l'interet.
 */
:root,
:root[data-theme="vert"] {
  color-scheme: light dark;

  /* Le tapis : trois arrets du degrade radial, du centre vers les bords. */
  --table: #14653f;
  --table-deep: #0d4a2d;
  --table-edge: #0a3b24;

  /* Le texte sur le tapis, et les panneaux qui s'y posent. */
  --text: #f6f2e6;
  --panel: #123a29;
  --overlay: rgba(8, 32, 22, .97);

  --raise-1: rgba(255, 255, 255, .08);
  --raise-2: rgba(255, 255, 255, .1);
  --raise-3: rgba(255, 255, 255, .2);
  --line-1: rgba(255, 255, 255, .13);
  --line-2: rgba(255, 255, 255, .18);
  --line-3: rgba(255, 255, 255, .25);
  --well-1: rgba(0, 0, 0, .16);
  --well-2: rgba(0, 0, 0, .24);
  --well-3: rgba(0, 0, 0, .28);

  --field-bg: rgba(0, 0, 0, .25);
  --backdrop: rgba(0, 0, 0, .5);
  --backdrop-soft: rgba(0, 0, 0, .22);

  /* La face des tuiles ne change pas de theme : l'ivoire reste de l'ivoire. */
  --paper: #f6f2e6;
  --paper-edge: #ccc0a2;
  --paper-shade: #e6dcc4;
  /* Tranche du dos : le bambou est plus chaud et plus fonce que la face. */
  --wood-edge: #b0834f;
  --tile-shadow: rgba(0, 0, 0, .3);
  --tile-shadow-strong: rgba(0, 0, 0, .35);

  --gold: #e0b464;
  /* Ce qui s'ecrit sur un bouton d'or : assez sombre pour se lire dessus. */
  --on-gold: #10281d;
  --danger: #c0392b;
  --plus: #7fd6a8;
  --minus: #f0907f;
  --count-fg: #fff;
  --count-bg: rgba(12, 40, 28, .92);

  --shadow: 0 1px 2px rgba(0, 0, 0, .18), 0 8px 24px rgba(0, 0, 0, .22);
}

/* Le tapis vert suit la lumiere ambiante ; les deux autres se sont choisis. */
@media (prefers-color-scheme: dark) {
  :root,
  :root[data-theme="vert"] {
    --table: #10402c;
    --table-deep: #0a2c1e;
    --table-edge: #061c13;
    --paper: #ece4d2;
    --paper-shade: #d6cab0;
  }
}

/*
 * Le tapis blanc.
 *
 * La difficulte est connue : les tuiles sont ivoire, et de l'ivoire sur du
 * blanc ne se voit pas. Le tapis descend donc vers un gris chaud, les tuiles
 * gardent leur face claire, leur tranche fonce et leur ombre s'affirme - c'est
 * l'ombre, ici, qui fait tenir les tuiles au-dessus du tapis.
 */
:root[data-theme="blanc"] {
  color-scheme: light;

  --table: #f2efe6;
  --table-deep: #e0dbcc;
  --table-edge: #cdc7b4;

  --text: #23292f;
  --panel: #fbf9f3;
  --overlay: rgba(251, 249, 243, .98);

  /*
   * Se soulever, sur un tapis clair, c'est aller vers le blanc - mais de peu.
   * Le premier niveau marque le siege au trait : trop appuye, il decoupait un
   * panneau blanc autour du joueur au lieu de le designer.
   */
  --raise-1: rgba(255, 255, 255, .42);
  --raise-2: rgba(255, 255, 255, .68);
  --raise-3: rgba(255, 255, 255, 1);
  /* Les filets, eux, doivent foncer : un filet blanc sur blanc n'existe pas. */
  --line-1: rgba(35, 41, 47, .14);
  --line-2: rgba(35, 41, 47, .22);
  --line-3: rgba(35, 41, 47, .34);
  --well-1: rgba(35, 41, 47, .05);
  --well-2: rgba(35, 41, 47, .07);
  --well-3: rgba(35, 41, 47, .1);

  --field-bg: rgba(255, 255, 255, .75);
  --backdrop: rgba(35, 41, 47, .38);
  --backdrop-soft: rgba(35, 41, 47, .16);

  --paper: #fffdf6;
  --paper-edge: #b6a986;
  --paper-shade: #efe7d2;
  --tile-shadow: rgba(35, 41, 47, .16);
  --tile-shadow-strong: rgba(35, 41, 47, .24);

  --gold: #cf9d3a;
  --on-gold: #2c2103;
  --danger: #b23325;
  /* Les verts et les rouges pales du tapis vert seraient illisibles ici. */
  --plus: #1c7a51;
  --minus: #b0402f;
  --count-bg: rgba(45, 52, 47, .92);

  --shadow: 0 1px 2px rgba(35, 41, 47, .1), 0 8px 24px rgba(35, 41, 47, .14);
}

/*
 * Le tapis noir.
 *
 * Rien a compenser : l'ivoire des tuiles porte tout seul sur du noir. Le tapis
 * s'arrete a un gris tres sombre plutot qu'au noir pur, faute de quoi les
 * ombres portees n'auraient plus rien sur quoi se poser.
 */
:root[data-theme="noir"] {
  color-scheme: dark;

  --table: #2a2c30;
  --table-deep: #17181b;
  --table-edge: #0a0b0c;

  --text: #ece9e1;
  --panel: #202226;
  --overlay: rgba(24, 26, 29, .97);

  --raise-1: rgba(255, 255, 255, .07);
  --raise-2: rgba(255, 255, 255, .11);
  --raise-3: rgba(255, 255, 255, .22);
  --line-1: rgba(255, 255, 255, .14);
  --line-2: rgba(255, 255, 255, .2);
  --line-3: rgba(255, 255, 255, .3);
  --well-1: rgba(0, 0, 0, .3);
  --well-2: rgba(0, 0, 0, .4);
  --well-3: rgba(0, 0, 0, .45);

  --field-bg: rgba(0, 0, 0, .4);
  --backdrop: rgba(0, 0, 0, .62);
  --backdrop-soft: rgba(0, 0, 0, .3);

  --paper: #f1ece0;
  --paper-edge: #b9ad93;
  --paper-shade: #ded5c1;
  --tile-shadow: rgba(0, 0, 0, .55);
  --tile-shadow-strong: rgba(0, 0, 0, .65);

  --gold: #dfb463;
  --on-gold: #211a08;
  --danger: #c0392b;
  --plus: #7fd6a8;
  --minus: #f0907f;
  --count-bg: rgba(0, 0, 0, .85);

  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .5);
}

/* ----------------------------------------------------------------- mesures */
/*
 * Les mesures, dans un bloc a elles - et **c'est la moitie du sujet**.
 *
 * Elles vivaient avec les couleurs, dans `:root, :root[data-theme="vert"]`. Ce
 * second selecteur pese (0,2,0), et `applyTheme` pose toujours l'attribut sur
 * la racine : un `@media { :root { --tile-w: ... } }`, qui ne pese que (0,1,0),
 * **perdait donc sans un mot**. Le calibrage des tuiles pour telephone et celui
 * des ecrans courts n'ont jamais pris effet - la regle etait bien dans la
 * feuille, la media query correspondait bien, elle etait simplement surclassee.
 * Constate en lisant `getComputedStyle`, pas en relisant le code.
 *
 * Ici, aucun theme ne les redefinit : un `:root` en media query l'emporte par
 * l'ordre, comme on l'attend.
 */
:root {
  /*
   * Le rail, et ce qu'il laisse au jeu.
   *
   * `--jeu` n'est pas une coquetterie : tout le calibrage des tuiles etait
   * exprime en `vw`, donc sur le viewport entier. Un rail de 190 px les aurait
   * dimensionnees comme si la table faisait toute la largeur, et `.table` porte
   * `overflow: hidden` - ce qui ne rentre pas est rogne **en silence**, sans
   * barre de defilement et sans erreur.
   *
   * Sur la substitution : une propriete personnalisee qui contient un `var()`
   * est substituee la ou elle est *declaree* (voir `--tile-h` ci-dessous, qui
   * s'y est deja fait prendre). Le piege ne mord que si la propriete referencee
   * est redefinie sur un descendant. `--jeu` ne vit que sur `:root` et n'est
   * jamais redefini ailleurs ; `--tile-w`, lui, peut l'etre sans risque.
   */
  --rail: 190px;
  /* La hauteur d'une entree du rail. */
  --rail-item: 40px;
  --jeu: calc(100vw - var(--rail));

  --tile-w: clamp(30px, calc(var(--jeu) * .034), 46px);
  /*
   * 80 / 60, le rapport du dessin des faces. La hauteur se calcule sur chaque
   * tuile a partir de sa propre largeur, et non ici : une propriete
   * personnalisee qui contient un `var()` est substituee la ou elle est
   * *declaree*. Ecrire `--tile-h: calc(var(--tile-w) * 1.33)` sur `:root`
   * figeait donc la hauteur sur la largeur de la racine, et les tuiles reduites
   * (defausses, mains adverses) gardaient la hauteur des grandes : 30 de large
   * pour 62 de haut, soit un rapport de 2 au lieu de 1,33.
   */
  --tile-ratio: 1.3333;
  --radius: 10px;
}

* { box-sizing: border-box; }

/*
 * `hidden` doit gagner, toujours.
 *
 * La regle du navigateur pour `[hidden]` ne pese qu'un attribut, et le moindre
 * `display` pose par un identifiant ou une classe l'emporte. Un element cense
 * etre cache reste alors affiche - et cela ne se voit pas du tout a la
 * relecture, `element.hidden = true` ayant l'air de suffire. C'est ce qui fait
 * taire les pastilles hors partie et le panneau des defausses.
 */
[hidden] { display: none !important; }

/*
 * Un `<dialog>` ferme reste ferme, meme si on lui donne un `display`.
 *
 * Meme lecon qu'au-dessus, et le meme piege : la regle du navigateur
 * (`dialog:not([open]) { display: none }`) appartient a la feuille de l'agent
 * utilisateur, et **toute** regle d'auteur la bat, quelle que soit sa
 * specificite. Un simple `display: flex` sur un panneau suffisait donc a
 * l'afficher en permanence - vide, puisque son contenu n'est construit qu'a
 * l'ouverture.
 */
dialog:not([open]) { display: none; }

/*
 * Le sprite des tuiles ne doit rien occuper.
 *
 * Il portait son `style="display:none"` sur lui, et cela marchait tant que la
 * page se servait toute seule. La politique de contenu (`style-src 'self'`,
 * voir `server/static.mjs`) refuse les styles en ligne : l'attribut etait
 * ignore, et le `<svg>` reprenait sa taille par defaut - 300x150 pixels vides,
 * puisqu'un `<symbol>` ne se dessine pas - qui poussaient toute la page vers le
 * bas. Invisible, mais bien la.
 *
 * La regle vit donc ici, ou la politique l'autorise. Les `<use href="#t-...">`
 * continuent de puiser dedans : un symbole reste referencable dans un arbre
 * cache.
 */
#tile-sprite { display: none; }

body {
  margin: 0;
  min-height: 100dvh;
  font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--text);
  background: radial-gradient(120% 90% at 50% 0%, var(--table) 0%, var(--table-deep) 55%, var(--table-edge) 100%);
  -webkit-font-smoothing: antialiased;
}

/*
 * Le rail a gauche, tout le reste dans une colonne.
 *
 * C'etait une grille en rangees ; elle devient une grille en colonnes, et les
 * rangees d'avant descendent d'un cran dans `.vue`. Sous 720px la colonne
 * disparait et le rail passe en bas - voir « petits ecrans ».
 */
#app {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  height: 100dvh;
  overflow: hidden;
}

.vue {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  overflow: hidden;
}

/* ----------------------------------------------------------------- pages */
/*
 * Les regles, les options et le joueur ont chacun une adresse, et occupent la
 * rangee du milieu - celle de la table, qui se cache pendant ce temps.
 *
 * `grid-row: 2` est explicite, et pas par gout : `#app` est en
 * `height: 100dvh; overflow: hidden`, et une page posee dans la rangee du
 * bandeau serait tronquee sans qu'aucune erreur ne le dise. Le defilement lui
 * appartient, pour la meme raison.
 */
#page {
  grid-row: 2;
  overflow: auto;
  padding: 20px clamp(12px, 3vw, 40px) 40px;
}

/*
 * Une page prend la place de la table, et rien ne l'encadre.
 *
 * Ces trois ecrans ont d'abord ete des panneaux modaux, et ils en avaient garde
 * le cadre : un fond, un rayon, une ombre portee. Sur une page ce cadre ne dit
 * plus rien - il n'y a rien derriere dont il faudrait se detacher - et il lui
 * donne l'air d'une boite de dialogue qu'on aurait oublie de fermer.
 *
 * **La mesure reste, elle.** Occuper toute la largeur n'est pas plus joli
 * partout : une ligne de prose de 1200 px ne se lit pas, l'oeil perd le retour
 * a la ligne bien avant. Chaque page prend donc la largeur qui lui convient, et
 * non toute celle qui est libre.
 */
.feuille {
  max-width: 1100px;
  margin: 0 auto;
  color: var(--text);
}
/* Un titre de page, et non de panneau : il n'a plus de cadre pour le porter. */
.feuille > h2 { margin: 0 0 20px; font-size: 21px; }

/* Les regles sont de la prose : elles gardent une mesure de lecture. */
#page-regles { max-width: 780px; }

/*
 * Les options sont une liste de champs courts, et c'est la que la largeur
 * gagnee sert vraiment : sur deux colonnes elles tiennent d'un seul ecran, ce
 * qui est tout ce qu'on demande a un panneau de reglages - voir ce qu'on regle
 * sans defiler.
 *
 * `columns` et non une grille : les blocs n'ont pas la meme hauteur, et une
 * grille alignerait chaque rangee sur le plus haut en laissant des trous sous
 * les autres. `break-inside` garde chaque reglage entier, et le titre traverse.
 */
#page-options { columns: 2 320px; column-gap: 48px; }
#page-options > * { break-inside: avoid; }
#page-options > h2 { column-span: all; }
.joueur-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }

/* --------------------------------------------------------------- accueil */
/* L'accueil vit au centre de la table, la ou s'affichent le vent et le mur une
   fois la partie lancee. Il n'a donc pas de fond a lui : c'est le tapis qu'on
   voit derriere, et c'est ce qui dit qu'on est deja dans le jeu. */
.accueil {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  max-width: min(360px, 100%);
  padding: 4px;
}

.accueil-titre {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
}

.accueil-intro { margin: 4px 0 14px; font-size: 12px; opacity: .6; line-height: 1.55; text-align: center; }

.accueil-actions { display: flex; flex-direction: column; gap: 8px; align-self: stretch; }

/* Le bouton porte son etiquette et, pour la reprise, ou l'on en etait : deux
   lignes centrees plutot qu'une phrase a cote que personne ne relie au bouton. */
.accueil-action {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 14px;
  font-size: 14px;
}
.accueil-action em { font-style: normal; font-size: 11px; font-weight: 400; opacity: .6; }

/* Le choix de la variante : trois boutons cote a cote, le dernier joue en
   avant. C'est une decision de partie, pas une preference - elle se prend donc
   ici et non dans les options, et les trois noms doivent se lire d'un coup
   d'oeil plutot que de s'ouvrir en menu. */
.accueil-variantes { align-self: stretch; margin-top: 14px; }
.accueil-variantes .accueil-mot { margin: 0 0 6px; text-align: center; }
.accueil-variantes .accueil-actions { flex-direction: row; flex-wrap: wrap; }
.accueil-variantes .variante { flex: 1 1 0; min-width: 96px; padding: 8px 10px; font-size: 12px; }
.accueil-variantes .variante em { font-size: 10px; line-height: 1.35; text-align: center; }
/* Le dernier joue se signale par un liseré, pas par une couleur de plus : il
   est deja le seul bouton plein des trois. */
.accueil-variantes .variante[data-dernier] { outline: 1px solid var(--gold); outline-offset: 2px; }

@media (max-width: 480px) {
  .accueil-variantes .accueil-actions { flex-direction: column; }
}

/* Le compte se donne sa propre zone : il n'est pas une facon de jouer de plus,
   il decide si l'on conserve quelque chose. */
.accueil-compte {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-1);
}

.accueil-mot { margin: 0; font-size: 11px; opacity: .6; line-height: 1.5; text-align: center; }
/* Ce qui a echoue se voit sans crier : on peut toujours jouer en invite. */
.accueil-mot.alerte { opacity: .9; color: var(--gold); }

/* ------------------------------------------------------------------- des */
/*
 * La ceremonie qui ouvre une partie : qui prend le vent d'Est.
 *
 * Elle occupe le centre de la table - la ou l'accueil se tenait un instant plus
 * tot, la ou le vent de la manche s'affichera un instant plus tard - et n'a
 * donc pas de fond a elle : c'est le tapis qu'on voit derriere.
 */
.ceremonie {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: min(340px, 100%);
  padding: 4px;
}
.ceremonie.passable { cursor: pointer; }

.ceremonie-titre {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  opacity: .6;
}

.des-series { align-self: stretch; margin: 12px 0 0; }

.des-ligne {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 2.4em;
  align-items: center;
  gap: 10px;
  padding: 3px 8px;
  border-radius: 8px;
  font-size: 13px;
}
/* Celui que le dernier lance designe. Les series precedentes n'ont pas de
   gagnant : elles n'ont produit qu'une egalite. */
.des-ligne.gagne { background: var(--raise-2); }
.des-nom { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.des-paire { display: flex; gap: 4px; }
.des-total { font-variant-numeric: tabular-nums; font-weight: 600; text-align: right; }
.des-ligne.gagne .des-total { color: var(--gold); }

.de { width: 22px; height: 22px; }
/* L'ivoire des tuiles, et de l'encre dessus : les des sortent du meme jeu, et
   ne changent donc pas de couleur avec le tapis. */
.de-face { fill: var(--paper); stroke: var(--paper-edge); stroke-width: 1; }
.de-point { fill: #3a2f22; }

.des-egalite { margin: 10px 0 2px; font-size: 11px; opacity: .55; text-align: center; }
.des-verdict { margin: 12px 0 0; font-size: 13px; text-align: center; }

/* La jauge et le bouton, une fois le tirage lu. Meme paire qu'a la
   revendication d'une defausse : on ne l'apprend donc qu'une fois. */
.des-attente { display: flex; align-items: center; gap: 12px; margin: 16px 0 0; }
.des-ok { min-width: 76px; }

/*
 * Les lances se decouvrent l'un apres l'autre : un tirage qui tombe d'un bloc
 * ne se distingue pas d'une liste ecrite d'avance, et c'est justement ce qu'on
 * remplace. Le pas vient de `src/ui/des.js`, qui tient aussi la duree que
 * l'arbitre attend avant de distribuer.
 */
.des-ligne, .des-egalite, .des-verdict, .des-attente {
  animation: de-entre .34s ease both;
  animation-delay: calc(var(--rang, 0) * var(--par-lance, 420ms));
}
/*
 * `visibility` et pas seulement `opacity` : un element transparent reste
 * cliquable et tabulable. Le bouton « OK » est pose des le depart, et sans
 * cette ligne on pourrait le presser - ou l'atteindre au clavier - avant meme
 * d'avoir vu tomber le premier de.
 */
@keyframes de-entre {
  from { opacity: 0; visibility: hidden; transform: translateY(7px); }
  to { opacity: 1; visibility: visible; transform: none; }
}

/* ------------------------------------------------------------------ rail */
/*
 * Il ne se deroule pas, il est la.
 *
 * Le fond n'est pas un panneau mais un creux - `--well-2`, celui que portait le
 * bandeau. Le rail se pose donc sur le tapis sans introduire une seule couleur,
 * et suit les trois themes sans une regle de plus.
 */
.rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 10px;
  background: var(--well-2);
  border-right: 1px solid var(--raise-1);
  overflow: hidden auto;
}

/* Le vide elastique : la navigation en haut, l'identite tout en bas. */
.rail-bas { margin-top: auto; padding-top: 12px; }
.rail-nav, .rail-groupe { display: flex; flex-direction: column; gap: 2px; }

.rail-marque {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px 16px;
  color: inherit;
  text-decoration: none;
}
/* Une vraie tuile du sprite, a taille fixe : elle ne suit pas celles du jeu. */
.rail-marque .tile { --tile-w: 22px; flex: 0 0 auto; }

.rail-titre {
  margin: 16px 0 4px;
  padding: 0 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .45;
}

/*
 * Une entree. Lien quand elle mene a une adresse, bouton quand elle agit - mais
 * une seule apparence, parce que le selecteur porte sur la classe et non sur la
 * balise. Le texte est attenue et s'eclaircit : c'est ce qui dit qu'il repond.
 */
.rail-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--rail-item);
  padding: 0 10px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: color-mix(in srgb, var(--text) 72%, transparent);
  background: none;
  border: none;
  border-radius: 8px;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.rail-item:hover { color: var(--text); background: var(--raise-1); }
.rail-item.actif { color: var(--text); background: var(--raise-2); }
.rail-item[aria-pressed="true"] { color: var(--gold); background: var(--raise-2); }

.icone {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.rail-libelle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* L'initiale du compte prend la place de l'icone : un visage sans photo. */
.rail-initiale {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  border-radius: 999px;
  font-size: 11px;
  color: var(--on-gold);
  background: var(--gold);
}

/* ------------------------------------------------------- etat de la partie */
/*
 * Manche, mur, colline - au ras du jeu, et non dans une barre globale : c'est
 * ce que la table raconte, pas ce que le site propose. Les pastilles se taisent
 * hors partie (`controller.js`), et `:empty` fait alors disparaitre le bandeau.
 */
.partie {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px clamp(10px, 2vw, 22px) 0;
}
.partie:empty { display: none; }
/* Les commandes de partie : rangees ici sur les ecrans ou le rail est une
   barre de cinq icones, et nulle part ailleurs. */
.partie-outils { display: none; margin-left: auto; gap: 4px; }

.title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Le lien coupe : visible sans etre alarmant, il revient tout seul. */
.badge.link { background: rgba(192, 57, 43, .35); opacity: 1; }

/*
 * Plus de pilule bordee : elle se battait avec le bandeau qui la portait. Sur
 * le tapis, un creux discret suffit - et le chiffre garde sa chasse fixe, sans
 * quoi le mur qui descend fait sautiller toute la ligne.
 */
.badge {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--well-1);
  white-space: nowrap;
  opacity: .85;
}

/* --------------------------------------------------------------- boutons */
.btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--on-gold);
  background: linear-gradient(#f2e3bd, var(--gold));
  border: 1px solid #b98f45;
  border-radius: 8px;
  /* Le rythme du rail : ses entrees font 40 px, un bouton en fait 34. */
  min-height: 34px;
  padding: 8px 14px;
  cursor: pointer;
  box-shadow: 0 2px 0 #a97f38;
  transition: transform .08s, box-shadow .08s, filter .12s;
}
.btn:hover { filter: brightness(1.06); }
.btn:active { transform: translateY(2px); box-shadow: 0 0 0 #a97f38; }
.btn[disabled] { opacity: .4; cursor: default; box-shadow: none; transform: none; filter: none; }

.btn.ghost {
  color: var(--text);
  background: var(--raise-1);
  /* Sans filet : le rail donne le ton, et un bouton cercle a cote de lui fait
     du bruit. La bordure reste, transparente, pour que la taille ne bouge pas. */
  border-color: transparent;
  box-shadow: none;
}
.btn.ghost[aria-pressed="true"] { background: var(--raise-3); }
.btn.danger { background: linear-gradient(#e58374, var(--danger)); border-color: #8f2b20; color: #fff; box-shadow: 0 2px 0 #8f2b20; }

/* ----------------------------------------------------------------- table */
.table {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    ".    top    ."
    "left center right"
    ".    bottom .";
  gap: clamp(4px, 1vw, 14px);
  padding: clamp(6px, 1.2vw, 16px);
  align-items: center;
  justify-items: center;
  min-height: 0;
  overflow: hidden;
}

.seat-top { grid-area: top; }
.seat-left { grid-area: left; }
.seat-right { grid-area: right; }
.seat-bottom { grid-area: bottom; }
.center { grid-area: center; }

/* ---------------------------------------------------------------- sieges */
.seat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 6px;
  border-radius: 14px;
  min-width: 0;
  min-height: 0;
  transition: background .25s, box-shadow .25s;
}
.seat.active { background: var(--raise-1); box-shadow: inset 0 0 0 1px rgba(224, 180, 100, .5); }

.nameplate {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  white-space: nowrap;
}
.nameplate .wind {
  display: grid;
  place-items: center;
  width: 24px; height: 24px;
  border-radius: 6px;
  background: var(--paper);
  color: #1d4e89;
  font-weight: 700;
  font-size: 13px;
}
.nameplate .score { font-variant-numeric: tabular-nums; opacity: .75; }
.nameplate .level { font-size: 11px; opacity: .6; }
/* Le pari de la main appelante : visible sans crier, mais pas discret. */
.nameplate .calling {
  font-size: 10px; text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
  color: var(--gold); border: 1px solid currentColor; border-radius: 999px; padding: 1px 6px;
}
.nameplate .away {
  font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
  opacity: .6; border: 1px dashed currentColor; border-radius: 999px; padding: 1px 6px;
}
.seat.active .nameplate { font-weight: 600; }

.hand, .melds { display: flex; gap: 2px; flex-wrap: nowrap; }
.melds { gap: 8px; }
.meld { display: flex; gap: 1px; }
.stack { display: flex; flex-direction: column; align-items: center; gap: 6px; }

/* Les sieges lateraux presentent leurs tuiles de profil : la main descend en
   colonne, les combinaisons se rangent a cote. */
.seat-left .stack, .seat-right .stack { flex-direction: row; align-items: flex-start; }
.seat-left .hand, .seat-right .hand,
.seat-left .melds, .seat-right .melds,
.seat-left .meld, .seat-right .meld { flex-direction: column; }
.seat-right .stack { flex-direction: row-reverse; }

/* ---------------------------------------------------------------- tuiles */
.tile {
  position: relative;
  width: var(--tile-w);
  height: calc(var(--tile-w) * var(--tile-ratio));
  flex: 0 0 auto;
  border-radius: var(--radius);
  background: linear-gradient(180deg, #fffdf4 0%, var(--paper) 62%, var(--paper-shade) 100%);
  border: 1px solid var(--paper-edge);
  box-shadow: 0 3px 0 var(--paper-edge), 0 5px 10px var(--tile-shadow);
  overflow: hidden;
  transition: transform .16s ease, box-shadow .16s ease, filter .16s;
}
.tile svg { width: 100%; height: 100%; display: block; }
/* Dos en bambou : le degrade miel vient d'ici, le fil du bois du sprite. */
.tile.back {
  background: linear-gradient(178deg, #f4dcb8 0%, #e8c79a 48%, #d3a172 100%);
  border-color: #a87c4a;
  box-shadow: 0 3px 0 var(--wood-edge), 0 5px 10px var(--tile-shadow);
}

.tile.small { --tile-w: clamp(20px, calc(var(--jeu) * .022), 30px); }
.tile.tiny { --tile-w: clamp(15px, calc(var(--jeu) * .016), 22px); }

/* Tuile vue de profil, pour les joueurs de gauche et de droite. */
.tile.side { width: calc(var(--tile-w) * var(--tile-ratio)); height: var(--tile-w); }
.tile.side svg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--tile-w);
  height: calc(var(--tile-w) * var(--tile-ratio));
  transform: translate(-50%, -50%) rotate(90deg);
}
.seat-left .tile.side svg { transform: translate(-50%, -50%) rotate(-90deg); }
.tile.side, .tile.side.back { box-shadow: 3px 0 0 var(--paper-edge), 5px 0 10px var(--tile-shadow); }
.seat-left .tile.side, .seat-left .tile.side.back { box-shadow: -3px 0 0 var(--paper-edge), -5px 0 10px var(--tile-shadow); }
.tile.side.back { box-shadow: 3px 0 0 var(--wood-edge), 5px 0 10px var(--tile-shadow); }
.seat-left .tile.side.back { box-shadow: -3px 0 0 var(--wood-edge), -5px 0 10px var(--tile-shadow); }

/* Tuiles jouables du joueur humain. */
.hand .tile.playable { cursor: pointer; }
.hand .tile.playable:hover { transform: translateY(-9px); box-shadow: 0 10px 0 var(--paper-edge), 0 14px 18px var(--tile-shadow-strong); }
.tile.drawn { box-shadow: 0 3px 0 var(--paper-edge), 0 5px 10px var(--tile-shadow), 0 0 0 2px var(--gold); }
.tile.dim { filter: brightness(.72) saturate(.7); }
/*
 * La tuile que le guide conseille, en partie guidée.
 *
 * Un halo, et non un déplacement : la main se soulève déjà au survol, et deux
 * tuiles qui bougent pour deux raisons différentes ne se lisent plus. Le vert
 * de `--plus` plutôt que l'or : l'or dit déjà la tuile piochée et la défausse
 * en jeu, et la tuile conseillée est souvent l'une des deux.
 */
.hand .tile.conseil {
  box-shadow:
    0 3px 0 var(--paper-edge),
    0 5px 10px var(--tile-shadow),
    0 0 0 3px var(--plus),
    /* Le halo, et non l'anneau seul : sur le tapis vert, un liseré vert se
       distingue mal du fond a un metre de l'ecran - constaté en regardant, pas
       en relisant. */
    0 0 14px 3px var(--plus);
}

@keyframes land { from { transform: translateY(-14px) scale(.9); opacity: 0; } }
.tile.fresh { animation: land .22s ease-out; }

@keyframes pulse { 50% { box-shadow: 0 3px 0 var(--paper-edge), 0 0 0 4px rgba(224, 180, 100, .85); } }
.tile.current { animation: pulse 1.1s ease-in-out infinite; }

/* ----------------------------------------------------------------- centre */
.center {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    ".    ptop   ."
    "pleft info pright"
    ".    pbot   .";
  gap: 8px;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 8px;
  border-radius: 18px;
  background: var(--well-1);
  box-shadow: inset 0 0 0 1px var(--raise-1), inset 0 12px 40px var(--well-3);
  place-items: center;
}
.pool-top { grid-area: ptop; }
.pool-left { grid-area: pleft; }
.pool-right { grid-area: pright; }
.pool-bottom { grid-area: pbot; }

.pool { display: grid; gap: 2px; grid-auto-flow: row; }
.pool-top, .pool-bottom { grid-template-columns: repeat(8, auto); }
.pool-left, .pool-right { grid-template-rows: repeat(6, auto); grid-auto-flow: column; }

.center-info {
  grid-area: info;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}
.center-info .caption { font-size: 12px; opacity: .7; }
.center-info .prevailing { font-size: 15px; font-weight: 600; opacity: .32; letter-spacing: .14em; text-transform: uppercase; }

/* --------------------------------------------------------------- le recit */
/*
 * Ce qui vient de se passer, en une ligne. Elle est aussi la region annoncee
 * aux lecteurs d'ecran : ce qui s'y ecrit se dit. Vide, elle ne prend pas de
 * place - une table sans nouvelle n'a pas de bandeau vide a montrer.
 */
.journal {
  margin: 0;
  padding: 4px clamp(10px, 2vw, 22px) 0;
  text-align: center;
  font-size: 12px;
  opacity: .7;
  min-height: 0;
}
.journal:empty { display: none; }

/* ---------------------------------------------------------------- le guide */
/*
 * La partie guidée parle ici, et nulle part ailleurs.
 *
 * Pas dans le récit, qui est une région `aria-live` et n'annoncerait plus rien
 * d'utile avec deux phrases de plus par tour ; pas dans la barre d'action, que
 * le contrôleur reconstruit à chaque question et qui porte des boutons.
 *
 * Deux phrases au plus : la leçon explique une règle du jeu, le conseil dit ce
 * qu'on en fait maintenant. Masqué, il ne prend aucune place - une partie non
 * guidée n'a pas de bandeau creux à montrer, comme le récit juste au-dessus.
 */
.guide {
  margin: 6px auto 0;
  max-width: 64ch;
  padding: 8px 14px;
  border-left: 3px solid var(--plus);
  border-radius: 0 8px 8px 0;
  background: var(--well-2);
  font-size: 13px;
  line-height: 1.45;
}
.guide p { margin: 0; }
/* La leçon s'efface un peu devant le conseil : elle explique, elle ne demande
   rien. Le filet entre les deux évite qu'elles se lisent comme un paragraphe. */
.guide .lecon { opacity: .8; }
.guide .lecon + .conseil {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--raise-1);
}
.guide .conseil { font-weight: 600; }

/* ------------------------------------------------------------ barre d'action */
.actionbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 62px;
  padding: 10px clamp(10px, 2vw, 22px);
  background: var(--well-2);
  border-top: 1px solid var(--raise-1);
  flex-wrap: wrap;
}
.actionbar .prompt { font-size: 13px; opacity: .8; margin-right: 4px; }
/* Vide, elle ne prend pas de place - comme le journal juste au-dessus. Une
   table sans partie n'a pas de bandeau creux a montrer, et pendant une manche
   la barre porte toujours au moins « untel reflechit… ». */
.actionbar:empty { display: none; }

.timer {
  position: relative;
  width: 120px; height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: var(--raise-3);
}
.timer > i { position: absolute; inset: 0; background: var(--gold); transform-origin: left; }

.claim-preview { display: flex; gap: 1px; margin-left: 6px; }

/* Les deux etageres l'une sous l'autre, et rien qui les additionne.
   Elles etaient cote a cote quand chacune n'alignait qu'une liste de chiffres ;
   depuis que tout tient dans un tableau, une demi-largeur ne suffit plus a ses
   huit colonnes. */
.carnet-stats { margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--line-1); }
.carnet-colonne + .carnet-colonne { margin-top: 16px; }
.carnet-colonne h3 { margin: 0 0 6px; font-size: 12px; font-weight: 600; opacity: .8; }

/* Ce qu'on a joue, bareme par bareme. En tableau et non en liste : ces chiffres
   ne s'additionnent pas - les trois reglements ne paient pas dans la meme
   monnaie - et c'est la moyenne par manche qui se compare d'une ligne a
   l'autre, donc il lui faut sa colonne. */
.carnet-grille { width: 100%; border-collapse: collapse; font-size: 12px; }
.carnet-grille th {
  text-align: left;
  font-weight: 400;
  opacity: .45;
  padding: 0 6px 3px 0;
  border-bottom: 1px solid var(--line-1);
}
.carnet-grille td { padding: 4px 6px 4px 0; }
.carnet-grille td.num, .carnet-grille th:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }

/* Ce qu'on vaut contre chaque niveau. En jauges et non en colonnes : les deux
   seuls chiffres qui se comparent d'un niveau a l'autre sont des proportions -
   un total de points melangerait les baremes - et une proportion se lit mieux
   en longueur qu'en chiffres.

   Le trait a un quart de la jauge est le repere : a quatre joueurs, le hasard
   en gagne une sur quatre. Sans lui, « 22 % » ne dit rien. */
.niveaux { margin-top: 12px; }
.niveaux h4 { margin: 0 0 6px; font-size: 12px; font-weight: 400; opacity: .45; }
.niveaux-liste { list-style: none; margin: 0; padding: 0; font-size: 12px; }
.niveaux-liste li {
  display: grid;
  grid-template-columns: 5.5em minmax(60px, 1fr) 2.8em auto;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
}
.niveaux-taux { text-align: right; font-variant-numeric: tabular-nums; }
.niveaux-detail { opacity: .55; }
.jauge {
  position: relative;
  height: 8px;
  border-radius: 4px;
  background: var(--well-2);
  overflow: hidden;
}
.jauge > i { display: block; height: 100%; background: var(--gold); border-radius: 4px; }
/* Un quart, et pas un pixel de plus : c'est ce que gagne un joueur au hasard. */
.jauge > .repere {
  position: absolute;
  top: 0; bottom: 0; left: 25%;
  width: 1px;
  background: var(--text);
  opacity: .45;
}

/* Sous les jauges, une grille a quatre colonnes ne tient plus : le nom passe
   au-dessus, et les chiffres suivent la jauge. */
@media (max-width: 560px) {
  .niveaux-liste li { grid-template-columns: minmax(60px, 1fr) 2.8em; }
  .niveaux-nom { grid-column: 1 / -1; }
  .niveaux-detail { grid-column: 1 / -1; }
}

/* --------------------------------------------------------------- problèmes */
/* Où l'on en est, sur la page du joueur : la jauge des niveaux, réemployée. */
.joueur-problemes { margin: 18px 0; }
.joueur-progres { display: flex; align-items: center; gap: 12px; max-width: 34rem; margin-bottom: 6px; }
.joueur-progres-nom { font-size: 12px; opacity: .8; min-width: 11rem; }
.joueur-progres .jauge { flex: 1; }
.joueur-progres-compte { font-size: 12px; opacity: .7; white-space: nowrap; }

/*
 * L'écran des problèmes emprunte la disposition du tutoriel, et volontairement :
 * ce qu'on apprend là doit se reconnaître ici. Seuls les boutons diffèrent, et
 * c'est tout le sujet de la page, donc c'est tout ce qui a son propre style.
 */
.probleme-table {
  margin: 14px 0;
  padding: 14px;
  border-radius: 14px;
  background: var(--well-1);
  box-shadow: inset 0 0 0 1px var(--raise-1);
  max-width: 46rem;
  margin-left: auto;
  margin-right: auto;
}
.probleme-centre {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 0 18px;
  border-bottom: 1px solid var(--line-1);
  margin-bottom: 16px;
}
.probleme-table .caption { font-size: 12px; opacity: .6; text-align: center; }
.probleme-siege { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.probleme-pose { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.probleme-main { display: flex; flex-wrap: wrap; row-gap: 6px; justify-content: center; }

/* Le numéro et le barème : le second ne sert que pour le MCR, mais l'afficher
   seulement quand il compte reviendrait à donner la réponse. */
.probleme-barre { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.probleme-compte { font-size: 12px; opacity: .55; }
.probleme-bareme {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--raise-1);
  opacity: .8;
}

/*
 * Les cinq boutons, toujours les cinq, et leur état une fois essayés.
 *
 * Un bouton refusé garde sa marque : la réponse d'un problème est l'ensemble de
 * ce qui est possible, et on doit pouvoir le relire d'un coup d'œil sans
 * retrouver dans quel ordre on a cliqué.
 */
.probleme-commandes { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 14px 0; }
.probleme-commandes .btn.trouve {
  box-shadow: 0 0 0 2px var(--plus);
}
/* Légal, mais on laisse filer quelque chose : ni juste ni faux. */
.probleme-commandes .btn.tiede { opacity: .75; box-shadow: 0 0 0 1px var(--line-1); }
.probleme-commandes .btn.refuse {
  opacity: .45;
  text-decoration: line-through;
  box-shadow: 0 0 0 1px var(--danger);
}

.probleme-reponse { margin-top: 6px; }
.probleme-refuse, .probleme-resolu, .probleme-encore {
  padding: 12px 14px;
  border-radius: 12px;
  border-left: 3px solid var(--danger);
  background: var(--well-1);
  margin-bottom: 12px;
}
.probleme-resolu { border-left-color: var(--plus); }
/* Juste, mais pas fini : ni le rouge du refus, ni le vert de la fin. Il reste
   quelque chose à trouver, et la couleur doit le dire avant le texte. */
.probleme-encore { border-left-color: var(--gold); }
.probleme-refuse .rule, .probleme-resolu .rule, .probleme-encore .rule { margin: 6px 0 0; }
.probleme-suite { margin-top: 12px; }
.probleme-reste { font-size: 12px; opacity: .6; margin: 0; }

/* ------------------------------------------------------------ pied de page */
/*
 * Ce que le site dit de lui-même, tout en bas de la colonne de droite.
 *
 * Il s'efface dès qu'une manche tourne : `Controller.render` pose le `hidden`,
 * donc il ne coûte rien à la table, qui est déjà serrée en hauteur.
 *
 * Une seule rangée, et elle se replie sur deux quand la place manque : le
 * copyright d'abord, les liens ensuite, parce que c'est l'ordre où on les
 * cherche.
 */
.pied {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 18px;
  padding: 10px clamp(10px, 2vw, 22px);
  border-top: 1px solid var(--line-1);
  font-size: 12px;
}
.pied-droits { margin: 0; opacity: .45; }
.pied-liens { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; }
/*
 * Ce qui agit reste un bouton, ce qui mène ailleurs est un lien, la règle du
 * rail. Les deux se ressemblent ici, donc les deux sélecteurs sont écrits
 * ensemble plutôt que dupliqués.
 */
.pied-lien {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: inherit;
  font: inherit;
  opacity: .7;
  cursor: pointer;
  text-decoration: none;
}
.pied-lien:hover { opacity: 1; background: var(--raise-1); }
.pied-lien .icone { width: 16px; height: 16px; }

/* ---------------------------------------------------------------- la visite */
/*
 * La visite guidée : un projecteur, une bulle, et rien d'autre.
 *
 * Le trou dans le voile n'est pas un masque SVG ni un second calque : c'est une
 * boîte vide dont l'ombre portée couvre tout l'écran, `0 0 0 9999px`. C'est la
 * façon la plus simple de percer un voile, et surtout la seule qui suive la
 * cible au pixel près quand la partie bouge derrière.
 *
 * `pointer-events: none` sur le voile : la partie continue de tourner et reste
 * cliquable, ce qui est le but. On montre l'écran vivant, pas une capture.
 * Seule la bulle reprend les clics, pour ses boutons.
 */
.visite-voile { position: fixed; inset: 0; z-index: 60; pointer-events: none; }
.visite-trou {
  position: fixed;
  border-radius: 14px;
  box-shadow: 0 0 0 9999px var(--backdrop), 0 0 0 2px var(--gold);
  transition: top .2s, left .2s, width .2s, height .2s;
}
.visite-bulle {
  position: fixed;
  width: min(30rem, calc(100vw - 24px));
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--panel);
  box-shadow: var(--shadow), 0 0 0 1px var(--line-2);
  pointer-events: auto;
}
.visite-bulle h3 { margin: 2px 0 6px; font-size: 15px; }
.visite-bulle .rule { margin: 0; }
.visite-compte { margin: 0; font-size: 11px; opacity: .45; }
.visite-commandes { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/*
 * La barre d'annonce montrée en exemple.
 *
 * Elle reprend `.actionbar` et `.btn` pour être dessinée exactement comme la
 * vraie, c'est tout l'intérêt, on doit la reconnaître ensuite. Mais elle est
 * inerte : `pointer-events: none` et le curseur par défaut, sinon elle invite
 * à un clic qui ne mènerait nulle part.
 */
.visite-illustration { margin-top: 12px; }
.visite-exemple {
  min-height: 0;
  padding: 10px;
  border-top: none;
  border-radius: 10px;
  gap: 6px;
  pointer-events: none;
}
.visite-exemple .btn { cursor: default; }
.visite-exemple .timer { width: 60px; }
.visite-legende { margin: 6px 0 0; font-size: 11px; opacity: .5; text-align: center; }

/* Le bouton de l'accueil : discret, il ne doit pas concurrencer « Nouvelle
   partie » : on ne fait la visite qu'une fois. */
.accueil-action.visite { margin-top: 14px; }

/* -------------------------------------------------------------- apprendre */
/*
 * Le tutoriel : une position posée, une chose à faire, une explication.
 *
 * La main reprend `.hand` de la table, mêmes tuiles, même taille, même
 * survol : ce qu'on apprend ici doit ressembler trait pour trait à ce qu'on
 * retrouvera en jouant, sinon la leçon ne se transporte pas.
 */
.tuto-table {
  margin: 14px 0;
  padding: 14px;
  border-radius: 14px;
  background: var(--well-1);
  box-shadow: inset 0 0 0 1px var(--raise-1);
}
/*
 * Le centre, puis votre siège, la disposition de la table, et pas une rangée
 * de tuiles mises bout à bout. Ce qui arrive vient d'ailleurs et doit se voir
 * venir d'ailleurs, sinon la leçon ne se transporte pas au moment de jouer.
 */
.tuto-centre {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 0 18px;
  border-bottom: 1px solid var(--line-1);
  margin-bottom: 16px;
  min-height: 40px;
}
.tuto-table .caption { font-size: 12px; opacity: .6; text-align: center; }
/* Celui qui veut la même tuile : c'est lui qui rend la règle de priorité
   concrète, et sans lui la leçon n'a rien à montrer. */
.tuto-concurrent { margin: 0; font-size: 12px; opacity: .75; text-align: center; }
.tuto-siege { display: flex; flex-direction: column; align-items: center; gap: 10px; }
/* Ce qui est posé devant soi : les combinaisons, puis les fleurs. Ni l'une ni
   l'autre ne se jettent. */
.tuto-devant { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.tuto-fleurs { opacity: .85; }
/* Ce que le coup vient de poser : on doit le voir arriver. */
.tuto-devant .meld.neuf {
  border-radius: 8px;
  box-shadow: 0 0 0 2px var(--plus), 0 0 12px 2px var(--plus);
}
.tuto-main { flex-wrap: wrap; row-gap: 6px; justify-content: center; }
/* La tuile piochée sort du rang, comme sur la table. */
.tuto-espace { display: inline-block; width: 14px; }

/* La progression, et le retour en arrière juste à côté : on relit une leçon là
   où l'on lit son numéro. */
.tuto-progression {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  opacity: .45;
  margin: 18px 0 2px;
}
.tuto-progression .btn { font-size: 11px; padding: 2px 8px; }

/*
 * Le cadre prend sa mesure : une main de cinq cents pixels au milieu de douze
 * cents donnait un grand vide qui n'était pas une intention.
 */
.tuto-table { max-width: 46rem; margin-left: auto; margin-right: auto; }

/*
 * Ce qu'on peut cliquer se voit sans avoir à le survoler.
 *
 * Sur une leçon de tour, rien ne distinguait une tuile jouable d'une tuile
 * décorative : il fallait promener la souris pour le découvrir. Le liseré est
 * discret, il ne doit pas concurrencer le halo du guide, qui lui désigne *une*
 * tuile en particulier.
 */
.tuto-main .tile.playable {
  box-shadow: 0 3px 0 var(--paper-edge), 0 5px 10px var(--tile-shadow), 0 0 0 1px var(--raise-3);
}
.tuto-commandes { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.tuto-commandes .note { margin: 0; }

/*
 * Le verdict, puis l'explication. Vert quand c'est juste, sans être une
 * récompense criarde : on marque le bord, pas le fond.
 */
.tuto-verdict {
  margin-top: 16px;
  padding: 12px 14px;
  border-left: 3px solid var(--minus);
  border-radius: 0 10px 10px 0;
  background: var(--well-2);
}
.tuto-verdict.juste { border-left-color: var(--plus); }
/* Une lecture n'est ni juste ni fausse : le rouge du verdict y lisait comme un
   reproche adresse a quelqu'un qui n'avait rien fait. */
.tuto-verdict.lecture { border-left-color: var(--line-3); }
.tuto-dit { margin: 0 0 8px; font-weight: 600; }
.tuto-verdict .rule { margin: 0; }

/* ------------------------------------------------------------- le classement */
/*
 * Il reprend la grille des statistiques : c'est un tableau de chiffres de la
 * même page, et lui donner une mise en forme à lui aurait fait deux tableaux
 * qui se ressemblent sans se ressembler tout à fait.
 *
 * Sa propre ligne est marquée comme la ligne gagnante d'un décompte : `.winner`
 * veut dire « celle-ci vous concerne », et porte désormais sur les deux
 * grilles - elle n'était écrite que pour `.summary-grid`.
 */
.classement { margin: 18px 0; padding-top: 14px; border-top: 1px solid var(--line-1); }
.classement h3 { margin: 0 0 4px; font-size: 12px; font-weight: 600; opacity: .8; }
.classement .note { margin: 4px 0 8px; }
.classement .rule { margin: 10px 0 4px; }

/*
 * Quatre colonnes étirées sur douze cents pixels ne se lisent pas : l'œil perd
 * la ligne entre le rang et la note. Le tableau prend donc sa mesure, et c'est
 * la colonne des noms qui absorbe le mou - les trois autres portent des nombres
 * courts, et rien ne gagne à les écarter.
 */
.classement .carnet-grille { max-width: 34rem; }
/* Le rang est un nombre, et la première colonne de `.carnet-grille` est alignée
   à gauche par défaut : ici c'est l'exception, pas la règle générale. */
.classement .carnet-grille th:first-child,
.classement .carnet-grille td:first-child { text-align: right; padding-right: 14px; }
/* Le nom, lui, se cale à gauche - en-tête compris. `th:not(:first-child)` de la
   grille les aligne tous à droite, ce qui convient aux nombres et pas à un nom :
   l'en-tête « Joueur » se retrouvait à l'opposé de ses propres valeurs. */
.classement .carnet-grille th:nth-child(2),
.classement .carnet-grille td:nth-child(2) { text-align: left; width: 100%; }

/* --------------------------------------------------------------- le deroule */
/*
 * Comment la partie s'est jouée : une courbe et deux phrases, avant le détail
 * manche par manche.
 *
 * Les quatre couleurs sont posées ici et non sur `:root` : elles ne servent
 * qu'à ce graphique, et les nommer globalement inviterait à s'en resservir
 * ailleurs pour dire tout autre chose. Les quatre jetons existent dans les
 * trois tapis, donc la courbe suit le thème sans une règle de plus.
 */
.deroule { margin: 14px 0 18px; }
.deroule h4 { margin: 0 0 8px; font-size: 12px; font-weight: 400; opacity: .45; }
.deroule-faits { margin: 8px 0 0; padding-left: 18px; font-size: 13px; line-height: 1.5; }

.courbe-cadre {
  /*
   * Le cadre prend sa mesure, et le SVG remplit le cadre.
   *
   * Borner la *hauteur* du SVG - ce qui était la première idée - laissait le
   * dessin centré au milieu d'une boîte pleine largeur, avec deux marges vides
   * de trois cents pixels : un SVG garde son rapport, et c'est la boîte qui
   * doit être à la bonne taille, pas le dessin qui doit s'y recroqueviller.
   */
  max-width: 40rem;
  --serie-0: var(--gold);
  --serie-1: var(--plus);
  --serie-2: var(--minus);
  --serie-3: var(--text);
}
.courbe { display: block; width: 100%; height: auto; overflow: visible; }
/* Le zéro sépare ce qu'on doit de ce qu'on nous doit : un trait, pas une
   graduation parmi d'autres. */
.courbe-zero { stroke: currentColor; stroke-width: 1; stroke-dasharray: 3 3; opacity: .28; }
.courbe-ligne {
  fill: none;
  stroke-width: 1.3;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: .62;
  /* Le SVG s'étire à la largeur disponible : sans cela, un trait d'un pixel en
     devient trois sur un écran large. */
  vector-effect: non-scaling-stroke;
}
/* Sa propre trajectoire est ce qu'on vient lire ; les trois autres sont le
   décor contre lequel elle se lit. */
.courbe-ligne.moi { stroke-width: 2.2; opacity: 1; }
.courbe-axe { fill: currentColor; opacity: .4; font-size: 9px; }

.courbe-ligne.s0, .courbe-legende .s0 i { stroke: var(--serie-0); background: var(--serie-0); }
.courbe-ligne.s1, .courbe-legende .s1 i { stroke: var(--serie-1); background: var(--serie-1); }
.courbe-ligne.s2, .courbe-legende .s2 i { stroke: var(--serie-2); background: var(--serie-2); }
.courbe-ligne.s3, .courbe-legende .s3 i { stroke: var(--serie-3); background: var(--serie-3); }
/* `background` n'a pas de sens sur une polyligne, et `stroke` n'en a pas sur un
   carré de légende : chaque sélecteur n'écoute que celle qui le concerne. */
.courbe-ligne { background: none; }

.courbe-legende {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  font-size: 11px;
  opacity: .7;
}
.courbe-legende li { display: flex; align-items: center; gap: 5px; }
.courbe-legende i { width: 12px; height: 2px; border-radius: 1px; }

/* ----------------------------------------------------------------- carnet */
.carnet-etageres { display: flex; gap: 8px; margin-bottom: 12px; }
.btn.ghost.actif { background: var(--raise-3); color: var(--gold); }

/* La variante d'une partie. Elle s'efface un peu - c'est un qualificatif, pas
   un resultat - et se cale a gauche comme la date : `.summary-grid` aligne a
   droite tout sauf la premiere colonne, ce qui va aux nombres et non a un nom.
   A droite, elle se collait aux adversaires et paraissait leur appartenir. */
/* La specificite compte ici : `.summary-grid td` pese (0,1,1), et une classe
   seule perdrait. Les deux selecteurs sont donc qualifies par la grille. */
.summary-grid th.col-variante, .summary-grid td.carnet-variante { text-align: left; }
.summary-grid td.carnet-variante { opacity: .7; white-space: nowrap; }

/* Une ligne de partie s'ouvre : elle doit le montrer, et se prendre au clavier. */
.carnet-ligne { cursor: pointer; }
.carnet-ligne:hover td { background: var(--raise-1); }
.carnet-ligne:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

.carnet-coups {
  margin: 8px 0 0;
  padding-left: 20px;
  font-size: 12px;
  line-height: 1.7;
  max-height: 40dvh;
  overflow: auto;
}
.carnet-coups li { opacity: .8; }
/* L'horodatage aligne : on lit une colonne, pas une phrase qui commence tard. */
.carnet-temps {
  display: inline-block;
  min-width: 62px;
  opacity: .45;
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- defausses */
/* Panneau compact, centre au-dessus de la barre d'action : il ne couvre que le
   centre de la table, jamais un joueur.

   Centre sur *la table* et non sur le viewport : `position: fixed` ignore le
   rail, et un `left: 50%` le poserait de travers, decale d'une demi-largeur de
   rail vers la gauche du jeu. */
.tracker {
  position: fixed;
  left: calc(var(--rail) + var(--jeu) / 2);
  top: 50%;
  transform: translate(-50%, -50%);
  width: max-content;
  max-width: calc(var(--jeu) - 24px);
  max-height: 58dvh;
  overflow: auto;
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--overlay);
  border: 1px solid var(--line-1);
  box-shadow: var(--shadow);
  z-index: 5;
}
/* Le panneau des dernieres actions partage le cadre des defausses : c'est le
   meme geste - on l'ouvre, on lit, on le referme - et deux cadres differents
   pour deux panneaux qui s'excluent seraient deux choses a apprendre. */
.tracker.actions ul { list-style: none; margin: 0; padding: 0; max-width: 42ch; }
.tracker.actions li {
  font-size: 12px;
  line-height: 1.5;
  padding: 4px 0 4px 10px;
  border-left: 2px solid transparent;
  opacity: .75;
}
/* La plus recente est en tete, et se lit en premier. */
.tracker.actions li:first-child { opacity: 1; }
/* Ce qui vous concerne : c'est pour cela que le panneau existe. */
.tracker.actions li.action-refus {
  opacity: 1;
  border-left-color: var(--gold);
  padding-left: 8px;
}
.tracker.actions li.muted { opacity: .5; border-left-color: transparent; }

.tracker h2 { margin: 0 0 3px; font-size: 13px; }
.tracker .muted { margin: 0 0 10px; font-size: 11px; opacity: .6; max-width: 42ch; }
.track-row { display: flex; align-items: center; gap: 3px; margin-bottom: 5px; }
.track-label { width: 76px; flex: 0 0 auto; font-size: 11px; opacity: .65; }
.track-cell { position: relative; --tile-w: 22px; }
.track-cell .count {
  position: absolute;
  right: -2px; bottom: -3px;
  min-width: 13px;
  font-size: 9px;
  font-weight: 700;
  line-height: 13px;
  text-align: center;
  border-radius: 7px;
  color: var(--count-fg);
  background: var(--count-bg);
  box-shadow: 0 0 0 1px var(--line-3);
}
/* Une seule tuile encore invisible : l'attente devient fragile. Plus aucune :
   elle est morte, il faut casser la combinaison qui l'attend. */
.track-cell[data-left="1"] .count { background: var(--gold); color: var(--on-gold); }
.track-cell[data-left="0"] .count { background: var(--danger); }
.track-cell[data-left="0"] .tile { filter: grayscale(1) brightness(.55); }

/* --------------------------------------------------------------- panneaux */
.sheet {
  border: none;
  border-radius: 16px;
  padding: 22px 24px;
  max-width: min(560px, calc(100vw - 32px));
  color: var(--text);
  background: var(--panel);
  box-shadow: var(--shadow);
}
/*
 * La croix de fermeture, en haut a droite du panneau.
 *
 * `float` et non `position: absolute` : le titre qui la suit doit lui laisser
 * la place plutot que passer dessous, et un panneau qui defile - le
 * recapitulatif de fin de manche en est un - emporterait une croix absolue hors
 * de vue au premier coup de molette.
 */
.sheet-croix {
  float: right;
  margin: -6px -8px 0 8px;
  width: 30px;
  height: 30px;
  font: 20px/1 ui-sans-serif, system-ui, sans-serif;
  color: inherit;
  background: none;
  border: none;
  border-radius: 8px;
  opacity: .5;
  cursor: pointer;
}
.sheet-croix:hover { opacity: 1; background: var(--raise-2); }
.sheet-croix:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.sheet.wide { max-width: min(760px, calc(100vw - 32px)); }
.sheet.narrow { max-width: min(380px, calc(100vw - 32px)); }
.sheet.scrollable { max-height: 84dvh; overflow: auto; }
:is(.sheet, .feuille) h3 { margin: 22px 0 8px; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; opacity: .65; }
:is(.sheet, .feuille) h3:first-of-type { margin-top: 16px; }

/* Panneau des regles : une combinaison se comprend mieux montree que decrite. */
.rule-example { display: flex; align-items: center; gap: 12px; margin: 8px 0; }
.rule-tiles { display: flex; gap: 2px; flex: 0 0 auto; flex-wrap: wrap; max-width: 46%; }
.rule-legend { display: flex; flex-direction: column; gap: 1px; font-size: 12px; min-width: 0; }
.rule-legend strong { font-weight: 600; }
.rule-legend > span { opacity: .6; }
.rule-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }

/* Nom courant de la combinaison : pung, kong, chow. */
.rule-term {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--raise-2);
  border: 1px solid var(--line-1);
  opacity: .75;
  white-space: nowrap;
}
.rule-pair { display: flex; flex-direction: column; gap: 1px; }
.rule-pair strong { font-weight: 600; }
.rule-grid td .limit-note { opacity: .55; }
.rule-grid.text td { text-align: left; }
.rule-grid.text td:first-child { white-space: nowrap; font-weight: 600; }
.rule-grid.text th:not(:first-child) { text-align: left; }
.rule-grid { width: 100%; border-collapse: collapse; font-size: 12px; margin: 6px 0 4px; }
.rule-grid th, .rule-grid td { padding: 5px 8px; text-align: left; border-bottom: 1px solid var(--raise-2); }
.rule-grid th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; opacity: .55; font-weight: 600; }
.rule-grid th:not(:first-child), .rule-grid td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rule-grid tr:last-child td { border-bottom: none; }
/* Pas de flou : en fin de manche les mains sont decouvertes, et c'est
   justement ce qu'on veut pouvoir lire derriere le recapitulatif. */
.sheet::backdrop { background: var(--backdrop); }
#summary::backdrop { background: var(--backdrop-soft); }
/* Le titre d'un panneau seulement : celui d'une page est plus grand, et se
   declare avec les pages - a specificite egale, c'est l'ordre qui tranche, et
   cette section-ci vient apres. */
.sheet h2 { margin: 0 0 14px; font-size: 17px; }
:is(.sheet, .feuille) .field { display: block; margin-bottom: 14px; font-size: 13px; }
:is(.sheet, .feuille) .field > span { display: block; margin-bottom: 5px; opacity: .8; }
:is(.sheet, .feuille) .field.row { display: flex; align-items: center; gap: 9px; }
:is(.sheet, .feuille) .field.row > span { margin: 0; }
:is(.sheet, .feuille) em { font-style: normal; opacity: .55; }
:is(.sheet, .feuille) input[type="text"], :is(.sheet, .feuille) select {
  font: inherit; width: 100%; padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--line-2); background: var(--field-bg); color: inherit;
}
:is(.sheet, .feuille) input[type="range"] { width: 100%; }
:is(.sheet, .feuille) .field > span.scale { display: flex; justify-content: space-between; margin: 2px 0 0; }
:is(.sheet, .feuille) .scale em { font-size: 11px; opacity: .5; }
/* Une jauge qui n'a pas deux extremes mais une valeur : elle se lit a droite. */
:is(.sheet, .feuille) .field > span.scale.value { justify-content: flex-end; }
:is(.sheet, .feuille) fieldset { border: 1px solid var(--line-1); border-radius: 10px; padding: 10px 12px; }
:is(.sheet, .feuille) legend { padding: 0 6px; opacity: .8; }
:is(.sheet, .feuille) .note { font-size: 11px; opacity: .55; margin: 14px 0 0; }
/* Une precision sous un champ, et non a cote : elle explique ce champ-la. */
:is(.sheet, .feuille) .field em.hint { display: block; margin-top: 5px; font-size: 11px; line-height: 1.45; }

/* Liste des grands jeux, depliable depuis les options. */
:is(.sheet, .feuille) .limits { margin: 6px 0 0 27px; }
:is(.sheet, .feuille) .limits summary { font-size: 12px; opacity: .6; cursor: pointer; }
:is(.sheet, .feuille) .limits summary:hover { opacity: .85; }
:is(.sheet, .feuille) .limits ul { list-style: none; margin: 8px 0 0; padding: 0; }
:is(.sheet, .feuille) .limits li { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; margin-bottom: 8px; font-size: 12px; }
:is(.sheet, .feuille) .limits strong { font-weight: 600; }
.limit-points { font-variant-numeric: tabular-nums; color: var(--gold); white-space: nowrap; }
.limit-note { grid-column: 1 / -1; opacity: .55; }
:is(.sheet, .feuille) menu { display: flex; justify-content: flex-end; gap: 8px; margin: 18px 0 0; padding: 0; }
.level-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 6px 0; }
.level-row select { width: 150px; }

/* ------------------------------------------------------- fin de manche */
.summary-grid { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 8px; }
.summary-grid th, .summary-grid td { padding: 6px 8px; text-align: right; border-bottom: 1px solid var(--raise-2); }
.summary-grid th:first-child, .summary-grid td:first-child { text-align: left; }
/* « Cette ligne vous concerne » : la ligne gagnante d'un décompte, et la vôtre
   au classement. Les deux grilles la portent - la règle était écrite pour la
   seule `.summary-grid`, et une classe posée sur l'autre ne faisait rien du
   tout, en silence. */
.summary-grid tr.winner, .carnet-grille tr.winner { color: var(--gold); font-weight: 600; }
.summary-grid td.delta.plus { color: var(--plus); }
.summary-grid td.delta.minus { color: var(--minus); }
/* Le detail du calcul, replie par defaut : le tableau dit qui gagne et
   combien, le detail dit pourquoi, et on ne le deplie que si on veut savoir. */
.fold { margin: 10px 0 0; }
.fold > summary { font-size: 12px; opacity: .6; cursor: pointer; padding: 2px 0; }
.fold > summary:hover { opacity: .85; }
.fold[open] > summary { margin-bottom: 6px; }
.fold > :last-child { margin-bottom: 0; }

.breakdown { font-size: 12px; opacity: .8; margin: 0 0 14px; padding-left: 18px; }
.breakdown li { margin: 1px 0; }
.breakdown li.total { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line-1); font-weight: 600; opacity: 1; }

/* Explication du reglement de la manche. */
/* Les onglets de la page des regles : une variante par onglet.
   On la lit pour choisir, donc avant d'avoir choisi - montrer la seule variante
   jouee serait montrer la seule dont on n'a pas besoin de lire les regles. */
.onglets {
  display: flex;
  gap: 4px;
  margin: 0 0 18px;
  border-bottom: 1px solid var(--line-1);
}
.onglet {
  appearance: none;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 13px;
  padding: 8px 14px;
  cursor: pointer;
  opacity: .55;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.onglet:hover { opacity: .85; }
.onglet.actif { opacity: 1; font-weight: 600; border-bottom-color: var(--gold); }
.onglet:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; border-radius: 4px 4px 0 0; }

/* Un palier de figures du MCR : douze blocs de 88 a 1 point, qui se suivent.
   Sans respiration entre eux, la liste des quatre-vingt-une se lit comme un
   seul bloc et l'on ne retrouve plus la valeur qu'on cherchait. */
.fan-palier { margin: 0 0 22px; }
.fan-palier .rule-title { margin-bottom: 8px; }

/* Le groupe de reglages d'une variante, dans les options.
   Les cases s'empilent : la regle `.field.row` du panneau ne vaut que pour un
   champ, et ces rangees-ci vivent dans un groupe. */
.regle-groupe { margin-bottom: 12px; }
.regle-groupe legend { font-size: 12px; font-weight: 600; padding: 0 4px; }
.regle-groupe .note { margin: 0 0 10px; }
:is(.sheet, .feuille) .regle-groupe .row {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin-bottom: 8px;
  line-height: 1.5;
}
:is(.sheet, .feuille) .regle-groupe .row > span { display: block; margin: 0; }
:is(.sheet, .feuille) .regle-groupe .row input { flex: 0 0 auto; }
:is(.sheet, .feuille) .regle-groupe .rule { margin: 0; }

/* La barre d'action quand elle repond a votre clic plutot qu'a votre tour :
   votre annonce n'a pas ete retenue, et cela doit se distinguer de « IA droite
   reflechit ». Elle reste jusqu'a votre prochaine decision. */
.prompt.alerte { color: var(--gold); font-weight: 600; }

.rule { font-size: 12px; line-height: 1.6; opacity: .8; margin: 0 0 10px; }
.rule b { color: var(--gold); font-weight: 600; }
.ledger { list-style: none; font-size: 12px; margin: 0 0 14px; padding: 0; }
.ledger li { display: flex; gap: 8px; align-items: baseline; margin: 3px 0; opacity: .85; }
.ledger .amount { flex: 0 0 auto; min-width: 52px; text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.ledger li.plus .amount { color: var(--plus); }
.ledger li.minus .amount { color: var(--minus); }
.ledger li.nil { opacity: .55; }
.ledger li.total { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line-1); font-weight: 600; opacity: 1; }
.headline { font-size: 15px; margin: 0 0 12px; }

/*
 * Le rail se reduit a ses icones.
 *
 * Le libelle **reste dans le DOM**, masque par recadrage : un `title` ou un
 * `aria-label` a la place ferait deux textes a traduire pour un seul mot, et le
 * second finirait par ne plus dire la meme chose que le premier.
 */
@media (max-width: 1000px) {
  :root { --rail: 64px; }
  .rail { padding: 12px 10px; align-items: center; }
  .rail-item { justify-content: center; padding: 0; }
  .rail-marque { padding: 4px 0 16px; }
  .rail-titre { margin: 12px 0 4px; padding: 0; text-align: center; letter-spacing: 0; font-size: 9px; }
  .rail-libelle, .rail-marque .title {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .pool-top, .pool-bottom { grid-template-columns: repeat(6, auto); }
  .pool-left, .pool-right { grid-template-rows: repeat(5, auto); }
}
@media (max-height: 760px) {
  :root { --tile-w: clamp(26px, calc(var(--jeu) * .028), 38px); }
  /*
   * Sur un écran court, le guide se resserre plutôt que de disparaître - le
   * lire est tout l'intérêt d'une partie guidée - et le défilement lui
   * appartient, sans quoi il pousserait la barre d'action hors de l'écran.
   *
   * Mais c'est la **leçon** qu'on borne, et non le bandeau. Borner le bandeau
   * poussait le conseil sous la ligne de flottaison d'une boîte qui ne montrait
   * aucune barre de défilement : la phrase qui répond à la question posée
   * disparaissait, et la règle de fond restait. Exactement l'inverse de ce
   * qu'il faut, et invisible partout ailleurs que sur un écran court.
   */
  .guide {
    padding: 6px 12px;
    font-size: 12px;
  }
  .guide .lecon {
    max-height: 3.2em;
    overflow-y: auto;
  }
}

/* ------------------------------------------------------------------ le focus */
/*
 * La feuille n'avait aucune regle de focus : on dependait de l'anneau par
 * defaut du navigateur, pose sur un degrade d'or ou sur une tuile ivoire. Or
 * depuis que les tuiles se defaussent au clavier, savoir ou l'on est est la
 * moitie du jeu.
 */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.tile:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 1px;
  /* La tuile visee se souleve comme sous le pointeur : le clavier n'a pas a
     etre le parent pauvre de la souris. */
  transform: translateY(-9px);
}

/* ------------------------------------------------------- petits ecrans */
/*
 * En portrait, une main de quatorze tuiles reclamait 446 px dans un
 * `overflow: hidden` : elle etait rognee, et les tuiles des bords devenaient
 * injouables. Elle se replie desormais sur deux rangs plutot que de sortir de
 * l'ecran, et les tuiles restent assez grandes pour etre touchees du doigt.
 */
@media (max-width: 720px) {
  /*
   * Le rail passe en bas, et rend sa largeur au jeu.
   *
   * `--rail: 0` fait de `--jeu` la totalite du viewport : tout le calibrage des
   * tuiles retombe donc exactement sur ce qu'il valait avant qu'un rail existe.
   * La grille de `#app` bascule de colonnes en rangees, et la barre prend la
   * seconde - sous le jeu, la ou le pouce la trouve.
   */
  :root { --rail: 0px; }
  /*
   * Les rangees ne suffisent pas : le rail est le premier enfant, et le
   * placement automatique le mettrait donc dans la premiere - au-dessus du jeu,
   * alors qu'on le veut sous le pouce. Chacun dit sa rangee.
   */
  #app { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  .vue { grid-row: 1; }
  .rail { grid-row: 2; }
  .rail {
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    gap: 0;
    padding: 4px 4px max(4px, env(safe-area-inset-bottom));
    border-right: none;
    border-top: 1px solid var(--raise-1);
    overflow: visible;
  }
  /* Le nom du jeu n'a pas sa place sur une barre de cinq icones. Le retour a la
     table reste la premiere d'entre elles. */
  .rail-marque { display: none; }
  .rail-nav, .rail-groupe, .rail-bas { flex-direction: row; gap: 0; }
  .rail-nav, .rail-bas { flex: 1; justify-content: space-around; }
  .rail-bas { margin-top: 0; padding-top: 0; display: flex; }
  .rail-item { width: auto; min-width: 48px; justify-content: center; padding: 0; }
  .rail-libelle { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); }

  /* Defausses et Quitter, ranges dans le bandeau de partie : sept icones sur
     une barre de 360 px ne se visent plus au pouce. `src/ui/rail.js` les y pose
     lui-meme, en ecoutant la meme largeur que celle-ci. */
  .partie-outils { display: flex; }
  .partie-outils .rail-item { min-height: 30px; min-width: 34px; }
  .partie-outils .icone { width: 17px; height: 17px; }

  :root { --tile-w: clamp(26px, calc(var(--jeu) * .085), 38px); }
  .seat-bottom .hand { flex-wrap: wrap; justify-content: center; row-gap: 4px; }
  .seat-bottom .stack { width: 100%; }
  .table { padding: 4px; gap: 3px; }
  .partie, .actionbar { padding-left: 8px; padding-right: 8px; }
  .center { padding: 4px; }
  /* Les tuiles de profil des voisins mangent la largeur : on les reduit. */
  .seat-left .tile, .seat-right .tile { --tile-w: clamp(18px, calc(var(--jeu) * .05), 26px); }

  /*
   * Ce qui poussait la table hors de l'ecran n'etait pas les tuiles mais les
   * plaques : « IA gauche · Maitre · +0 » ne se coupe pas, et imposait 170 px a
   * une colonne dont les tuiles en font 33. Le nom peut passer a la ligne, et
   * le niveau se lit dans les options - il n'a pas sa place sur un telephone.
   */
  .seat-left .nameplate, .seat-right .nameplate { white-space: normal; }
  .seat-left, .seat-right { max-width: calc(var(--jeu) * .24); }
  .nameplate { gap: 4px; font-size: 12px; }
  .nameplate .level { display: none; }

  .btn { min-height: 30px; padding: 6px 10px; }
  .pool-top, .pool-bottom { grid-template-columns: repeat(5, auto); }
}

/*
 * Sans survol, rien ne disait qu'une tuile etait jouable, et le survol simule
 * d'iOS reste colle apres le tap. Le doigt a droit au meme retour que le
 * pointeur, au moment ou il appuie.
 */
.hand .tile.playable:active { transform: translateY(-9px); }

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