/* -------------------------------------------------------------------------
   WordsGame — feuille de style principale
   Les sections suivent l'ordre visuel de la page : base, en-tête, plateau,
   chevalet, commandes, puis adaptations pour les petits écrans.
   ------------------------------------------------------------------------- */

/*
  Courier Prime est distribuée avec le jeu (licence OFL dans assets/fonts).
  Le navigateur n'a donc rien à télécharger et le dessin des lettres reste
  identique sur macOS, Windows, iOS et Android.
*/
@font-face {
  font-family: "WordZeu Typewriter";
  src: url("assets/fonts/courier-prime-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: "WordZeu Typewriter";
  src: url("assets/fonts/courier-prime-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

/*
  Archivo SemiCondensed est embarquée avec le jeu : la lettre utilise Medium,
  plus présente, tandis que sa valeur utilise Regular pour rester discrète.
*/
@font-face {
  font-family: "WordZeu Tile";
  src: url("assets/fonts/archivo-semicondensed-regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

/* Variante légère réservée au mot « WORDS » du titre. */
@font-face {
  font-family: "WordZeu Tile";
  src: url("assets/fonts/archivo-semicondensed-light.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: "WordZeu Tile";
  src: url("assets/fonts/archivo-semicondensed-medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: "WordZeu Tile";
  src: url("assets/fonts/archivo-semicondensed-bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

/* Archivo 500 est la police unique des lettres et valeurs de toutes les tuiles. */
@font-face {
  font-family: "WordZeu Archivo";
  src: url("assets/fonts/archivo-500.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: block;
}

/* Variante très grasse réservée à « RORO » dans le titre. */
@font-face {
  font-family: "WordZeu Archivo";
  src: url("assets/fonts/archivo-800.ttf") format("truetype");
  font-weight: 800;
  font-style: normal;
  font-display: block;
}

/* Version étendue et très grasse : le mot RORO reste lisible à petite taille. */
@font-face {
  /* Famille dédiée : aucun repli vers une autre graisse Archivo n'est possible. */
  font-family: "WordZeu Roro Display";
  /* Le paramètre de version force aussi le rechargement du fichier de police. */
  src: url("assets/fonts/archivo-expanded-black.ttf?v=20260819-roro-font") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

:root {
  /*
    THÈME « TABLE DE CASINO »
    Ces variables regroupent toutes les couleurs de l'interface. Pour essayer
    une autre ambiance, il suffit généralement de modifier ce seul bloc.

    - felt-* : le feutre sombre du fond de page ;
    - surface-* : les surfaces plus claires (panneaux, menus et boutons) ;
    - ink / white : les couleurs de texte à fort contraste.
  */
  --felt-dark: #075b3b;
  --felt-deep: #034329;
  --felt-light: #119661;
  --surface: #28a66f;
  --surface-hover: #3cbd82;
  --surface-line: rgba(234, 255, 244, .64);
  /* Panneaux flottants : assez sombres pour laisser le texte blanc lisible
     même lorsqu'ils passent devant le plateau clair. */
  --info-panel-background: rgba(3, 67, 41, .62);
  --ink: #053b29;
  --white: #f5fff9;
  /* Crème plus clair et plus saturé : les tuiles restent nettes sur le feutre. */
  --cream: #ffe4a3;
  --cream-deep: #f3c96d;
  --grid: #fff;
  --ordinary-cell: #edf8f1;
  --dialog-surface: #fff;
  --dialog-input-line: #bcd1e8;
  --line: var(--surface-line);
  --highlight: #f9ce45;
  --cpu-highlight: #75e59c;
  --cpu-glow: rgba(117, 229, 156, .95);
  --cpu-word-frame: #e8ff32;
  --dialog-backdrop: rgba(0, 30, 20, .7);
  --dialog-shadow: rgba(0, 0, 0, .35);
  --cell-inner-shadow: inset 0 1px 2px rgba(5, 59, 41, .30);
  --placed-tile-shadow: 1px 2px 2px rgba(0, 25, 15, .60);
  /* Même relief que sur le plateau, agrandi pour les tuiles du chevalet. */
  --rack-tile-shadow: 2px 3px 3px rgba(0, 25, 15, .60);
  --title-tile-shadow: 2px 3px 12px rgba(0, 25, 15, .7);
  --cpu-search: #ff3b30;
  --cpu-search-glow: rgba(255, 59, 48, .92);
  --title-brick: #e6372f;
  /* Lettres compactes et très lisibles, proches des tuiles Scrabble classiques. */
  --tile-font: "WordZeu Tile", "Avenir Next Condensed", "Helvetica Neue Condensed", "Arial Narrow", sans-serif;
  --tile-letter-font: "WordZeu Tile", "Avenir Next Condensed", "Helvetica Neue Condensed", "Arial Narrow", sans-serif;
  --title-shadow: rgba(0, 25, 15, .46);
  /* Cadre blanc pur et ombre douce pour détacher le plateau du tapis vert. */
  --board-frame-color: var(--grid);
  --board-outline: var(--surface);

  /* Ancien nom conservé pour les quelques composants qui l'emploient. */
  --blue: var(--felt-dark);

  /* Palette des bonus du plateau. */
  --bonus-word-3: #b77a80;
  --bonus-word-2: #e7c7cb;
  --bonus-letter-3: #6f95b7;
  --bonus-letter-2: #b7d6e2;

  /* Réglages de mise en page les plus utiles à ajuster. */
  --game-width: 720px;
  --panel-height: 52px;
  --profile-height: 96px;
  --profile-menu-width: 154px;
  --panel-padding: 8px;
  --panel-gap: 8px;
  --section-gap: 20px;
  --board-line: 3px;
  --board-frame: 16px;
  --cell-radius: clamp(1.5px, .65vw, 5px);
}

* { box-sizing: border-box; }

body {
  display: grid;
  grid-template-rows: 2fr auto 3fr;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  /* Le fond s'assombrit progressivement dans le dernier tiers de la fenêtre. */
  background-color: var(--felt-dark);
  background-image: linear-gradient(to bottom, var(--felt-light), var(--felt-dark) 66%, var(--felt-deep));
  color: var(--white);
  font-family: "Avenir Next", Avenir, "Nunito Sans", "Segoe UI", sans-serif;
}

/* Indicateur discret, figé dans le coin inférieur droit de la fenêtre. */
.performance-meter {
  position: fixed;
  /* 20 px depuis le bord vertical droit de la fenêtre. */
  right: 20px;
  bottom: 20px;
  z-index: 20;
  display: grid;
  gap: 1px;
  min-width: 230px;
  padding: 8px 10px;
  border-radius: 7px;
  /* Ce vert profond isole les chiffres blancs du plateau sous-jacent. */
  background: var(--info-panel-background);
  box-shadow: 0 4px 12px rgba(0, 25, 15, .32);
  color: var(--white);
  font-family: "Avenir Next", Avenir, "Nunito Sans", "Segoe UI", sans-serif;
  font-weight: 400;
  font-size: clamp(9px, .9vw, 13px);
  line-height: 1.15;
  opacity: 1;
  pointer-events: auto;
  text-align: right;
  text-shadow: none;
}

.performance-meter span { display: block; }

/*
  Historique des mots de l'ordinateur. Il reste volontairement sans panneau
  opaque afin de conserver l'impression d'un simple carnet posé sur le tapis.
*/
.cpu-word-history {
  position: fixed;
  z-index: 15;
  top: 20px;
  /* 20 px depuis le bord vertical gauche de la fenêtre. */
  left: 20px;
  width: min(280px, calc(100vw - 80px));
  bottom: 20px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 8px 10px;
  border-radius: 7px;
  background: var(--info-panel-background);
  box-shadow: 0 4px 12px rgba(0, 25, 15, .32);
  /* Le panneau lui-même reste ouvert afin de laisser voir son triangle externe. */
  overflow: visible;
  color: var(--white);
  font-size: clamp(11px, 1.1vw, 16px);
  line-height: 1.15;
  text-shadow: none;
}

/* L'en-tête reste fixe ; la liste seule se déroule jusqu'en bas de la fenêtre. */
.cpu-word-history h2 {
  flex: 0 0 auto;
  margin: 0 0 8px;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--white);
  color: var(--white);
  font-size: 1em;
  font-weight: 700;
  line-height: 1.1;
  text-align: left;
}
#cpuWordHistoryContent {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.cpu-word-entry { margin: 0 0 9px; }

.cpu-word-entry strong {
  display: block;
  font-weight: 700;
  text-transform: uppercase;
}

.cpu-word-entry em {
  display: block;
  margin-top: 0;
  font-style: italic;
  font-weight: 400;
}

/* Sur les navigateurs récents, le feutre reste perceptible sous le panneau. */
@supports ((-webkit-backdrop-filter: blur(8px)) or (backdrop-filter: blur(8px))) {
  .performance-meter,
  .cpu-word-history {
    /* Le flou adoucit le plateau sous le panneau, le vert maintient le contraste. */
    background: var(--info-panel-background);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }
}

/* Un triangle transparent reste accroché au haut du panneau et indique son état. */
.info-panel {
  /* Les informations ne doivent jamais intercepter une tuile ou un tap du jeu. */
  pointer-events: none;
  transition: width .2s ease, min-width .2s ease, padding .2s ease, background .2s ease;
}
/* Avant la première partie, les indicateurs ne distraient pas du choix de profil. */
.pre-game .info-panel { display: none; }
.info-toggle {
  position: absolute;
  z-index: 1;
  display: grid;
  width: 22px;
  height: 20px;
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--white);
  cursor: pointer;
  pointer-events: auto;
}
.info-toggle::before { font-size: 15px; line-height: 1; }
.cpu-word-history .info-toggle { top: 0; right: -26px; transform: none; }
.cpu-word-history .info-toggle::before { content: "◀"; }
.performance-meter .info-toggle { top: 0; left: -26px; transform: none; }
.performance-meter .info-toggle::before { content: "▶"; }
/*
  Réduit, seul le triangle existe visuellement. Le panneau conserve toutefois
  son encombrement transparent : pour le panneau fixé en bas, cela empêche le
  triangle de remonter ou descendre entre les deux états.
*/
.info-panel.is-collapsed {
  overflow: visible;
  background: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
/* Le contenu reste dans le flux pour conserver la position du triangle. */
.info-panel.is-collapsed > :not(.info-toggle) { visibility: hidden; }
/* L'historique se replie vers la gauche : son triangle revient près du bord. */
.cpu-word-history.is-collapsed .info-toggle { right: auto; left: 0; }
.cpu-word-history.is-collapsed .info-toggle::before { content: "▶"; }
.performance-meter.is-collapsed .info-toggle { left: auto; right: 0; }
.performance-meter.is-collapsed .info-toggle::before { content: "◀"; }

button,
select,
input,
dialog { font: inherit; }

main {
  grid-row: 2;
  width: min(1100px, 96vw);
  margin: 0 auto;
  padding: 0;
  text-align: center;
}

/* -------------------------------------------------------------------------
   En-tête, profil et panneaux de score
   ------------------------------------------------------------------------- */

.game-header {
  position: relative;
  display: flex;
  align-items: flex-end;
  /* Le titre se cale sur le bord droit du même espace que le plateau. */
  justify-content: flex-end;
  width: min(100%, var(--game-width));
  min-height: var(--profile-height);
  margin: 0 auto;
  padding-bottom: 4px;
  overflow: visible;
}

h1 {
  position: relative;
  z-index: 2;
  display: flex;
  /* La rangée de tuiles est centrée sur la hauteur du mot RORO. */
  align-items: center;
  justify-content: flex-end;
  width: 100%;
  margin: 0;
  color: #ff2d21;
  /* Le titre reste compact, mais avec deux graisses distinctes. */
  font-size: clamp(32px, 7vw, 56px);
  line-height: 1;
  font-family: "WordZeu Roro Display", sans-serif;
  font-weight: 400;
  letter-spacing: 0;
  white-space: nowrap;
  text-transform: none;
  text-align: right;
  text-shadow: 1px 2px 4px var(--title-shadow);
  transform: translateY(5px);
  overflow: visible;
}
/* Pendant la pluie, les tuiles passent au premier plan, y compris devant RORO. */
h1.title-rain-active { z-index: 1000; }

/* « RORO » est typographique ; « WORDS GAME » est fabriqué avec des tuiles. */
.title-roro {
  position: absolute;
  left: 50%;
  /* Le sommet suit le centre de WORD ; la base rejoint celle du nom joueur. */
  /* Relèvement de 3 px pour resserrer l'alignement avec le nom du joueur. */
  top: calc(24% - 3px);
  width: auto;
  height: clamp(31px, 8.4vw, 69px);
  /* Évite que du rembourrage soit inclus dans la hauteur du dessin. */
  box-sizing: content-box;
  padding: 0;
  margin: 0;
  /* Le dessin fourni est déjà rouge vif : aucune police du poste n'intervient. */
  /* Relèvement optique d'un pixel : sa base rejoint celle du nom du joueur. */
  /* Son centre reste celui du plateau, quelle que soit la largeur des tuiles. */
  /* Les variables sont changées par JavaScript pendant la pluie de tuiles.
     Elles conservent ensuite le dernier angle obtenu, sans modifier la taille. */
  transform: translateX(-50%) rotate(var(--roro-angle, 0deg)) scale(var(--roro-scale, 1));
  transform-origin: center;
  transition: transform 80ms ease-out;
  /* Une ombre suivant les contours du SVG, identique à l'esprit des tuiles. */
  filter: drop-shadow(2px 3px 12px rgba(0, 25, 15, .70));
}

/* Les tuiles de « WORDS GAME » suivent directement RORO, à droite du plateau. */
.title-tiles {
  position: relative;
  z-index: 3;
  display: grid;
  gap: 4px;
  align-items: center;
  justify-items: end;
  margin-left: clamp(6px, 1.2vw, 14px);
  /* Les deux rangées restent légèrement plus hautes que le logo. */
  transform: translateY(-5px);
  pointer-events: none;
}
.title-tiles.is-raining { z-index: 1001; }

/* Deux lignes alignées à droite : WORDS puis GAME. */
.title-word {
  display: flex;
  gap: 2px;
  align-items: center;
  justify-content: flex-end;
}

.title-tile {
  position: relative;
  flex: 0 0 clamp(18px, 5.6vw, 43px);
  width: clamp(18px, 5.6vw, 43px);
  height: clamp(18px, 5.6vw, 43px);
  border: 2px solid transparent;
  border-radius: 6px;
  background: var(--cream);
  box-shadow: var(--title-tile-shadow);
  color: var(--ink);
  font-family: var(--tile-letter-font);
  font-size: clamp(12px, 3vw, 25px);
  font-weight: 500;
  letter-spacing: -.045em;
  line-height: 1;
  /* Le pourcentage de recouvrement est relatif à la tuile elle-même. */
  margin-left: 0;
  transform: translate(calc(var(--tile-x, 0px) + var(--tile-overlap, 0px)), var(--tile-y, 0px)) rotate(var(--tile-angle, 0deg));
  animation: none;
  /* Les tuiles sont dans le h1 : elles ne doivent pas hériter de l'ombre du
     logo, car seule leur ombre portée extérieure est souhaitée. */
  text-shadow: none;
}

.title-tile .tile-letter,
.title-tile i { text-shadow: none; }

/* La phase invisible évite d'afficher les tuiles déjà rangées avant leur chute. */
.title-tile.title-rain-pending { opacity: 0; }
.title-tile.title-raining {
  animation: title-tile-rain .85s cubic-bezier(.18, .78, .28, 1.05) both;
  animation-delay: var(--rain-delay, 0s);
}
.title-tile.title-assembling {
  animation: title-tile-assemble .52s cubic-bezier(.18, .82, .24, 1) both;
  animation-delay: var(--assemble-delay, 0s);
}
/* Les douze tuiles de trop existent seulement pendant la présentation. */
.title-rain-extra {
  position: fixed;
  z-index: 1002;
  margin: 0;
  pointer-events: none;
}
.title-tile.title-sweeping {
  animation: title-tile-sweep .42s cubic-bezier(.2, .78, .32, 1) both;
  animation-delay: var(--sweep-delay, 0s);
}
@keyframes title-tile-rain {
  from { opacity: 0; transform: translate(var(--rain-start-x), var(--rain-start-y)) rotate(var(--rain-start-angle)); }
  to { opacity: 1; transform: translate(var(--rain-x), var(--rain-y)) rotate(var(--rain-angle)); }
}
@keyframes title-tile-assemble {
  from { opacity: 1; transform: translate(var(--rain-x), var(--rain-y)) rotate(var(--rain-angle)); }
  to { opacity: 1; transform: translate(0, 0) rotate(var(--tile-angle, 0deg)); }
}
/* Les tuiles superflues sont balayées vers le bas, hors de la fenêtre. */
@keyframes title-tile-sweep {
  from { opacity: 1; transform: translate(var(--rain-x), var(--rain-y)) rotate(var(--rain-angle)); }
  to { opacity: 0; transform: translate(var(--sweep-x), var(--sweep-y)) rotate(var(--sweep-angle)); }
}

.title-tile i {
  position: absolute;
  right: 2px;
  bottom: 0;
  font-family: var(--tile-font);
  font-size: clamp(5px, calc(1.1vw - 1px), 9px);
  font-style: normal;
  font-weight: 500;
  letter-spacing: .04em;
}

.title-tile {
  display: grid;
  place-items: center;
}

.top {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr)) repeat(2, minmax(0, 1.6fr));
  gap: var(--panel-gap);
  width: min(100%, var(--game-width));
  /* Marge haute réduite : les panneaux remontent légèrement sous le titre. */
  margin: 10px auto var(--section-gap);
}

.score,
.level {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  height: var(--panel-height);
  padding: var(--panel-padding);
  /* Même aplat vert que les boutons d'action, sans contour. */
  border: 0;
  border-radius: 5px;
  background: var(--surface);
  font-size: 13px;
  line-height: 1.15;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.score { text-align: center; }

.score strong {
  display: block;
  margin-top: 1px;
  font-size: 23px;
  line-height: 1;
}

/* L'augmentation d'un score reste lisible, même lorsque le total monte vite. */
.score strong.is-animating { animation: score-total-pop .46s ease-in-out; }

@keyframes score-total-pop {
  0%, 100% { transform: scale(1); }
  45% { transform: scale(1.32); }
}

.level { text-align: left; }

/* Les intitulés des deux réglages restent centrés au-dessus de leur menu. */
.top .level { text-align: center; }
.top .level select { text-align: left; }

.level select {
  width: 100%;
  margin: 3px 0 0;
  border: 0;
  background: transparent;
  color: var(--white);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}

.level option {
  background: var(--surface);
  color: var(--white);
  font-size: 13px;
  font-weight: 700;
}

/* Le bouton affiche uniquement le dernier profil utilisé et ouvre son dialogue. */
.game-header .profile-panel {
  position: absolute;
  z-index: 4;
  bottom: 5px;
  left: 0;
  width: var(--profile-menu-width);
  text-align: left;
}

.profile-button {
  /* Son bas suit celui de RORO, même avec une taille volontairement discrète. */
  display: flex;
  align-items: flex-end;
  transform: translateY(5px);
  width: 100%;
  height: 27px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--cream);
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1;
  text-align: left;
  text-overflow: ellipsis;
  text-transform: none;
  white-space: nowrap;
  overflow: hidden;
}

#profileChoice {
  display: block;
  width: calc(100% - 56px);
  margin: 0 auto 12px;
  /* Flèche placée à 10 px du bord droit, comme le texte l'est à gauche. */
  padding: 10px 34px 10px 10px;
  border: 1px solid var(--dialog-input-line);
  border-radius: 6px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--dialog-surface) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m3 6 5 5 5-5' fill='none' stroke='%23062f22' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'/%3E%3C/svg%3E") no-repeat right 10px center / 16px;
  color: var(--ink);
}

#profileChoice option {
  background: var(--surface);
  color: var(--white);
  /* Les choix reprennent exactement la typographie du libellé sélectionné. */
  font-family: inherit;
  font-size: 16px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

/* Le bouton plein écran reste dans le coin de la fenêtre. */
.fullscreen-toggle {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 20;
  display: grid;
  width: 38px;
  height: 38px;
  padding: 0;
  place-items: center;
  border: 1px solid var(--white);
  border-radius: 50%;
  background: var(--white);
  color: var(--surface);
  cursor: pointer;
}

.fullscreen-toggle svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.fullscreen-toggle:hover {
  background: var(--white);
  color: var(--felt-dark);
}
.fullscreen-toggle .exit-fullscreen { display: none; }
.fullscreen-toggle.is-active .enter-fullscreen { display: none; }
.fullscreen-toggle.is-active .exit-fullscreen { display: block; }

/* -------------------------------------------------------------------------
   Plateau et tuiles
   ------------------------------------------------------------------------- */

.game { display: block; }

/* Plateau et chevalet forment une seule surface de déplacement au pointeur. */
.play-surface {
  position: relative;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Les tuiles peuvent se déplacer sous le pointeur : garder un curseur unique
   pendant le geste évite l'alternance visuelle entre main et flèche. */
.play-surface.is-dragging,
.play-surface.is-dragging * { cursor: grabbing !important; }

/* Copie flottante créée par JavaScript : elle ne masque jamais la cible sous le doigt. */
.drag-ghost {
  position: fixed;
  z-index: 100;
  pointer-events: none;
  /* Référence des unités cqi de la tuile copiée elle-même (rayon, bordure,
     corps de lettre). Sans ce conteneur parent, la tuile du chevalet peut
     prendre les proportions de la fenêtre pendant le survol. */
  container-type: inline-size;
  /* Les board-tile héritent de ce rayon lorsqu'elles deviennent flottantes. */
  border-radius: 6px;
  --drag-scale: 1;
  /* L'inclinaison est alimentée par le script selon la direction du geste. */
  --drag-tilt: 0deg;
  transform: translate(-50%, calc(-50% - 16px)) rotate(var(--drag-tilt)) scale(var(--drag-scale));
  transform-origin: center;
  /* Pendant le geste, la copie suit immédiatement le pointeur. L'animation
     n'intervient qu'au dépôt : une transition sur left/top crée sinon 100 ms
     de retard perceptible, particulièrement entre les cases du chevalet. */
  will-change: transform, left, top;
  transition: none;
  /* Une simple pression ne doit jamais masquer la tuile d'origine. */
  opacity: 0;
  filter: drop-shadow(0 18px 12px rgba(0, 25, 15, .52));
}

/* La copie ne devient visible qu'après le petit déplacement qui confirme le geste. */
.drag-ghost.is-active,
.drag-ghost.is-settling { opacity: 1; }

/* À l'arrivée, la tuile quitte son décalage aérien pour coïncider exactement avec la cible. */
.drag-ghost.is-settling {
  transform: translate(-50%, -50%) rotate(0deg) scale(var(--drag-scale));
  transition: transform .20s cubic-bezier(.42, 0, 1, 1), left .20s cubic-bezier(.42, 0, 1, 1), top .20s cubic-bezier(.42, 0, 1, 1);
  /* Départ doux, puis accélération vers la taille exacte de la cible. */
}

/* Après un très bref arrêt, la tuile revient délicatement à l'horizontale. */
.drag-ghost.is-resting { transform-origin: center; }

/* La typographie de la copie suit la proportion de la cible, pas celle de la tuile de départ. */
.drag-ghost.over-board .tile,
.drag-ghost.over-board .board-tile,
.drag-ghost.over-rack .tile,
.drag-ghost.over-rack .board-tile { font-size: var(--drag-letter-size); }

.drag-ghost.over-board .tile i,
.drag-ghost.over-board .board-tile i,
.drag-ghost.over-rack .tile i,
.drag-ghost.over-rack .board-tile i { font-size: var(--drag-value-size); }

.drag-ghost .tile,
.drag-ghost .board-tile {
  position: relative;
  inset: auto;
  display: grid;
  width: 100%;
  height: 100%;
  /* Les décalages de la lettre et de sa valeur utilisent des unités cqi.
     La copie flottante doit donc devenir elle-même leur conteneur : sans cela,
     ces unités se réfèrent à une taille extérieure et le texte sort de la tuile. */
  container-type: inline-size;
  place-items: center;
  /* La copie flottante porte déjà son ombre aérienne : celle de la tuile
     posée ne doit pas se cumuler pendant le survol. */
  box-shadow: none;
}

/* La tuile réellement saisie conserve son emplacement pour le calcul du
   chevalet, mais elle ne doit jamais rester visible derrière sa copie mobile. */
.drag-source { opacity: 0 !important; visibility: hidden !important; }

.board {
  position: relative;
  display: grid;
  grid-template-columns: repeat(15, 1fr);
  gap: var(--board-line);
  width: min(100%, var(--game-width));
  margin: auto;
  /* Cadre extérieur distinct de la grille blanche qui sépare les 225 cases. */
  border: var(--board-frame) solid var(--board-frame-color);
  border-radius: 14px;
  background: var(--grid);
  /* Liseré extérieur sombre, puis même ombre douce que le titre. */
  box-shadow: 0 0 0 4px var(--board-outline), 4px 8px 18px var(--title-shadow);
}

.cell {
  position: relative;
  container-type: inline-size;
  aspect-ratio: 1;
  overflow: visible;
  padding: 0;
  border: 0;
  border-radius: var(--cell-radius);
  background: var(--ordinary-cell);
  box-shadow: var(--cell-inner-shadow);
  color: var(--ink);
  font-size: clamp(9px, 1.7vw, 19px);
  font-weight: 400;
  cursor: pointer;
  touch-action: none;
}


.cell.center { container-type: inline-size; background: var(--bonus-word-2); }
.cell.center::after {
  content: "★";
  color: var(--white);
  font-size: clamp(11px, 56cqi, 27px);
  opacity: 1;
  pointer-events: none;
}

/* Cases bonus : MT, MD, LT et LD. */
.cell.tw { background: var(--bonus-word-3); }
.cell.dw { background: var(--bonus-word-2); }
.cell.tl { background: var(--bonus-letter-3); }
.cell.dl { background: var(--bonus-letter-2); }

.cell small {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: var(--white);
  font-family: "Avenir Next", Avenir, "Nunito Sans", "Segoe UI", sans-serif;
  font-size: 0;
  font-weight: 700;
  line-height: 1;
  opacity: 1;
}

.cell small::after {
  display: block;
  font-size: clamp(6px, 31cqi, 15px);
  line-height: 1;
}

.cell.tw small::after { content: "MT"; }
.cell.dw small::after { content: "MD"; }
.cell.tl small::after { content: "LT"; }
.cell.dl small::after { content: "LD"; }

.board-tile {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  padding: 0;
  border-radius: inherit;
  background: var(--cream);
  box-shadow: var(--placed-tile-shadow);
  color: var(--ink);
  font-family: var(--tile-letter-font);
  /* Le dessin du plateau part de cette proportion, quelle que soit sa taille. */
  font-size: 74cqi;
  font-weight: 500;
  letter-spacing: -.045em;
  /* Chaque tuile posée reçoit sa valeur depuis le script, entre -2 et +2°. */
  transform: rotate(var(--placed-tile-angle, 0deg));
}

.board-tile i,
.tile i {
  position: absolute;
  /* Valeur et décalages suivent la même échelle que la tuile. */
  right: 9cqi;
  bottom: 5cqi;
  font-size: 23cqi;
  font-family: var(--tile-font);
  font-style: normal;
  /* Archivo Medium pour la valeur chiffrée de la tuile. */
  font-weight: 500;
  letter-spacing: .04em;
}

/* La lettre, séparée de sa valeur, peut être réglée sans déplacer le chiffre. */
.title-tile > .tile-letter,
.board-tile > .tile-letter,
.tile > .tile-letter {
  display: inline-block;
  font: inherit;
  font-weight: inherit;
  line-height: 1;
}

/* Le léger relèvement de la lettre diminue avec la tuile au lieu de rester
   fixé à un pixel : l'équilibre typographique reste identique à toute taille. */
.title-tile > .tile-letter { transform: translateY(-1px); }
.board-tile > .tile-letter { transform: translateY(-2.3cqi); }
.tile > .tile-letter { transform: translateY(-1.75cqi); }

/* Filet interne commun aux tuiles du plateau et du chevalet. Le masque ne
   conserve qu'une ligne de 1 px : le dégradé part du crème très clair en haut
   à gauche et se réchauffe jusqu'au bas droit, sans réduire la surface utile. */
.board-tile::after,
.tile::after {
  content: "";
  position: absolute;
  /* inset: 0 place le filet exactement sur le bord, sans modifier la taille. */
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(115deg, #fffdf2 0%, #fff0c4 25%, #d5a854 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

/* Les tuiles du chevalet ont une bordure transparente de 2 px (réservée à la
   sélection). Leur pseudo-élément doit donc remonter ces 2 px pour atteindre
   exactement le bord extérieur, comme une tuile du plateau. */
.tile::after { inset: -3.5cqi; }

.board-tile.cpu-new { animation: cpu-tile-in .24s cubic-bezier(.18, .82, .28, 1.25) both; }
.board-tile.joker,
.tile.joker {
  color: var(--accent);
}

/* La valeur du chevalet grandit avec ses tuiles, plus grandes que celles du plateau. */
.tile i {
  /* Les grandes tuiles gardent leur valeur dans le coin, proportionnellement. */
  right: 3.5cqi;
  bottom: 0;
  font-size: 23cqi;
}

.move-score {
  position: absolute;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  min-height: 30px;
  padding: 5px 9px;
  transform: translate(-12%, -72%);
  border: 2px solid var(--grid);
  border-radius: 999px;
  background: var(--highlight);
  color: var(--ink);
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
}

.last-move-frame {
  position: absolute;
  z-index: 5;
  box-sizing: border-box;
  border: 5px solid var(--highlight);
  border-radius: 11px;
  pointer-events: none;
}

.last-move-frame.cpu { border-color: var(--highlight); }
.last-move-frame.is-pulsing { animation: cpu-word-frame-pulse 1.1s ease-in-out infinite; }

@keyframes cpu-word-frame-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.025); }
}

.cpu-search-marker {
  position: absolute;
  z-index: 6;
  background: var(--cpu-search);
  /* Un disque attire moins l'œil qu'un carré tout en suivant la recherche. */
  border-radius: 50%;
  /* Ombre plus courte et dense : le carré reste net sur les tuiles claires. */
  box-shadow: 0 1px 3px rgba(82, 0, 0, .86), 0 0 3px var(--cpu-search-glow);
  transform: translate(-50%, -50%);
  transition: left .28s ease-in-out, top .28s ease-in-out, width .14s ease-in-out, height .14s ease-in-out;
  pointer-events: none;
}

/* -------------------------------------------------------------------------
   Chevalet et commandes
   ------------------------------------------------------------------------- */

.rack-wrap {
  max-width: var(--game-width);
  margin: var(--section-gap) auto;
}

.rack-line {
  --rack-tile-size: 57px;
  /* Rapport lettre/tuile rapproché de celui des tuiles du plateau. */
  --rack-letter-size: 45px;
  --rack-value-size: 13px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  width: 100%;
}

.rack {
  position: relative;
  display: grid;
  grid-column: 2;
  grid-template-columns: repeat(7, var(--rack-tile-size));
  gap: 7px;
  width: max-content;
  min-height: calc(var(--rack-tile-size) + 20px);
  padding: 10px;
  border: 1px solid #fff;
  border-radius: 12px;
  /* Dégradé linéaire continu : le bas volontairement plus brun fait mieux
     ressortir les tuiles crème. */
  background-color: var(--cream);
  background-image: linear-gradient(to bottom, var(--cream) 0%, #9b6b29 100%);
}

/* Pendant la validation du dépôt, aucun retour transitoire des tuiles ne doit
   être visible entre l'aperçu de permutation et leur ordre définitif. */
.rack.is-finalizing .tile {
  transition: none !important;
  animation: none !important;
}

/* Pendant le survol, l'ordre aperçu doit suivre le pointeur sans traînée.
   L'animation du dépôt final reste, elle, intacte. */
.rack.is-previewing .tile {
  transition: none;
}

/* Les sept emplacements restent visibles : une lettre posée laisse donc sa place. */
.rack-slot {
  position: relative;
  container-type: inline-size;
  width: var(--rack-tile-size);
  height: var(--rack-tile-size);
  border-radius: 6px;
}

.rack-slot.empty {
  border: 0;
  background: transparent;
}


.tile {
  position: relative;
  flex: 0 0 var(--rack-tile-size);
  width: var(--rack-tile-size);
  height: var(--rack-tile-size);
  padding: 0;
  border: 3.5cqi solid transparent;
  border-radius: 10.5cqi;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--tile-letter-font);
  font-size: 79cqi;
  /* Archivo 500, comme les valeurs, pour une seule identité typographique. */
  font-weight: 500;
  letter-spacing: -.045em;
  /* La permutation n'anime que ce calque de composition. */
  will-change: transform;
  box-shadow: var(--rack-tile-shadow);
  transform: translateX(var(--rack-preview-shift, 0));
  transition: transform .16s ease-out, box-shadow .16s ease-out;
  cursor: pointer;
  touch-action: none;
}


/* Les tuiles arrivent en douceur dans leur nouvelle case du chevalet. */
.tile.rack-tile-landed { animation: rack-tile-land .25s ease-out both; }

@keyframes rack-tile-land {
  from { transform: translateY(-7px) scale(.92); opacity: .45; }
  to { transform: translateY(0) scale(1); opacity: 1; }
}

.tile.selected { border-color: var(--highlight); }

/* En mode échange, le repère reste entièrement à l'extérieur : il ne modifie
   ni la taille ni la position de la tuile sélectionnée. */
.tile.exchange-selected {
  border-color: transparent;
  box-shadow: var(--rack-tile-shadow);
}

.tile.exchange-selected::before {
  content: "";
  position: absolute;
  inset: -5px;
  pointer-events: none;
  border: 5px solid var(--highlight);
  border-radius: 11px;
}

.rack-music,
.rack-sound,
.rack-shuffle {
  display: grid;
  width: 38px;
  height: 38px;
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--surface);
  color: var(--white);
  cursor: pointer;
}

.rack-audio-controls {
  grid-column: 1;
  justify-self: end;
  /* Même respiration que sous le chevalet, puis 20 px jusqu'au chevalet. */
  margin-right: 20px;
  display: flex;
  gap: 7px;
}

.rack-shuffle {
  grid-column: 3;
  justify-self: start;
  /* Symétrie exacte avec le groupe musique / son placé à gauche. */
  margin-left: 20px;
}

.rack-music svg,
.rack-sound svg,
.rack-shuffle svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.7;
}

/* La pointe de la flèche pèse visuellement en haut à droite : ce léger décalage
   redonne un centrage optique dans le cercle. */
.rack-shuffle svg { transform: translate(-1px, -1px); }

.rack-music .music-off,
.rack-sound .sound-off { display: none; }
.rack-music.is-muted .music-on { display: none; }
.rack-music.is-muted .music-off { display: block; }
.rack-sound.is-muted .sound-on { display: none; }
.rack-sound.is-muted .sound-off { display: block; }

.rack-music:hover,
.rack-sound:hover,
.rack-shuffle:hover,
.actions button:hover {
  background: var(--surface-hover);
  color: var(--white);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 7px;
  width: min(100%, var(--game-width));
  margin: 0 auto;
}

.actions button {
  padding: 8px 12px;
  border: 0;
  border-radius: 999px;
  background: var(--surface);
  color: var(--white);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .055em;
  text-transform: uppercase;
  cursor: pointer;
}

/* Pendant le choix des lettres, le bouton invite doucement à valider l'échange. */
.actions #exchange.is-selecting {
  animation: exchange-validation-pulse 1.15s ease-in-out infinite;
  transform-origin: center;
}

@keyframes exchange-validation-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.045); }
}

.actions button:disabled,
.rack-music:disabled,
.rack-sound:disabled,
.rack-shuffle:disabled {
  opacity: .35;
  cursor: default;
  pointer-events: none;
}

.play-feedback {
  display: grid;
  justify-items: center;
  margin-top: 15px;
}

#status,
#status .dictionary-info {
  min-height: 1.5em;
  margin: 0;
  font-size: 15px;
  /* 600 correspond au demi-gras dans la famille Avenir Next. */
  font-weight: 600;
}

/* -------------------------------------------------------------------------
   Dialogues et animations
   ------------------------------------------------------------------------- */

dialog {
  /* Tous les dialogues partagent le même gabarit et restent centrés par showModal(). */
  box-sizing: border-box;
  width: min(390px, 88vw);
  max-width: min(390px, 88vw);
  min-height: 300px;
  padding: 0;
  border: 0;
  border-radius: 12px;
  background: var(--dialog-surface);
  box-shadow: 0 20px 70px var(--dialog-shadow);
  color: var(--ink);
  text-align: center;
  overflow: hidden;
}

dialog::backdrop { background: var(--dialog-backdrop); }
dialog[open] { display: flex; flex-direction: column; }
/* Bandeau identique sur toutes les boîtes : les quatre marges du titre sont nulles. */
dialog h2 {
  display: grid;
  flex: 0 0 40px;
  width: 100%;
  height: 40px;
  margin: 0;
  padding: 0;
  place-items: center;
  background: var(--surface);
  color: var(--white);
  font-size: 20px;
  font-weight: 600;
  line-height: 1;
}
/* Le texte principal démarre toujours 80 px sous le bandeau de titre. */
dialog p { margin: 80px 28px 12px; font-size: 16px; line-height: 1.45; }
dialog > div,
dialog > select,
dialog > input { margin-left: 28px; margin-right: 28px; }
.joker-letters {
  display: grid;
  grid-template-columns: repeat(7, minmax(34px, 1fr));
  gap: 8px;
  width: calc(100% - 56px);
  margin: 0 auto 16px;
}
.joker-letters button {
  min-width: 0;
  padding: 8px 4px;
  font-family: "WordZeu Typewriter", "Courier New", monospace;
  font-size: 18px;
}
/* Groupe aligné à droite, avec les mêmes marges que les contenus du dialogue. */
.dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  width: calc(100% - 56px);
  margin: auto 28px 30px;
}
/* Boutons de confirmation : même langage visuel que les actions, sans contour. */
.dialog-actions > button,
.joker-letters button {
  min-height: 44px;
  padding: 10px 18px;
  border: 0;
  border-radius: 999px;
  background: var(--surface);
  color: var(--white);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .055em;
  text-transform: none;
  cursor: pointer;
}
.dialog-actions > button:hover,
.joker-letters button:hover { background: var(--surface-hover); }

/* Résultat de manche : les scores reprennent la lecture des panneaux du jeu,
   en noir sur blanc pour rester nets dans le dialogue. */
.end-scoreboard {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin: 0 0 16px;
}

.end-scoreboard span {
  display: grid;
  gap: 2px;
  padding: 8px;
  border: 1px solid #111;
  border-radius: 8px;
  color: #111;
  background: #fff;
  font-size: 13px;
  font-weight: 700;
}

.end-scoreboard strong { font-size: 25px; line-height: 1; }

#endMessage p {
  margin: 0 0 12px;
  color: #111;
  line-height: 1.35;
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
}

#roundMessage {
  margin: 80px 28px 12px;
  color: #111;
  line-height: 1.35;
  overflow-wrap: normal;
  word-break: normal;
  hyphens: none;
  white-space: pre-line;
}

#endMessage {
  width: calc(100% - 56px);
  margin: 80px auto 12px;
}

#endMessage .end-question { margin-top: 18px; font-weight: 700; }

#profileDialog input {
  display: block;
  width: calc(100% - 56px);
  margin: 0 auto 12px;
  height: 44px;
  padding: 10px;
  border: 1px solid var(--dialog-input-line);
  border-radius: 6px;
  color: var(--ink);
}

#profileDialog input,
#profileChoice {
  box-sizing: border-box;
  height: 44px;
}

@keyframes cpu-tile-in {
  from { transform: scale(.25) rotate(-9deg); opacity: 0; }
  to { transform: scale(1) rotate(0); opacity: 1; }
}


/* -------------------------------------------------------------------------
   Petits écrans
   ------------------------------------------------------------------------- */

/* iPad : mêmes espaces réguliers, avec des libellés légèrement plus compacts. */
@media (min-width: 601px) and (max-width: 1024px) {
  .top { gap: 8px; margin-top: 8px; }
  .score, .level, .level select { font-size: 12px; }
  .fullscreen-toggle { display: none; }
}

@media (max-width: 600px) {
  main { width: 100%; padding: 0 8px; }

  .cpu-word-history {
    top: 12px;
    left: 20px;
    width: min(230px, calc(100vw - 58px));
    max-height: 24dvh;
    padding: 6px 8px;
  }

  .performance-meter {
    right: 20px;
    bottom: 12px;
    min-width: 0;
    width: min(230px, calc(100vw - 58px));
    padding: 6px 8px;
  }

  .cpu-word-history .info-toggle { top: 0; right: -26px; }
  .performance-meter .info-toggle { top: 0; left: -26px; }

  .game-header {
    justify-content: flex-end;
    min-height: var(--profile-height);
    padding-right: 42px;
  }

  h1 {
    font-size: clamp(30px, 8vw, 40px);
    letter-spacing: 0;
  }

  .game-header .profile-panel {
    left: 0;
    width: min(var(--profile-menu-width), 42vw);
  }

  .fullscreen-toggle {
    display: none;
  }

  .fullscreen-toggle svg { width: 18px; height: 18px; }

  .top {
    grid-template-columns: repeat(6, 1fr);
    gap: 8px;
    width: 70%;
  }

  .top > .score { grid-column: span 2; }
  .top > .level { grid-column: span 3; }

  .score,
  .level { font-size: 12px; }

  .score strong { font-size: 21px; }
  .level select { font-size: 12px; }

  .board { width: min(100%, calc(100vw - 16px)); }

  .rack-line {
    --rack-tile-size: min(39px, calc((100vw - 60px) / 7));
    --rack-letter-size: min(34px, calc((100vw - 60px) / 7.8));
    --rack-value-size: min(10px, calc((100vw - 60px) / 28 - 1px));
    gap: 8px;
  }

  .rack { gap: 3px; }
  .tile { box-shadow: 1.3px 2.6px 2.6px rgba(0, 25, 15, .60); }
  .tile i { right: 3.5cqi; bottom: 0; font-size: 23cqi; }

  .rack-shuffle {
    width: 32px;
    height: 32px;
    margin-left: clamp(7px, 2.8vw, 12px);
  }

  .rack-audio-controls {
    margin-right: clamp(7px, 2.8vw, 12px);
  }

  .rack-music,
  .rack-sound {
    width: 32px;
    height: 32px;
  }

  .actions { gap: 4px; width: 100%; }
  .actions button { padding: 6px 7px; font-size: 9px; letter-spacing: .025em; }
  #status,
  #status .dictionary-info { font-size: 14px; }
  .move-score { min-width: 25px; min-height: 25px; padding: 4px 7px; font-size: 11px; }
}
