/* Trois registres, trois familles. Le projet écrit de la prose, mesure des
   nombres et montre une partie : les confondre dans une seule fonte, c'est
   effacer la différence.

   La prose a sa police à elle, servie par le site : Roboto Serif, sous
   licence libre (fonts/OFL-RobotoSerif.txt). Une pile système change de
   dessin d'une machine à l'autre ; celle-ci est la même partout, et
   l'interface ne dépend toujours d'aucun serveur de polices tiers. Deux
   fichiers variables, romain et italique, couvrent les graisses de 400 à 700 :
   ce sont les sous-ensembles latins que Google Fonts distribue, tels quels.
   Le sans et la mono restent ceux du système. */
@font-face { font-family: "Roboto Serif"; font-style: normal; font-weight: 400 700;
             font-display: swap;
             src: url("fonts/RobotoSerif-normal-400-700.woff2") format("woff2"); }
@font-face { font-family: "Roboto Serif"; font-style: italic; font-weight: 400 700;
             font-display: swap;
             src: url("fonts/RobotoSerif-italic-400-700.woff2") format("woff2"); }

:root {
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  /* Une police de presse : trait robuste, peu de contraste, empattements
     carrés. Derrière elle, ses cousines système, le temps qu'elle arrive.
     Palatino, en tête de l'ancienne pile, donnait à la page un air de
     traitement de texte. */
  --serif: "Roboto Serif", Charter, "Bitstream Charter", "Sitka Text",
           Cambria, Georgia, serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* Le fond n'est plus un gris de bureau mais un papier : c'est le registre du
     projet, qui est un texte sur les jeux avant d'être un tableau de bord. */
  --bg: #f4f3ee;
  --panel: #fbfaf6;
  --sunk: #e6e3d8;            /* le creux où repose le plateau */
  --ink: #1b1d22;
  --muted: #5f626b;   /* 5,6 sur le papier : le gris sert à de petits textes */
  --line: #dcd8cc;
  --accent: #2f62d0;
  --on-accent: #fbfaf6;   /* ce qui se lit sur l'accent */
  --nav: #2f62d0;         /* la navigation, partout ou elle se trouve */
  --good: #2b7a4b;
  --good-bg: #e4efe6;
  --mid: #b26a1f;
  --mid-bg: #f7ecdd;
  --bad: #b0453a;
  --bad-bg: #f6e6e3;
  /* Les deux camps : un bleu vif et un corail. Deux nuances par camp, parce
     qu'un corail assez vif pour être gai ne se lit pas en texte sur le papier
     (3,2 de contraste, il en faut 4,5) : la vive sert aux pions, aux logos,
     aux barres ; la « texte » aux phrases écrites dans la couleur d'un camp.
     Bleu contre orange, parce que c'est la paire que les daltoniens
     distinguent sans peine. */
  --x: #2f62d0;
  --o: #e8573a;
  --x-texte: #2f62d0;
  --o-texte: #bf3f22;
  --ramp-ink: 28%;
  --fenetre-lum: 42%;   /* clarté des traits de fenêtre, lisible sur papier */
  --ramp-bg: 90%;
  --ramp-line: 74%;
  --lit-line: 55%;
  --lit-bg: 88%;
  --lvl-0: #8f8a7f;
  --lvl-1: #b26a1f;
  --lvl-2: #2f62d0;
  --lvl-3: #2b7a4b;

  /* Les six pions du Cluedo. Au carton, le nom du personnage *est* sa couleur :
     c'est le seul repère qui tienne dans une case de couloir de seize pixels,
     et c'est celui que les joueurs connaissent déjà. */
  --pion-rose: #b0457a;      --pion-rose-ink: #fbfaf6;
  --pion-moutarde: #a8841f;  --pion-moutarde-ink: #fbfaf6;
  --pion-leblanc: #cfc9ba;   --pion-leblanc-ink: #1b1d22;
  --pion-violet: #6b4a9e;    --pion-violet-ink: #fbfaf6;
  --pion-pervenche: #4e7fb5; --pion-pervenche-ink: #fbfaf6;
  --pion-olive: #6f7a2e;     --pion-olive-ink: #fbfaf6;
}

/* Pas de thème sombre, même quand le système en demande un. Le site parle à
   des enfants, et le sombre évoque l'écran de travail ou le jeu vidéo ; le
   papier clair évoque le livre et la table de jeu. `color-scheme` le dit aussi
   aux champs et aux barres de défilement, qui sinon passeraient au noir. */
:root { color-scheme: light; }

/* Le panneau de droite n'est pas un troisième volet de la même page : c'est
   l'appareil de mesure. Il porte donc son propre jeu de jetons, et tout ce
   qu'on y pose — verdict, mini-plateau, carnet, barres du défi — suit sans
   avoir à être retouché pièce par pièce. Il était ardoise sombre même en
   thème clair : l'élément le plus froid du site. Il devient crème, un ton en
   dessous de la page, pour rester un instrument sans trancher sur elle. */
.right {
  --bg: #ece7dc;
  --panel: #f5f1e8;
  --sunk: #e0d9ca;
  --ink: #1b1d22;
  --muted: #5f626b;
  --line: #d6cfbf;
  --accent: #9a5b12;          /* la couleur de signal du cadran, lisible sur crème */
  --on-accent: #fbfaf6;
  --nav: #2f62d0;
  --good: #2b7a4b;
  --good-bg: #e4efe6;
  --mid: #9a5b12;
  --mid-bg: #f5ead8;
  --bad: #b0453a;
  --bad-bg: #f6e6e3;
  --x: #2f62d0;
  --o: #e8573a;
  --x-texte: #2f62d0;
  --o-texte: #bf3f22;
  --ramp-ink: 28%;
  --ramp-bg: 90%;
  --ramp-line: 74%;
}

* { box-sizing: border-box; }

/* .view et .board sont en flex : sans !important, [hidden] ne masquerait rien. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.5 var(--sans);
  display: flex;
  flex-direction: column;
  overflow: hidden;           /* tout tient dans l'écran */
}

/* --- barre de titre --- */

/* Pas de fond, pas de trait dessous : c'est l'espace qui separe le nom du
   travail. Un bandeau colore donnait de la structure, mais il la donnait a la
   place de la typographie au lieu de la lui laisser. */
.topbar {
  flex: 0 0 auto;
  padding: 1.15rem 1.6rem .95rem;
  display: flex;
  /* `center` et non `baseline` : un dessin n'a pas de ligne de base utile, et
     aligne dessus la marque s'assoit trop bas sous le titre. */
  align-items: center;
  gap: .62rem;
}

/* La marque du bandeau est nue, sans le cartouche de l'icone d'onglet : un
   carre de couleur a besoin d'un fond pour tenir dans une rangee d'onglets,
   une marque posee sur son propre bandeau n'en a pas besoin. Meme dessin, deux
   traitements ; `currentColor` la fait suivre la couleur du bandeau. */
.logo { display: block; width: 1.6rem; height: 1.6rem; flex: 0 0 auto; }
/* Le nom porte l'identité, seul : il lui faut un corps de titre, pas de
   sous-titre. Il n'y a plus de glose à côté — le nom dit déjà ce qu'on vient
   chercher, et la barre n'a rien d'autre à porter que lui. */
.topbar h1 { font: 600 1.5rem/1 var(--serif); margin: 0; letter-spacing: -.012em; }
/* Le titre est le chemin du retour : il doit se voir cliquable sans devenir un
   bouton, qui en ferait une commande parmi d'autres. */
.topbar h1 { cursor: pointer; }
.topbar h1:hover { color: var(--accent); }

/* --- l'accueil --- */

/* `margin: auto` plutôt que `justify-content` : dans une colonne flexible, il
   centre aussi bien, et quand le texte dépasse la hauteur il laisse défiler au
   lieu de rogner le haut. */
.accueil { max-width: 42rem; margin: auto; padding: 1.5rem 0; }
/* « Bonjour » n'est pas un titre mais une salutation : en gros et en gras, il
   annonçait une section qui n'existe pas. Taille de phrase, donc, et hors de la
   hiérarchie des titres -- celle-ci commence à « À quoi veux-tu jouer ? ». */
.accueil .salut { margin: 0 0 .9rem; font-family: var(--serif); font-size: 1.05rem; }
.accueil p { margin: 0 0 .9rem; line-height: 1.6; }
.accueil ul { margin: 0 0 1.6rem; padding-left: 1.1rem; line-height: 1.6; }
.accueil li { margin-bottom: .3rem; }
/* De l'air au-dessus : le texte se lit, puis on choisit. Collé au dernier
   paragraphe, le titre avait l'air d'en être la suite. */
.accueil h3 { font: 600 1.1rem/1.2 var(--serif); margin: 2.6rem 0 1rem;
              text-transform: none; letter-spacing: normal; color: var(--ink); }

/* Quatre boutons de même poids : le choix est ouvert, aucun jeu n'est
   recommandé avant de savoir ce que le visiteur cherche. */
.choix { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .6rem; }
.choix .jeu {
  display: flex; flex-direction: column; align-items: center; gap: .7rem;
  padding: 1.2rem .6rem 1rem;
  font: 600 1rem/1.2 var(--serif);
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.choix .jeu:hover { border-color: var(--accent); color: var(--accent); }

/* Le dessin de chaque jeu. Il arrive du jeu sans couleur : trois classes, et
   c'est ici qu'elles prennent celles du site -- les deux camps du plateau, et
   l'estompe du favicon pour ce qui n'est que suggéré. */
/* 46 pixels pour une grille de 46 : une unité du dessin tombe sur un pixel.
   À 2.9rem le navigateur étirait de 0,4 px et lissait tous les bords. */
.choix .logo { width: 46px; height: 46px; display: block; }
.logo .fantome { fill: var(--ink); opacity: .13; }
.logo .ligne { fill: var(--ink); opacity: .28; }      /* une barre fine, donc plus appuyée */
.logo .a { fill: var(--x); }
.logo .b { fill: var(--o); }
.logo .sur-a { fill: var(--on-accent); }
.logo .trait { fill: none; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.logo .a.trait { stroke: var(--x); }
.logo .b.trait { stroke: var(--o); }
.logo .sur-a.trait { stroke: var(--on-accent); }

/* Le pied : discret, et souligné seulement au survol. Un lien de contact qui
   crie enlève de la place à ce qu'on est venu lire.

   Dans la barre latérale, `margin-top: auto` le pousse tout en bas : il occupe
   la place vide sous les modes, sans rien prendre en hauteur aux plateaux --
   ce qu'un vrai pied de page sur toute l'application aurait fait. */
.pied a { color: var(--muted); font-size: .88rem; text-decoration: none; }
.pied a:hover { color: var(--accent); text-decoration: underline; }
/* L'enveloppe dit « écrire » avant qu'on ait lu la phrase. Au trait, pas en
   aplat : elle reste de la couleur du lien et ne pèse pas plus que lui. */
/* Calée sur la première ligne, pas au milieu : dans la barre étroite la phrase
   passe sur deux lignes, et centrée l'icône flottait entre les deux. */
.pied a { display: inline-flex; align-items: flex-start; gap: .42em; }
.pied .enveloppe { width: 1.05em; height: 1.05em; flex: 0 0 auto; margin-top: .2em;
                   fill: none; stroke: currentColor; stroke-width: 1.3;
                   stroke-linejoin: round; stroke-linecap: round; }
.side { display: flex; flex-direction: column; }
.side .pied { margin-top: auto; padding-top: 1.4rem; }

/* --- ossature trois panneaux --- */

.app {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: 215px minmax(0, 1fr) 330px;
}

/* Avant le choix d'un jeu, une seule colonne : les deux autres n'auraient que
   du vide à montrer, et trois panneaux dont deux vides se lisent comme une page
   cassée. */
.app.sans-cote { grid-template-columns: minmax(0, 1fr); }
.app.sans-cote > .side, .app.sans-cote > .right { display: none; }

.panel { min-height: 0; overflow-y: auto; padding: 1.1rem 1.2rem 1.4rem; }
/* Aucun trait entre les colonnes : celui de droite doublait un changement de
   fond qui separe deja tout seul, et celui de gauche ne travaillait que parce
   que les gouttieres etaient etroites. On elargit, on retire. */
.side   { padding-left: 1.6rem; padding-right: 1.4rem; }
.center { padding-left: 2rem; padding-right: 1.6rem; }
.right  { background: var(--panel); color: var(--ink); }
.center { display: flex; flex-direction: column; }
.view { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }

/* Le plateau du quiz est enveloppe dans #quizplay, qui n'est pas un enfant direct
   de .view : sans colonne flexible ici, .board-wrap n'a aucune hauteur à partager
   et fitBoard mesure zéro. */
#quizplay { display: flex; flex-direction: column; flex: 1 1 0; min-height: 0; }

h2 { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em;
     color: var(--muted); margin: 0 0 .5rem; }
h3 { font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
     color: var(--muted); margin: 1.3rem 0 .5rem; }

/* --- panneau de gauche --- */

/* Sans aplat ni cadre, c'est l'interligne qui tient la liste ensemble. */
.stack { display: flex; flex-direction: column; gap: 2px; margin-top: 1.5rem; }

/* Trois entrees, une choisie : le poids de l'encre suffit a le dire. Un aplat
   pleine largeur etait tres au-dessus du travail a faire, et c'etait le seul
   endroit de la colonne ou du blanc sur bleu apparaissait. */
.navitem {
  text-align: left;
  padding: .3rem 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  transition: color .12s ease;
}
.navitem:hover { color: var(--ink); }
.navitem.active { color: var(--ink); font-weight: 600; }
.navitem[disabled] { color: var(--muted); cursor: not-allowed; }

/* Le jeu commande tout le reste : son menu en prend la taille. Il reste un
   menu et pas un bouton — il ouvre une liste, il ne déclenche rien. */
#games { padding: .42rem .5rem; font-size: .88rem; }

select {
  width: 100%;
  font: inherit;
  font-size: .8rem;
  padding: .3rem .4rem;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
}


/* --- panneau central : partie --- */

/* La ligne d'etat est la voix de la page, pas une etiquette d'interface : elle
   prend le serif du titre et des chapitres, et l'outil arrete de parler comme
   un formulaire des qu'on entre dans la partie. */
.status { margin: 0 0 .35rem; font: 500 1.08rem/1.35 var(--serif); }
.status .seat { color: var(--muted); font-weight: 400; font-size: .94em; }
/* Le journal du tour. Borné en hauteur : la page ne défile pas, mais un tour de
   table à quatre peut faire quinze lignes -- c'est lui qui défile, pas l'écran. */
/* Hauteur **fixe**, et non `min-height`. Le plateau prend la place qui reste :
   si le journal gagne une ligne — et il en gagne une au premier coup, deux
   quand la phrase passe a la ligne —, c'est le plateau qui retrecit d'autant,
   et il change de taille en pleine partie. Ce qui deborde defile ici plutot
   que de pousser le jeu. Deux tailles suffisent : les jeux a grille tiennent en
   deux lignes, ceux qui racontent un tour de table en demandent davantage. */
.opp {
  list-style: none; margin: 0 0 .6rem; padding: 0;
  color: var(--muted); font-size: .85rem;
  height: 2.7rem; overflow-y: auto;
}
.opp.recit { height: 7rem; }
/* Le coup d'une grille, au-dessus du titre : la place reste réservée pour que le
   plateau ne saute pas, et la phrase se colle au titre, en bas de cette place. */
.opp:not(.recit):not([hidden]) { display: flex; flex-direction: column;
                                 justify-content: flex-end; margin: 0 0 .15rem; }
.opp li { padding: .12rem 0; }
.opp li + li { border-top: 1px solid var(--line); }
/* Le tour de table qui se déroule : la ligne d'attente dit combien il reste, et
   le bloc entier se clique pour tout voir d'un coup. */
.opp.en-cours { cursor: pointer; }
.opp .suite { color: var(--muted); font-style: italic; }
.opp em { font-style: normal; color: var(--ink); font-weight: 600; }
#hover { margin: .5rem 0 0; }

.board-wrap {
  /* Base zéro, et non `auto` : sinon la hauteur du conteneur dépend de celle du
     plateau, que fitBoard calcule à partir d'elle. La boucle se verrouille sur
     la première valeur, aussi petite soit-elle. */
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Le plateau remplit exactement son enveloppe, donc rien ne l'écarte tout
     seul de la ligne qui le précède. La marge est ici et non en `padding` :
     `clientHeight` compte le padding, et fitBoard le rendrait au plateau. */
  margin: .6rem 0 .3rem;
}

.board {
  /* Taille posée par fitBoard ; ces valeurs ne servent qu'avant le premier rendu. */
  height: 100%;
  max-width: 100%;
  aspect-ratio: var(--cols, 3) / var(--rows, 3);
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), 1fr);
  grid-template-rows: repeat(var(--rows, 3), 1fr);
  gap: 6px;
  /* Un plateau est un objet pose sur une table, pas une rangée de boutons : le
     creux le détache du panneau, et c'est ce qui manquait le plus à Puissance 4,
     dont les cases blanches sur fond blanc ne se voyaient pas. `position` porte
     le trait qui barre l'alignement gagnant. */
  position: relative;
  padding: 9px;
  border-radius: 14px;
  background: var(--sunk);
  border: 1px solid var(--line);
  box-shadow: inset 0 2px 5px rgb(0 0 0 / .12);
}

.cell {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  font-size: clamp(.9rem, 6vh, 2.8rem);
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: var(--ink);
  cursor: default;
  box-shadow: 0 1px 1px rgb(0 0 0 / .05);
  transition: transform .08s ease, border-color .12s ease;
}

/* Le jeton tombe. C'est la seule règle de Puissance 4 que le plateau puisse
   montrer tout seul, et une case qui s'allume ne la montre pas. Ailleurs le
   symbole se pose, sans chute : rien ne tombe au morpion. */
@keyframes tombe {
  0%   { transform: translateY(var(--chute, -300%)); }
  70%  { transform: translateY(0); }
  85%  { transform: translateY(-6%); }
  100% { transform: translateY(0); }
}
@keyframes pose {
  from { transform: scale(.72); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
.board.discs .cell.neuf { animation: tombe .34s cubic-bezier(.4, 1.5, .6, 1) both; }
.board .cell.neuf       { animation: pose .16s ease-out both; }

/* Trois cases vertes disent « ces cases-la ». Un trait porte d'un bout à
   l'autre dit « alignement », qui est le sujet commun des deux jeux. */
.winline {
  position: absolute;
  height: 6px;
  border-radius: 3px;
  background: var(--good);
  transform-origin: left center;
  pointer-events: none;
  opacity: .85;
  animation: barre .34s cubic-bezier(.4, 0, .2, 1) both;
}
@keyframes barre { from { width: 0; } }

/* La chute et le trait disent quelque chose du jeu ; ils ne sont pourtant pas
   la seule façon de le dire, puisque le jeton et le trait restent la sans eux.
   Qui demande moins de mouvement les reçoit poses. */
@media (prefers-reduced-motion: reduce) {
  .board .cell.neuf, .board.discs .cell.neuf, .winline { animation: none; }
  .cell .trace path, .cell .trace circle { animation: none; stroke-dashoffset: 0; }
  .bar .fill { transition: none; }
  .cell { transition: none; }
}
/* A Puissance 4 les cases ne sont pas des jetons blancs poses sur le plateau :
   ce sont des trous percés dedans. L'ombre est donc interne, et le relief que
   portent les cases carrées n'a rien à faire ici. */
.board.discs .cell {
  border-radius: 50%;
  box-shadow: inset 0 2px 3px rgb(0 0 0 / .10);
}
.board.discs .cell .coord { top: auto; bottom: 4px; left: 50%; transform: translateX(-50%); }

.cell.playable { cursor: pointer; }
.cell.playable:hover { border-color: var(--accent); transform: translateY(-1px); }
.cell.x { color: var(--x); }
.cell.o { color: var(--o); }
.cell.winning { background: var(--good-bg);
                border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }

/* Un glyphe tape au clavier est une lettre ; un trait est un geste. On ne
   dessine que ce qu'on sait dessiner — tout autre symbole reste du texte, et un
   jeu qui en apporterait s'afficherait comme avant. */
.cell .trace { width: 56%; height: 56%; overflow: visible; }
.cell .trace path, .cell .trace circle {
  fill: none; stroke: currentColor; stroke-width: 5; stroke-linecap: round;
}
.cell .trace path   { --len: 40; }
.cell .trace circle { --len: 85; }

/* Au morpion c'est le trait qui fait l'evenement : la case n'a plus a se poser
   en meme temps. A Puissance 4 c'est la chute, et le symbole arrive avec elle. */
.board:not(.discs) .cell.neuf:has(.trace) { animation: none; }
.board:not(.discs) .cell.neuf .trace path,
.board:not(.discs) .cell.neuf .trace circle {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: tracer .32s ease forwards;
}
.board:not(.discs) .cell.neuf .trace path:nth-of-type(2) { animation-delay: .13s; }
@keyframes tracer { to { stroke-dashoffset: 0; } }

/* Le plateau ne dit pas de quel côté on joue : plutôt que de l'écrire, le
   symbole s'affiche en filigrane sur la case où le coup se poserait. Il est
   trace comme le vrai, sinon le survol promet une lettre et la pose donne un
   trait. */
.cell .fantome {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: .3;
  pointer-events: none;
}
.cell .fantome.x { color: var(--x); }
.cell .fantome.o { color: var(--o); }

/* « Son coup » : l'étiquette du dernier coup de l'adversaire. Au morpion, dans
   la case, sous le symbole ; à Puissance 4, à cheval sur le haut du jeton. */
.son-coup { position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%);
            white-space: nowrap; font: 500 .62rem/1 var(--sans); color: var(--muted);
            pointer-events: none; }
.son-coup.dessus { bottom: auto; top: 0; transform: translate(-50%, -60%);
                   background: var(--panel); border: 1px solid var(--line);
                   border-radius: 4px; padding: 2px 5px; z-index: 2; }
.board.discs .cell.sous-etiquette .coord { bottom: auto; top: 4px; }
.cell .coord {
  position: absolute; top: 5px; left: 8px;
  font-size: .62rem; font-weight: 500; color: var(--muted);
}

/* Survol : les alignements encore ouverts que le coup rejoindrait, d'autant plus
   marques qu'ils sont avancés. Le score de forme, rendu visible. */
.cell.lit-0 { border-color: hsl(230 25% var(--lit-line)); }
.cell.lit-1 { border-color: hsl(230 45% var(--lit-line));
              background: hsl(230 35% var(--lit-bg)); }
.cell.lit-2 { border-color: hsl(230 60% var(--lit-line));
              background: hsl(230 50% var(--lit-bg)); }
.cell.lit-3 { border-color: hsl(230 80% var(--lit-line));
              background: hsl(230 65% var(--lit-bg)); }
.cell.origin { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Le motif qu'un coup prépare : la position d'arrivée, pas le chemin. Les cases
   gagnantes sont cerclées, en vert pour toi, en rouge pour lui ; tes jetons à
   venir sont en filigrane, un ton plus léger que celui sous le curseur. Le
   cercle est une ombre interne : il suit la forme de la case, ronde ou carrée. */
.cell.motif-gain { box-shadow: inset 0 0 0 3px var(--good); border-color: var(--good);
                   background: var(--good-bg); }
.cell.motif-perte { box-shadow: inset 0 0 0 3px var(--bad); border-color: var(--bad);
                    background: var(--bad-bg); }
.cell .fantome.futur { opacity: .2; }
/* Un groupe de cases qu'on montre du doigt, sans prejuger de sa valeur. */
.cell.groupe { outline: 2px dashed var(--muted); outline-offset: -3px; }

.v-good { background: var(--good-bg); color: var(--good); }
.v-mid  { background: var(--mid-bg);  color: var(--mid); }
.v-bad  { background: var(--bad-bg);  color: var(--bad); }

.controls { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center;
            gap: .6rem; margin-top: .9rem; }

/* --- le tirage ------------------------------------------------------------
   Cet écran n'est pas une porte devant l'outil : c'est le tirage lui-même. La
   partie n'existe pas encore côté serveur, et le hasard se produit vraiment au
   moment où ce bouton la demande. Une pièce qui tourne avant d'atterrir sur un
   résultat déjà tiré ferait semblant ; celui-ci ne fait pas semblant. */

.tirage {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  text-align: center;
}
/* Une page sans plateau n'a aucun centre de gravite : le sceau en donne un, et
   le titre peut alors porter sans hurler. Pas de cadre autour : cet ecran est le
   tirage lui-meme, pas une porte posee devant l'outil. */
.tirage .sceau { display: block; width: 3.2rem; height: 3.2rem; }
.tirage .q { font: 500 1.55rem/1.2 var(--serif); margin: 0; }
.tirage button { padding: .5rem 1.15rem; }

/* Le symbole garde la couleur qu'il a sur le plateau, partout ou on le pose :
   c'est le meme objet qu'on designe. La classe la porte avec lui, plutot que
   chaque conteneur qui l'accueille. */
.sym { font-weight: 600; }
.sym.x { color: var(--x-texte); }
.sym.o { color: var(--o-texte); }
/* Une phrase entière dans la couleur de l'adversaire : sa couleur suffit à
   dire que c'est lui, le gras en ferait un cri. */
.sym.phrase { font-weight: 400; }

button {
  font: inherit;
  padding: .38rem .8rem;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--ink);
  cursor: pointer;
}
button:hover { border-color: var(--accent); }
button.primary { background: var(--accent); border-color: var(--accent);
                 color: var(--on-accent); }
button.primary:hover { filter: brightness(1.08); }
/* Les cases du plateau sont aussi des boutons : les estomper une fois occupées
   effacerait la partie elle-même au profit des cases encore vides. */
button:disabled:not(.cell) { opacity: .45; cursor: not-allowed; }
.cell:disabled { cursor: default; }

.toggle { display: flex; align-items: center; gap: .35rem; color: var(--muted); font-size: .85rem; }

/* Le choix de l'adversaire relance la partie : il appartient aux commandes de
   partie, pas au panneau du coach ni à la navigation. */
.against { display: flex; align-items: center; gap: .4rem; color: var(--muted);
           font-size: .85rem; }
.against select { width: auto; min-width: 8rem; }
/* « Nouvelle partie contre Spontané » est une phrase, donc un seul bloc : elle
   se serre, et elle passe à la ligne d'un tenant plutôt que de se couper en
   deux. */
.newgame { display: flex; align-items: center; gap: .55rem; }
/* La barre de mise en place ouvre la vue : elle n'a rien au-dessus d'elle dont
   il faudrait la détacher, et c'est le plateau qu'elle doit laisser respirer. */
.controls.setup { margin-top: 0; margin-bottom: .7rem; }
.tally { color: var(--muted); font-size: .82rem; margin-left: auto; }

/* --- panneau de droite --- */

.verdict {
  border-left: 3px solid var(--line);
  padding: .7rem .9rem;
  border-radius: 0 8px 8px 0;
  background: var(--bg);
}
.verdict.empty { color: var(--muted); }
.verdict .tag { display: inline-block; font-size: .66rem; font-weight: 700;
                text-transform: uppercase; letter-spacing: .07em; margin-bottom: .3rem; }
.verdict.optimal { border-color: var(--good); background: var(--good-bg); }
.verdict.optimal .tag { color: var(--good); }
.verdict.bon, .verdict.imprecision { border-color: var(--mid); background: var(--mid-bg); }
.verdict.bon .tag, .verdict.imprecision .tag { color: var(--mid); }
.verdict.erreur { border-color: var(--bad); background: var(--bad-bg); }
.verdict.erreur .tag { color: var(--bad); }
/* Le conseil n'est pas un verdict : pas de barre de couleur à son bord, elle
   ne porterait aucun sens. */
.verdict.hint { border-left: none; border-radius: 8px; }
.heat-intro { margin: 0 0 .2rem; }
.verdict .why { color: var(--muted); font-size: .86rem; margin: .35rem 0 0; }
.verdict dl { margin: .4rem 0 0; display: grid; grid-template-columns: auto 1fr;
              gap: .1rem .6rem; font-size: .85rem; }
.verdict dt { color: var(--muted); }
.verdict dd { margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums; }

.mini {
  display: grid;
  /* minmax(0, 1fr) et non 1fr : une colonne en 1fr ne descend jamais sous la
     largeur de son contenu, et le mot « perdu » élargissait la sienne jusqu'à
     pousser la septième hors du panneau. */
  grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));
  gap: 3px;
  width: min(100%, calc(var(--cols, 3) * 46px));
  margin: .5rem 0 .6rem;
}
.mini div {
  aspect-ratio: 1; border: 1px solid var(--line); border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font: 500 clamp(.55rem, 1.6vw, .82rem)/1 var(--mono);
  font-variant-numeric: tabular-nums;
}

/* Une carte de chaleur sans échelle demande de croire la couleur. Celle-ci est
   la même rampe que les cases, prise à ses deux bouts. */
.rampe { display: flex; align-items: center; gap: .45rem; margin: .8rem 0 .55rem;
         font: 400 .68rem/1 var(--mono); color: var(--muted); }
.rampe span { flex: 1; height: 7px; border-radius: 2px; border: 1px solid var(--line); }
/* Les chiffres passent en mono, pas les symboles : dans une chasse fixe,
   le O du morpion se lit comme un zéro. */
.mini .mark { font: 600 1.05rem/1 var(--sans); }
/* Dans le panneau, le plateau prend toute la largeur, quel que soit le jeu : au
   morpion, trois cases de 46px laissaient la moitié du panneau vide. Les
   figures de la Théorie gardent leur taille bornée. */
#advice .mini { width: 100%; gap: 4px; }
#advice .mini[style*="--cols:3"] div { font-size: 1.05rem; }
#advice .mini[style*="--cols:3"] .mark,
#advice .mini[style*="--cols:3"] div.lettre { font-size: 1.5rem; }
.mini div { min-width: 0; overflow: hidden; }
/* G et P : une lettre à la place d'un mot, qu'on lit d'un coup d'oeil. */
.mini div.lettre { font: 700 .95rem/1 var(--sans); }

/* La légende à exemples : une case type, puis ce qu'elle veut dire. */
.cles { display: flex; flex-direction: column; gap: .95rem; margin-top: 1.15rem; }
/* La case d'exemple reste carrée, comme sur le plateau, mais petite : centrée
   sur la première ligne de texte, elle se lit comme le mot qui ouvre la phrase.
   Elle est assez large pour « +182 ». */
.cle-ligne { display: grid; grid-template-columns: 1.8rem minmax(0, 1fr);
             gap: .6rem; align-items: start; }
.cle-ligne p { margin: 0; font-size: .8rem; line-height: 1.45; color: var(--muted); }
.cle-ligne p b { color: var(--ink); font-weight: 600; }
.exemple { width: 1.8rem; aspect-ratio: 1; margin-top: -.32rem; display: flex;
           align-items: center; justify-content: center; border: 1px solid var(--line);
           border-radius: 5px; font: 500 .6rem/1 var(--mono);
           font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.exemple.lettre { font: 700 .75rem/1 var(--sans); }
.exemple.detail { flex-direction: column; gap: 2px; padding: 0 3px; }
.exemple.detail b { font-weight: 500; font-size: .56rem; }
.exemple.detail .issues { width: 100%; height: 3px; margin: 0; }
.exemple.best { outline: 2px solid var(--good); outline-offset: -2px; }
/* La case qui sait ce qui arrive : la part gagnée au-dessus, la barre des
   trois issues en dessous, sur toute sa largeur. */
.mini div.detail { flex-direction: column; gap: 3px; padding: 0 6px; }
.mini div.detail b { font-weight: 500; }
.mini div.detail .issues { width: 100%; margin: 0; }
.mini .best { outline: 2px solid var(--good); outline-offset: -2px; }
.mini .played { outline: 2px dashed var(--accent); outline-offset: -2px; }
.legend, .note { color: var(--muted); font-size: .78rem; margin: 0 0 .5rem; }
.legend em { font-style: normal; color: var(--ink); }
.stale { color: var(--bad); font-weight: 500; }

.caveat { border-top: 1px solid var(--line); padding-top: .6rem; margin-top: .9rem; }

/* Sous le plateau, alignee sur lui. Deux lignes reservees : la phrase change a
   chaque case parcourue, et sans hauteur fixe le bas de la colonne danserait a
   chaque mouvement de souris. */
.hover {
  min-height: 3.9em;
  margin: .2rem 0 0;
  text-align: center;
  color: var(--muted);
  font-size: .84rem;
}
.hover b { color: var(--ink); font-weight: 600; }
.hover em { font-style: normal; font-weight: 600; color: var(--ink); }


/* --- écrans etroits : on empile et on autorise le défilement --- */

@media (max-width: 900px) {
  body { overflow: auto; }
  /* `flex: 1 1 auto` + `min-height: 0` enferme la grille dans la hauteur de
     l'ecran : c'est ce qui fait tenir les trois panneaux sans defilement en
     mode large. Empile, il faut l'inverse — la grille prend la hauteur de son
     contenu, et c'est la page qui defile. */
  .app { grid-template-columns: 1fr; flex: 0 0 auto; }
  .side, .right { border: none; border-top: 1px solid var(--line); }
  /* Le defilement interne des panneaux sert l'ecran unique du mode large. Une
     fois empile, c'est la page qui defile : le garder rognait chaque panneau a
     la hauteur d'un tiers d'ecran. */
  .panel { overflow-y: visible; }
  .board { height: auto; width: min(100%, 360px); }
  /* Empile, plus rien n'a de hauteur definie : `flex: 1 1 0` faisait retomber
     l'enveloppe a zero, et le plateau debordait par-dessus la suite de la page.
     Ici c'est le plateau qui donne sa hauteur, pas l'inverse. */
  .board-wrap { flex: 0 0 auto; }
  /* L'ecran du tirage prend la place du plateau, avec le meme `flex: 1 1 0` :
     il retombait a zero de la meme facon, et son contenu, centre sur une hauteur
     nulle, debordait par-dessus la navigation et le pied de page. La marge
     remplace la hauteur d'ecran qu'il n'a plus pour respirer. */
  .tirage { flex: 0 0 auto; padding: 2.5rem 0; }
  /* Le plateau du défi est enveloppé dans #quizplay, au même `flex: 1 1 0` :
     empilé, il retombait à zéro, et le plateau passait par-dessus le score. */
  #quizplay { flex: 0 0 auto; }
  /* Avant la premiere partie, le panneau du coach n'a rien a dire : tous ses
     enfants sont masques. En trois colonnes, sa place vide tient la mise en
     page ; empile, il ne reste que son fond et son filet, une bande sans rien
     dedans entre le tirage et le pied de page. */
  .app > .right:not(:has(> :not([hidden]))) { display: none; }
  /* Sur un telephone, le menu de l'adversaire prend la largeur et le bouton se
     coupait en « Nouvelle / partie ». C'est au menu de ceder, pas au libelle. */
  #new { white-space: nowrap; flex: 0 0 auto; }
}

/* --- petits écrans ---------------------------------------------------------
   Deux choses distinctes. Le blocage de certains jeux se décide en JavaScript
   (`jeuTropGrand`), sur la règle « moins de 700 px de large ou 500 de haut ».
   La mise en page compacte, ici, ne vaut que lorsque les panneaux sont déjà
   empilés : un écran large mais bas garde ses trois colonnes. */

/* Le mot d'excuse, au centre de la place qu'aurait prise le plateau. */
.avis-ecran {
  margin: auto; max-width: 30rem; padding: 2rem 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: 1rem;
}
.avis-ecran p { margin: 0; line-height: 1.6; font-family: var(--serif); font-size: 1.05rem; }

/* Le même mot, en tête de la Théorie : il dit pourquoi on est arrivé là. */
.avis {
  margin: 0 0 1rem; padding: .7rem .9rem; line-height: 1.55;
  border-left: 3px solid var(--accent); background: var(--panel);
  border-radius: 0 6px 6px 0;
}

/* Le sommaire replié n'existe que sur un téléphone : ailleurs, la colonne de
   droite le porte. */
.sommaire-mobile { display: none; }

@media (max-width: 699px), (max-width: 899px) and (max-height: 499px) {
  /* La barre latérale se défait : ses enfants deviennent des rangées de la
     grille. Le menu et les trois modes tiennent en deux lignes au lieu de 206
     pixels, et le lien de contact descend tout en bas de la page -- sans quoi
     il s'intercalait entre la navigation et le jeu. */
  .app > .side { display: contents; }
  /* `width: auto` : la largeur pleine qu'il porte ailleurs s'ajoutait aux
     marges, et le menu sortait de l'écran. En élément de grille, il s'étire
     tout seul jusqu'aux marges. */
  .side > #games { margin: .75rem 1.4rem 0 1.6rem; width: auto; justify-self: stretch; }
  .side > .stack { flex-direction: row; flex-wrap: wrap; gap: .2rem 1.2rem;
                   margin: .45rem 1.4rem 0 1.6rem; }
  .side > .pied { order: 1; margin: 0; padding: 1.2rem 1.4rem 1.6rem 1.6rem;
                  border-top: 1px solid var(--line); }

  .app[data-vue="analysis"] > .right,
  .app[data-vue="bloque"] > .right { display: none; }

  .sommaire-mobile { display: block; margin: 0 0 1rem; }
  .sommaire-mobile summary { cursor: pointer; color: var(--muted); font-size: .9rem; }
  .sommaire-mobile .toc { margin-top: .5rem; }

  /* Le plateau porte déjà la carte des conseils (`peindrePlateau`) : la même,
     répétée sous la page, ne dirait rien de plus. Restent la phrase qui dit ce
     que valent les chiffres, l'échelle des couleurs et la légende. */
  .app[data-vue="training"] #advice .mini { display: none; }
}

/* --- teste ton niveau --- */

.bar { margin-bottom: .7rem; }
.bar .row { display: flex; justify-content: space-between; align-items: baseline;
            font-size: .85rem; margin-bottom: .2rem; }
.bar .row b { font-weight: 600; }
.bar .row span { color: var(--muted); font-family: var(--mono);
                 font-variant-numeric: tabular-nums; }
.bar .track { height: 8px; border-radius: 4px; background: var(--bg);
              border: 1px solid var(--line); overflow: hidden; }
/* La barre se deplace, elle n'est pas remplacee : c'est le deplacement qui est
   l'information. Seul le niveau en tete porte la couleur — trois barres de la
   meme teinte ne designent personne. */
.bar .fill { height: 100%; background: var(--muted);
             transition: width .45s cubic-bezier(.4, 0, .2, 1),
                         background-color .45s ease; }
.bar.lead .fill { background: var(--accent); }
.bar.lead .row b, .bar.lead .row span { color: var(--accent); }

/* Une fois la reponse donnee, le plateau du defi porte le jugement du niveau de
   reference : la couleur dit le sens, le chiffre dit l'ecart. Le contour reste
   reserve au coup du joueur — c'est le seul reperage dont il a besoin pour se
   situer, et le doubler d'un contour « meilleur coup » brouillerait les deux. */
/* Les meilleurs coups se distinguent par la graisse, pas par un souligne : sur
   le web, souligner veut dire « lien », et ces cases ne se cliquent plus une
   fois la reponse donnee. */
.cell .val { font-size: .5em; font-weight: 500; letter-spacing: -.02em; }
.cell.juge { cursor: help; }
.cell.juge.top .val { font-weight: 800; }

/* L'entraînement sur un téléphone : la carte des conseils posée sur le plateau.
   Les cases restent jouables -- le curseur le dit -- et le meilleur coup porte
   le contour vert de la carte, pas la graisse du défi. */
.cell.juge.playable { cursor: pointer; }
.cell .val.detail { display: flex; flex-direction: column; align-items: center;
                    gap: 3px; width: 62%; font-size: .42em; }
.cell .val.detail b { font-weight: 600; }
.cell .val.detail .issues { width: 100%; margin: 0; }
.cell .val.lettre { font-weight: 700; }
.cell.best { outline: 2px solid var(--good); outline-offset: -3px; }
/* À Puissance 4, le numéro de colonne loge sous le centre du rond, là où
   tombe le chiffre : il remonte en haut pour lui laisser la place. */
.board.discs .cell.juge .coord { bottom: auto; top: 3px; }
.board.discs .cell.juge .val { font-size: .4em; margin-top: .5em; }

/* Apres les couleurs du jugement, pour que le contour du coup joue reste
   lisible par-dessus le fond qu'elles posent. */
.cell.answered { outline: 3px solid var(--accent); outline-offset: -3px; }

/* Le temps qui reste avant que le problème suivant arrive. Une barre plutôt
   qu'un compte à rebours : on la voit sans la lire, et on sait qu'un clic
   n'est pas obligatoire. */
.avance-barre {
  display: block; height: 2px; margin-top: .45rem; border-radius: 1px;
  background: var(--accent); transform-origin: left;
  animation: avance linear both;
}
@keyframes avance { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
  .avance-barre { animation: none; opacity: .4; }
}

/* Le verdict affiche, toute la colonne tourne la page : le curseur doit le dire
   partout, sinon seul le bouton a l'air cliquable. Les cases comprises — elles
   ne sont plus jouables, mais elles avancent comme le reste. */
#view-quiz.avance, #view-quiz.avance .cell, #view-quiz.avance .sk-card { cursor: pointer; }
.opp .aside { opacity: .65; }

/* --- « ton niveau » : le cumul, puis la preuve ----------------------------
   La barre du haut est la reponse du moment ; les dix colonnes sont ce dont
   elle est faite. Les teintes sont celles des courbes du bilan et de la grille
   de partage, pour qu'un niveau garde la meme couleur partout. */

#posterior .cumul-track {
  display: flex; height: 12px; border-radius: 6px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--line);
}
#posterior .cumul-track .seg { transition: width .35s ease; }
#posterior .cumul-legend {
  display: flex; flex-wrap: wrap; gap: .15rem .9rem; margin-top: .5rem;
  font-size: .78rem; color: var(--muted);
}
#posterior .cumul-legend .item { display: flex; align-items: center; gap: .32rem; }
#posterior .cumul-legend i { width: 9px; height: 9px; border-radius: 2px; flex: 0 0 auto; }
#posterior .cumul-legend b { color: var(--ink); font: 600 .78rem/1 var(--mono); }
/* Le niveau en tete se detache par l'encre, pas par une couleur de plus :
   la sienne sert deja a l'identifier dans les barres. */
#posterior .cumul-legend .item.lead .nom { color: var(--ink); font-weight: 600; }

#posterior .titre-coups { margin: 1.15rem 0 .5rem; }
#posterior .colonnes { display: flex; gap: 5px; align-items: flex-end; }
#posterior .colonne { flex: 1 1 0; display: flex; flex-direction: column;
                      align-items: center; gap: .3rem; min-width: 0; }
/* column-reverse : une colonne se remplit par le bas. */
#posterior .pile {
  width: 100%; height: 66px; display: flex; flex-direction: column-reverse;
  border-radius: 4px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--line);
}
#posterior .pile .seg { width: 100%; transition: height .35s ease; }
#posterior .colonne.vide .pile { border-style: dashed; }
#posterior .colonne.courant .pile { border-style: solid; border-color: var(--accent); }
#posterior .colonne.courant .num { color: var(--accent); }
#posterior .num { font: 500 .62rem/1 var(--mono); color: var(--muted); }

#posterior .lvl-0 { background: var(--lvl-0); }
#posterior .lvl-1 { background: var(--lvl-1); }
#posterior .lvl-2 { background: var(--lvl-2); }
#posterior .lvl-3 { background: var(--lvl-3); }

.reveal { margin-top: 1.2rem; }
.reveal .bar { margin-bottom: .8rem; }
.reveal .pick { font-size: .78rem; color: var(--muted); margin-top: .2rem; }
.reveal .bar.lead .pick { color: var(--good); }
.reveal .bar.lead .fill { background: var(--good); }

/* le bilan des dix questions */
.result { display: flex; flex-direction: column; gap: .6rem; max-width: 520px; }
.result .kicker { margin: 0; font-size: .78rem; text-transform: uppercase; letter-spacing: .08em;
                  color: var(--muted); }
.result h2 { margin: 0; font-size: 1.6rem; text-transform: none; letter-spacing: 0; }
/* Le palier est une phrase, pas un nombre : il vit sous le chiffre. */
.result h2 .pts { display: block; color: var(--muted); font-weight: 400;
                  font-size: .95rem; margin-top: .2rem; }
.result h2 .stars { margin-left: .35rem; }
.result h2 .stars { color: var(--mid); letter-spacing: .05em; white-space: nowrap; }
.result .opp { margin: 0; }
.result .chart svg { width: 100%; height: auto; display: block; }
.result .chart text { fill: var(--muted); font-size: 10px; }
.result .chart .grid { stroke: var(--line); stroke-width: 1; }
.result .chart .curve { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.result .chart .dot { stroke: var(--panel); stroke-width: 1.5; }
.result .chart .name { font-weight: 600; font-size: 11px; }
.result .lvl-0 { stroke: var(--lvl-0); fill: var(--lvl-0); }
.result .lvl-1 { stroke: var(--lvl-1); fill: var(--lvl-1); }
.result .lvl-2 { stroke: var(--lvl-2); fill: var(--lvl-2); }
.result .lvl-3 { stroke: var(--lvl-3); fill: var(--lvl-3); }
.result .chart text.lvl-0 { stroke: none; fill: var(--lvl-0); }
.result .chart text.lvl-1 { stroke: none; fill: var(--lvl-1); }
.result .chart text.lvl-2 { stroke: none; fill: var(--lvl-2); }
.result .chart text.lvl-3 { stroke: none; fill: var(--lvl-3); }
.result .legend { font-size: .8rem; color: var(--muted); margin: 0; }

/* Dix cases, une par reponse, a la couleur du niveau en tete a ce moment-la.
   Ce n'est pas une grille de bonnes et de mauvaises reponses : le defi ne juge
   aucun coup, il deplace une croyance. Ce qu'on montre, c'est le deplacement.
   Les teintes sont celles des courbes, pour que les deux se lisent ensemble. */
.trajgrid { display: flex; gap: 4px; flex-wrap: wrap; }
.trajgrid i {
  width: 26px; height: 26px; border-radius: 5px; display: block;
  border: 1px solid rgb(0 0 0 / .08);
}
/* Une case par probleme : rate, approche, trouve. */
.trajgrid i.n0 { background: var(--bg); border: 1px solid var(--line); }
.trajgrid i.n1 { background: var(--mid); }
.trajgrid i.n2 { background: var(--good); }

/* Ce qu'on copie, montre tel qu'il sera colle. Rien a imaginer. */
.partage {
  margin: 0; padding: .7rem .85rem; border-radius: 8px;
  background: var(--bg); border: 1px solid var(--line);
  font: 400 .78rem/1.6 var(--mono); color: var(--muted);
  white-space: pre-wrap; overflow-x: auto;
}

/* --- Skyjo ---------------------------------------------------------------
   Représentation neutre : des cases numérotées et une échelle de teintes à
   nous, pas une imitation des cartes du commerce. */

.skyjo { display: flex; flex-direction: column; gap: 1rem;
         align-items: center; justify-content: center;
         height: 100%; overflow: auto; }
.sk-side { text-align: center; }
.sk-side h3 { margin: 0 0 .4rem; }
.sk-side.mine h3 { color: var(--ink); }

.sk-grid { display: grid; grid-template-columns: repeat(var(--cols, 4), 1fr); gap: 6px; }

.sk-card {
  width: 52px; height: 68px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel); color: var(--ink);
  font: 600 1.15rem/1 inherit; display: flex; align-items: center;
  justify-content: center; padding: 0; cursor: default;
}
.sk-card.down { color: var(--muted); background: var(--bg); }
.sk-card.gone { border-style: dashed; color: transparent; background: none; }
.sk-card.playable { cursor: pointer; }
.sk-card.playable:hover { border-color: var(--accent); transform: translateY(-1px); }
.sk-card.static { cursor: default; }

.band-bleu-fonce { background: hsl(230 45% var(--lit-bg)); border-color: hsl(230 50% var(--lit-line)); }
.band-bleu-clair { background: hsl(200 45% var(--lit-bg)); border-color: hsl(200 50% var(--lit-line)); }
.band-vert       { background: hsl(145 40% var(--lit-bg)); border-color: hsl(145 45% var(--lit-line)); }
.band-jaune      { background: hsl(45 55% var(--lit-bg));  border-color: hsl(45 55% var(--lit-line)); }
.band-rouge      { background: hsl(10 50% var(--lit-bg));  border-color: hsl(10 55% var(--lit-line)); }

/* À quatre, on passe en 2x2 : quatre grilles empilées ne tiennent pas. */
.skyjo.dense {
  display: grid;
  grid-template-columns: repeat(2, auto);
  justify-content: center;
  align-content: center;
  gap: .6rem 1.6rem;
}
.skyjo.dense .sk-pile { grid-column: 1 / -1; justify-self: center; margin-bottom: .3rem; }
.skyjo.dense .sk-card { width: 34px; height: 44px; font-size: .9rem; border-radius: 6px; }
.skyjo.dense .sk-grid { gap: 4px; }
.skyjo.dense .sk-side h3 { font-size: .68rem; margin-bottom: .25rem; }
.skyjo.dense .sk-pile .sk-card { width: 40px; height: 52px; }

.sk-pile { display: flex; gap: 1.2rem; align-items: flex-start; }
.sk-slot { display: flex; flex-direction: column; align-items: center; gap: .3rem; }
.sk-slot span { font-size: .72rem; color: var(--muted); }

.ranked { list-style: none; margin: .5rem 0 .6rem; padding: 0; }
.ranked li { display: flex; justify-content: space-between; align-items: center;
             gap: .6rem; font-size: .84rem; padding: .25rem .4rem; border-radius: 6px; }
.ranked li b { font: 500 .8rem/1.5 var(--mono); font-variant-numeric: tabular-nums;
               padding: 1px 6px; border-radius: 5px; border: 1px solid transparent; }
.ranked li.best { outline: 1px solid var(--good); }
.ranked li.played { outline: 1px dashed var(--accent); }

/* --- vue Théorie (`analysis` dans le code et l'API) ----------------------- */

/* La prose garde sa mesure ; les tableaux et les encarts chiffres, non. Une
   matrice se lit mieux large, une phrase non — la grille laisse donc les deux
   vivre leur vie dans la même colonne. */

/* La vue Théorie est de la prose : elle en prend la fonte et la mesure. Les
   chiffres qu'elle cite restent en mono, pour qu'un relevé ne se lise jamais
   comme une phrase. */
.prose {
  overflow-y: auto; min-height: 0; padding-right: 1rem;
  align-content: start;
  /* 16 px. À 15, tous les romains se ressemblent et la page se lit comme une
     notice. Roboto Serif a des minuscules très hautes : à 16 elle occupe
     autant qu'une autre à 17 ou 18, la taille de la presse, et elle demande
     un interligne ouvert. */
  font: 400 1rem/1.7 var(--serif);
  /* Trois colonnes : le texte tient la mesure au centre, ce qui gagne à être
     large déborde des deux côtés. */
  display: grid;
  /* Les suffixes -start / -end ne sont pas décoratifs : c'est eux qui font
     marcher la forme courte « grid-column: texte ». */
  grid-template-columns:
    [large-start] minmax(0, 1fr)
    [texte-start] minmax(0, 64ch) [texte-end]
    minmax(0, 1fr) [large-end];
}
.prose > * { grid-column: texte; }
.prose > table, .prose > .fig {
  grid-column: large; justify-self: center; width: 100%; max-width: 46rem;
}
.prose b, .prose strong { font-weight: 600; }
.prose h2 { font: 500 1.45rem/1.25 var(--serif); text-transform: none;
            letter-spacing: 0; color: var(--ink); margin: 2rem 0 .6rem;
            padding-top: 1.2rem; border-top: 1px solid var(--line); }
.prose h2:first-child { margin-top: 0; padding-top: 0; border-top: none; }

/* Le chapeau de la page : ce n'est pas une section, il n'a donc pas de titre.
   Sa place en tête suffit à le distinguer : en gris, trois paragraphes
   entiers se lisaient mal, et seuls les gras ressortaient. Pas de filet a sa fin : le titre suivant
   porte deja le sien — `h2:first-child` ne le retire qu'au tout premier, et le
   chapeau est passe devant. Les deux ensemble faisaient un double trait. */
.chapeau { margin: 0; }
.prose h3 { font: 600 1.15rem/1.3 var(--serif); text-transform: none;
            letter-spacing: 0; color: var(--ink); margin: 1.2rem 0 .3rem; }
/* Un intertitre en capitales de chasse fixe : à .7rem il passait sous le corps
   du texte qu'il annonce, et collait au paragraphe précédent. Il reste plus
   petit que le texte -- c'est une étiquette, pas un titre de section -- mais il
   se tient, et l'air au-dessus dit qu'il ouvre quelque chose. */
.prose h4 { font: 500 .82rem/1.4 var(--mono); color: var(--muted);
            margin: 2.2rem 0 .6rem; text-transform: uppercase;
            letter-spacing: .08em; }
.prose p { margin: 0 0 .7rem; }
.prose ol, .prose ul { margin: 0 0 .8rem; padding-left: 1.3rem; }
.prose li { margin-bottom: .35rem; }
/* L'italique de ces serifs est nettement plus etroit que le romain : au meme
   corps et a la meme approche, il se lit serre. On lui rend l'air que sa
   chasse lui prend — un cheveu d'approche, un peu d'interligne — pour qu'il
   pese comme le texte courant au lieu de paraitre tasse. */
/* Une ligne d'état -- « Chargement… », « Pas encore calculé » --, et rien
   d'autre : les chapeaux qui expliquent comment lire une section sont du texte
   ordinaire, et huit lignes de gris italique se lisent mal. */
.prose .lede { color: var(--muted); font-style: italic;
               letter-spacing: .015em; line-height: 1.76; }

/* Un tableau n'est pas un paragraphe : il s'arrete de lire et se regarde. Il
   lui faut donc plus d'air que l'interligne courant, au-dessus comme au-dessous
   — la marge basse est reprise ci-dessous quand une legende suit, parce qu'elle
   appartient au tableau et ne doit pas s'en detacher. */
/* Ce que vaut chaque place : quatre tables, une par configuration, et à chaque
   coin la part des parties que cette place gagne. Même repère que la cascade --
   les unités du dessin sont les pixels qu'il occupe à 34 rem, donc l'échelle
   vaut 1 et rien n'est flou. */
.prose .rangs-cadre { width: 100%; max-width: 34rem; aspect-ratio: 544 / 432;
                      margin: 2.4rem 0 1rem; }
.prose .rangs-cadre + .legend { font-style: italic; margin-bottom: 2.2rem; }
/* Le bloc entier -- l'annonce, le dessin, sa légende et ce qu'on en tire. Comme
   pour la cascade, `display: contents` laisse chaque paragraphe prendre sa
   rangée, et la règle de placement doit être redite pour ses enfants. */
.rangs-bloc { display: contents; }
.rangs-bloc > * { grid-column: texte; }
.rangs { display: block; width: 100%; height: 100%; }
.rangs .titre { font: 500 13px/1 var(--sans); fill: var(--ink); text-anchor: middle; }
.rangs .table { fill: none; stroke: var(--line); stroke-width: 1; }
.rangs .tour { fill: none; stroke: var(--line); stroke-width: 1; }
.rangs .tour.pleine { fill: var(--line); stroke: none; }
.rangs .rang { font: 400 11px/1 var(--sans); fill: var(--muted); text-anchor: middle; }
.rangs .taux { font: 600 14px/1 var(--mono); fill: var(--ink); text-anchor: middle; }
/* Le cercle entier, c'est toutes les parties ; la part remplie, celles que
   cette place gagne. Deux couleurs, une par niveau. */
.rangs .cercle { fill: none; stroke-width: 1; opacity: .5; }
.rangs .reflechi { fill: var(--accent); stroke: var(--accent); }
.rangs .spontane { fill: var(--muted); stroke: var(--muted); }

/* La cascade : une barre par marche, qui repart où la précédente s'arrête.
   Dessinée dans le texte du jeu, en SVG, parce que ce sont ses chiffres : la
   page ne fait que la mettre à l'échelle et lui donner ses couleurs. */
/* `aspect-ratio` et non la hauteur deduite du viewBox : en element de grille,
   celle-ci ne remonte pas a la rangee, et le texte suivant se replie par-dessus
   le dessin. Le rapport doit suivre le viewBox du SVG, 520 x 428. */
/* La cascade : une barre par marche, qui repart où la précédente s'arrête, et
   qui porte sa phrase entière. Dessinée dans le texte du jeu, en SVG, parce que
   ce sont ses chiffres : la page ne fait que lui donner ses couleurs.

   Elle ne s'affiche pas sur un téléphone. Les phrases y tomberaient sous dix
   pixels, et les couper autrement reviendrait à faire deux dessins. Le reste de
   la section se lit très bien sans elle. */
/* `display: contents` pour que chaque paragraphe garde sa propre rangée dans la
   grille de la colonne, comme s'il n'y avait pas de bloc. La règle de placement
   ne vise que les enfants directs de `.prose` : il faut la redire ici, sans
   quoi les paragraphes tombent dans une colonne implicite, large d'un mot. */
.cascade-bloc { display: contents; }
.cascade-bloc > * { grid-column: texte; }
/* Le cadre est un bloc ordinaire : en élément de grille direct, un SVG ne
   transmet pas sa hauteur à sa rangée, et le texte suivant se replie par-dessus
   le dessin. Le rapport suit le viewBox, 544 x 401 -- et 544 px, c'est 34 rem :
   a sa taille maximale le dessin est a l'echelle 1, donc ni le texte ni les
   barres ne tombent entre deux pixels. */
.prose .cascade-cadre { width: 100%; max-width: 34rem; aspect-ratio: 544 / 301;
                        margin: 2.4rem 0 1rem; }
/* La legende de ce dessin-la seulement : en italique, et detachee du paragraphe
   qui suit, pour qu'on voie qu'elle appartient au dessin et pas au texte. */
.prose .cascade-cadre + .legend { font-style: italic; margin-bottom: 2.2rem; }
.cascade { display: block; width: 100%; height: 100%; }
/* La taille de la legende : le dessin est dense, et ses phrases sont des
   notes posees a cote des barres, pas le texte courant. */
.cascade .nom { font: 400 12.5px/1 var(--sans); fill: var(--ink); }
.cascade .titre { font: 400 12px/1 var(--sans); fill: var(--muted);
                  text-anchor: end; }
/* Des bords nets : une barre de quatorze pixels de haut n'a pas a etre lissee. */
.cascade rect { shape-rendering: crispEdges; }
.cascade .val { font: 600 15px/1 var(--mono); fill: var(--muted);
                text-anchor: end; }
.cascade .depart { fill: var(--muted); opacity: .35; }
/* L'arrivée reprend l'accent en plus pâle : c'est le même chemin, vu d'en bas. */
.cascade .arrivee { fill: var(--accent); opacity: .5; }
.cascade .gain { fill: var(--accent); }
.cascade .val.gain { fill: var(--accent); }
/* Une marche qui ne change rien : un trait de la meme couleur que les gains --
   sa largeur dit deja qu'elle ne rapporte rien, la couleur n'a pas a le redire. */
.cascade .rien { fill: var(--accent); }
/* Le fil qui descend d'une barre à la suivante, et qui fait la cascade. */
.cascade .fil { stroke: var(--line); stroke-width: 1; }

/* Les quatre issues d'une même donne. Les deux parts que la donne décide à
   elle seule s'écartent du centre le long de leur propre bissectrice et pâlissent
   ensemble : ce qui reste en pleine couleur est ce que la façon de jouer change.
   Même repère que les deux autres dessins -- les unités sont les pixels occupés
   à 34 rem, donc l'échelle vaut 1. */
.camembert-bloc { display: contents; }
.camembert-bloc > * { grid-column: texte; }
.prose .camembert-cadre { width: 100%; max-width: 34rem; aspect-ratio: 544 / 304;
                          margin: 2.4rem 0 1rem; }
.prose .camembert-cadre + .legend { font-style: italic; margin-bottom: 2.2rem; }
.camembert { display: block; width: 100%; height: 100%; }
.camembert .nom { font: 400 12.5px/1 var(--sans); fill: var(--ink); }
/* Le libellé d'une part pâle pâlit avec elle : il nomme ce que le joueur ne
   décide pas. */
.camembert .nom.pale { fill: var(--muted); }
.camembert .groupe { font: 500 12.5px/1 var(--sans); fill: var(--ink); }
.camembert .fin, .camembert .groupe { text-anchor: end; }
.camembert .debut { text-anchor: start; }
.camembert .val { font: 600 15px/1 var(--mono); fill: var(--muted); }
.camembert .val.tranche { fill: var(--accent); }
/* Ce que la stratégie tranche, dans la couleur du Réfléchi ; la part minuscule
   qu'il perd en pâle, parce qu'elle va dans l'autre sens. */
.camembert .tranche { fill: var(--accent); }
.camembert .tranche.faible { fill: var(--accent); opacity: .5; }
/* Deux gris, pas un : sans la différence, la frontière entre les parties
   gagnées d'avance et les parties perdues d'avance ne se voit pas. */
.camembert .avance { fill: var(--muted); }
.camembert .avance.gagnee { opacity: .45; }
.camembert .avance.perdue { opacity: .22; }
.camembert .accolade,
.camembert .fil { fill: none; stroke: var(--line); stroke-width: 1; }
/* La liste ne sert que de repli sur téléphone, là où le dessin ne tient pas. */
.camembert-texte { display: none; }

/* Trois choses qu'on ne choisit pas : pour chaque critère, la configuration la
   moins favorable, la plus favorable, et ce qui les sépare. Les pictogrammes
   parlent le même langage que les autres dessins de la page -- la table carrée
   à quatre sièges vient de celui des rangs. */
.critere-bloc { display: contents; }
.critere-bloc > * { grid-column: texte; }
.prose .critere-cadre { width: 100%; max-width: 34rem; aspect-ratio: 544 / 270;
                      margin: 2.4rem 0 1rem; }
.prose .critere-cadre + .legend { font-style: italic; margin-bottom: 2.2rem; }
.critere { display: block; width: 100%; height: 100%; }
.critere .titre { font: 500 13px/1 var(--sans); fill: var(--ink); }
/* L'en-tête légende la colonne des écarts : il se centre sur elle, pas sur le
   dessin. */
.critere .unite { font: 400 12px/1 var(--sans); fill: var(--muted);
                  text-anchor: middle; }
.critere .nom { font: 400 12.5px/1 var(--sans); fill: var(--ink); }
.critere .sous { font: 400 11px/1 var(--sans); fill: var(--muted);
               text-anchor: middle; }
.critere .val { font: 600 15px/1 var(--mono); fill: var(--accent);
              text-anchor: middle; }
.critere .table { fill: none; stroke: var(--line); stroke-width: 1; }
/* L'arc fait presque le tour : il part du premier siège et s'arrête avant d'y
   revenir, donc il dit le sens et le cycle à la fois. */
.critere .tour { fill: none; stroke: var(--line); stroke-width: 1; }
.critere .tour.pleine { fill: var(--line); stroke: none; }
.critere .siege { fill: var(--line); }
.critere .siege.moi { fill: var(--accent); }
/* La cinquième carte en pleine couleur : c'est elle qu'on compare aux quatre
   de gauche. */
.critere .carte { fill: var(--accent); opacity: .4; }
.critere .carte.phare { opacity: 1; }
.critere .carte.piece { fill: var(--muted); opacity: .3; }
.critere .carte.suspect,
.critere .carte.arme { opacity: .75; }
/* Les familles se lisent en découpe dans la carte : une maison, un
   personnage, une clé anglaise. */
.critere .glyphe { fill: var(--bg); }
.critere .glyphe.trait { fill: none; stroke: var(--bg); stroke-width: 1.6;
                       stroke-linecap: round; }
.critere .glyphe.trait.epais { stroke-width: 1.8; }
.critere .fleche { stroke: var(--line); stroke-width: 1; fill: none; }
.critere .pointe { fill: var(--line); }
.critere-texte { display: none; }

@media (max-width: 699px) {
  /* À 316 px de large, les titres de ces dessins tomberaient sous huit pixels.
     Les refaire autrement reviendrait à en tenir deux ; le texte dit les mêmes
     chiffres, et se lit sans eux. */
  .cascade-bloc,
  .rangs-bloc { display: none; }
  /* Le camembert, lui, porte des chiffres que rien d'autre ne dit : il cède la
     place à sa liste au lieu d'emporter la section avec lui. */
  .camembert-cadre,
  .prose .camembert-cadre + .legend { display: none; }
  .camembert-texte { display: block; }
  /* Même raison pour les trois critères : les pictogrammes y tomberaient à
     quatre pixels, et leurs trois chiffres ne sont écrits nulle part ailleurs. */
  .critere-cadre,
  .prose .critere-cadre + .legend { display: none; }
  .critere-texte { display: block; }
}

.prose table { border-collapse: collapse; margin: 1.7rem 0 1.6rem;
               font-size: .86rem; }
.prose table + .legend { margin-top: -1.1rem; margin-bottom: 1.7rem; }
.prose th, .prose td { padding: .34rem .7rem; text-align: right;
                       border-bottom: 1px solid var(--line);
                       font: 500 .82rem/1.4 var(--mono); font-variant-numeric: tabular-nums; }
.prose td:first-child, .prose tbody th { font: 500 .84rem/1.4 var(--sans); }
.prose th:first-child, .prose td:first-child { text-align: left; }
.prose thead th { color: var(--muted); font: 500 .68rem/1.4 var(--mono);
                  text-transform: uppercase; letter-spacing: .06em;
                  vertical-align: bottom; }
/* La rangée des axes chapeaute celle des noms : pas de filet entre les deux,
   sans quoi elles se liraient comme deux en-tetes concurrents. */
.prose thead tr:first-child th { border-bottom: none; padding-bottom: 0;
                                 color: var(--muted); }
.prose thead tr:first-child th[colspan] { text-align: center; }

/* Trois nombres dans une case : l'agrege tient la ligne, les deux sieges se
   lisent dessous, plus petits. Le trait est en gras, son absence en italique —
   deux signes qui se distinguent sans couleur, la couleur etant deja prise par
   l'ecart au partage egal. */
.prose td .agrege { display: block; }
.prose td .trait-split {
  display: block; margin-top: .15rem;
  font-size: .72rem; letter-spacing: .02em;
}
.prose td .trait-split b { font-weight: 600; }
.prose td .trait-split i { font-style: italic; margin-left: .5rem; }
.prose .legend b { font-weight: 600; }
.prose .legend i { font-style: italic; }

.prose .figure { border: 1px solid var(--line); border-radius: 10px;
                 padding: .9rem 1rem; margin: .8rem 0 1rem; background: var(--panel); }
.prose .figure b { font: 600 1.4rem/1 var(--mono); font-variant-numeric: tabular-nums; }

/* --- Cluedo ---------------------------------------------------------------
   Le plateau est une grille de 24 x 25. Les pièces sont des blocs poses par
   dessus, les couloirs des cases individuelles : c'est la seule façon de garder
   la géométrie du plateau imprimé sans dessiner une image. */

.cluedo {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  grid-template-rows: repeat(25, 1fr);
  aspect-ratio: 24 / 25;
  height: 100%;
  max-height: 100%;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 6px;
  overflow: hidden;
}
.cluedo .void { background: var(--bg); }
.cluedo .corridor { background: var(--panel); }
/* Une porte se dessine comme sur un plan : un seuil épais dans le mur de la
   pièce, et le quart de cercle du battant qui s'ouvre vers l'intérieur. Le
   palier, lui, n'est qu'une case de couloir -- y être ne fait pas entrer. */
.cluedo .corridor.palier { position: relative; }
.cluedo .porte {
  position: absolute; inset: 0; pointer-events: none; z-index: 2;
}
.cluedo .porte svg { width: 100%; height: 100%; display: block; overflow: visible; }
.cluedo .porte path { fill: none; vector-effect: non-scaling-stroke; }
.cluedo .porte .seuil { stroke: var(--ink); stroke-width: 2.4; stroke-linecap: butt; }
.cluedo .porte .battant { stroke: var(--ink); stroke-width: 1; opacity: .5; }
/* Le battant se pose sur la première case de la pièce : on décale d'une case,
   puis on tourne pour que le mur tombe du bon côté. */
.cluedo .porte-haut    { transform: translateY(-100%); }
.cluedo .porte-bas     { transform: translateY(100%) rotate(180deg); }
.cluedo .porte-gauche  { transform: translateX(-100%) rotate(-90deg); }
.cluedo .porte-droite  { transform: translateX(100%) rotate(90deg); }

/* Le passage secret des quatre angles. Terne tant qu'on ne peut pas le prendre,
   vif et cliquable quand c'est notre pièce et notre tour. */
.cluedo .room .passage {
  font-size: .52rem; color: var(--muted); white-space: nowrap;
  border-top: 1px dotted var(--line); margin-top: 1px; padding-top: 1px;
}
.cluedo .room .passage.dispo {
  color: var(--good); font-weight: 600; cursor: pointer;
  border-top-color: var(--good);
}
.cluedo .room .passage.dispo:hover { text-decoration: underline; }
/* L'autre bout du passage s'allume comme les pièces où l'on peut marcher ; son
   propre rappel passe au vert, pour que la paire se lise d'un coup d'œil. */
.cluedo .room.par-passage .passage { color: var(--good); border-top-color: var(--good); }
.cluedo .corridor.reachable { background: color-mix(in srgb, var(--good) 30%, var(--panel)); cursor: pointer; }
.cluedo .corridor.reachable:hover { background: var(--good); }
.cluedo .corridor.tip { background: var(--accent); box-shadow: inset 0 0 0 1px var(--panel); }
.cluedo .room {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 2px;
  font-size: .62rem;
  line-height: 1.1;
  text-align: center;
  color: var(--muted);
  overflow: hidden;
}
.cluedo .room.reachable { border-color: var(--good); background: color-mix(in srgb, var(--good) 16%, var(--bg)); cursor: pointer; }
.cluedo .room.reachable:hover { background: color-mix(in srgb, var(--good) 34%, var(--bg)); }
.cluedo .room.here { border-color: var(--accent); border-width: 2px; }
.cluedo .room.tip { outline: 2px dashed var(--accent); outline-offset: -3px; }
.cluedo .room .rname { font-weight: 600; color: var(--ink); }
.cluedo .chips { display: flex; flex-wrap: wrap; gap: 2px; justify-content: center; }
.cluedo .chip {
  font-size: .55rem; padding: 0 3px; border-radius: 3px;
  background: var(--line); color: var(--ink); white-space: nowrap;
}
/* Un pion porte sa couleur partout : la même dans la pièce et dans le couloir.
   C'est ce qui rend la pastille de couloir lisible sans légende -- on a vu le
   nom écrit dans une pièce, on reconnaît la teinte ailleurs. */
.cluedo .pion-rose      { --pion: var(--pion-rose);      --pion-ink: var(--pion-rose-ink); }
.cluedo .pion-moutarde  { --pion: var(--pion-moutarde);  --pion-ink: var(--pion-moutarde-ink); }
.cluedo .pion-leblanc   { --pion: var(--pion-leblanc);   --pion-ink: var(--pion-leblanc-ink); }
.cluedo .pion-violet    { --pion: var(--pion-violet);    --pion-ink: var(--pion-violet-ink); }
.cluedo .pion-pervenche { --pion: var(--pion-pervenche); --pion-ink: var(--pion-pervenche-ink); }
.cluedo .pion-olive     { --pion: var(--pion-olive);     --pion-ink: var(--pion-olive-ink); }

/* --- la tablee : qui est assis ou ----------------------------------------
   Une hypothese se repond dans l'ordre de la table, a partir du voisin de
   gauche, et on s'arrete au premier qui montre. Cette bande rend l'ordre
   visible, parce que c'est une donnee du raisonnement et pas un decor : sans
   elle, « un tel ne peut rien montrer » ne se pese pas. */
.tablee { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem;
          margin: 0 0 .6rem; }
.tablee .siege { display: inline-flex; align-items: center; gap: .34rem;
                 padding: .16rem .5rem .16rem .18rem; border-radius: 999px;
                 border: 1px solid transparent; font-size: .82rem;
                 color: var(--muted); }
.tablee .jeton { width: 1.2rem; height: 1.2rem; border-radius: 50%;
                 display: grid; place-items: center;
                 font: 600 .68rem/1 var(--sans);
                 background: var(--pion, var(--accent));
                 color: var(--pion-ink, var(--on-accent)); }
.tablee .siege b { font-weight: 500; }
.tablee .siege em { font: 500 .66rem/1 var(--mono); font-style: normal;
                    opacity: .7; }
/* Trois questions, trois signes. La couleur dit qui — c'est celle du pion sur
   le plateau, la meme partout. Le lisere vert dit lequel est a vous, comme sur
   le plateau. Le fond dit a qui le tour. Une quatrieme couleur les melangerait. */
.tablee .siege.moi .jeton { box-shadow: 0 0 0 2px var(--good); }
.tablee .siege.actif { background: var(--sunk); border-color: var(--line);
                       color: var(--ink); }
.tablee .siege.dehors { opacity: .45; }
.tablee .siege.dehors b { text-decoration: line-through; }
.tablee .fleche { color: var(--line); font-style: normal; font-size: .72rem; }

.tablee .pion-rose      { --pion: var(--pion-rose);      --pion-ink: var(--pion-rose-ink); }
.tablee .pion-moutarde  { --pion: var(--pion-moutarde);  --pion-ink: var(--pion-moutarde-ink); }
.tablee .pion-leblanc   { --pion: var(--pion-leblanc);   --pion-ink: var(--pion-leblanc-ink); }
.tablee .pion-violet    { --pion: var(--pion-violet);    --pion-ink: var(--pion-violet-ink); }
.tablee .pion-pervenche { --pion: var(--pion-pervenche); --pion-ink: var(--pion-pervenche-ink); }
.tablee .pion-olive     { --pion: var(--pion-olive);     --pion-ink: var(--pion-olive-ink); }

.cluedo .chip.pion { background: var(--pion, var(--accent)); color: var(--pion-ink, var(--on-accent)); }
/* Le vôtre : un liseré, pas une autre couleur. La couleur dit qui, le liseré
   dit lequel est à vous -- deux questions, deux signes. */
.cluedo .chip.pion.moi { box-shadow: 0 0 0 1.5px var(--good); }
.cluedo .pawn {
  align-self: center; justify-self: center;
  width: 86%; height: 86%; border-radius: 50%;
  background: var(--pion, var(--accent)); border: 1px solid var(--panel);
  display: flex; align-items: center; justify-content: center;
  color: var(--pion-ink, var(--on-accent));
  font-size: .5rem; font-weight: 700; line-height: 1;
  /* L'initiale double la couleur : elle sauve la lecture quand le plateau est
     petit, et elle ne demande rien à qui distingue mal les teintes. */
}
.cluedo .pawn.moi { box-shadow: 0 0 0 2px var(--good); }

/* La bande « qui est qui » sous le plateau de la Théorie : le pion tel qu'on le
   voit dans un couloir, puis le nom entier. */
.pions-legende { display: flex; flex-wrap: wrap; justify-content: center;
                 gap: .35rem 1.1rem; margin: .7rem 0 0;
                 font-size: .82rem; color: var(--muted); }
.pions-legende span { display: inline-flex; align-items: center; gap: .35rem; }
.pions-legende .pawn { width: 1.15rem; height: 1.15rem; flex: none;
                       border-radius: 50%; border: 1px solid var(--panel);
                       display: inline-flex; align-items: center;
                       justify-content: center; font: 700 .62rem/1 var(--sans);
                       background: var(--pion, var(--accent));
                       color: var(--pion-ink, var(--on-accent));
                       font-style: normal; }
.pions-legende .pion-rose { --pion: var(--pion-rose); --pion-ink: var(--pion-rose-ink); }
.pions-legende .pion-moutarde { --pion: var(--pion-moutarde); --pion-ink: var(--pion-moutarde-ink); }
.pions-legende .pion-leblanc { --pion: var(--pion-leblanc); --pion-ink: var(--pion-leblanc-ink); }
.pions-legende .pion-violet { --pion: var(--pion-violet); --pion-ink: var(--pion-violet-ink); }
.pions-legende .pion-pervenche { --pion: var(--pion-pervenche); --pion-ink: var(--pion-pervenche-ink); }
.pions-legende .pion-olive { --pion: var(--pion-olive); --pion-ink: var(--pion-olive-ink); }

/* La case qui éteint le coup conseillé : une habitude de jeu, pas une
   commande. Elle se tient donc au pied du plateau, en petit et en gris, sous
   les boutons qui agissent. */
.sans-tip { display: flex; align-items: center; gap: .4rem; margin: .7rem 0 0;
            font-size: .85rem; color: var(--muted); cursor: pointer; }
.sans-tip input { cursor: pointer; }

.cluedo-controls { display: flex; flex-direction: column; gap: .5rem; margin: .6rem 0 0; }
.cluedo-controls .ask { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.cluedo-controls .ask select { font-size: .85rem; flex: 0 1 auto;
                              max-width: 15rem; min-width: 9rem; }
.cluedo-controls .prompt { font-size: .85rem; color: var(--muted); margin: 0; }
.cluedo-controls .cards { display: flex; flex-wrap: wrap; gap: .35rem; }

/* le carnet */
.carnet { font-size: .68rem; }
.carnet table { width: 100%; border-collapse: collapse; }
.carnet th { font-weight: 500; color: var(--muted); font-size: .62rem;
             padding: 2px 1px; text-align: center; }
.carnet th.cardcol { text-align: left; }
.carnet td { padding: 1px 2px; border-top: 1px solid var(--line); text-align: center; }
.carnet td.cardcol { text-align: left; color: var(--ink); white-space: nowrap;
                     overflow: hidden; text-overflow: ellipsis; max-width: 108px; }
.carnet tr.gone td.cardcol { color: var(--muted); text-decoration: line-through; }
.carnet .grp td { background: var(--bg); font-weight: 600; color: var(--ink);
                  text-align: left; font-size: .66rem; padding: 3px 2px; }
.carnet .no { color: var(--bad); }
.carnet .yes { color: var(--good); font-weight: 600; }
.carnet .env { color: var(--accent); font-weight: 600; }
/* Le chiffre d'une réfutation vue de loin, posé dans la case : le même numéro
   dans trois cases d'une colonne dit « il détient l'une de ces trois ». Barré,
   c'est une carte qu'on lui a retirée depuis. Plus petit que les croix, parce
   qu'il dit moins. */
.carnet .disj { font-style: normal; font-size: .58rem; color: var(--muted);
                margin-left: 1px; vertical-align: 1px; }
.carnet .disj.barre { text-decoration: line-through; opacity: .55; }
.carnet .vu { font-style: normal; font-size: .58rem; color: var(--muted);
              margin-left: 2px; letter-spacing: .02em; }
.carnet td.envcol { border-left: 1px solid var(--line); }

/* Le carnet montre du doigt : la case que l'étape vient de remplir s'allume, et
   l'infobulle dit pourquoi, à côté. Une liste séparée obligeait à chercher la
   case ; ici la marque est déjà là où on l'écrirait à la main. */
#carnetPanel { position: relative; }
.carnet td.neuve {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: 3px;
}
.bulle {
  position: absolute; z-index: 5; max-width: 100%;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--accent); border-radius: 6px;
  padding: .35rem .5rem; font-size: .74rem; line-height: 1.35;
  box-shadow: 0 6px 18px rgb(0 0 0 / .18);
}
.bulle .acte { font-weight: 700; }
.bulle .acte.no { color: var(--bad); }
.bulle .acte.yes { color: var(--good); }
.bulle .pourquoi { display: block; color: var(--muted); margin-top: .15rem; }

.fresh { list-style: none; margin: 0 0 .8rem; padding: 0; font-size: .78rem; }
.fresh li { padding: .25rem 0; border-bottom: 1px solid var(--line); }
.fresh .act { font-weight: 600; }
.fresh .act.no { color: var(--bad); }
.fresh .act.yes { color: var(--good); }
.fresh .why { color: var(--muted); display: block; font-size: .72rem; }
.fresh .none { color: var(--muted); border: none; }

.margin { list-style: none; margin: 0 0 .8rem; padding: 0; font-size: .78rem; }
.margin li { padding: .25rem 0; border-bottom: 1px solid var(--line); color: var(--muted); }
.margin b { color: var(--ink); font-weight: 600; }

/* Le sommaire de la Théorie. Une page de plusieurs écrans sans prise est une
   page qu'on abandonne au deuxième. */
.toc { list-style: none; margin: 0; padding: 0; }
.toc li { margin-bottom: .1rem; }
.toc a {
  display: block; padding: .3rem .5rem; border-radius: 6px;
  color: var(--muted); text-decoration: none; font-size: .84rem;
  border-left: 2px solid transparent;
}
.toc a:hover { background: var(--bg); color: var(--ink); }
/* Le sommaire navigue, il ne mesure rien : il suit donc la couleur du menu de
   gauche et non celle du cadran, qui signale les mesures. */
.toc a.here { color: var(--nav); border-left-color: var(--nav); font-weight: 600; }
.toc li.sub a { padding-left: 1.1rem; font-size: .78rem; }

/* la grille du carnet */
.fig-carnet { display: flex; flex-wrap: wrap; gap: 1.4rem 2rem;
              justify-content: center; }
.fig-carnet svg { flex: 1 1 16rem; max-width: 22rem; }
.fig-carnet figcaption { flex-basis: 100%; margin-top: 0; }
.fig-grid-title { fill: var(--ink); font: 600 11px var(--sans, system-ui);
                  text-transform: uppercase; letter-spacing: .06em; }
.fig-rule { stroke: var(--line); stroke-width: 1; }
.fig-no { fill: var(--bad); font: 600 15px var(--sans, system-ui); }
.fig-yes { fill: var(--good); font: 600 15px var(--sans, system-ui); }
.fig-note { fill: var(--mid); font: 500 10.5px var(--sans, system-ui); }



/* Skyjo : les figures reprennent les cartes de la partie, en plus petit. */
.fig-skyjo .sk-card { width: 34px; height: 44px; font-size: .92rem;
                      border-radius: 6px; }
.fig-rangee { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; }
.fig-moyenne { text-align: center; font-size: .8rem; color: var(--muted);
               margin-top: .6rem; }
.fig-moyenne b { color: var(--ink); }
.fig-grid-titre { font: 600 11px var(--sans, system-ui); color: var(--ink);
                  text-transform: uppercase; letter-spacing: .06em;
                  margin-bottom: .5rem; text-align: center; }
.fig-skyjo .sk-card.fig-vise { outline: 2px solid var(--accent);
                               outline-offset: 1px; }

/* Deux figures cote a cote, quand elles racontent un avant et un apres. */
/* Le duo remplace la marge de chaque figure par la sienne : sans elle, le bloc
   entier collait au texte, au-dessus comme en-dessous. */
.fig-duo { grid-column: large; display: grid; gap: 1.5rem;
           grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
           margin: 2rem 0 2.2rem; }
.fig-duo .fig { margin: 0; }
/* Le carnet d'une figure est une grille reduite : elle n'a pas a remplir
   la largeur comme celle du panneau de partie. */
.fig .carnet table { max-width: 26rem; margin: 0 auto; }
/* Le carnet du Spontané : la feuille de la boîte, réduite à une colonne. */
.fig .carnet.liste table { max-width: 12rem; }
.carnet.liste td:last-child { width: 2.2rem; border-left: 1px solid var(--line); }

/* Une figure n'est pas un panneau de jeu : le plateau s'y dimensionne par la
   largeur, et la legende s'aligne sur la mesure du texte pour que l'oeil
   retrouve sa colonne. */
/* Les figures de la Theorie. Ce ne sont pas des dessins : ce sont des
   positions, peintes par le code qui peint la partie. Seules leur marge et leur
   legende sont propres a la page. */
.fig { margin: 1.2rem 0 1.4rem; }
/* Une photo n'a pas besoin de la largeur d'un tableau : cadree a la mesure du
   texte, elle se regarde, au-dela elle s'impose. Le portrait est le cas le plus
   exposé — une hauteur libre pousserait le reste de la page hors de l'ecran. */
.fig img { display: block; margin: 0 auto; width: 100%; max-width: 20rem;
           height: auto; border-radius: 8px; border: 1px solid var(--line); }
.fig figcaption { font-size: .8rem; line-height: 1.55; color: var(--muted);
                  margin-top: .6rem; }
.fig figcaption b { color: var(--ink); font-weight: 600; }
.fig-mini .mini { margin: 0 auto; }

/* Les quatre planches de fenêtres, côte à côte. Elles se replient à deux par
   ligne quand la colonne se resserre : un plateau de sept colonnes ne se lit
   plus en dessous d'environ 150 px. */
.fenetres { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
/* Sous ~640px, quatre plateaux de sept colonnes deviennent des timbres : on
   passe à deux par ligne plutôt que de les rendre illisibles. */
@media (max-width: 640px) { .fenetres { grid-template-columns: repeat(2, 1fr); } }
.fenetres figure { margin: 0; min-width: 0; }
.fenetres svg { width: 100%; height: auto; display: block; }
.fenetres .case { fill: var(--sunk); }
/* La teinte sépare les traits voisins ; la clarté, elle, suit le thème —
   sinon les traits pâlissent sur le fond papier. */
.fenetres line { stroke: hsl(var(--h) 68% var(--fenetre-lum));
                 stroke-width: 2; stroke-linecap: round; opacity: .9; }
.fenetres figcaption { margin-top: .35rem; text-align: center;
                       font-size: .78rem; color: var(--muted); }
.fenetres figcaption b { color: var(--ink); font-weight: 600; }
/* La premiere legende respire sous le plateau : collee a lui, elle se lisait
   comme une rangee de plus. */
.fig-mini .mini + .legend { margin-top: .9rem; }

.fig > .cluedo, .fig > .board { height: auto; width: 100%; max-width: 34rem;
                                margin: 0 auto; }
/* Borner la largeur laisse une grille carree occuper 34rem de haut, quand un
   plateau large tient dans la moitie. Le plateau porte son propre rapport dans
   --cols / --rows : on convertit donc une hauteur maximale en largeur, et
   toutes les figures recoivent le meme budget vertical. */
.fig > .board { max-width: min(34rem, 21rem * var(--cols) / var(--rows)); }
/* La legende se centre sous la figure, comme elle. Alignee a gauche, elle
   partait du bord du bloc et non de celui du plateau, qui est centre.
   Les legendes du mini-plateau — ce que dit le chiffre, ce que dit la barre —
   suivent la meme regle : sans elle, elles s'etalaient sur toute la largeur
   du bloc pendant que le plateau restait centre au milieu. */
.fig figcaption, .fig .legend { max-width: 64ch; margin-left: auto;
                                margin-right: auto; text-align: center; }

/* --- le defi : un seul chiffre, et son detail coup par coup ---------------
   Zero, c'est le hasard. Cent, c'est le niveau vise. Deux reperes, pas trois
   modeles : le panneau ne pose qu'une question et n'y repond qu'une fois. */
.score-ligne { display: flex; align-items: baseline; gap: .35rem; }
.score-chiffre { font-size: 2.1rem; font-weight: 700; color: var(--accent);
                 font-variant-numeric: tabular-nums; line-height: 1; }
.score-sur { font-size: .9rem; color: var(--muted); }
.score-track { height: 10px; border-radius: 5px; background: var(--bg);
               border: 1px solid var(--line); overflow: hidden;
               margin: .45rem 0 .3rem; }
.score-fill { height: 100%; background: var(--accent); transition: width .25s; }
.score-reperes { display: flex; justify-content: space-between; margin: 0 0 1rem;
                 font-size: .72rem; color: var(--muted); }

#posterior .colonne .seg.accord { background: var(--accent); width: 100%;
                                  align-self: flex-end; border-radius: 3px 3px 0 0; }
/* Rien a trouver ici : la colonne le dit, sinon une barre vide se lirait
   « tu t'es trompe ». */

/* Le detail des issues, dans le tableau croise comme dans les cartes du coach.
   Une part de points ne dit pas comment elle est faite : 50% peut etre que des
   nulles, ou moitie gagnees moitie perdues. */
.issues { display: flex; height: 4px; border-radius: 2px;
          overflow: hidden; margin-top: 4px; }
.issues i { display: block; min-width: 0; }
.issues .g, .cle.g { background: var(--good); }
.issues .n, .cle.n { background: var(--line); }
.issues .p, .cle.p { background: var(--bad); }
.cle { display: inline-block; width: 9px; height: 9px; border-radius: 2px;
               vertical-align: -1px; }

/* Deux lignes par case : quand elle commence, quand elle ne commence pas.
   Chacune porte sa part de points et le detail de ses issues. */
.croise .place { display: grid; grid-template-columns: 2.8em 1fr;
                 align-items: center; gap: 5px; margin-top: 4px; }
.croise .place b { font-weight: 500; font-size: .74rem; opacity: .85;
                   text-align: right; }

/* Trois nombres empiles sans etiquette ne se lisent pas : on nomme les deux
   lignes de detail, la phrase entiere restant dans l'infobulle. */
.croise .place { display: block; margin-top: 6px; }
.croise .place .quand { display: block; font-size: .62rem; color: var(--muted);
                        text-transform: none; letter-spacing: 0;
                        white-space: nowrap; margin-bottom: 1px;
                        text-align: left; }
/* Le chiffre est la part gagnee : il se pose a gauche, contre le depart de la
   barre, la ou le vert commence. Le point vert dit lequel des trois segments
   il mesure. */
.croise .mesure { display: grid; grid-template-columns: auto 1fr;
                  align-items: center; gap: 5px; }
.croise .place b { font-weight: 500; font-size: .74rem; text-align: left;
                   white-space: nowrap; }
.croise .place b::before { content: ""; display: inline-block; width: 6px;
                           height: 6px; border-radius: 2px; margin-right: 4px;
                           background: var(--good); vertical-align: 1px; }
.croise .place .issues { margin-top: 0; }

/* La premiere ligne d'une case porte le total : elle se detache des deux
   lignes de detail qui la suivent. */
/* Une hierarchie, pas trois lignes egales : le total se lit d'abord, le detail
   par place ne se lit que si on le cherche. */
.croise .place:first-child { margin-top: 0; }
.croise .place:first-child .quand { font-size: .58rem; opacity: .6; }
.croise .place:first-child b { font-size: .95rem; font-weight: 600; }
.croise .place:first-child .issues { height: 6px; }
.croise .place:first-child b::before { width: 7px; height: 7px; }

/* Le detail : plus petit, plus pale, legerement en retrait. */
.croise .place + .place { margin-top: 4px; margin-left: 10px; opacity: .62; }
.croise .place:first-child { margin-bottom: 7px; }
.croise .place + .place .quand { font-size: .58rem; }
.croise .place + .place b { font-size: .68rem; font-weight: 400; }
.croise .place + .place .issues { height: 3px; }
.croise .place + .place b::before { width: 5px; height: 5px; margin-right: 3px; }
/* Au survol, le detail reprend sa pleine force : on le cherche, il repond. */
.croise td:hover .place + .place { opacity: .9; }

/* --- les énigmes de carnet --- */
.enigme { max-width: 38rem; margin: 0 auto; padding-block: .5rem 2rem; }
.enigme .numero { color: var(--muted); font-size: .8rem; text-transform: uppercase;
                  letter-spacing: .06em; margin: 0 0 .6rem; }
.enigme .recit { list-style: none; margin: 0 0 1.2rem; padding: 0;
                 font: 400 1rem/1.55 var(--serif); }
.enigme .recit li { padding: .35rem 0; border-top: 1px solid var(--line); }
.enigme .recit li:last-child { border-bottom: 1px solid var(--line); }
.enigme .consigne { color: var(--muted); font-size: .85rem; margin: 0 0 .4rem; }
.enigme .carnet { font-size: .9rem; margin: 0 0 1rem; }
.enigme .carnet table { max-width: 28rem; }
/* Les cases à remplir se voient comme des cases : un cadre chacune, comme sur
   la feuille de la boîte. */
.carnet.saisie td { padding: 2px; }
.carnet.saisie .case { width: 100%; min-width: 2.6rem; height: 2rem;
                       border: 1px solid var(--line); background: var(--panel);
                       color: var(--ink); font-size: .95rem; cursor: pointer;
                       border-radius: 4px; }
.carnet.saisie .case:hover { border-color: var(--accent); }
.enigme .question { font: 500 1.05rem/1.4 var(--serif); margin: .4rem 0 .5rem; }
.enigme .options { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0 0 .4rem; }
.enigme .option.choisie { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.enigme .erreur { color: var(--bad); font-size: .82rem; min-height: 1.1em; margin: 0 0 .4rem; }
.enigme .correction { margin-top: .8rem; }
.enigme .verdict-enigme { font-weight: 600; margin: 0 0 .6rem; }
.enigme .verdict-enigme.juste { color: var(--good); }
.enigme .verdict-enigme.faux { color: var(--bad); }
.enigme .explication { font: 400 .95rem/1.55 var(--serif); margin: 0 0 1rem; }
.suivi { list-style: none; margin: 0; padding: 0; font-size: .85rem; }
.suivi li { padding: .35rem 0; border-top: 1px solid var(--line); color: var(--muted); }
.suivi li.courante { color: var(--ink); font-weight: 600; }
.suivi li.juste { color: var(--good); }
.suivi li.faux { color: var(--bad); }
.enigme .carnet .disj { font-size: .8rem; }
