/* King of Tokyo — s'appuie sur style.css (variables, thèmes, boutons,
   tiroirs, overlays). Seul ce qui est propre aux monstres et aux dés est ici. */

/* ===== Ambiance de fond ========================================= */

/* Vignette : le fond s'assombrit vers les bords, ce qui donne une direction à
   l'œil et évite que les fiches isolées sur les côtés flottent dans le néant.
   En noir et non dans la couleur du feutre : les quatre thèmes en héritent sans
   qu'on écrive quatre variantes.
   `z-index: -1` : derrière tout le contenu, devant le fond de page. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 78% 62% at 50% 34%,
      rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, .34) 78%, rgba(0, 0, 0, .5) 100%);
}

/* La poussière qui monte. Uniquement `transform` et `opacity` : composé par le
   GPU, zéro travail par image côté JavaScript — c'est ce qui permet de la laisser
   tourner pendant les dés et les retours de coups sans rien coûter. */
.amb-dust {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}
.amb-dust i {
  position: absolute;
  bottom: -6px;
  border-radius: 50%;
  background: rgba(255, 253, 245, .85);
  opacity: 0;
  animation-name: amb-rise;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
@keyframes amb-rise {
  0% { transform: translate3d(0, 0, 0); opacity: 0; }
  12% { opacity: var(--peak, .2); }
  80% { opacity: var(--peak, .2); }
  100% { transform: translate3d(var(--drift, 0), -100vh, 0); opacity: 0; }
}

/* La lumière de fond se concentre autour de ce qui a la priorité. Un halo doux,
   pas un projecteur : très large, très diffus, et il glisse d'une cible à l'autre
   au lieu de sauter. Centré sur son origine, pour que le JavaScript n'ait qu'une
   position à poser. */
.amb-halo {
  position: fixed;
  left: 0;
  top: 0;
  z-index: -1;
  width: 62vmax;
  height: 62vmax;
  margin: -31vmax 0 0 -31vmax;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(closest-side,
      rgba(255, 246, 214, .1), rgba(255, 246, 214, .045) 45%, transparent 72%);
  transition: transform 1.1s cubic-bezier(.3, .8, .3, 1), opacity .9s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  /* Ni la poussière ni le halo ne sont créés dans ce cas (voir ambience.js) ; les
     règles restent comme garde-fou s'ils l'étaient par un autre chemin. */
  .amb-dust,
  .amb-halo { display: none; }
}

/* En rem, pas en pixels : le rem grandit avec la fenêtre (voir « Grands
   écrans » en bas de cette feuille), et une boîte figée en pixels laisserait le
   texte gonfler à l'intérieur sans que la boîte suive. */
.center-card-wide { width: min(94vw, 32.5rem); }
.center-card { width: min(92vw, 26.25rem); }
.overlay-card { width: min(92vw, 27rem); }

/* ===== Lobby ==================================================== */

.lobby-monsters {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  margin: 6px 0;
}
.lobby-monster {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 6px;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, .22);
  border: 1px solid var(--m-color, rgba(255, 253, 245, .2));
}
.lobby-monster-name { font-weight: 700; font-size: .9rem; }
.lobby-monster-species { font-size: .72rem; opacity: .7; }

.tag-bot, .tag-off {
  font-style: normal;
  font-size: .62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  padding: 1px 5px;
  border-radius: 999px;
  vertical-align: 1px;
}
.tag-bot { background: rgba(255, 253, 245, .18); }
.tag-off { background: rgba(220, 38, 38, .3); color: #fecaca; }

/* ===== Barre de tour ============================================ */

.turn-pill {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-size: .85rem;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .28);
}

/* Ce que fait le monstre actif, sur sa propre ligne : « Zaza garde 2 dés · 1
   relance ». En retrait, parce que le nom du joueur reste l'information
   principale — mais sur toute la largeur, parce qu'une phrase tronquée à
   « Zaza garde 5 d… » ne renseigne personne. */
.turn-what {
  margin: 0 auto;
  padding: 0 8px;
  max-width: 100%;
  text-align: center;
  font-size: .74rem;
  opacity: .68;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.turn-what:empty { display: none; }
.turn-pill.mine {
  background: var(--gold);
  color: #442c00;
  animation: turn-glow 1.6s ease-in-out infinite;
}
@keyframes turn-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, .5); }
  50% { box-shadow: 0 0 0 7px rgba(251, 191, 36, 0); }
}

/* ===== Disposition : plateau au centre, monstres autour ========= */

/* La table remplit la hauteur : barre en haut, anneau de joueurs, moi en bas. */
.screen-table {
  flex-direction: column;
  gap: 6px;

  /* Tailles fluides. Les bornes basses reprennent exactement les valeurs
     d'avant : en dessous de ~1450 px rien ne change. Au-delà, tout grandit
     avec la fenêtre au lieu de plafonner, chacun à son rythme — le plateau et
     les portraits gagnent plus que le texte, sinon on obtient une interface
     simplement zoomée, sans plus d'information à l'écran.
     Bornes en px et non en rem : le rem grandit lui aussi (voir plus bas), et
     deux échelles qui se multiplient deviennent impossibles à prévoir. */
  --portrait: clamp(56px, 4.4vw, 116px);
  --portrait-big: clamp(76px, 6vw, 150px);
  --die: 58px;
}
.table-body { flex: 1; display: flex; gap: 10px; min-height: 0; }

.table-grid {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 6px;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr auto auto;
  grid-template-areas:
    "ring"
    "kot-board"
    "market"
    "me";
}

/* Mobile : les trois zones se fondent en un seul bandeau scrollable. Le
   `display: contents` fait des fiches les enfants directs du bandeau, donc un
   seul défilement horizontal pour tout le monde, dans l'ordre de jeu. */
.kot-seats-ring {
  grid-area: ring;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.kot-seats-ring::-webkit-scrollbar { display: none; }
.kot-seats { display: contents; }
.kot-seats > .monster-card { flex: 0 0 auto; width: 168px; }

/* Le conteneur du plateau : c'est lui qui définit « la place disponible », et
   `container-type: size` permet au plateau de s'y ajuster en la mesurant dans
   les deux sens (voir .kot-board). La containment de taille a un corollaire
   utile : la cellule est dimensionnée par la grille et jamais par son contenu,
   donc le plateau ne peut plus pousser la rangée. */
.kot-board-cell {
  grid-area: kot-board;
  position: relative;
  container-type: size;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Sur bureau, le panneau n'est qu'une rangée de la table : sa poignée et sa
   barre d'action ne servent qu'en mode coulissant. */
.market-sheet { grid-area: market; min-width: 0; }
.market-grip,
.sheet-actions { display: none; }

.me-zone {
  grid-area: me;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: opacity .25s, filter .25s;
}
.me-zone[hidden] { display: none; }
/* Ce n'est pas mon tour : ma zone se met en retrait, sans disparaître. */
.me-zone.idle { opacity: .62; filter: saturate(.55); }
.kot-me-card > .monster-card { width: 100%; }
.me-play { display: flex; flex-direction: column; gap: 4px; }

/* ===== Plateau et zones ========================================= */

/* Le plateau garde le rapport de l'illustration : les zones peuvent alors
   être positionnées en pourcentages et rester collées aux bons quartiers,
   quelle que soit la taille de l'écran. */
.kot-board {
  position: relative;
  /* « La plus grande boîte au rapport 1920/1047 qui tient dans la place
     disponible » : la largeur est bornée à la fois par la largeur du conteneur
     et par sa hauteur convertie en largeur. Les deux bornes sont nécessaires,
     et c'est pour ça qu'il faut les unités de conteneur :
       — `max-height` seul ne rétrécit pas la largeur d'une boîte dont la
         largeur est définie : l'image se faisait rogner et les zones
         glissaient hors de leurs quartiers ;
       — laisser le flex écraser la hauteur aplatissait le plateau en bandeau
         là où la place verticale manque (mobile) ;
       — un budget en `vh` ne connaît pas la hauteur réellement laissée par la
         grille : il débordait sur le marché.
     `cqh`/`cqw` mesurent la cellule elle-même, donc la hauteur demandée ne peut
     jamais dépasser celle qui est disponible. */
  width: min(100cqw, calc(100cqh * 1920 / 1047));
  aspect-ratio: 1920 / 1047;
  flex: none;
  /* `auto` sur les quatre côtés : le plateau se centre dans la place que la
     grille lui laisse, plutôt que de laisser un vide sous lui. */
  margin: auto;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid rgba(255, 253, 245, .14);
  box-shadow: inset 0 0 40px rgba(0, 0, 0, .5);
  transition: filter .25s;
}
/* Quand l'action est ailleurs (mes dés, le marché), le plateau se retire. */
.kot-board.dimmed { filter: saturate(.85) brightness(.9); }

.kot-board-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #101a3a url('/kot/board.jpg') center/cover no-repeat;
}

/* ===== L'illustration remise en mouvement ======================= */

/* Trois calques par-dessus l'image, sans la retoucher : le ciel qui dérive, un
   scintillement sur l'eau, et le monorail qui glisse sur son viaduc.
   Au-dessus du fond, sous les zones (z-index 1) : l'ambiance ne passe jamais
   devant l'information. */
.kot-board-amb {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

/* Le ciel occupe le quart haut de l'illustration. La dérive est une oscillation
   très lente et de très faible amplitude : ça évite le saut d'une boucle qui
   revient à zéro, et à cette amplitude personne ne perçoit le changement de
   sens. */
.amb-sky {
  position: absolute;
  inset: 0 -4% auto -4%;
  height: 26%;
  background:
    radial-gradient(38% 120% at 18% 55%, rgba(255, 255, 255, .07), transparent 72%),
    radial-gradient(30% 110% at 47% 35%, rgba(255, 255, 255, .05), transparent 72%),
    radial-gradient(44% 130% at 86% 62%, rgba(255, 255, 255, .06), transparent 72%);
  animation: amb-sky-drift 74s ease-in-out infinite alternate;
}
@keyframes amb-sky-drift {
  from { transform: translate3d(-1.6%, 0, 0); }
  to { transform: translate3d(1.6%, 0, 0); }
}

/* Le chenal central, découpé en pourcentages du plateau pour suivre le cadrage
   à toutes les tailles. Les bandes du dégradé sont perpendiculaires au courant :
   ça se lit comme un reflet sur l'eau, pas comme une trame. */
.amb-water {
  position: absolute;
  inset: 0;
  clip-path: polygon(55% 24%, 68% 21%, 60% 100%, 24% 100%);
  background: repeating-linear-gradient(116deg,
      transparent 0 17px,
      rgba(196, 255, 246, .075) 17px 25px,
      transparent 25px 46px);
  animation: amb-water 8.5s ease-in-out infinite alternate;
}
@keyframes amb-water {
  from { transform: translate3d(0, 0, 0); opacity: .5; }
  to { transform: translate3d(0, 11px, 0); opacity: 1; }
}

/* Le monorail. Le SVG partage le repère de l'image, donc les coordonnées des
   images-clés sont relevées une fois sur l'illustration et restent justes quelle
   que soit la taille du plateau. Les paliers suivent la courbe du viaduc, et
   leurs pourcentages sont proportionnels à la longueur de chaque segment pour
   que la vitesse reste constante. */
.amb-rail {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.amb-train {
  fill: #cfe8ff;
  filter: drop-shadow(0 0 7px rgba(160, 220, 255, .85));
  animation: amb-train 24s linear infinite;
}
@keyframes amb-train {
  0% { transform: translate(1000px, 545px) rotate(141deg); opacity: 0; }
  6% { opacity: .9; }
  16% { transform: translate(880px, 640px) rotate(141deg); }
  37% { transform: translate(720px, 765px) rotate(142deg); }
  54% { transform: translate(580px, 862px) rotate(145deg); }
  72% { transform: translate(430px, 950px) rotate(150deg); }
  90% { transform: translate(270px, 1035px) rotate(152deg); opacity: .9; }
  100% { transform: translate(190px, 1080px) rotate(152deg); opacity: 0; }
}

/* Sous mouvement réduit, l'ambiance du plateau disparaît entièrement : rien ne
   bouge, et l'illustration reste parfaitement lisible. */
@media (prefers-reduced-motion: reduce) {
  .kot-board-amb { display: none; }
}

/* Les zones sont posées sur les quartiers de l'illustration : le quartier
   d'affaires à gauche, la zone portuaire à droite. */
.zone {
  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  border: 2px dashed rgba(255, 253, 245, .3);
  background: rgba(6, 12, 30, .28);
  transition: border-color .3s, box-shadow .3s, background .3s;
}
.zone[hidden] { display: none; }
.zone-city { left: 3%; top: 20%; width: 44%; bottom: 6%; }
.zone-bay { right: 3%; top: 24%; width: 38%; bottom: 5%; }
/* À quatre joueurs ou moins, Tokyo Bay n'existe pas : Tokyo prend la place. */
.kot-board:not(.with-bay) .zone-city { width: 62%; left: 6%; }

.zone.occupied {
  border-style: solid;
  border-color: var(--zone-color, var(--gold));
  background: rgba(6, 12, 30, .12);
  box-shadow: 0 0 22px -4px var(--zone-color, var(--gold)),
              inset 0 0 30px -10px var(--zone-color, var(--gold));
}

.zone-name {
  position: absolute;
  top: 6px;
  left: 9px;
  z-index: 2;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-shadow: 0 1px 4px #000, 0 0 10px #000;
}
.zone-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 4px;
}
.zone-empty {
  align-self: center;
  font-size: .78rem;
  font-style: italic;
  opacity: .6;
  text-shadow: 0 1px 4px #000;
}

/* L'occupant apparaît en portrait complet, plus grand que sur sa fiche : on
   doit voir qui domine la ville. */
.zone-monster {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
  min-height: 0;
}
/* L'occupant grandit avec le plateau — il doit se voir plus grand que sur sa
   fiche. Mais il est plafonné : à remplir toute la zone sur un écran 4K, il
   masquait la ville qu'il est censé dominer. `contain` garantit qu'il ne se
   déforme jamais quel que soit le rapport de la zone, et l'ancrage en bas le
   pose sur les toits au lieu de le faire flotter. */
.zone-monster .monster-portrait {
  height: 100%;
  width: 100%;
  min-height: 0;
  max-height: clamp(140px, 20vh, 300px);
  object-fit: contain;
  object-position: bottom center;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .65));
}
.zone-tag {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .62);
  font-size: .72rem;
  font-weight: 700;
  white-space: nowrap;
}
.zone-tag b { color: var(--zone-color, var(--cream)); }

/* Bonus de contrôle : lisible sans aller lire le journal. */
.zone-bonus {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 2;
  font-size: .62rem;
  font-weight: 800;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .6);
  color: var(--gold);
  border: 1px solid rgba(251, 191, 36, .45);
}
.zone-bonus[hidden] { display: none; }
.zone-bonus.pulse { animation: bonus-pulse 1.8s ease-in-out infinite; }
@keyframes bonus-pulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(251, 191, 36, .45); }
  50% { transform: scale(1.06); box-shadow: 0 0 0 6px rgba(251, 191, 36, 0); }
}

/* ===== Panneau latéral en onglets =============================== */

.side-tabs { display: flex; gap: 4px; margin-bottom: 8px; }
.side-tab {
  flex: 1;
  font-size: .72rem;
  font-weight: 700;
  padding: 6px 4px;
  border: none;
  border-radius: 9px;
  background: rgba(255, 253, 245, .08);
  color: var(--cream);
  cursor: pointer;
  transition: background .15s;
}
.side-tab:hover { background: rgba(255, 253, 245, .16); }
.side-tab.on { background: rgba(251, 191, 36, .2); color: var(--gold); }

.side-pane { display: none; flex: 1; min-height: 0; flex-direction: column; }
.side-pane.on { display: flex; }

.rank-list { list-style: none; display: flex; flex-direction: column; gap: 4px; overflow-y: auto; }
.rank-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 9px;
  border-radius: 9px;
  background: rgba(255, 253, 245, .07);
  border-left: 3px solid var(--m-color);
  font-size: .84rem;
}
.rank-row.me { background: rgba(251, 191, 36, .14); }
.rank-row.dead { opacity: .45; }
.rank-pos { font-weight: 800; opacity: .5; font-size: .75rem; width: 12px; }
.rank-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rank-vp { font-weight: 800; color: var(--gold); }
.rank-hp { font-size: .76rem; opacity: .7; width: 44px; text-align: right; }

/* ===== Fiches de monstres ======================================= */

.monster-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 12px;
  background: linear-gradient(120deg, rgba(0, 0, 0, .32), rgba(0, 0, 0, .18));
  border: 1px solid var(--m-color);
  overflow: hidden;
  transition: box-shadow .2s, transform .15s;
}
.monster-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--m-color);
  opacity: .1;
}
.monster-card > * { position: relative; }
/* Actif : contour lumineux et carte légèrement agrandie. C'est le seul accent
   visuel de la fiche, comme le demande le brief — pas de lueur en plus. */
.monster-card.current {
  box-shadow: 0 0 0 2px var(--gold), 0 6px 16px rgba(0, 0, 0, .35);
  transform: translateY(-1px) scale(1.03);
  z-index: 1;
}
/* Blessé : teinte plus froide par-dessus la couleur du monstre, qui reste
   reconnaissable — c'est elle qui identifie l'adversaire d'un coup d'œil. */
.monster-card.hurt::before {
  background: linear-gradient(160deg, var(--m-color), #1e3a8a);
  opacity: .18;
}
.monster-card.in-tokyo::after {
  content: '👑';
  position: absolute;
  top: 2px;
  right: 5px;
  font-size: .8rem;
}
/* Éliminé : la carte reste en place pour ne pas réorganiser la table en pleine
   partie, mais elle se retire visuellement — affaissée, grise, et sa barre de vie
   vide d'elle-même puisque tous ses segments sont éteints. L'affaissement est
   dans l'état, pas seulement dans l'animation : sinon la carte se relèverait à la
   fin de celle-ci. */
.monster-card.dead {
  filter: grayscale(1);
  opacity: .45;
  transform: translateY(4px) scale(.97) rotate(1deg);
}
.monster-card.dead::before { opacity: .04; }
.monster-card.compact { padding: 4px 6px; }
.monster-card.big { padding: 9px 11px; gap: 11px; }
.monster-card.big .monster-name { font-size: 1rem; }
.monster-card.on-board { border-width: 2px; padding: 8px 10px; gap: 10px; }

.monster-avatar { flex-shrink: 0; line-height: 0; }
.monster-portrait {
  display: block;
  width: auto;
  filter: drop-shadow(0 3px 4px rgba(0, 0, 0, .5));
}
/* Le `height` posé par monsterPortrait() n'est qu'un attribut HTML : il réserve
   la place pendant le chargement, et le CSS reprend la main pour la taille
   réelle. Le portrait suit donc l'échelle fluide sans toucher au JS. */
.monster-card .monster-portrait { height: var(--portrait); }
.monster-card.big .monster-portrait { height: var(--portrait-big); }
.monster-card.compact .monster-portrait { height: calc(var(--portrait) * .75); }
/* Blessé : un portrait plus froid et plus terne, sans redessiner d'illustration. */
.monster-portrait.hurt { filter: drop-shadow(0 3px 4px rgba(0, 0, 0, .5)) saturate(.5) brightness(.82) hue-rotate(-8deg); }

/* La respiration au repos : une oscillation d'échelle à peine perceptible, pour
   qu'aucun monstre ne soit une image plate collée à l'écran. Le décalage vient de
   `--breathe`, calculé sur l'horloge à chaque rendu (voir breatheDelay dans
   panels.js) : sans lui, l'animation repartirait de zéro à chaque état reçu.
   `scale` seul, jamais `width`/`height` : composé par le GPU, aucun recalcul de
   mise en page. */
.monster-portrait {
  animation: mon-breathe 6.5s ease-in-out infinite;
  animation-delay: var(--breathe, 0s);
}
@keyframes mon-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.024); }
}
/* Un monstre éliminé ne respire plus. */
.monster-card.dead .monster-portrait { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .monster-portrait { animation: none; }
}

.monster-info { min-width: 0; flex: 1; }
.monster-name {
  font-weight: 800;
  font-size: .9rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.monster-card.me .monster-name { color: var(--gold); }
.monster-species { font-size: .66rem; opacity: .6; margin-bottom: 2px; }

/* Barre de vie segmentée : un segment par point. Perdre trois points se lit
   comme trois segments qui s'éteignent, ce qu'une barre qui rétrécit d'un
   pourcentage ne raconte pas. Les segments sont en flex : à 10 ou à 12 (Chair
   Mutante), la barre garde la même largeur. */
.hp-bar {
  display: flex;
  gap: 2px;
  height: 7px;
  margin: 4px 0;
}
.hp-seg {
  flex: 1;
  min-width: 0;
  border-radius: 2px;
  background: rgba(0, 0, 0, .5);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .25);
  transition: background .3s, box-shadow .3s;
}
.hp-seg.on {
  background: linear-gradient(180deg, #fca5a5, #f87171);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35);
}
/* Blessé : les segments restants passent au rouge sombre — la couleur dit la
   gravité, le nombre dit la quantité. */
.monster-card.hurt .hp-seg.on { background: linear-gradient(180deg, #f87171, #b91c1c); }
.monster-card.compact .hp-bar { height: 5px; gap: 1px; margin: 2px 0; }

/* Les pastilles se dimensionnent sur leur contenu et s'enroulent si la fiche est
   trop étroite. Surtout pas de colonnes de largeur égale : sur les fiches
   d'adversaires en mobile, une troisième colonne d'un tiers de 90 px rognait le
   chiffre et il ne restait que l'icône. */
.monster-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  font-size: .82rem;
  font-weight: 700;
}
/* Pastilles distinctes : les points de victoire et l'énergie ne doivent pas se
   lire comme du texte courant, ce sont les compteurs qu'on cherche du regard.
   Chacune reste sur une ligne : « ❤️ 6 » ne doit jamais se couper entre l'icône
   et le chiffre. */
.stat {
  white-space: nowrap;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(0, 0, 0, .32);
}
.stat i { font-style: normal; font-size: .68rem; opacity: .55; }
.stat-hp { color: #fca5a5; background: rgba(248, 113, 113, .16); }
.stat-vp { color: var(--gold); background: rgba(251, 191, 36, .18); }
.stat-en { color: #7dd3fc; background: rgba(125, 211, 252, .16); }

.monster-ko {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-weight: 900;
  letter-spacing: .2em;
  background: rgba(0, 0, 0, .35);
}

/* ===== Dés ====================================================== */

/* Informations en haut, contrôles en bas : les dés et les boutons restent à
   portée de pouce quelle que soit la hauteur de l'écran. */
.table-main { overflow-y: auto; }

/* Un vrai plateau de lancer, pour que le bas de l'écran soit une zone et pas
   un vide. */
.dice-tray {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px;
  /* Un peu d'air en haut : c'est la hauteur que prend l'envol du lancer, sinon
     les dés sortent du plateau en montant. */
  padding: 18px 8px 12px;
  min-height: 82px;
  border-radius: var(--radius);
  background:
    radial-gradient(ellipse at 50% 0%, rgba(0, 0, 0, .32), rgba(0, 0, 0, .12));
  border: 1px solid rgba(255, 253, 245, .1);
  box-shadow: inset 0 6px 14px rgba(0, 0, 0, .3);
}
.dice-tray:empty { display: none; }

/* --lift porte la hauteur de repos propre à chaque état. Les images-clés du
   lancer y reviennent au lieu de retomber à zéro : sans ça, le dé sauterait
   de deux pixels à la fin de l'animation, quand la règle d'état reprend la
   main sur `transform`. */
/* Les trois états doivent se reconnaître sans lire le texte : ils ne se
   distinguent donc pas seulement par la hauteur, mais aussi par la portée de
   l'ombre (un objet qui flotte projette loin, un objet posé projette court) et
   par la présence à l'écran (un dé hors jeu se retire un peu). */
.die {
  --lift: -3px;
  width: var(--die);
  height: var(--die);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  border-radius: 12px;
  transform: translateY(var(--lift));
  transition: transform .12s, filter .15s, box-shadow .15s, opacity .15s;
  filter: drop-shadow(0 7px 8px rgba(0, 0, 0, .6));
}
.die svg { display: block; width: 100%; height: 100%; }

/* 1. À relancer : surélevé, ombre longue, opacité pleine, curseur pointeur.
   C'est l'état qui appelle le clic. */
.die:hover:not(:disabled) { --lift: -6px; }
.die:active:not(:disabled) { transform: translateY(1px) scale(.94); }

/* 2. Gardé : enfoncé de quelques pixels, cerclé d'or, ombre courte — il repose
   sur le plateau au lieu de flotter au-dessus. */
.die.kept {
  --lift: 3px;
  transform: translateY(var(--lift)) scale(.93);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
  box-shadow: 0 0 0 3px var(--gold), inset 0 0 12px rgba(0, 0, 0, .3);
}
.die.kept:hover:not(:disabled) { transform: translateY(1px) scale(.95); }

/* 3. Verrouillé : posé à plat, contour neutre, légèrement en retrait. Il n'y a
   plus rien à décider, le dé ne doit plus réclamer l'attention. */
.die.locked {
  --lift: 0px;
  cursor: default;
  opacity: .78;
  filter: saturate(.7) drop-shadow(0 2px 2px rgba(0, 0, 0, .45));
  box-shadow: 0 0 0 1px rgba(255, 253, 245, .18);
}
.die:disabled { cursor: default; }

/* Lancer : envol, deux tours et demi… trois en fait (il faut un multiple de
   360° pour que le cœur et l'éclair ne retombent pas de travers), puis
   atterrissage avec dépassement. La courbe du premier segment est celle du
   brief : le dé est comme arraché du plateau. Seuls les dés relancés bougent —
   les dés gardés n'ont même pas cette classe. */
.die.rolling {
  animation: die-roll .6s linear both;
  /* Un dé en vol est pleinement présent, même quand il atterrira verrouillé
     (cas du dernier lancer : plus de relance, mais la chute doit se voir). */
  box-shadow: none;
  opacity: 1;
  filter: drop-shadow(0 10px 8px rgba(0, 0, 0, .5));
}
@keyframes die-roll {
  0% {
    transform: translateY(var(--lift)) rotate(0) scale(1);
    animation-timing-function: cubic-bezier(.3, -0.4, .7, 1.4);
  }
  30%  { transform: translateY(-28px) rotate(400deg) scale(1.06); }
  62%  { transform: translateY(-17px) rotate(780deg) scale(1.03); }
  /* Impact : le dé s'écrase légèrement… */
  82%  { transform: translateY(5px) rotate(1030deg) scale(.94); }
  /* …rebondit… */
  92%  { transform: translateY(-6px) rotate(1068deg) scale(1.02); }
  /* …et se pose. */
  100% { transform: translateY(var(--lift)) rotate(1080deg) scale(1); }
}

/* Résolution : le groupe en cours de décompte s'allume, pour qu'on voie d'où
   vient le gain. Un seul accent à la fois, donc il remplace l'ombre portée au
   lieu de s'y ajouter. */
.die.lit {
  box-shadow: 0 0 0 2px var(--gold);
  opacity: 1;
  filter: drop-shadow(0 0 8px rgba(251, 191, 36, .7));
  animation: die-lit .4s ease-out;
}
@keyframes die-lit {
  0%   { transform: translateY(var(--lift)) scale(1); }
  45%  { transform: translateY(calc(var(--lift) - 4px)) scale(1.12); }
  100% { transform: translateY(var(--lift)) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .die,
  .die.rolling,
  .die.lit { animation: none; transition: none; }
}

/* Sur mobile, six dés ne tiennent pas sur une ligne : au fil de l'enroulement
   naturel, le sixième se retrouve seul sur une deuxième rangée et la relance
   devient illisible. Deux rangées de trois, à des places qui ne bougent
   jamais, se lisent d'un coup d'œil. */
@media (max-width: 699px) {
  .dice-tray {
    display: grid;
    grid-template-columns: repeat(3, auto);
    justify-content: center;
  }
}

.dice-hint { text-align: center; font-size: .78rem; opacity: .7; min-height: 1.1em; }

/* ===== Hiérarchie visuelle : une seule chose à faire à la fois ===== */

/* focusAttention() posait déjà ces classes, rien ne les dessinait. « Une seule
   chose doit attirer l'œil à un instant donné » (§2) : la zone où il faut agir
   reçoit un contour discret, le reste se met en retrait. Contour seul, sans
   lueur : le §9 interdit d'empiler les deux. */
.dice-tray.active,
.market.active {
  box-shadow: inset 0 6px 14px rgba(0, 0, 0, .3), 0 0 0 2px rgba(251, 191, 36, .55);
  transition: box-shadow .25s;
}
.market.active { box-shadow: 0 0 0 2px rgba(251, 191, 36, .45); border-radius: var(--radius); }
/* Ce n'est pas mon tour : mes dés se taisent (voir aussi .me-zone.idle). */
.me-zone.idle .dice-tray { box-shadow: inset 0 6px 14px rgba(0, 0, 0, .3); }

@media (prefers-reduced-motion: reduce) {
  .dice-tray.active, .market.active { transition: none; }
}

/* ===== Résolution =============================================== */

/* Posé par-dessus le plateau, et non en dessous : en occupant de la place dans
   la colonne, le récapitulatif volait quelques dizaines de pixels de hauteur au
   plateau, qui changeait donc de taille à chaque résolution.
   En haut, parce que c'est là qu'il y a du ciel : en bas il masquait les rues et
   le monstre qui tient la ville. Sur une seule ligne qui défile plutôt que sur
   plusieurs, pour ne pas descendre jusqu'aux libellés de zone. */
.resolve-summary {
  position: absolute;
  left: 50%;
  top: 6px;
  transform: translateX(-50%);
  z-index: 3;
  max-width: 94%;
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  justify-content: flex-start;
  gap: 6px;
  padding: 6px;
  border-radius: 10px;
  background: rgba(0, 0, 0, .62);
  backdrop-filter: blur(2px);
}
.resolve-summary::-webkit-scrollbar { display: none; }
.resolve-summary span { flex: 0 0 auto; }
.resolve-summary[hidden] { display: none; }
.resolve-summary span {
  font-size: .78rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  animation: gain-pop .3s cubic-bezier(.2, 1.6, .4, 1);
}
.resolve-summary .gain { background: rgba(74, 222, 128, .22); color: #bbf7d0; }
.resolve-summary .hit { background: rgba(220, 38, 38, .25); color: #fecaca; }
@keyframes gain-pop { from { transform: scale(.6); opacity: 0; } }

/* ===== Marché de cartes ========================================= */

.market { margin: 4px 0; }
.market-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 5px;
}
.market-title { font-size: .78rem; font-weight: 700; opacity: .85; }
.market-title em { font-style: normal; font-weight: 400; opacity: .6; margin-left: 6px; }

.market-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }

/* Une carte du marché, par catégorie. Chaque catégorie apporte trois choses et
   trois seulement : une couleur d'accent (--cat), un lavis pour les fonds
   (--wash) et une texture de fond (--tex). Le reste des règles est commun, donc
   ajouter une catégorie tient en une ligne.
   Les textures sont en blanc à très faible opacité, jamais dans la couleur
   d'accent : superposées au lavis elles se teintent d'elles-mêmes, et on garde
   une seule définition par forme. */
/* Les accents sont volontairement clairs : la table est verte et sombre, et les
   teintes « justes » du document (vert sauge, bleu acier) s'y évanouissaient —
   bande d'accent invisible, icône illisible. On garde la teinte, on remonte la
   luminosité. */
.market-card[data-cat='combat'] {
  --cat: #ff8b58;
  --wash: rgba(255, 139, 88, .18);
  --tex: repeating-linear-gradient(135deg,
      rgba(255, 255, 255, .07) 0 1px, transparent 1px 7px);
}
.market-card[data-cat='defense'] {
  --cat: #8dc0ea;
  --wash: rgba(141, 192, 234, .18);
  --tex: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='19'%3E%3Cpath d='M5.5 .5 16.5 .5 22 9.5 16.5 18.5 5.5 18.5 0 9.5z' fill='none' stroke='%23fff' stroke-opacity='.1'/%3E%3C/svg%3E");
}
.market-card[data-cat='energie'] {
  --cat: #f5b528;
  --wash: rgba(245, 181, 40, .18);
  --tex: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18'%3E%3Cpath d='M10 2 5 10h3l-1 6 6-8h-3z' fill='%23fff' fill-opacity='.09'/%3E%3C/svg%3E");
}
.market-card[data-cat='victoire'] {
  --cat: #ffd75e;
  --wash: rgba(255, 215, 94, .17);
  --tex: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M10 3 11.4 8.6 17 10 11.4 11.4 10 17 8.6 11.4 3 10 8.6 8.6z' fill='%23fff' fill-opacity='.09'/%3E%3C/svg%3E");
}
.market-card[data-cat='des'] {
  --cat: #c0aaff;
  --wash: rgba(192, 170, 255, .18);
  --tex: radial-gradient(rgba(255, 255, 255, .11) 1.2px, transparent 1.4px);
  background-size: 11px 11px, auto;
}
.market-card[data-cat='utilitaire'] {
  --cat: #b3dcab;
  --wash: rgba(179, 220, 171, .18);
  --tex: radial-gradient(circle,
      transparent 3.2px, rgba(255, 255, 255, .09) 3.6px, transparent 4.8px);
  background-size: 15px 15px, auto;
}

.market-card {
  --cat: rgba(255, 253, 245, .3);
  --wash: rgba(255, 253, 245, .08);
  --tex: none;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 5px;
  /* De la place en haut pour la bande d'accent. */
  padding: 13px 10px 9px;
  border-radius: 12px;
  overflow: hidden;
  background: var(--tex), linear-gradient(160deg, var(--wash), rgba(0, 0, 0, .34) 62%);
  border: 1px solid rgba(255, 253, 245, .16);
  min-height: 168px;
  transition: transform .16s, box-shadow .2s, border-color .2s, filter .2s;
}
/* La bande d'accent : le premier repère, avant même de lire le texte. */
.market-card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 5px;
  background: var(--cat);
}
.market-card.empty {
  align-items: center;
  justify-content: center;
  font-style: italic;
  opacity: .5;
  min-height: 0;
}
.market-card.empty::before { display: none; }

.market-head { display: flex; justify-content: space-between; align-items: center; gap: 6px; }
/* Le médaillon : l'icône est posée sur un fond teinté, pas flottante. */
.market-medal {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  flex: none;
  border-radius: 8px;
  background: var(--wash);
  border: 1px solid var(--cat);
  color: var(--cat);
}
.market-icon { display: block; width: 18px; height: 18px; }
/* Le coût reprend le fond du médaillon pour ne pas flotter seul. */
.market-cost {
  font-size: .92rem;
  font-weight: 800;
  padding: 3px 9px;
  border-radius: 8px;
  background: var(--wash);
  border: 1px solid var(--cat);
  color: var(--cream);
  white-space: nowrap;
}

/* Pouvoir ou Action : un repère épinglé contre un éclair simple, plus un fond
   distinct. La couleur d'accent appartient à la catégorie, la distinction de
   type passe donc par la forme. */
.market-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  flex-wrap: wrap;
}
.market-kind {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: .6rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  cursor: help;
}
.market-mark { display: block; width: 11px; height: 11px; }
.market-card[data-type='keep'] .market-kind { background: rgba(125, 211, 252, .2); color: #bae6fd; }
.market-card[data-type='action'] .market-kind { background: rgba(248, 113, 113, .22); color: #fecaca; }
.market-name { font-size: .86rem; font-weight: 800; line-height: 1.15; }
.market-text { font-size: .7rem; line-height: 1.35; opacity: .82; flex: 1; }
.market-buy { padding: 5px 8px; font-size: .75rem; }

/* Abordable : un léger relief au repos, qui s'accentue et prend la couleur de la
   catégorie au survol — la carte doit donner envie d'être prise. */
.market-card.buyable {
  box-shadow: 0 3px 10px rgba(0, 0, 0, .38);
  cursor: pointer;
}
.market-card.buyable:hover {
  transform: translateY(-3px);
  border-color: var(--cat);
  box-shadow: 0 12px 22px -8px rgba(0, 0, 0, .6);
}
.market-card.buyable:hover .market-medal { background: var(--cat); color: #10281c; }

/* Trop chère : désaturée, bande d'accent grise, coût en rouge sourd, et aucun
   relief au survol — rien n'invite à cliquer. */
.market-card.costly { filter: saturate(.4); }
.market-card.costly::before { background: rgba(255, 253, 245, .22); }
.market-card.costly .market-cost {
  color: #f0a3a3;
  border-color: rgba(240, 163, 163, .45);
  background: rgba(240, 163, 163, .1);
}

/* ===== Le cadre des cartes (§5 de l'ambiance) ==================== */

/* Ornements d'angle : deux équerres opposées dans la couleur de catégorie. Ça
   referme visuellement le cadre, et ça coûte quatre dégradés — pas une
   illustration par carte, ce que le brief interdit explicitement. */
.market-frame {
  position: absolute;
  inset: 7px;
  pointer-events: none;
  border-radius: 7px;
  background:
    linear-gradient(var(--cat), var(--cat)) left top / 13px 1.5px no-repeat,
    linear-gradient(var(--cat), var(--cat)) left top / 1.5px 13px no-repeat,
    linear-gradient(var(--cat), var(--cat)) right bottom / 13px 1.5px no-repeat,
    linear-gradient(var(--cat), var(--cat)) right bottom / 1.5px 13px no-repeat;
  opacity: .55;
}

/* Le grain : une trame très fine par-dessus la texture de catégorie, pour que le
   fond ne soit pas un aplat. Sur le cadre et non sur la carte, dont la pile de
   fonds est déjà composée par catégorie. */
.market-frame::before {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 12px;
  background-image: radial-gradient(rgba(255, 253, 245, .05) .5px, transparent .6px);
  background-size: 3px 3px;
}

/* Le reflet au survol : il traverse la carte, au lieu du seul changement de
   couleur de bordure. Posé sur le cadre parce que ::after de la carte sert déjà
   au reflet d'arrivée — les deux peuvent donc se produire sans se voler. */
.market-frame::after {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 12px;
  opacity: 0;
  background: linear-gradient(102deg,
      transparent 34%, rgba(255, 255, 255, .22) 50%, transparent 66%);
}
.market-card.buyable:hover .market-frame { opacity: .9; }
.market-card.buyable:hover .market-frame::after { animation: market-sheen .62s ease-out; }
@keyframes market-sheen {
  0% { opacity: 0; transform: translateX(-58%); }
  22% { opacity: 1; }
  100% { opacity: 0; transform: translateX(58%); }
}

@media (prefers-reduced-motion: reduce) {
  .market-card.buyable:hover .market-frame::after { animation: none; }
}

/* Une carte qui arrive : un reflet la traverse une fois, pour qu'un
   renouvellement ne passe pas pour un remplacement silencieux. */
.market-card.fresh::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
      transparent 32%, rgba(255, 255, 255, .3) 48%, transparent 64%);
  transform: translateX(-130%);
  animation: card-shimmer .8s ease-out .16s;
}
@keyframes card-shimmer { to { transform: translateX(130%); } }

/* Les pouvoirs gardés, en miniatures alignées sous la fiche. Ceux des
   adversaires comptent autant que les miens : c'est une information publique, et
   stratégique. En vignette plutôt qu'en pastille — un petit rectangle à liseré,
   pour qu'on lise « une carte posée » et pas « une étiquette ». */
.card-badges { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }
.card-badge {
  position: relative;
  font-size: .62rem;
  font-weight: 700;
  padding: 2px 6px 2px 9px;
  border-radius: 4px;
  background: linear-gradient(150deg, rgba(125, 211, 252, .26), rgba(125, 211, 252, .1));
  border: 1px solid rgba(125, 211, 252, .35);
  color: #d8f1ff;
  white-space: nowrap;
  cursor: help;
  transition: background .15s, border-color .15s;
}
/* Le liseré : le bord d'une carte qui dépasse d'un paquet. */
.card-badge::before {
  content: '';
  position: absolute;
  left: 3px;
  top: 3px;
  bottom: 3px;
  width: 2px;
  border-radius: 999px;
  background: #7dd3fc;
}
.card-badge:hover {
  background: linear-gradient(150deg, rgba(125, 211, 252, .4), rgba(125, 211, 252, .18));
  border-color: rgba(125, 211, 252, .6);
}
/* Les jetons posés sur une carte (Chair Mutante, Batterie de Secours…). */
.card-badge b { color: var(--gold); }

/* ===== Règles du jeu ============================================ */

/* Le tiroir des règles est plus large que celui du journal : il y a des dés, des
   icônes et des paragraphes, pas une liste de lignes courtes. */
#rules-panel .drawer-inner { width: min(94vw, 31rem); }

.rules-content { overflow-y: auto; flex: 1; }

/* Sections repliables. La première est ouverte, les autres attendent : c'est ce
   qui évite le mur de texte, et ça tient sur un écran de mobile sans défiler. */
.rules-section {
  border: 1px solid rgba(255, 253, 245, .14);
  border-radius: 10px;
  background: rgba(0, 0, 0, .2);
  margin-bottom: 6px;
}
.rules-section > summary {
  cursor: pointer;
  padding: 9px 11px;
  font-weight: 800;
  font-size: .9rem;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.rules-section > summary::-webkit-details-marker { display: none; }
.rules-section > summary::after {
  content: '▾';
  opacity: .55;
  font-size: .8rem;
  transition: transform .2s;
}
.rules-section[open] > summary::after { transform: rotate(180deg); }
.rules-section > summary:hover { background: rgba(255, 253, 245, .06); }

.rules-body {
  padding: 0 11px 11px;
  font-size: .84rem;
  line-height: 1.5;
  opacity: .92;
}
.rules-body p { margin: 0 0 8px; }
.rules-body ul { margin: 0 0 8px; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.rules-body b { color: var(--cream); }
.rules-note {
  font-size: .76rem;
  opacity: .6;
  border-left: 2px solid rgba(255, 253, 245, .2);
  padding-left: 8px;
}

/* Les faces de dés, en vrai : mêmes matières que sur la table. La ligne s'enroule
   quand la place manque — les trois dés des chiffres laissaient sinon une colonne
   de texte de deux mots de large sur mobile. */
.rules-faces { list-style: none; padding-left: 0 !important; }
.rules-faces li { display: flex; align-items: flex-start; gap: 9px; flex-wrap: wrap; }
/* Le texte seulement : viser `li > span` attrapait aussi les dés, qui sont des
   spans — ils héritaient du `flex-grow` et s'empilaient un par ligne. */
.rules-face-text { flex: 1 1 11rem; min-width: 0; }
.die-static { display: inline-block; flex: none; cursor: default; }
.die-static svg { display: block; width: 100%; height: 100%; }

/* Les six catégories de cartes, avec leurs vraies icônes. */
.rules-cats { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
.rules-cat {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .72rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(255, 253, 245, .09);
  border: 1px solid rgba(255, 253, 245, .16);
}
.rules-cat-icon { width: 13px; height: 13px; opacity: .85; }
.rules-type-icon { width: 12px; height: 12px; vertical-align: -1px; }

@media (prefers-reduced-motion: reduce) {
  .rules-section > summary::after { transition: none; }
}

/* ===== Barre d'action =========================================== */

.action-bar {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
  padding-bottom: 4px;
  min-height: 46px;
}
.action-bar .btn { min-width: 140px; }

/* ===== Céder Tokyo / fin de partie ============================== */

/* Le compte à rebours de la cession : un arc qui se vide, pas seulement un
   nombre. C'est une décision sous contrainte de temps, la pression doit se voir.
   L'arc part du haut et tourne dans le sens des aiguilles ; sa durée est posée en
   JavaScript depuis l'échéance réelle envoyée par le serveur, donc il reste juste
   même si on ouvre la page en cours de décompte. */
.yield-clock {
  position: relative;
  width: 88px;
  height: 88px;
  margin: 2px auto 10px;
}
.yield-clock svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  overflow: visible;
}
.yield-clock circle {
  fill: none;
  stroke-width: 5;
  /* 2π × 19, le périmètre du cercle : l'unité de tout le tracé. */
  stroke-dasharray: 119.4;
}
.yield-clock-track { stroke: rgba(255, 253, 245, .14); }
.yield-clock-arc {
  stroke: var(--gold);
  stroke-linecap: round;
  stroke-dashoffset: 0;
  transition: stroke .3s;
}
@keyframes yield-drain { to { stroke-dashoffset: 119.4; } }
.yield-clock-count {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 1.6rem;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
}
/* Les dernières secondes : l'arc et le chiffre passent au rouge et pulsent. */
.yield-clock.urgent .yield-clock-arc { stroke: #f87171; }
.yield-clock.urgent .yield-clock-count {
  color: #fca5a5;
  animation: yield-urgent .6s ease-in-out infinite;
}
@keyframes yield-urgent {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.12); }
}

/* Deux boutons franchement distincts : tenir est l'action affirmative, céder est
   une porte de sortie. Ils ne doivent pas se confondre dans l'urgence. */
.yield-buttons { display: flex; gap: 10px; margin-top: 6px; }
.yield-buttons .btn { flex: 1; }
.btn-leave {
  background: rgba(248, 113, 113, .16);
  border: 1px solid rgba(248, 113, 113, .5);
  color: #fecaca;
}
.btn-leave:hover { background: rgba(248, 113, 113, .28); }

@media (prefers-reduced-motion: reduce) {
  /* Le mouvement part, le chiffre reste : c'est lui qui porte l'information. */
  .yield-clock-arc { animation: none !important; }
  .yield-clock.urgent .yield-clock-count { animation: none; }
}

.over-scores { display: flex; flex-direction: column; gap: 5px; margin: 8px 0; }
.over-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 9px;
  border-radius: 10px;
  background: rgba(255, 253, 245, .08);
  font-size: .88rem;
}
.over-row span:nth-of-type(1) { flex: 1; text-align: left; }
.over-row.win { background: rgba(251, 191, 36, .22); border: 1px solid var(--gold); }
.over-hp { opacity: .7; font-size: .8rem; }

/* ===== Effets : coups, gains, déplacements ====================== */

/* Couche à part : la table se redessine à chaque état reçu, une animation
   posée dedans serait coupée net.
   Au-dessus du plateau mais SOUS les panneaux et les surcouches (tiroirs 30,
   marché 45, overlays 50+) : à 60, les portraits qui glissaient vers Tokyo et
   les chiffres qui montaient se dessinaient par-dessus les règles, le journal,
   le chat et même la décision de céder Tokyo. */
.fx-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 20;
  overflow: hidden;
}

.fx-float {
  position: absolute;
  transform: translate(-50%, -50%);
  font-size: .95rem;
  font-weight: 900;
  white-space: nowrap;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .85);
  animation: fx-rise 1.25s cubic-bezier(.2, .8, .3, 1) forwards;
}
@keyframes fx-rise {
  0%   { transform: translate(-50%, -50%) scale(.5); opacity: 0; }
  18%  { transform: translate(-50%, -80%) scale(1.15); opacity: 1; }
  30%  { transform: translate(-50%, -90%) scale(1); }
  100% { transform: translate(-50%, -190%) scale(.95); opacity: 0; }
}
/* Sous mouvement réduit : l'indicateur reste, seul le mouvement part. */
.fx-float.still { animation: fx-hold 1.1s ease-out forwards; }
@keyframes fx-hold {
  0% { opacity: 0; }
  12%, 70% { opacity: 1; }
  100% { opacity: 0; }
}

.fx-vp { color: var(--gold); }
.fx-vp-loss { color: #d6bb6a; }
.fx-hp { color: #86efac; }
.fx-hp-loss { color: #fca5a5; }
.fx-energy { color: #7dd3fc; }
.fx-energy-loss { color: #6b9dbb; }
.fx-ko { color: #fecaca; font-size: 1.1rem; }

/* Le coup se voit sur la fiche : secousse, plus un voile rouge léger.
   Le voile est discret à dessein — le flash franc, lui, est sur la barre de vie
   (§6), et le rôle de celui-ci est seulement d'attraper l'œil en vision
   périphérique quand cinq fiches entourent le plateau.
   `opacity` sans `!important` : avec, la déclaration battait l'animation dans la
   cascade, le voile restait donc figé à pleine intensité au lieu de s'effacer, et
   couvrait le nom du monstre pendant une demi-seconde. */
.fx-hit { animation: fx-shake .5s cubic-bezier(.36, .07, .19, .97); }
.fx-hit::before {
  background: #dc2626 !important;
  animation: fx-flash .5s ease-out forwards;
}
@keyframes fx-shake {
  0%, 100% { transform: translate(0, 0); }
  15% { transform: translate(-5px, 2px) rotate(-1deg); }
  30% { transform: translate(5px, -2px) rotate(1deg); }
  45% { transform: translate(-4px, 1px) rotate(-.6deg); }
  60% { transform: translate(3px, -1px) rotate(.4deg); }
  80% { transform: translate(-1px, 1px); }
}
@keyframes fx-flash {
  0% { opacity: .42; }
  100% { opacity: .1; }
}

/* Flash rouge sur la barre de vie, là où le coup se compte. */
.hp-bar.fx-bar-hit { position: relative; }
.hp-bar.fx-bar-hit::after {
  content: '';
  position: absolute;
  inset: -2px -3px;
  border-radius: 4px;
  background: #dc2626;
  animation: fx-bar-flash .48s ease-out forwards;
  pointer-events: none;
}
@keyframes fx-bar-flash {
  0% { opacity: .8; }
  100% { opacity: 0; }
}

/* Les segments qui s'éteignent (ou se rallument) un par un. `backwards` fait
   tenir l'apparence de départ pendant le décalage, donc le segment reste dans
   son ancien état jusqu'à son tour — sans que le JS ait à retoucher le DOM. */
.hp-seg.fx-seg-out { animation: fx-seg-out .34s ease-out backwards; }
.hp-seg.fx-seg-in { animation: fx-seg-in .34s ease-out backwards; }
@keyframes fx-seg-out {
  0% { background: linear-gradient(180deg, #fca5a5, #f87171); transform: scaleY(1); }
  35% { background: #fff; transform: scaleY(1.5); }
  100% { background: rgba(0, 0, 0, .5); transform: scaleY(1); }
}
@keyframes fx-seg-in {
  0% { background: rgba(0, 0, 0, .5); transform: scaleY(1); }
  45% { background: #dcfce7; transform: scaleY(1.4); }
  100% { background: linear-gradient(180deg, #fca5a5, #f87171); transform: scaleY(1); }
}

/* Le trait de l'attaquant vers sa cible. Le tracé se dévoile par `clip-path`
   pour ne pas écraser la rotation posée en ligne. */
.fx-tracer {
  position: absolute;
  height: 3px;
  margin-top: -1.5px;
  transform-origin: 0 50%;
  border-radius: 999px;
  background: linear-gradient(90deg, rgba(252, 165, 165, 0), #fca5a5 55%, #fffdf5);
  box-shadow: 0 0 8px rgba(248, 113, 113, .7);
  animation: fx-trace .26s ease-out forwards;
}
@keyframes fx-trace {
  0% { clip-path: inset(0 100% 0 0); opacity: .5; }
  50% { clip-path: inset(0 0 0 0); opacity: 1; }
  100% { clip-path: inset(0 0 0 0); opacity: 0; }
}

/* L'élimination : la fiche s'affaisse, puis vire au gris (la classe `dead` prend
   le relais et la garde en place). */
.monster-card.fx-die { animation: fx-die .65s cubic-bezier(.3, .8, .4, 1) forwards; }
@keyframes fx-die {
  0% { transform: none; filter: none; }
  25% { transform: translateY(-3px) rotate(-1deg); }
  100% { transform: translateY(4px) scale(.97) rotate(1deg); filter: grayscale(1); }
}

/* Le contour de la zone s'allume à l'arrivée de son occupant. */
.zone.fx-zone-lit { animation: fx-zone-lit .9s ease-out; }
@keyframes fx-zone-lit {
  0% { box-shadow: 0 0 0 0 var(--zone-color, var(--gold)); }
  30% { box-shadow: 0 0 26px 6px var(--zone-color, var(--gold)); }
  100% { box-shadow: 0 0 22px -4px var(--zone-color, var(--gold)); }
}

/* Une carte du marché qui s'en va : elle se soulève et s'efface. */
.fx-card-out {
  position: absolute;
  pointer-events: none;
  animation: fx-card-out .38s ease-out forwards;
}
.fx-card-out .market-card { width: 100%; height: 100%; }
@keyframes fx-card-out {
  0% { transform: none; opacity: 1; }
  100% { transform: translateY(-26px) scale(.94) rotate(-2deg); opacity: 0; }
}

.fx-glow-vp { animation: glow-vp .7s ease-out; }
.fx-glow-hp { animation: glow-hp .7s ease-out; }
@keyframes glow-vp {
  0% { box-shadow: 0 0 0 0 rgba(251, 191, 36, .9); }
  100% { box-shadow: 0 0 0 14px rgba(251, 191, 36, 0); }
}
@keyframes glow-hp {
  0% { box-shadow: 0 0 0 0 rgba(74, 222, 128, .9); }
  100% { box-shadow: 0 0 0 14px rgba(74, 222, 128, 0); }
}

/* Le monstre qui entre dans Tokyo (ou en sort) glisse jusqu'à sa place. */
.fx-ghost {
  position: absolute;
  transform-origin: top left;
  will-change: transform, opacity;
}
.fx-ghost .monster-card { width: 100%; height: 100%; }

/* Une carte fraîchement piochée descend à sa place. */
.market-card.fresh { animation: card-deal .4s cubic-bezier(.2, 1.2, .35, 1); }
@keyframes card-deal {
  0% { transform: translateY(-22px) rotate(-4deg); opacity: 0; }
  100% { transform: none; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .fx-hit, .fx-glow-vp, .fx-glow-hp, .market-card.fresh { animation: none; }
  .market-card.fresh::after { animation: none; }
  .market-card, .market-card.buyable:hover { transition: none; transform: none; }
  .hp-seg { transition: none; }
  /* Les retours de coups deviennent des changements d'état : plus de secousse,
     plus de trait, plus d'affaissement. Les indicateurs numériques restent
     (voir .fx-float.still). */
  .hp-seg.fx-seg-out,
  .hp-seg.fx-seg-in,
  .hp-bar.fx-bar-hit::after,
  .fx-tracer,
  .monster-card.fx-die,
  .zone.fx-zone-lit,
  .fx-card-out { animation: none; }
}

/* ===== Écrans plus larges ======================================= */

/* Mobile : la rangée du plateau réserve une hauteur minimale, sinon la grille la
   réduit à rien (le contenu ne compte pas, la cellule est en containment) et le
   plateau redevient un bandeau. Le marché sortant de la colonne (panneau
   coulissant, plus bas), la place suffit désormais pour le plateau, l'anneau et
   mes dés ; `min-height: min-content` reste le garde-fou si un texte de carte
   inhabituellement long fait déborder l'ensemble. */
@media (max-width: 999px) {
  /* Le plateau prend sa hauteur, et c'est MA zone qui reçoit la place restante :
     les dés se retrouvent donc calés en bas de l'écran, comme le veut le brief.
     Avec un `1fr` sur le plateau, le surplus rendu par le marché devenait du
     vide réparti autour de lui — le plateau est borné par la largeur de l'écran
     et ne peut pas en profiter. */
  .table-grid {
    grid-template-rows: auto auto auto 1fr;
    /* Sans ça, la grille est étirée à la hauteur du parent par le flex, et
       comme elle ne peut pas déborder elle comprime ses rangées `auto` : c'est
       l'anneau des adversaires qui se faisait rogner. `min-content` l'autorise
       à dépasser, et c'est .table-body qui défile. */
    min-height: min-content;
  }
  .table-body { overflow-y: auto; }
  /* Un seul axe commande ici : la largeur de l'écran. Pas besoin de mesurer la
     place disponible, donc pas de containment — et la rangée `auto` peut alors
     se dimensionner sur le plateau. */
  .kot-board-cell { container-type: normal; }
  .kot-board { width: 100%; }

  /* Ma zone est calée en bas de l'écran, et le plateau de lancer garde sa place
     même vide : sans ça, la rangée des dés apparaissait au premier lancer et
     toute la colonne sautait de 150 px en plein tour. */
  .me-zone { justify-content: flex-end; }
  .dice-tray:empty { display: flex; }
  .dice-tray { min-height: calc(var(--die) + 30px); }
  /* L'anneau défile horizontalement, c'est donc un conteneur de défilement : sa
     hauteur intrinsèque ne remonte pas jusqu'à la grille, et la rangée se
     réduisait à sa seule marge. On la lui donne explicitement, calculée depuis
     le portrait — c'est lui qui commande la hauteur d'une fiche. */
  .kot-seats-ring { min-height: calc(var(--portrait) + 5.25rem); }

  /* ----- Le marché en panneau coulissant (§2, disposition mobile) -----
     Il ne prend plus de place dans la colonne : sorti du flux, il rend ses
     ~200 px au plateau et aux dés, et ne monte que pendant la phase d'achat.
     `visibility` en plus de la translation : replié, le panneau ne doit pas être
     atteignable au clavier ni lu par un lecteur d'écran. */
  .market-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 45;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 76vh;
    padding: 4px 10px calc(10px + env(safe-area-inset-bottom));
    border-radius: 18px 18px 0 0;
    border-top: 1px solid rgba(255, 253, 245, .18);
    background: linear-gradient(180deg, #14532d, #0d3a20);
    box-shadow: 0 -12px 32px rgba(0, 0, 0, .55);
    transform: translateY(100%);
    visibility: hidden;
    transition: transform .32s cubic-bezier(.22, 1, .36, 1), visibility .32s;
  }
  .market-sheet.open {
    transform: none;
    visibility: visible;
  }
  /* Les cartes défilent à l'intérieur du panneau plutôt que de le faire grandir
     au-delà de l'écran. */
  .market { overflow-y: auto; min-height: 0; margin: 0; }

  .market-grip {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 14px 2px 2px;
    flex: none;
  }
  /* La barre de préhension, purement visuelle : elle dit « ça vient du bas ». */
  .market-grip::before {
    content: '';
    position: absolute;
    top: 4px;
    left: 50%;
    transform: translateX(-50%);
    width: 42px;
    height: 4px;
    border-radius: 999px;
    background: rgba(255, 253, 245, .3);
  }
  .market-grip-title { font-weight: 800; font-size: .9rem; }
  .market-grip-title em { font-style: normal; font-weight: 400; opacity: .6; font-size: .76rem; }
  /* La poignée nomme déjà le panneau et donne la pioche : le titre interne
     ferait doublon, seul le bouton « Renouveler » reste utile. */
  .market-sheet .market-title { display: none; }
  .market-sheet .market-head-row { justify-content: flex-end; min-height: 0; }
  .market-sheet .market-head-row:empty { display: none; }
  /* Mon énergie, ici : ma fiche est cachée derrière le panneau, et c'est
     l'information dont dépend chaque décision d'achat. */
  .market-grip-energy { flex: 1; font-weight: 800; color: #7dd3fc; font-size: .88rem; }
  .market-grip-close {
    flex: none;
    border: none;
    background: rgba(255, 253, 245, .12);
    color: var(--cream);
    font-size: .9rem;
    line-height: 1;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    cursor: pointer;
  }
  .market-grip-close:hover { background: rgba(255, 253, 245, .22); }

  /* La barre d'action passe dans le panneau quand il est ouvert : sans elle,
     « Fin du tour » se retrouverait caché dessous. */
  .sheet-actions { display: flex; flex: none; padding-bottom: 0; }
  .market-sheet.open ~ .me-zone .action-bar { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .market-sheet { transition: none; }
}

/* En dessous de 700 px les dés tiennent sur deux rangées (voir la grille à 3
   colonnes plus haut) : la place réservée au plateau de lancer doit suivre.
   Après le bloc ci-dessus, car les deux s'appliquent à cette largeur. */
@media (max-width: 699px) {
  .dice-tray { min-height: calc(var(--die) * 2 + 38px); }

  /* Le brief ne demande que « avatar, vie, PV, énergie en format compact » dans
     le bandeau mobile des adversaires : l'espèce y prenait trois lignes sur
     168 px de large, au détriment des compteurs. Elle reste sur ma fiche. */
  .kot-seats .monster-species { display: none; }
}

@media (min-width: 700px) {
  .screen-table { --die: 68px; }
  .kot-seats > .monster-card { width: 190px; }
}

/* Bureau : les adversaires prennent leur place autour du plateau et n'en
   bougent plus de toute la partie. */
@media (min-width: 1000px) {
  .screen-table {
    --seat-col: clamp(190px, 13.5vw, 340px);
    --me-card: clamp(250px, 17vw, 420px);
    --die: clamp(74px, 4.4vw, 112px);
    /* Plus étroites et plus hautes qu'avant : une carte doit se lire comme une
       carte. Le portrait franc n'est atteignable qu'en mobile, où les trois
       colonnes sont étroites ; sur un grand écran, trois cartes en ratio
       portrait mangeraient la hauteur du plateau, donc on s'arrête au carré. */
    --market-card: clamp(210px, 14vw, 320px);
  }

  .table-grid {
    grid-template-columns:
      minmax(150px, var(--seat-col)) minmax(0, 1fr) minmax(150px, var(--seat-col));
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
      "kot-seats-top  kot-seats-top  kot-seats-top"
      "kot-seats-left kot-board    kot-seats-right"
      "market   market   market"
      "me       me       me";
  }
  /* Chaque zone reprend sa place dans la grille. */
  .kot-seats-ring { display: contents; }
  .kot-seats { display: flex; gap: 6px; min-width: 0; }
  .kot-seats-top { grid-area: kot-seats-top; justify-content: center; }
  .kot-seats-left, .kot-seats-right { flex-direction: column; justify-content: center; }
  .kot-seats-left { grid-area: kot-seats-left; }
  .kot-seats-right { grid-area: kot-seats-right; }
  .kot-seats > .monster-card { width: 100%; max-width: var(--seat-col); }
  .kot-seats-top > .monster-card { width: var(--seat-col); }

  .zone { padding: 10px; }

  /* Ma zone est la plus large : fiche à gauche, dés et boutons à droite. */
  .me-zone { flex-direction: row; align-items: stretch; gap: 10px; }
  .kot-me-card { width: var(--me-card); flex-shrink: 0; }
  .me-play { flex: 1; min-width: 0; justify-content: center; }
  /* Le plateau de lancer suit la taille des dés, il ne s'étire pas sur toute la
     largeur : six dés perdus au milieu d'un tapis de deux mètres ne font pas
     une zone de jeu. */
  .dice-tray {
    margin-top: 0;
    width: min(100%, calc(var(--die) * 6 + 22rem));
    margin-inline: auto;
  }

  /* Le marché grandit, mais les cartes gardent un format de carte : étalées sur
     toute la largeur d'un écran 4K, elles deviendraient des bandeaux. Elles
     grossissent jusqu'à une borne et la rangée se centre. */
  .market-row {
    grid-template-columns: repeat(3, minmax(0, var(--market-card)));
    justify-content: center;
  }
  .market-card { min-height: clamp(160px, 11vw, 250px); }
  /* Le titre et le bouton « Renouveler » se cadrent sur les cartes : sur la
     largeur entière, le bouton partait à l'autre bout de l'écran, à un mètre du
     marché auquel il s'applique. */
  .market-head-row {
    width: min(100%, calc(var(--market-card) * 3 + 12px));
    margin-inline: auto;
  }

  /* La pastille de tour se lit au centre : étirée sur 2400 px, elle devient une
     barre vide avec trois mots au milieu. */
  .turn-pill { max-width: 46rem; margin-inline: auto; }

  /* Le marché est une rangée de la table : plus rien à ouvrir. */
  #btn-market { display: none; }
}

/* ===== Grands écrans : l'interface grandit avec la fenêtre ======= */

/* Presque toutes les tailles de cette feuille sont en rem (textes, marges,
   rayons, pastilles) : faire grandir le rem avec la largeur de fenêtre les fait
   toutes grandir d'un seul geste, au lieu de laisser l'interface plafonner et
   flotter au milieu du vide.
   La borne basse en rem — et non en px — préserve la taille de police choisie
   dans le navigateur : on multiplie la préférence de l'utilisateur, on ne la
   remplace pas. Le terme en vw ne dépasse la borne basse qu'au-delà de
   ~1450 px, donc rien ne bouge en dessous. */
html { font-size: clamp(1rem, .44rem + .6vw, 1.5rem); }

@media (min-width: 950px) {
  /* style.css plafonne la table à 1400 px pour le Domino. Ici le plateau est le
     centre de la scène : aucune borne, parce que toutes les autres pièces ont la
     leur (colonnes de joueurs, panneau, cartes du marché, tapis de dés). Toute
     largeur supplémentaire va donc au plateau, et lui seul — jusqu'à ce que ce
     soit la hauteur de la fenêtre qui l'arrête, via la rangée de la grille. */
  .screen-table { max-width: none; }
  .table-side { width: clamp(310px, 20vw, 470px); }
}
