/**
 * PlayAwale — Page « Jouer en réseau »
 * -----------------------------------------------------------------------------
 * Deux joueurs, deux écrans.
 *
 * ELLE NE REDESSINE RIEN DU JEU
 * Le plateau, les panneaux, les scores, les pendules, la barre d'outils et les
 * fenêtres viennent des composants, et sont ceux du solo. La pendule du camp du
 * haut vient elle aussi du composant — elle n'appartient plus à la partie à deux
 * depuis que ce mode la demande.
 *
 * ELLE N'AJOUTE QUE CE QUI N'EXISTE QU'ICI, ET QUI PRÉCÈDE LA PARTIE :
 *   1. le choix d'un salon — créer, ou rejoindre avec un code ;
 *   2. l'attente de l'adversaire, où le code s'affiche en grand ;
 *   3. la ligne qui dit où en est la liaison.
 *
 * RIEN N'EST RETOURNÉ
 * Contrairement au même écran, les deux joueurs ne partagent pas l'appareil :
 * chacun lit le sien à l'endroit. C'est l'affichage du plateau qui présente le
 * camp de celui qui regarde en bas — voir « viewpoint » dans board-view.js —, et
 * il n'y a donc aucune rotation à écrire ici.
 *
 * @package   PlayAwale
 * @version   2.0
 * @author    Nicolas Lion — Développeur web — https://nlion.fr
 * @copyright 2026 Nicolas Lion
 */

/* =============================================================================
   MISE EN PAGE
   La même que le solo et la partie à deux : une colonne centrée.
   ============================================================================= */

.net-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}


/* =============================================================================
   LE CHOIX D'UN SALON
   -----------------------------------------------------------------------------
   Le composant « picker » porte le panneau et la liste ; cette feuille n'ajoute
   que le titre, la phrase d'introduction et le champ de code.
   ============================================================================= */

/* LES DEUX ÉCRANS D'AVANT-PARTIE SUIVENT LA MISE EN PAGE DE L'ACCUEIL
   Titre brun sur le décor, avec l'ombre qui l'en détache ; phrase d'explication
   dans le brun adouci du texte secondaire, sans ombre. Ce sont les mêmes règles
   que « .home-title » et « .home-baseline » — voir pages/home.css —, et ces
   écrans-là précèdent le jeu exactement comme l'accueil le précède. */

.net-lobby__title {
  margin: 0 0 var(--space-xs);

  font-family: var(--font-display);
  font-size: var(--font-size-title);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  text-shadow: var(--shadow-title);
}

.net-lobby__lead {
  margin: 0 0 var(--space-lg);

  font-size: var(--font-size-lg);
  line-height: var(--line-height-base);
  color: var(--color-text-muted);
}


/* -----------------------------------------------------------------------------
   Les parties en cours
   -----------------------------------------------------------------------------
   Une ligne par partie : son code, où elle en est, et les deux gestes qu'on
   peut poser dessus — y revenir, ou y renoncer.

   La grille garde les quatre colonnes alignées d'une ligne à l'autre, quelle
   que soit la longueur de l'état traduit. Sur écran étroit, l'état passe sous
   le code plutôt que de tout comprimer : c'est le code qu'on lit d'abord.
   -------------------------------------------------------------------------- */

.net-games {
  margin-bottom: var(--space-lg);
  text-align: left;
}

.net-games__title {
  margin: 0 0 var(--space-sm);

  font-family: var(--font-display);
  font-size: var(--font-size-button);
  color: var(--color-text);
  text-shadow: var(--shadow-heading);
}

.net-games__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);

  margin: 0;
  padding: 0;
  list-style: none;
}

/* Cinq colonnes : le code, l'état qui prend ce qui reste, puis les trois
   gestes — reprendre, partager, renoncer. */
.net-game {
  display: grid;
  grid-template-columns: auto 1fr auto auto auto;
  align-items: center;
  gap: var(--space-sm) var(--space-md);

  padding: var(--space-sm) var(--space-md);

  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-raised);
}

/* Le code, comme partout : espacé et à chasse fixe, pour qu'aucune lettre ne
   se confonde avec sa voisine. */
.net-game__code {
  font-family: var(--font-body);
  font-size: var(--font-size-lg);
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--color-text);
}

.net-game__state {
  display: flex;
  align-items: center;
  gap: var(--space-xs);

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);
  color: var(--color-text-muted);
}

/* LA PASTILLE DE PRÉSENCE
   -----------------------------------------------------------------------------
   Un point vert quand l'adversaire est en ligne, et RIEN quand il ne l'est pas :
   marquer aussi l'absence ferait un tableau de bord anxieux, alors qu'être
   absent est le cas ordinaire — on joue en réseau comme par correspondance.

   « hidden » la retire du flux, et l'écart de la ligne disparaît avec elle : le
   texte d'état se recale de lui-même contre le code.

   Elle se dimensionne en « em » : elle suit la taille du texte à côté duquel
   elle se pose, sur un téléphone comme sur un grand écran. */
.net-game__peer {
  flex: none;
  width: 0.7em;
  height: 0.7em;

  background-color: var(--color-online);
  border-radius: var(--radius-full);

  /* Un halo très léger, qui la détache du fond clair de la carte sans lui
     dessiner de bordure. */
  box-shadow: 0 0 0 2px var(--color-online-halo);
}

/* Le bouton de reprise garde la matière des autres, en plus discret : c'est un
   geste courant, pas la décision de l'écran. */
.net-game__resume {
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--font-size-base);
}

.net-game__resume[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Le partage et le drapeau reprennent la pastille de la barre d'outils : le même
   geste porte le même dessin, ici comme en partie. */
.net-game__share,
.net-game__resign {
  position: static;
}

@media (max-width: 34rem) {

  /* Le code et son état l'un sous l'autre, les trois gestes à droite. */
  .net-game {
    grid-template-columns: 1fr auto auto auto;
  }

  .net-game__code {
    grid-column: 1;
  }

  .net-game__state {
    grid-column: 1;
    grid-row: 2;
  }

  .net-game__resume {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  .net-game__share {
    grid-column: 3;
    grid-row: 1 / span 2;
  }

  .net-game__resign {
    grid-column: 4;
    grid-row: 1 / span 2;
  }
}


/* -----------------------------------------------------------------------------
   Créer, et rejoindre : deux cartes du même dessin
   -----------------------------------------------------------------------------
   Les deux gestes pèsent autant : chacun a sa carte, avec un titre, une ligne où
   l'on agit — le choix « publique ou privée » et « Créer » d'un côté, le code et
   « Rejoindre » de l'autre —, et une précision. Côte à côte sur grand écran,
   l'une sous l'autre ailleurs : deux cartes de deux cent cinquante pixels ne
   laisseraient la place ni au choix ni au code.
   -------------------------------------------------------------------------- */

.net-lobby .picker__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

@media (min-width: 56.25rem) {
  .net-lobby .picker__list {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Le choix prend la place que le champ du code prend dans l'autre carte. */
.net-visibility {
  flex: 1 1 10em;
  min-width: 0;

  margin: 0;
  padding: 0;
  border: none;
}

/* Deux pastilles dans un creux : le vocabulaire du sélecteur de vitesse du
   rejeu, qui est celui d'un trou du plateau. */
.net-visibility__switch {
  display: flex;
  gap: 2px;
  padding: 3px;

  background-color: var(--color-sand-100);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-hollow);
}

.net-visibility__choice {
  position: relative;
  flex: 1 1 0;
  cursor: pointer;
}

/* Le bouton radio reste le vrai contrôle — clavier, lecteur d'écran —, posé
   invisible sur son libellé : c'est le libellé qui se dessine. */
.net-visibility__input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.net-visibility__label {
  display: block;
  padding: var(--space-xs) var(--space-sm);

  font-family: var(--font-body);
  font-size: var(--font-size-base);
  text-align: center;
  color: var(--color-text-muted);

  border-radius: var(--radius-full);
  transition: background var(--duration-fast), color var(--duration-fast);
}

.net-visibility__input:checked + .net-visibility__label {
  font-weight: 700;
  color: var(--color-text);
  background-image: var(--gradient-amber-raised);
}

.net-visibility__input:focus-visible + .net-visibility__label {
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
}


/* -----------------------------------------------------------------------------
   Les parties en ligne
   -----------------------------------------------------------------------------
   Une carte par partie, de la matière des lignes « vos parties » : les deux
   joueurs face à face avec leur score, dessous ce que devient la partie et qui
   la regarde, à droite le geste. Rien de plus : une liste qu'on parcourt du
   regard, pas un tableau qu'on lit.
   -------------------------------------------------------------------------- */

.net-public {
  margin-top: var(--space-xl);
  text-align: left;
}

.net-public__empty {
  margin: 0;

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
  color: var(--color-text-muted);
}

.net-public__list {
  display: grid;
  gap: var(--space-sm);

  margin: 0;
  padding: 0;
  list-style: none;
}

/* Sur grand écran, le choix passe sur deux colonnes : la liste le suit. */
@media (min-width: 56.25rem) {
  .net-public__list {
    grid-template-columns: repeat(2, 1fr);
  }
}

.net-room {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-xs) var(--space-md);

  padding: var(--space-sm) var(--space-md);

  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-raised);
}

.net-room__match {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

/* Le portrait d'un joueur, logé dans un creux rond — celui des icônes du choix,
   voir « picker-button__icon ». */
.net-room__face,
.net-room__seat {
  flex: none;
  width: 44px;
  height: 44px;

  object-fit: contain;
  background-color: rgba(60, 39, 35, 0.08);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-hollow);
}

/* Un joueur parti s'estompe : sa place est peut-être à prendre. */
.net-room__face--away {
  opacity: 0.45;
  filter: grayscale(0.6);
}

/* La place libre d'une partie qui attend : un creux en pointillés, qu'on vient
   remplir. */
.net-room__seat {
  display: grid;
  place-items: center;

  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  color: var(--color-text-muted);

  background: none;
  border: 2px dashed var(--color-sand-400);
  box-shadow: none;
}

.net-room__score {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--space-2xs);
  min-width: 3.2em;

  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  line-height: 1;
  color: var(--color-text);
}

.net-room__score-sep {
  color: var(--color-text-muted);
}

.net-room__info {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.net-room__state {
  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);
  color: var(--color-text);
}

.net-room__crowd {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.net-room__eye {
  flex: none;
  width: 16px;
  height: 16px;
}

.net-room__count {
  margin-right: var(--space-2xs);
  font-weight: 700;
}

/* Les visages en petit se chevauchent, cerclés du fond de la carte : une petite
   foule, et non une rangée de vignettes. */
.net-room__faces {
  display: inline-flex;
}

.net-room__mini {
  flex: none;
  width: 22px;
  height: 22px;

  object-fit: contain;
  background-color: var(--color-sand-100);
  border-radius: var(--radius-full);
  box-shadow: 0 0 0 2px var(--color-surface);
}

.net-room__faces .net-room__mini + .net-room__mini {
  margin-left: -6px;
}

.net-room__more,
.net-room__anon-count {
  font-size: 0.85em;
}

/* Les spectateurs sans visage : une seule silhouette, et leur nombre. */
.net-room__anon {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: var(--space-2xs);
}

.net-room__go {
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--font-size-base);
}

/* Téléphone : le geste reste à droite des joueurs, les spectateurs passent
   dessous et prennent toute la largeur. */
@media (max-width: 34rem) {

  .net-room {
    grid-template-columns: 1fr auto;
  }

  .net-room__info {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .net-room__face,
  .net-room__seat {
    width: 38px;
    height: 38px;
  }
}


/* =============================================================================
   PARTAGER UNE PARTIE
   -----------------------------------------------------------------------------
   La fenêtre de repli, là où le partage du système manque. Elle emprunte tout au
   composant de fenêtre modale ; il n'y a ici que l'invitation, la liste des
   canaux, et la mention de copie.
   ============================================================================= */

/* L'invitation telle qu'elle partira : on doit pouvoir la lire avant de
   l'envoyer, et la sélectionner à la main si le presse-papiers se refuse. */
.share-invite {
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-md);

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
  text-align: left;
  word-break: break-word;

  /* L'invitation porte un passage à la ligne entre la phrase et le lien : il
     doit se voir ici comme il se verra dans le message envoyé. */
  white-space: pre-line;

  color: var(--color-text);
  background-color: var(--color-sand-100);
  border-radius: var(--radius-sm);
}

/* Les canaux : des boutons de même taille, qui se replient d'eux-mêmes sur un
   écran étroit. */
.share-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);

  margin: 0 0 var(--space-md);
  padding: 0;
  list-style: none;
}

.share-link {
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--font-size-base);

  /* Un lien, pas un bouton : le soulignement du texte courant n'a rien à faire
     sur une pastille. */
  text-decoration: none;
}

.share-copied {
  margin: 0 0 var(--space-md);

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  color: var(--color-online);
}


/* -----------------------------------------------------------------------------
   Ce qui n'a pas pu se faire
   -----------------------------------------------------------------------------
   Un code incomplet, la liste déjà pleine. Une ligne dans la page, et non une
   fenêtre du navigateur : le site n'en ouvre nulle part ailleurs.
   -------------------------------------------------------------------------- */

.net-lobby__message {
  margin: var(--space-md) 0 0;

  font-family: var(--font-body);
  font-size: var(--font-size-base);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  text-shadow: var(--shadow-heading);
}


/* -----------------------------------------------------------------------------
   Rejoindre avec un code
   -----------------------------------------------------------------------------
   Un formulaire, et non deux boutons : la touche Entrée valide alors la saisie
   sans qu'on ait à viser quoi que ce soit.

   Il reprend la matière des boutons de la liste — même fond, même arrondi, même
   remplissage — pour former avec eux une seule suite de choix, et non un bloc
   rapporté.
   -------------------------------------------------------------------------- */

.net-create,
.net-join {
  padding: var(--space-md) var(--space-lg);

  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-raised);
  text-align: left;
}

.net-create__title,
.net-join__label {
  display: block;
  margin: 0 0 var(--space-xs);

  font-family: var(--font-display);
  font-size: var(--font-size-button);
  color: var(--color-text);
}

/* Le champ et son bouton sur une ligne, le champ prenant ce qui reste.
   « wrap » les met l'un sous l'autre quand la largeur ne suffit plus, plutôt
   que de réduire le champ à quelques caractères. */
.net-create__row,
.net-join__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

/* LE CODE SE LIT CARACTÈRE PAR CARACTÈRE
   Il se dicte à voix haute et se recopie à la main : les lettres sont donc
   espacées, en capitales et à chasse fixe, pour qu'aucune ne se confonde avec
   sa voisine. L'alphabet du relais écarte déjà les signes qu'on confond — ni
   0/O, ni 1/I/L, ni 5/S —, la mise en forme fait le reste. */
.net-join__field {
  flex: 1 1 8em;
  min-width: 6em;
  padding: var(--space-sm) var(--space-md);

  font-family: var(--font-body);
  font-size: var(--font-size-button);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;

  color: var(--color-text);
  background-color: var(--color-sand-100);
  border: 2px solid var(--color-sand-400);
  border-radius: var(--radius-sm);
}

.net-join__field:focus-visible {
  outline: 3px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.net-create__note,
.net-join__note {
  margin: var(--space-sm) 0 0;

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);
  color: var(--color-text-muted);
}


/* =============================================================================
   L'ATTENTE DE L'ADVERSAIRE
   -----------------------------------------------------------------------------
   Le code occupe le centre de l'écran : c'est la seule chose à faire pendant
   cette attente — le lire, et le donner.
   ============================================================================= */

.net-waiting {
  width: 100%;
  max-width: min(92vw, 520px);
  text-align: center;
}

.net-waiting__title {
  margin: 0 0 var(--space-xs);

  font-family: var(--font-display);
  font-size: var(--font-size-title);
  line-height: var(--line-height-tight);
  color: var(--color-text);
  text-shadow: var(--shadow-title);
}

.net-waiting__text {
  margin: 0 0 var(--space-lg);

  font-size: var(--font-size-lg);
  line-height: var(--line-height-base);
  color: var(--color-text-muted);
}

/* Six caractères, aussi grands que la place le permet : on doit pouvoir les
   lire d'un téléphone tenu à bout de bras, et les dicter sans hésiter. */
.net-code {
  margin: 0 0 var(--space-lg);
  padding: var(--space-md) var(--space-lg);

  font-family: var(--font-body);
  font-size: clamp(2rem, 9vw, 3.5rem);
  font-weight: 700;
  letter-spacing: 0.22em;

  /* La lettre finale porte l'espacement comme les autres : sans ce retrait, le
     bloc paraîtrait décalé vers la gauche. */
  text-indent: 0.22em;

  color: var(--color-text);
  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
}

/* Publique ou privée : une précision sous l'explication, plus discrète qu'elle. */
.net-waiting__visibility {
  margin: calc(-1 * var(--space-md)) 0 var(--space-lg);

  font-size: var(--font-size-sm);
  line-height: var(--line-height-base);
  color: var(--color-text-muted);
}

.net-waiting__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-md);
}


/* =============================================================================
   OÙ EN EST LA LIAISON
   -----------------------------------------------------------------------------
   L'adversaire s'est absenté, il est revenu, le réseau a lâché. Une seule ligne,
   au-dessus de tout, et qui ne prend de place que lorsqu'elle a quelque chose à
   dire — « hidden » la retire du flux, le reste du temps.

   POSÉE, ET NON INSÉRÉE
   L'aire de jeu ancre ses panneaux aux quatre coins de l'écran et calcule la
   place du plateau à partir d'eux. Une ligne insérée dans le flux décalerait
   tout ; celle-ci flotte au-dessus, en haut au centre, dans la bande que
   personne n'occupe — entre le portrait d'en haut à gauche et la barre d'outils
   d'en haut à droite.
   ============================================================================= */

.net-status {
  position: fixed;
  top: var(--space-xs);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-overlay);

  /* Elle s'arrête avant les deux bulles d'humeur, qui se tiennent chacune
     après la portée de son panneau : centrée, elle s'étendrait sinon jusque
     sous celle du camp du haut sur un écran couché. */
  max-width: min(60vw, 30rem,
                 calc(100vw - 2 * (var(--panel-reach) + var(--mood-bubble-size))
                      - 2 * var(--space-md)));
  margin: 0;
  padding: var(--space-xs) var(--space-md);

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);
  text-align: center;

  color: var(--color-brand-cream);
  background-color: var(--color-surface-dark);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);

  /* Elle informe, elle ne s'actionne pas : rien ne doit devenir inatteignable
     parce qu'un message passe devant. */
  pointer-events: none;
}


/* -----------------------------------------------------------------------------
   Téléphone en portrait : la bulle se range à DROITE
   -----------------------------------------------------------------------------
   Centrée, elle mordait sur le portrait de l'adversaire. Sur un écran de 390 px,
   60 % de la largeur font 234 px : la bulle s'étend alors de 78 à 312 px, et le
   panneau du haut occupe les 118 premiers. Les deux se croisent, et c'est le
   message qui passe dessous — un avertissement qu'on ne lit pas ne sert à rien.

   Le coin haut droit, lui, est libre : cette page n'y pose aucune commande,
   contrairement à la partie à deux sur un même écran. La bulle s'y adosse.

   SA LARGEUR SE DÉDUIT DE CELLE DU PANNEAU
   « --panel-reach » est la place qu'un portrait prend depuis son bord, mesure
   dont le plateau se sert déjà pour se tenir à distance. La bulle lui emprunte
   la même : le jour où le portrait changera de taille, elle suivra sans qu'on
   ait à y revenir.

   Deux lignes ou plus tiennent mal dans une pastille : l'arrondi se resserre.
   -------------------------------------------------------------------------- */
@media (max-width: 34rem) and (orientation: portrait) {

  .net-status {
    left: auto;
    right: var(--space-xs);
    transform: none;

    /* ELLE S'ARRÊTE APRÈS LA BULLE, PAS APRÈS LE PORTRAIT
       La bulle d'humeur du camp du haut se tient juste après la portée du
       panneau, dans la même bande : sans ce terme, la ligne d'état venait se
       poser dessus dès qu'un message s'affichait. */
    max-width: calc(100vw - var(--panel-reach) - var(--mood-bubble-size)
                    - 2 * var(--space-md));
    text-align: right;
    border-radius: var(--radius-md);
  }
}


/* =============================================================================
   LA PLACE D'UN JOUEUR PARTI
   -----------------------------------------------------------------------------
   La bande qu'un spectateur voit quand une place se propose : une phrase et un
   bouton. Posée où se pose la ligne d'état — la bande que personne n'occupe —,
   dans la même matière sombre, et elle la masque tant qu'elle est là : deux
   bulles l'une sur l'autre ne se liraient ni l'une ni l'autre.
   ============================================================================= */

.net-offer {
  position: fixed;
  top: var(--space-xs);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-overlay);

  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs) var(--space-sm);

  max-width: min(90vw, 30rem);
  margin: 0;
  padding: var(--space-xs) var(--space-xs) var(--space-xs) var(--space-md);

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);

  color: var(--color-brand-cream);
  background-color: var(--color-surface-dark);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.net-offer:not([hidden]) + .net-status {
  visibility: hidden;
}

.net-offer__take {
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--font-size-sm);
}

/* Téléphone en portrait : adossée à droite, comme la ligne d'état, pour ne pas
   mordre sur le portrait du camp du haut. */
@media (max-width: 34rem) and (orientation: portrait) {

  .net-offer {
    left: auto;
    right: var(--space-xs);
    transform: none;
    max-width: calc(100vw - var(--panel-reach) - 2 * var(--space-xs));
  }
}


/* =============================================================================
   LA REVANCHE
   -----------------------------------------------------------------------------
   Dans l'écran de fin, entre le résultat et les sorties, séparée par un simple
   filet : la question et ses secondes sur une ligne, une barre fine qui se vide,
   les deux gestes, et où en est l'autre. Pas de creux ni de phrase d'aide — la
   première version en portait six lignes dans une fenêtre de quatre cents
   pixels. La réponse donnée reste enfoncée : on voit ce qu'on a dit, et qu'on
   peut encore se raviser.
   ============================================================================= */

.rematch {
  margin: 0 0 var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-sand-300);
}

.rematch__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
}

.rematch__question {
  margin: 0;
  text-align: left;

  font-family: var(--font-display);
  font-size: var(--font-size-lg);
  line-height: var(--line-height-tight);
  color: var(--color-text);
}

.rematch__timer {
  height: 4px;
  margin: var(--space-xs) 0 var(--space-sm);
  overflow: hidden;

  background-color: var(--color-sand-300);
  border-radius: var(--radius-full);
}

/* Elle se vide par une transformation, que le script règle à chaque quart de
   seconde : la transition lisse le pas. */
.rematch__fill {
  display: block;
  height: 100%;

  background-color: var(--color-brand-amber);
  transform-origin: left center;
  transition: transform 250ms linear;
}

.rematch__votes {
  margin: var(--space-xs) 0 0;

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-tight);
  color: var(--color-text-muted);
}

.rematch__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-sm);
}

.rematch__actions .btn {
  flex: 1 1 0;
  padding: var(--space-xs) var(--space-md);
  font-size: var(--font-size-base);
}

.rematch__actions .btn[aria-pressed="true"] {
  font-weight: 700;
  background-color: var(--color-brand-amber);
  background-image: none;
  box-shadow: var(--shadow-hollow);
  transform: translateY(1px);
}

/* Les chiffres gardent leur largeur : « 10 s » puis « 9 s » ne font pas
   sautiller la ligne. */
.rematch__seconds {
  flex: none;

  font-family: var(--font-body);
  font-size: var(--font-size-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--color-text-muted);
}

@media (prefers-reduced-motion: reduce) {

  .rematch__fill {
    transition: none;
  }
}


/* -----------------------------------------------------------------------------
   L'écran de fin, allégé
   -----------------------------------------------------------------------------
   Le score en grand — la feuille du solo, qui le dessine, n'est pas chargée
   ici —, une ligne de détail, et les sorties sur UNE rangée de petits boutons :
   trois barres empilées sous la revanche faisaient une fenêtre plus haute que
   large, où l'on cherchait la question.
   -------------------------------------------------------------------------- */

.net-end .game-over__score {
  margin: 0 0 var(--space-xs);

  font-family: var(--font-display);
  font-size: clamp(1.6rem, 6vw, 2.4rem);
  line-height: 1;
  color: var(--color-text);
}

/* Un forfait n'a pas de score : la ligne ne garde pas sa place. */
.net-end .game-over__score:empty {
  display: none;
}

.net-end .modal__text {
  margin-bottom: var(--space-md);
}

.net-end__actions {
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs);
}

.net-end__actions .btn {
  width: auto;
  padding: var(--space-2xs) var(--space-md);
  font-size: var(--font-size-base);
}

/* Pendant la revanche, « Rejouer » est le seul geste en couleur : « Revoir la
   partie » redevient un bouton ordinaire. */
.rematch:not([hidden]) ~ .net-end__actions .btn--accent {
  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  box-shadow: var(--shadow-raised);
}


/* =============================================================================
   LES HUMEURS
   -----------------------------------------------------------------------------
   Six visages qu'on s'envoie pendant la partie. Deux choses à placer : la
   palette qui les propose, et la scène où ils apparaissent.

   LA SCÈNE EST AU-DESSUS DU PLATEAU, JAMAIS DESSUS
   Une grimace en travers des trous empêcherait de suivre le semis, et c'est le
   semis qu'on est venu voir. Elle prend donc sa place dans le flux, au-dessus
   de l'aire de jeu — et la garde même vide, pour que rien ne saute quand une
   humeur arrive.

   DEUX EMPLACEMENTS, PAS UN
   À gauche celle du camp du haut, à droite celle du camp du bas : c'est la
   diagonale des deux portraits, et l'on sait donc d'un coup d'œil qui parle
   sans qu'un mot l'explique.
   ============================================================================= */

.net-page {
  /* Côté d'une bulle d'humeur.

     ELLE EST NOMMÉE PARCE QU'ELLE SERT TROIS FOIS
     Ici pour la bulle elle-même, plus bas pour la largeur que le plateau doit
     lui laisser à côté du portrait, et plus haut pour la ligne d'état, qui
     s'arrête avant elle. Des chiffres écrits séparément finiraient par
     diverger, et l'on ne s'en apercevrait que le jour où une grimace
     recouvrirait un trou.

     ELLE SUIT LE PORTRAIT
     Un peu moins des deux tiers de son côté : assez pour qu'une grimace se
     lise d'un coup d'œil à côté du visage, pas assez pour lui voler la place.
     Elle grandit et rétrécit avec lui, sans mesure à tenir à part. */
  --mood-bubble-size: calc(var(--avatar-panel-size) * 0.62);

  /* Où se trouve le visage dans le portrait, en fraction de son côté depuis le
     haut. Les visages dessinés comme la silhouette ont la tête dans les deux
     premiers cinquièmes du médaillon : la pointe de la bulle vise là, et non le
     milieu, où se trouvent le col — et, sur le côté, le score. */
  --mood-face-y: 0.4;

  /* Demi-hauteur de la pointe, à sa base. */
  --mood-tail-half: calc(var(--mood-bubble-size) * 0.16);

  /* Distance entre un portrait et le bord de l'écran : celle de
     components/game-panel.css, qui la resserre pour le camp du bas sur un écran
     étroit. La pointe en a besoin pour s'arrêter juste avant le visage. */
  --mood-edge-top: 20px;
  --mood-edge-bottom: 20px;
}

@media (max-width: 34rem) {

  .net-page {
    --mood-edge-bottom: 12px;
  }
}

/* CHAQUE BULLE SE TIENT CONTRE SON PORTRAIT
   Elle était d'abord posée au milieu, au-dessus du plateau : on voyait bien la
   grimace, mais il fallait se souvenir que la gauche appartenait à l'un et la
   droite à l'autre. Contre le visage, il n'y a plus rien à se rappeler.

   ELLE NE PREND PAS DE PLACE DANS LE FLUX
   Les deux bulles sont posées comme les portraits eux-mêmes, en fixe et aux
   coins : elles ne poussent pas le plateau. Ce qu'elles demandent, c'est un peu
   de largeur à côté du portrait — voir la réserve plus bas. */
.mood-stage {
  pointer-events: none;
}

/* UNE BULLE DE BANDE DESSINÉE, QUI PARLE PAR SA POINTE
   La première version était une bulle de PENSÉE : un rond, et deux petits ronds
   vers le portrait. Mal orientés, ils ne visaient rien — en haut ils montaient
   vers le coin de l'écran, en bas ils tombaient sur le score. C'est désormais
   la bulle du reste du site — celle de la mascotte et du didacticiel : trait
   sombre, fond crème, lumière d'en haut —, et sa POINTE vise le visage, à
   hauteur de la tête, en passant au-dessus du score. On sait qui grimace sans
   avoir à chercher.

   LA POINTE SE LOGE DANS LA PORTÉE DU PANNEAU
   La bulle commence là où s'arrête « --panel-reach » — la place qu'un portrait
   et son score prennent depuis le bord. La pointe, elle, remonte à l'intérieur
   de cette portée jusqu'à quatre pixels du visage : elle ne coûte donc rien de
   plus au plateau. Sa longueur se déduit de la portée, du bord et du portrait,
   et suit donc toutes les tailles d'écran. */
.mood-stage__slot {
  position: fixed;
  z-index: var(--z-content);

  display: flex;
  align-items: center;
  justify-content: center;

  box-sizing: border-box;
  width: var(--mood-bubble-size);
  height: var(--mood-bubble-size);

  background-color: var(--color-surface);
  background-image: var(--gradient-surface-raised);
  border: 2px solid var(--color-brand-dark);

  /* Un carré aux angles très arrondis plutôt qu'un rond : la pointe s'y
     raccorde sur un bord droit, comme sur les autres bulles du site. */
  border-radius: 38%;
  box-shadow: 0 6px 14px rgba(60, 39, 35, 0.18);

  font-size: calc(var(--mood-bubble-size) * 0.56);
  line-height: 1;

  /* Invisible tant qu'aucune humeur n'est là. Le grandissement part de la
     POINTE : la bulle sort de la bouche, pas de nulle part. */
  opacity: 0;
  transform-origin: var(--mood-tip-x) 50%;
}

/* La pointe : deux triangles superposés, comme dans components/speech-bubble.css
   — le premier pour le trait, le second pour le remplissage, légèrement plus
   court et plus fin. Le second déborde de deux pixels dans la bulle pour
   effacer le trait à la jonction. */
.mood-stage__slot::before,
.mood-stage__slot::after {
  content: '';
  position: absolute;
  top: 50%;

  width: 0;
  height: 0;
  border-style: solid;
}

.mood-stage__slot::before {
  margin-top: calc(-1 * var(--mood-tail-half));
}

.mood-stage__slot::after {
  margin-top: calc(-1 * var(--mood-tail-half) + 1px);
}

/* L'emoji lui-même : il se dandine une fois la bulle posée. */
.mood-stage__sign {
  display: block;
}


/* --- Camp du haut : son portrait tient le coin supérieur GAUCHE.
       La bulle se pose à sa droite, la pointe vers la GAUCHE. */
.mood-stage__slot--top {
  top: calc(var(--mood-edge-top) + var(--avatar-panel-size) * var(--mood-face-y)
            - var(--mood-bubble-size) / 2);
  left: var(--panel-reach);

  /* De la bulle jusqu'à quatre pixels du visage. */
  --mood-tail: calc(var(--panel-reach) - var(--mood-edge-top)
                    - var(--avatar-panel-size) - 4px);
  --mood-tip-x: calc(-1 * var(--mood-tail));
  --mood-tilt: -12deg;
}

.mood-stage__slot--top::before {
  left: calc(-1 * var(--mood-tail) - 2px);
  border-width: var(--mood-tail-half) var(--mood-tail) var(--mood-tail-half) 0;
  border-color: transparent var(--color-brand-dark) transparent transparent;
}

.mood-stage__slot--top::after {
  left: calc(-1 * var(--mood-tail) + 2px);
  border-width: calc(var(--mood-tail-half) - 1px) calc(var(--mood-tail) - 1px)
                calc(var(--mood-tail-half) - 1px) 0;
  border-color: transparent var(--color-surface) transparent transparent;
}


/* --- Camp du bas : son portrait tient le coin inférieur DROIT.
       La bulle se pose à sa gauche, la pointe vers la DROITE — le miroir exact
       de celle d'en face. Le score est à gauche du portrait, en bas : la pointe
       passe au-dessus de lui, à hauteur du visage. */
.mood-stage__slot--bottom {
  bottom: calc(var(--mood-edge-bottom) + env(safe-area-inset-bottom, 0px)
               + var(--avatar-panel-size) * (1 - var(--mood-face-y))
               - var(--mood-bubble-size) / 2);
  right: var(--panel-reach);

  --mood-tail: calc(var(--panel-reach) - var(--mood-edge-bottom)
                    - var(--avatar-panel-size) - 4px);
  --mood-tip-x: calc(100% + var(--mood-tail));
  --mood-tilt: 12deg;
}

.mood-stage__slot--bottom::before {
  right: calc(-1 * var(--mood-tail) - 2px);
  border-width: var(--mood-tail-half) 0 var(--mood-tail-half) var(--mood-tail);
  border-color: transparent transparent transparent var(--color-brand-dark);
}

.mood-stage__slot--bottom::after {
  right: calc(-1 * var(--mood-tail) + 2px);
  border-width: calc(var(--mood-tail-half) - 1px) 0
                calc(var(--mood-tail-half) - 1px) calc(var(--mood-tail) - 1px);
  border-color: transparent transparent transparent var(--color-surface);
}


/* --- L'apparition, et le départ ---------------------------------------------
   Elle JAILLIT de la pointe — petite et penchée vers le visage, elle dépasse
   un peu sa taille puis s'y pose —, et la grimace se dandine une fois arrivée.
   Deux dixièmes de seconde et demi pour le bond, six pour le dandinement : on
   le remarque, et c'est fini avant de gêner. Au départ, elle se replie vers la
   pointe au lieu de s'éteindre d'un coup. */
@keyframes mood-pop {
  0%   { opacity: 0; transform: scale(0.25) rotate(var(--mood-tilt)); }
  60%  { opacity: 1; transform: scale(1.12) rotate(calc(var(--mood-tilt) * -0.25)); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

@keyframes mood-wiggle {
  0%, 100% { transform: rotate(0deg) scale(1); }
  25%      { transform: rotate(-14deg) scale(1.08); }
  55%      { transform: rotate(10deg) scale(1.04); }
  80%      { transform: rotate(-5deg) scale(1); }
}

@keyframes mood-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(0.4); }
}

.mood-stage__slot.is-shown {
  opacity: 1;
  animation: mood-pop 420ms ease-out both;
}

.mood-stage__slot.is-shown .mood-stage__sign {
  animation: mood-wiggle 600ms ease-in-out 380ms both;
}

.mood-stage__slot.is-leaving {
  animation: mood-out 200ms ease-in both;
}

/* Un joueur qui préfère les pages immobiles obtient la bulle, sans le bond ni
   le dandinement : elle est rattachée au visage par sa pointe, et cette
   information ne dépend pas du mouvement. */
@media (prefers-reduced-motion: reduce) {

  .mood-stage__slot.is-shown,
  .mood-stage__slot.is-shown .mood-stage__sign,
  .mood-stage__slot.is-leaving {
    animation: none;
  }

  .mood-stage__slot.is-leaving {
    opacity: 0;
  }
}


/* --- La palette ----------------------------------------------------------- */

/* Le bouton qui l'ouvre porte un caractère, non un tracé : c'est le seul de la
   barre d'outils dans ce cas, et c'est justement ce qui le fait reconnaître. */
.game-tool__mood {
  font-size: 1.15rem;
  line-height: 1;
}

/* Elle se déplie AU-DESSUS de la barre d'outils, dans le coin qu'elle occupe
   déjà : rien de neuf n'est pris au plateau, et le pouce est au bon endroit sur
   un téléphone — celui qui vient d'ouvrir la palette a déjà la main là. */
.mood-palette {
  position: fixed;
  left: var(--space-md);
  bottom: calc(var(--tool-size, 40px) + var(--space-lg));
  z-index: var(--z-content);

  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);

  /* Les six sur une ligne, même sur le plus petit téléphone. La largeur suit
     le contenu sans jamais dépasser l'écran : une humeur ajoutée un jour
     passerait à la ligne plutôt que de déborder. */
  max-width: min(88vw, 320px);

  padding: var(--space-xs);

  border-radius: var(--radius-md);
  background: var(--gradient-surface-raised);
  box-shadow: var(--shadow-raised);
}

.mood-palette__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: clamp(38px, 10vw, 46px);
  height: clamp(38px, 10vw, 46px);

  border: none;
  border-radius: var(--radius-sm);
  background: none;

  font-size: clamp(20px, 5.5vw, 26px);
  line-height: 1;
  cursor: pointer;

  transition: background var(--duration-fast), transform var(--duration-fast);
}

.mood-palette__btn:hover,
.mood-palette__btn:focus-visible {
  background: var(--color-brand-amber);
  transform: translateY(-1px);
}

/* Le temps du délai imposé par le relais, les boutons s'éteignent : un geste
   sans effet visible est un geste qu'on refait, et l'on croirait la liaison
   rompue. */
.mood-palette.is-waiting {
  opacity: 0.45;
  pointer-events: none;
}

/* LE PLUS PETIT TÉLÉPHONE : DES BOUTONS PLUS SERRÉS, PAS UNE SECONDE RANGÉE
   À 38 px, six boutons font 264 px : sur un écran de 320, la palette ouverte
   venait recouvrir le portrait du joueur, qui commence à 236 px du bord gauche.
   Deux rangées de trois l'auraient fait monter sur la rangée du bas du plateau
   — la sienne, celle qu'il regarde. Les boutons se resserrent donc : 32 px, et
   la palette tient en 210 px, avant le portrait. C'est encore plus que les
   pastilles de la barre d'outils à cette largeur, qui descendent à 26 px.

   384 px : au-delà, les six boutons ordinaires laissent au moins vingt pixels
   entre la palette et le portrait. */
@media (max-width: 24rem) {

  .mood-palette {
    gap: 2px;
    padding: var(--space-2xs);
  }

  .mood-palette__btn {
    width: 32px;
    height: 32px;
    font-size: 20px;
  }
}

/* TÉLÉPHONE COUCHÉ : À CÔTÉ DE LA COLONNE, PAS AU-DESSUS
   Couché, la barre d'outils se dresse en colonne sous le portrait d'en face
   — voir components/game-tools.css. « Au-dessus de la barre » ne voulait plus
   dire au-dessus : c'était le milieu de la colonne. Ouverte, la palette
   recouvrait les pastilles et mordait sur le plateau (844 × 390 : de 267 à
   329 px de haut, quand le plateau descend à 289).

   Elle se pose donc à droite de la colonne, sur la même ligne de base que la
   dernière pastille — celle des humeurs, que le pouce vient de toucher. Là,
   elle occupe la bande que le plateau laisse sous lui : la réserve couchée
   est de 168 px, soit 84 px sous le plateau centré.

   DES BOUTONS DE 38 PX, POUR TENIR DANS CETTE BANDE
   Vingt pixels de marge, deux fois huit de rembourrage et 38 px de bouton :
   74 px, dix de moins que la bande. Les 46 px ordinaires en demandaient 82,
   à deux pixels du plateau. */
@media (orientation: landscape) and (max-height: 34rem) {

  .mood-palette {
    left: calc(20px + var(--avatar-panel-size) + var(--space-xs));
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  }

  .mood-palette__btn {
    width: 38px;
    height: 38px;
    font-size: 22px;
  }
}


/* --- Les spectateurs -------------------------------------------------------
   Un œil et un nombre, dans la barre d'outils : combien regardent la partie.

   CE N'EST PAS UN BOUTON
   Il ne fait rien quand on le touche, et ne doit donc pas en avoir l'air : ni
   curseur de clic, ni élévation au survol. Il garde la pastille de la barre,
   parce qu'il en est un élément, et porte le nombre en médaillon sur son bord.

   IL N'APPARAÎT QUE LORSQUE QUELQU'UN REGARDE
   Un « 0 » à chaque partie n'apprendrait rien, et encombrerait la barre de
   ceux qui jouent entre eux. */
.game-tool--audience {
  position: relative;
  cursor: default;
}

.game-tool--audience:hover,
.game-tool--audience:active {
  background-color: var(--color-surface);
  box-shadow: var(--shadow-raised);
  transform: none;
}

.game-tool__count {
  position: absolute;
  top: -5px;
  right: -7px;

  min-width: 1.45em;
  padding: 0 0.3em;

  font-family: var(--font-body);
  font-size: clamp(10px, 2.6vw, 12px);
  font-weight: 700;
  line-height: 1.45em;
  text-align: center;
  font-variant-numeric: tabular-nums;

  color: var(--color-text);
  background: var(--color-brand-amber);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow-sm);
}

/* LE PLUS PETIT TÉLÉPHONE DEBOUT : AU-DESSUS DE LA BARRE, PAS AU BOUT
   Cinquième pastille de la rangée, le compteur l'allongeait jusque sous la
   bulle d'humeur du joueur sur un écran de moins de 352 px. Il se pose donc
   au-dessus de la première pastille, dans la bande que le plateau laisse déjà
   libre, et la rangée retrouve la longueur qu'elle avait. La palette, quand on
   l'ouvre, passe devant lui le temps d'un choix. */
@media (max-width: 22.5rem) and (orientation: portrait) {

  .game-tool--audience {
    position: absolute;
    left: 0;
    bottom: calc(100% + var(--tool-gap, 8px));
  }
}


/* La scène des humeurs prend sa place au-dessus du plateau, et le plateau la
   lui laisse.

   POURQUOI ELLE EST DANS LE FLUX
   Posée par-dessus, elle aurait recouvert tantôt la ligne d'état, tantôt le
   portrait d'en face, selon la hauteur de l'écran. Dans le flux, elle a sa
   bande à elle, et il suffit de la retrancher au plateau — la mécanique que
   tout le reste emploie déjà.

   ELLE GARDE SA HAUTEUR MÊME VIDE : sans quoi le plateau sauterait de quarante
   pixels à chaque grimace. */
/* CE QUE LES BULLES DEMANDENT AU PLATEAU
   Rien en hauteur : elles ne sont pas dans le flux. Mais chacune se pose à côté
   de son portrait, vers l'intérieur de l'écran — c'est donc de la LARGEUR
   qu'il leur faut, et seulement là où les portraits se font face sur les
   côtés, c'est-à-dire couché.

   Debout, les deux panneaux s'empilent en haut et en bas : la bulle se loge
   dans leur bande, à côté du visage, et ne coûte rien de plus. */
.net-page .board-container {
  --board-reserve-y: calc(2 * var(--panel-reach));
}

@media (orientation: landscape) {

  .net-page .board-container {
    /* La bulle seule : sa pointe se loge dans la portée du panneau, entre le
       visage et elle. Les petits ronds de la bulle de pensée débordaient en
       arrière d'elle et demandaient un facteur 1,6 ; la pointe, non. */
    --board-reserve-x: calc(2 * (var(--panel-reach) + var(--mood-bubble-size)));
  }
}

@media (orientation: landscape) {

  /* Couché, la ligne d'état occupe la bande du haut, comme la bulle du
     didacticiel en solo : la même réserve, pour la même raison. */
  .net-page .board-container {
    --board-reserve-y: 168px;
  }
}
