/* ═══════════════════════════════════════════════════════════════════════════
   CAMPAGNE — UI de la boucle à 3 paliers (refonte scoring 2026-06, Plan A)
   ───────────────────────────────────────────────────────────────────────────
   Post-its de choix (camp / mouvement / parti), panneau de bulletins de vote,
   bouton urne, bandeau de dépouillement. 100 % tokens V3 (tokens.css +
   tokens-v3.css). Le polish visuel/sonore complet est prévu au Plan C.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Post-its des choix — RANGÉE horizontale dans la bande .gm-cta-row, SOUS
   l'hémicycle (refonte 2026-06-15 : avant en overlay sur l'arc, ils le recouvraient
   et bloquaient les clics). camp → mouvement → parti, l'un après l'autre. ───────── */
.stage-postits {
  display: flex;
  flex-direction: row;           /* rangée horizontale */
  flex-wrap: nowrap;             /* UNE seule ligne (les pastilles se CHEVAUCHENT au lieu de wrapper) */
  align-items: center;
  justify-content: flex-end;     /* alignés à DROITE (le chrono prend la gauche) */
  width: auto;                   /* largeur au contenu : partage la ligne avec le chrono */
  flex: 0 0 auto;
  margin-left: auto;             /* pousse les pastilles à DROITE ; tout débord part vers la droite
                                    (hors écran), jamais sur le chrono ancré à gauche (retour user 2026-06-17) */
  /* LIMITE de largeur totale (retour user) : la bande ne peut plus pousser sur le chrono — on
     réserve ~84px à sa gauche. Au-delà, le léger chevauchement des pastilles (margin négative
     ci-dessous) absorbe l'excédent. */
  max-width: calc(100% - 84px);
  padding: 2px 8px;
  z-index: var(--z-postit);
  pointer-events: none;          /* la bande ne bloque rien… */
}
.stage-postits[hidden] { display: none; }
.stage-postit {
  pointer-events: auto;          /* …mais chaque post-it reste cliquable */
  font-family: var(--font-heading);
  /* (item 12) police flexible : se réduit pour tenir sur 1 ligne au lieu de
     déborder/se superposer (bug Safari iOS 16). */
  font-size: clamp(10px, 2.3vw, 14px);
  line-height: 1.15;
  text-transform: uppercase;
  text-align: left;
  /* Couleur du choix = couleur du secteur cliqué (--stage-bg posé par JS). */
  color: var(--ink);
  background: var(--stage-bg, var(--yellow));
  border: 3px solid var(--ink);
  /* Look ALIGNÉ sur les clones du dépouillement (ombre 7/8 + tilt -2° uniforme) pour que le
     relais pastille-de-jeu → clone soit invisible (retour user : sinon micro-« jump » de look). */
  box-shadow: 7px 8px 0 var(--ink);
  padding: 6px 11px;             /* un peu plus haut/large (retour user) */
  cursor: pointer;
  transform: rotate(-2deg);
  /* Le cadre suit le CONTENU (comme les clones du dépouillement, en inline-block) → le texte ne
     déborde plus du cadre (retour user). On NE rétrécit PLUS la boîte sous son contenu (flex-shrink
     0) : c'était la cause du débord ; la police reste fluide via le clamp ci-dessus pour tenir sur
     une ligne en écran étroit. */
  flex: 0 0 auto;
  width: max-content;
  max-width: 140px;
  position: relative;            /* pour le z-index du chevauchement */
}
/* Léger CHEVAUCHEMENT (deck) entre pastilles : chacune mord la précédente — gagne de la place sur
   la ligne (anti-débord sur le chrono) ET donne le même look « pile » que les clones du dépouillement
   (qui héritent du chevauchement : ils spawnent sur les rects mesurés de ces pastilles). Empilement
   plus-récent-au-dessus (comme les clones, en ordre DOM). */
.stage-postit + .stage-postit { margin-left: -14px; }
.stage-postit:nth-child(2) { z-index: 2; }
.stage-postit:nth-child(3) { z-index: 3; }
.stage-postit.is-dark { color: #FFFDF5; }
.stage-postit[hidden] { display: none; }
.stage-postit .k {
  display: block;
  font-size: 8px;
  opacity: 0.75;
  letter-spacing: 0.08em;
}
.stage-postit:hover { box-shadow: 9px 10px 0 var(--ink); }
.stage-postit:active { box-shadow: var(--sh-press); transform: translate(3px, 3px) rotate(-2deg); }
/* Physicalité : punch à la POSE d'un choix (re-triggé par JS via .pop). Tilt -2° comme les clones. */
.stage-postit.pop { animation: stage-pop 260ms var(--ease-out, ease-out); }
@keyframes stage-pop {
  0%   { transform: scale(0.55) rotate(-2deg); }
  60%  { transform: scale(1.18) rotate(-2deg); }
  100% { transform: scale(1) rotate(-2deg); }
}
@media (prefers-reduced-motion: reduce) {
  .stage-postit.pop { animation: none; }
}

/* ── Chrono — post-it haut-GAUCHE de la zone hémicycle ──────────────────── */
/* Surcharge du .speed-timer historique (style.css:4071, position absolue top-droite
   carte) : désormais EN FLUX dans la bande .gm-cta-row, à GAUCHE, sur la même ligne
   que les post-its de choix (retour user 2026-06-15 : on descend le chrono dans
   l'espace libéré sous l'hémicycle). margin-right:auto l'épingle à gauche et pousse
   les choix à droite. La taille est un peu FLUIDE selon la place dispo. */
.game-panel { position: relative; }
.gm-cta-row > .speed-timer {
  position: relative !important;   /* en flux, mais `top` négatif pour le REMONTER vers l'hémicycle */
  top: -14px !important;           /* remonté jusqu'au bas de l'hémicycle (retour user 2026-06-15) */
  right: auto !important; bottom: auto !important; left: auto !important;  /* neutralise l'absolu hérité */
  margin-right: 0;                 /* ancrage gauche porté par .gm-cta-row{justify-content:flex-start} ;
                                      margin-right:auto laissait le chrono déborder à GAUCHE quand les
                                      choix devenaient larges (retour user 2026-06-17) */
  font-size: clamp(18px, 5.8vw, 26px);   /* ENCORE plus grand (retour user) + un peu flex selon la place */
  padding: 7px 13px 8px;
  z-index: var(--z-postit, 40);
  flex: 0 0 auto;
  /* Apparition élastique au retrait de .hidden (display none → flex), puis le sway
     permanent (keyframes style.css) reprend la main. */
  animation:
    chrono-in 380ms cubic-bezier(0.2, 0.8, 0.3, 1.25) both,
    speedTimerSway 4.2s ease-in-out 380ms infinite;
}
@keyframes chrono-in {
  from { transform: scale(0.2) rotate(-12deg); opacity: 0; }
  70%  { transform: scale(1.12) rotate(-3deg); opacity: 1; }
  to   { transform: scale(1) rotate(-4deg); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gm-cta-row > .speed-timer { animation: none; }
}

/* 4e stat-card « Bulletins » : la rangée passe à 4 cartes en Campagne. La
   troncature + l'incohérence de taille des chiffres (retour user 2026-06-13)
   sont corrigées dans fitStatNums() qui pose désormais une taille de chiffre
   UNIFORME (le plus grand qui tient pour TOUTES les cartes) — pas de CSS de
   layout spécial nécessaire, la rangée flex existante suffit. */

/* Enveloppes/bulletins qui s'ENVOLENT vers la 4e stat-card « Bulletins » au gain
   (flyBulletins) — petites enveloppes jaunes à rabat ink, lancées depuis le
   « +N pts » du reveal, qui aspirent vers le compteur (qui tique à l'atterrissage). */
.bulletin-fly {
  position: fixed;
  z-index: 600;
  width: 22px; height: 15px;
  background: var(--yellow);
  border: 2px solid var(--ink);
  box-shadow: 1.5px 1.5px 0 var(--ink);
  pointer-events: none;
  will-change: transform, opacity;
  transition: transform 540ms cubic-bezier(0.45, 0.05, 0.25, 1), opacity 540ms ease-in;
}
.bulletin-fly::before {   /* rabat d'enveloppe (triangle ink) */
  content: '';
  position: absolute; left: -2px; right: -2px; top: -2px; height: 0;
  border-left: 11px solid transparent;
  border-right: 11px solid transparent;
  border-top: 7px solid var(--ink);
}
/* Pulse de la stat-card Bulletins à chaque enveloppe encaissée. */
.stat-card[data-tone="bulletins"].bull-pulse { animation: bull-pulse 260ms cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes bull-pulse {
  0%, 100% { transform: scale(1); }
  40% { transform: scale(1.08); }
}
@media (prefers-reduced-motion: reduce) {
  .bulletin-fly { transition: none !important; }
  .stat-card[data-tone="bulletins"].bull-pulse { animation: none !important; }
}

/* ── Post-it indice à stock épuisé — GRISÉ (cohérent avec .skip-out) ────── */
.hint-postit.hint-empty {
  opacity: 0.45;
  pointer-events: none;
}

/* ── Chip volant — lien physique secteur cliqué → post-it de choix ──────── */
.fly-chip {
  position: fixed;
  z-index: 200;
  pointer-events: none;
  font-family: var(--font-heading);
  font-size: 12px;
  text-transform: uppercase;
  color: #FFFDF5;
  background: var(--yellow);
  border: 3px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 4px 10px;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  opacity: 1;
  transition: transform 420ms cubic-bezier(0.2, 0.75, 0.3, 1), opacity 420ms ease-out;
}
.fly-chip.is-light { color: var(--ink); }

/* (Le bouton Valider séparé a été retiré — retour user 2026-06-12 : la
   validation vit dans l'hémicycle, niveau « valider ». Les bulletins aussi.) */

/* ═══ DÉPOUILLEMENT « POST-ITS AU TAMPON » — séquence sur la fiche ═══════ */
/* (Remplace la machine à dépouiller, rejetée au test user 2026-06-12.)
   La fiche reveal apparaît à T+450 en mode CHANTIER (.reveal-buildup) : ses
   pièces sont masquées, js/depouillement.js les dévoile palier par palier en
   posant .bu-on pendant que les post-its de choix se font tamponner dessus.
   À la fin (seqMs+650 ou skip), finishRevealBuildup() bascule sur
   .reveal-finish : méta + contrôles + barre reviennent en cascade. */
.reveal-screen.reveal-buildup .reveal-party:not(.bu-on),
.reveal-screen.reveal-buildup .reveal-mvt-mention:not(.bu-on),
.reveal-screen.reveal-buildup .reveal-party-sub:not(.bu-on),
.reveal-screen.reveal-buildup .reveal-groupe-sigle:not(.bu-on),
.reveal-screen.reveal-buildup .reveal-meta,
.reveal-screen.reveal-buildup .reveal-controls,
.reveal-screen.reveal-buildup .reveal-progress {
  opacity: 0;
  pointer-events: none;
}
/* Pièce dévoilée par un palier — pop court. `backwards` (PAS both) : à la fin
   de l'anim l'élément retombe sur ses règles normales, sans figer un
   transform:scale qui écraserait la rotation/flip propre de la pilule. */
.reveal-screen.reveal-buildup .bu-on {
  animation: bu-pop 260ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards;
}
@keyframes bu-pop {
  from { transform: scale(0.6); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
/* Fin de chantier : cascade de retour des pièces restantes. */
.reveal-screen.reveal-finish .reveal-meta     { animation: bu-pop 240ms 60ms  cubic-bezier(0.2, 1.4, 0.4, 1) backwards; }
.reveal-screen.reveal-finish .reveal-controls { animation: bu-pop 240ms 150ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards; }
.reveal-screen.reveal-finish .reveal-progress { animation: bu-pop 240ms 240ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards; }
@media (prefers-reduced-motion: reduce) {
  .reveal-screen.reveal-buildup .bu-on,
  .reveal-screen.reveal-finish .reveal-meta,
  .reveal-screen.reveal-finish .reveal-controls,
  .reveal-screen.reveal-finish .reveal-progress { animation: none; }
}

/* ── Bouton « Avance rapide » ───────────────────────────────────────────────
   Visible PENDANT le dépouillement (.reveal-buildup), au MÊME endroit que
   « Suivant », pour signaler aux nouveaux joueurs qu'on peut accélérer la séquence
   (clic = ×2 puis saut, comme un tap). Placé HORS de .reveal-controls (qui sont en
   opacity:0 pendant le buildup → l'opacité du parent tuerait un enfant). Disparaît
   à la fin (.reveal-finish) où « Suivant » reprend la place. */
.reveal-fast{
  position:absolute; bottom:-20px; right:14px; z-index:6;
  display:none; align-items:center; gap:5px;
  font-family:var(--font-display);
  background:#2e2e2e; color:var(--paper);
  border:3px solid var(--ink); box-shadow:var(--sh-sm);
  padding:6px 11px 7px; cursor:pointer;                /* (retour user 2026-06-22 : « avance rapide » réduit) */
  font-size:11px; letter-spacing:.05em; text-transform:uppercase;
  transform:rotate(-2deg);
  transition:transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
  animation:rfast-hint 1.5s ease-in-out 600ms 3;   /* 3 pulsations pour attirer l'œil */
}
.reveal-fast .rfast-ico{ display:flex; }
.reveal-fast .rfast-ico svg{ width:12px; height:12px; display:block; }
.reveal-screen.reveal-buildup .reveal-fast{ display:inline-flex; }
.reveal-screen.reveal-finish .reveal-fast{ display:none; }
html.dlp-can-hover .reveal-fast:hover{ transform:rotate(-2deg) translate(-2px,-2px); box-shadow:var(--sh-lg); }
.reveal-fast:focus-visible{ transform:rotate(-2deg) translate(-2px,-2px); box-shadow:var(--sh-lg); }
.reveal-fast:active{ transform:rotate(-2deg) translate(2px,2px); box-shadow:var(--sh-sm); transition-duration:70ms; }
@keyframes rfast-hint{ 0%,100%{ transform:rotate(-2deg) scale(1); } 50%{ transform:rotate(-2deg) scale(1.07); } }
@media (prefers-reduced-motion:reduce){ .reveal-fast{ animation:none; } }

/* ── Styles de la séquence elle-même (.rvd-*) — v4 FLIP (2026-06-12) :
      chaque post-it vole sur la PIÈCE RÉELLE de la fiche, s'y fait tamponner
      puis se RETOURNE (flip 3D) pour révéler la vérité ; duo VOIX × MULT
      central et GROS, fusion = vol vers le « +N pts » au coin haut-droit.
      Orchestrée par js/depouillement.js (v4). ──────────────────────────────── */

/* Overlay de séquence sur #game-panel — z 102 : la fiche #reveal-screen est
   un SIBLING à z 101 (game-v3.css:274, --z-overlay+1) qui aplatit tout son
   contenu (verdict et gain compris) dans le contexte du panel — l'overlay
   doit donc passer au-dessus d'elle pour que clones/duo se posent SUR le
   papier. Même empilement que le proto (scorebar z32 > gain z31, fx z60) :
   le duo recouvre l'ancre du gain qu'il « devient » à la fusion, et l'overlay
   est PURGÉ avant le verdict balistique (jamais rien au-dessus de lui).
   Tout est pointer-events:none : les taps passent à la fiche / au panneau. */
.rvd-overlay {
  position: absolute;
  inset: 0;
  z-index: 102;
  pointer-events: none;
  overflow: visible;
}

/* Clone de post-it qui se détache pour passer au tampon : l'EXTÉRIEUR vole
   (translate inline), l'INTÉRIEUR (.rvd-body) garde le visuel + windup/recul. */
.rvd-clone {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform;
  transition: transform 380ms cubic-bezier(0.25, 0.9, 0.3, 1), opacity 200ms var(--ease-out);
}
.rvd-body { position: relative; display: inline-block; }
.rvd-clone .stage-postit {
  display: inline-block;
  transform: rotate(-2deg);
  /* MÊME taille que le post-it de bande : le clone est posé EXACTEMENT dessus au
     spawn — un mismatch le faisait paraître « gonflé sur place » avant le vol (retour
     user : « animation en deux temps »). La bande a été AGRANDIE (14px/12px) le
     2026-06-15 → on HÉRITE de .stage-postit (plus de font-size fixe à 11px) pour
     rester pile à la même taille. Le zoom de jugement vient ensuite du scale(k) de
     mountIntro, en UN seul mouvement (translate + scale). */
  cursor: default;
}
/* Crossfade du libellé de choix (sigle → label complet du parti) PENDANT le
   déplacement vers la colonne (retour user 2026-06-15 : plus de jump avant le vol). */
.rvd-clone-lbl { transition: opacity 160ms ease; }
.rvd-clone-lbl.rvd-lbl-fade { opacity: 0; }
.rvd-clone.lift .rvd-body > .stage-postit { box-shadow: 7px 8px 0 var(--ink); }
/* (Le fondu 0.45 des clones en attente est SUPPRIMÉ — c'était le « post-it
   fantôme » du retour user : les clones pas encore jugés restaient semi-
   transparents sur leur position de spawn, derrière l'acteur. Ils patientent
   désormais pleins, réduits, dans la pile bas-droit — parkWaiting). */
.rvd-clone.windup .rvd-body { animation: rvd-windup var(--rvd-wind, 420ms) cubic-bezier(0.4, 0, 0.6, 1) both; }
@keyframes rvd-windup {
  0%   { transform: rotate(0) scale(1); }
  28%  { transform: rotate(-2.6deg) scale(1.04); }
  58%  { transform: rotate(2.2deg) scale(1.07); }
  100% { transform: rotate(-1.4deg) scale(1.11); }
}
.rvd-clone.recoil .rvd-body { animation: rvd-recoil 220ms var(--ease-out) both; }
@keyframes rvd-recoil {
  0%   { transform: scale(1.11) rotate(-1.4deg); }
  35%  { transform: scale(0.94) rotate(0.6deg); }
  100% { transform: scale(1) rotate(0); }
}
/* RÉSOLUTION SÈCHE (retour user : pas de rotation 3D) — le post-it de choix
   disparaît d'un pop instantané, en concomitance avec la révélation (.bu-on)
   de la vraie pièce. Placé APRÈS .recoil : la dernière règle gagne quand les
   deux classes co-existent un instant. */
.rvd-clone.rvd-pop-out .rvd-body { animation: rvd-pop-out 150ms cubic-bezier(0.4, 0, 0.8, 0.4) both; }
@keyframes rvd-pop-out {
  0%   { transform: scale(1); opacity: 1; }
  30%  { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(0.5); opacity: 0; }
}
/* Satisfaction à l'atterrissage d'un flip de CONFIRMATION (choix juste). */
.rvd-clone.rvd-landpulse .rvd-body > .stage-postit { animation: rvd-match-pulse 700ms ease-out; }
/* Après la révélation, le post-it de choix ne sert plus à rien (retour user :
   « c'est juste du clutter ») : il FOND pendant que la vraie pièce pop. */
.rvd-clone.rvd-out { opacity: 0; }
/* Beat score (spotlight du duo) : ce qui reste de la scène s'efface à demi
   (vérité camp résiduelle) — le score est le SEUL focus. */
.rvd-overlay.rvd-spot .rvd-clone { opacity: 0.3; }
/* Fin de scène (pop du gain) : tout fond avant le verdict balistique. */
.rvd-overlay.rvd-done .rvd-clone { opacity: 0; }

/* Tampon de palier — verdict À CÔTÉ du post-it jugé (retour user : plus
   superposé à la pilule de choix), mordant son bord droit. PLUS GRAND
   (19 px) et PLUS LOURD : slam depuis 2.6, double rebond, et le shake de la
   fiche part à l'impact (cf. .reveal-screen.rvd-shake). Variante .stamp-left
   posée par JS quand le bord droit de la fiche manque de place. */
.rvd-stamp {
  position: absolute;
  top: 50%;
  left: 100%;
  margin-left: -12px;          /* mord le bord du post-it (lien physique) */
  z-index: 50;                 /* AU-DESSUS de la pastille de points (retour user 2026-06-15) */
  font-family: var(--font-display);
  font-size: 13px;             /* RÉDUIT : un poil plus petit que les pastilles de choix (retour user) */
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 5px 10px 6px;
  border: 3px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  --srot: -7deg;
  animation: rvd-stamp-slam 250ms both;
}
.rvd-stamp.stamp-left {
  left: auto;
  right: 100%;
  margin-left: 0;
  margin-right: -12px;
  --srot: 7deg;
}
/* Ni à droite ni à gauche (mobile, colonne au bord) : EN TRAVERS du post-it,
   esprit tampon assumé — JS proportionne la police au post-it. */
.rvd-stamp.stamp-over {
  left: 50%;
  right: auto;
  top: 42%;
  margin-left: 0;
  --srot: -6deg;
  animation-name: rvd-stamp-slam-over;
}
/* Départ 1.7 (pas 2.6 — audit 2, B1) : en travers d'un post-it collé au bord
   gauche, le pop d'impact à 2.6 sortait de l'écran mobile (x = −47 px). */
@keyframes rvd-stamp-slam-over {
  0%   { opacity: 0; transform: translate(-50%, -50%) rotate(var(--srot, -6deg)) scale(1.7); }
  46%  { opacity: 1; transform: translate(-50%, -50%) rotate(var(--srot, -6deg)) scale(0.84); }
  68%  { transform: translate(-50%, -50%) rotate(var(--srot, -6deg)) scale(1.12); }
  86%  { transform: translate(-50%, -50%) rotate(var(--srot, -6deg)) scale(0.97); }
  100% { transform: translate(-50%, -50%) rotate(var(--srot, -6deg)) scale(1); }
}
.rvd-stamp[data-cat="ok"]     { background: var(--green-2); color: var(--paper); }
.rvd-stamp[data-cat="almost"] { background: var(--orange); color: var(--ink); }
/* (item 3) "accepté" (tech) = VERT RAYÉ, identique au grand verdict accepté
   (.reveal-verdict[data-tone="almost-correct"], style.css). */
.rvd-stamp[data-cat="tech"] {
  background:
    repeating-linear-gradient(135deg, transparent 0 14px, rgba(255,255,255,0.28) 14px 28px),
    var(--green);
  color: var(--ink);
}
.rvd-stamp[data-cat="wrong"],
.rvd-stamp[data-cat="late"]   { background: var(--red); color: var(--paper); --srot: 5deg; }
.rvd-stamp.stamp-left[data-cat="wrong"],
.rvd-stamp.stamp-left[data-cat="late"] { --srot: -5deg; }
@keyframes rvd-stamp-slam {
  0%   { opacity: 0; transform: translateY(-50%) rotate(var(--srot, -7deg)) scale(2.6); }
  46%  { opacity: 1; transform: translateY(-50%) rotate(var(--srot, -7deg)) scale(0.84); }
  68%  { transform: translateY(-50%) rotate(var(--srot, -7deg)) scale(1.12); }
  86%  { transform: translateY(-50%) rotate(var(--srot, -7deg)) scale(0.97); }
  100% { transform: translateY(-50%) rotate(var(--srot, -7deg)) scale(1); }
}
/* SHAKE de la fiche à l'impact du tampon — amplitude crescendo par palier
   (--shx posé par JS : 3 → 5 → 7 px). La fiche garde ses positions (anim
   one-shot, classe retirée par JS après coup). */
.reveal-screen.rvd-shake { animation: rvd-screen-shake 280ms cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes rvd-screen-shake {
  0%, 100% { transform: translate(0, 0); }
  15% { transform: translate(calc(var(--shx, 5px) * -1), 2px) rotate(-0.3deg); }
  35% { transform: translate(var(--shx, 5px), -2px) rotate(0.25deg); }
  55% { transform: translate(calc(var(--shx, 5px) * -0.6), 1px); }
  75% { transform: translate(calc(var(--shx, 5px) * 0.4), -1px); }
}

/* Chip « +N » qui vole du post-it tamponné vers le compteur VOIX central. */
.rvd-chip {
  position: absolute;
  left: 0;
  top: 0;
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 20px;
  color: var(--green-2);
  text-shadow: 2px 2px 0 var(--paper), 4px 4px 0 rgba(10, 10, 10, 0.3);
  transition: transform 380ms cubic-bezier(0.5, -0.1, 0.2, 1), opacity 380ms ease-in;
  will-change: transform;
}

/* ── Duo VOIX × MULT — LE point focal : GROS, CENTRÉ (JS) dans la bande méta
      masquée de la fiche. Case jaune VOIX (le papier exact du gain), case
      encre ×MULT (chiffre jaune sur noir). Chiffres Syne italic 800. À la
      fusion finale il VOLE se contracter vers le « +N pts » réel (.flyout). */
.rvd-sb {
  position: absolute;
  display: flex;
  align-items: flex-start;
  opacity: 0;
  pointer-events: none;
}
.rvd-sb.on { opacity: 1; }
/* BEAT SCORE (retour user : « un moment où le score devient LE focus ») :
   le duo GLISSE au centre de la fiche en grossissant — left/top re-posés par
   JS, la transition fait le voyage ; transform porte le grossissement (et
   reste libre pour .flyout qui le remplace à la fusion). */
.rvd-sb.spot {
  transition: left 420ms cubic-bezier(0.3, 0.9, 0.3, 1), top 420ms cubic-bezier(0.3, 0.9, 0.3, 1),
              transform 420ms cubic-bezier(0.2, 1.3, 0.4, 1);
  transform: scale(1.18);
  /* Origine GAUCHE (retour user 2026-06-15) : ainsi l'apparition de la case ×MULT à
     droite n'« écarte » plus la pastille Points (le pivot du scale reste à gauche) →
     un SEUL déplacement du duo (plus de 2e glissement vers la gauche). */
  transform-origin: left center;
}
/* .pop est ONE-SHOT (retiré par JS après l'anim) : sinon rumble → retour à
   l'animation .pop la rejouerait (re-déclaration d'animation = restart). */
.rvd-sb.pop { animation: rvd-sb-in 260ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
@keyframes rvd-sb-in {
  0%   { transform: scale(0.3) rotate(5deg); opacity: 0; }
  60%  { transform: scale(1.12) rotate(-1deg); opacity: 1; }
  100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
.rvd-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  border: 3px solid var(--ink);
  padding: 4px 12px 6px;
  min-width: 66px;
  transform: rotate(var(--cr, -2deg));
}
.rvd-k {
  font-family: var(--font-display);
  font-size: 9px;
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.7;
  margin-bottom: 3px;
}
.rvd-v {
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 22px;
  line-height: 1;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.rvd-voix { --cr: -2deg; --voix-bg: #4b3cd1; background: var(--voix-bg); color: var(--paper); box-shadow: 4px 4px 0 var(--ink); z-index: 2; }
.rvd-voix .rvd-k { opacity: 0.78; }
.rvd-mult { --cr: 2.2deg; background: #ff2a6d; color: var(--paper); box-shadow: 3px 3px 0 var(--ink); margin: 9px 0 0 -5px; padding: 4px 9px 5px; min-width: 54px; }
/* Borne de largeur des chips (content-box → max-width = aire de contenu = cible de fitNum) : le
   chip s'élargit jusqu'à cette borne puis c'est le CHIFFRE qui réduit (fitNum), pour qu'un gros
   total / un gros ×MULT ne déborde plus la fiche (retour user). Responsive pour tenir aussi en
   petit écran (le duo est ensuite mis à l'échelle ×1.18 par .spot). */
.rvd-voix { max-width: min(140px, 34vw); }
.rvd-mult { max-width: min(88px, 22vw); }
/* Case ×MULT réduite à 70 % (retour user 2026-06-15). Via la propriété INDIVIDUELLE
   `scale:` → se COMPOSE avec le `rotate(--cr)` de .rvd-cell et avec les animations
   punch/fuse/reveal-in (qui pilotent `transform`), sans les écraser. */
.rvd-mult { scale: 0.7; transform-origin: center center; }

/* ── FLAMMES (B1/B3) — on allume une pastille (Points >100, ×MULT élevé) via igniteFlames
   (depouillement.js) qui crée un moteur SVG INTERNE par pastille (cf. bloc .dlp-afire plus
   bas). v4 2026-06-16 : remplace l'ancien calque .rvd-flames + filtre #cflame-fire-pin, qui
   cassaient sur iOS 26 Safari (un filter:url() sur du HTML y rend les particules brutes). */
/* « +N pts » du crédit façon « carte dans l'urne » (retour user 2026-06-15) : même chip
   que .collection-bump (style.css) mais JAUNE (gain de points) et un peu plus grand. */
.points-bump {
  color: var(--ink) !important;
  background: var(--yellow) !important;
  font-size: 22px !important;
}

/* ── FLAMMES — SILHOUETTE UNIFIÉE v4 (moteur SVG INTERNE, iOS-safe — 2026-06-16) ──
   La pastille « s'enflamme » : igniteFlames (depouillement.js) crée un <svg> .rvd-flame-svg
   DERRIÈRE le texte qui dessine carte + contour + ombre + flammes en UNE silhouette (filtre
   gooey porté par un <g>, JAMAIS par du HTML → seul rendu correct sur iOS 26 Safari, contre
   l'ancien filter:url() sur la pastille qui y cassait). On rend donc la pastille CSS
   TRANSPARENTE (fond + bordure + ombre) pour ne voir que le SVG ; le texte (.rvd-k/.rvd-v)
   reste net au-dessus. Contour (= border .rvd-cell 3px) et ombre (= box-shadow pastille) sont
   gérés DANS le moteur, à partir des tokens du jeu. */
.dlp-afire { position: relative; }
.rvd-voix.dlp-afire,
.rvd-mult.dlp-afire {
  background: transparent;
  border-color: transparent;   /* le contour vient du SVG ; on garde la place (3px) → zéro saut de layout */
  box-shadow: none;
}
.rvd-flame-svg {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: -1;                 /* derrière le texte de la pastille */
  pointer-events: none;
  overflow: visible;           /* langues (haut) + ombre (bas) débordent de la box */
  display: block;
}
@media (prefers-reduced-motion: reduce) { .rvd-flame-svg { display: none; } }
.rvd-mult .rvd-k { opacity: 0.55; }
.rvd-mult .rvd-v { color: var(--yellow); font-size: 16px; padding-top: 2px; }
/* La case ×MULT n'existe visuellement qu'au 1er multiplicateur appliqué (retour
   user) : masquée à ×1, révélée par un pop (revealMultCell). */
.rvd-mult.is-hidden { display: none; }
.rvd-mult.reveal-in { animation: rvd-sb-in 260ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
/* Variante CENTRALE (v4) : chiffres ~32 px, on voit le score MONTER. */
.rvd-sb.big .rvd-cell { padding: 6px 16px 8px; min-width: 92px; }
.rvd-sb.big .rvd-k { font-size: 11px; }
.rvd-sb.big .rvd-v { font-size: 32px; }
.rvd-sb.big .rvd-mult { margin: 12px 0 0 -6px; }
.rvd-sb.big .rvd-mult { padding: 6px 12px 7px; min-width: 76px; }
.rvd-sb.big .rvd-mult .rvd-v { font-size: 21px; }
/* Encaissement : punch d'échelle re-déclenchable, crescendo piloté par --pk. */
.rvd-cell.punch { animation: rvd-sb-punch 240ms cubic-bezier(0.2, 1.6, 0.4, 1); }
@keyframes rvd-sb-punch {
  0%   { transform: rotate(var(--cr, -2deg)) scale(1); }
  35%  { transform: rotate(var(--cr, -2deg)) scale(var(--pk, 1.18)); }
  100% { transform: rotate(var(--cr, -2deg)) scale(1); }
}
.rvd-sb.rumble { animation: rvd-sb-rumble 110ms linear infinite; }
@keyframes rvd-sb-rumble {
  0%, 100% { transform: translate(0, 0); }
  30% { transform: translate(-1px, 1px); }
  70% { transform: translate(1px, -1px); }
}

/* Étiquettes bonus — stickers HORIZONTAUX nom + valeur, empilés en colonne
   propre À CÔTÉ du duo (à droite si la place — .tags-side — sinon dessous,
   .tags-below), JAMAIS en travers d'un texte. Caractère tampon gardé
   (rotation ±2,5°, slam court) mais la lisibilité prime. */
.rvd-tags {
  position: absolute;
  top: calc(100% + 9px);
  right: 2px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 7px;
}
/* tags-side = À GAUCHE du duo (audit C2 : à droite, elles sortaient de la
   fiche partout et de l'ÉCRAN sur mobile — l'espace libre est à gauche, les
   post-its de choix ont fondu au moment du beat score). */
.rvd-sb.tags-side .rvd-tags {
  top: 2px;
  left: auto;
  right: calc(100% + 12px);
  align-items: flex-end;
}
.rvd-sb.tags-below .rvd-tags {
  top: calc(100% + 10px);
  right: auto;
  left: 50%;
  transform: translateX(-50%);
  align-items: center;
}
.rvd-tag {
  display: flex;
  align-items: baseline;
  gap: 7px;
  white-space: nowrap;
  border: 3px solid var(--ink);
  box-shadow: 3px 3px 0 var(--ink);
  padding: 3px 9px 4px;
  transform: rotate(var(--tr, -2.5deg));
  animation: rvd-tag-slam 230ms both;
}
.rvd-tname {
  font-family: var(--font-display);
  font-size: 9px;
  line-height: 1.1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.rvd-tval {
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 15px;
  line-height: 1;
}
@keyframes rvd-tag-slam {
  0%   { opacity: 0; transform: rotate(var(--tr, -2.5deg)) scale(2.1); }
  52%  { opacity: 1; transform: rotate(var(--tr, -2.5deg)) scale(0.9); }
  74%  { transform: rotate(var(--tr, -2.5deg)) scale(1.06); }
  100% { transform: rotate(var(--tr, -2.5deg)) scale(1); }
}

/* FUSION : ×MULT s'écrase sur VOIX, les étiquettes sont aspirées vers lui,
   puis le duo entier VOLE se contracter vers le « +N pts » réel au coin
   haut-droit (.flyout, transform inline posé par JS) — il y disparaît à
   l'instant où le gain pop : continuité d'ancre À LA FIN.
   !important : tag-slam (fill both) tient transform/opacity et battrait des
   déclarations normales (cascade : animations > déclarations normales). */
.rvd-mult.fuse {
  transition: transform 200ms cubic-bezier(0.5, -0.3, 0.8, 0.5), opacity 120ms ease-in 100ms;
  transform: translate(-64px, -8px) rotate(-8deg) scale(0.55);
  opacity: 0;
}
.rvd-tag.suck {
  transition: transform 220ms cubic-bezier(0.6, -0.2, 0.9, 0.4), opacity 200ms ease-in;
  transform: translate(34px, -54px) scale(0.05) !important;
  opacity: 0 !important;
}
/* tags à GAUCHE → aspiration vers le duo à DROITE. */
.rvd-sb.tags-side .rvd-tag.suck { transform: translate(90px, -10px) scale(0.05) !important; }
.rvd-sb.tags-below .rvd-tag.suck { transform: translate(0, -70px) scale(0.05) !important; }
.rvd-sb.flyout {
  transition: transform 380ms cubic-bezier(0.55, -0.05, 0.3, 1), opacity 140ms ease-in 280ms;
  opacity: 0 !important;
}
/* 0 pt : le duo s'éteint sur place, sans célébration ni voyage (× reste ×1). */
.rvd-sb.fade {
  transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out);
  opacity: 0 !important;
  transform: scale(0.92) !important;
}

/* ── PETITE carte de multiplicateur « série » (refonte 2026-06-14) — pastille
      ORANGE néo-brutaliste « Nom ×N » qui DESCEND juste au-dessus de la case
      ×MULT et FUSIONNE dedans en la faisant tiquer (dans l'esprit du beat
      chrono). Positionnée par JS (left/top). Le liseré coloré (--mchip-src)
      rappelle la source : élan violet, brillante or, holo lavande.
      NB : .rvd-MCHIP (≠ .rvd-chip, la puce « +N » qui vole vers VOIX). */
.rvd-mchip {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 6;
  display: flex;
  align-items: baseline;
  gap: 7px;
  white-space: nowrap;
  pointer-events: none;
  will-change: transform, opacity;
  --mchip-src: var(--ink);
  background: var(--orange);     /* série = orange (couleur de la carte série) */
  color: var(--ink);
  /* (item 10) contour NOIR homogène + ombre dure néo-brut (plus de liseré coloré). */
  border: 3px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 5px 12px 6px;
  transform: rotate(-2deg);
}
/* (item 10) HOLO : motif STRICTEMENT repris du fond de carte holo (renderHoloPole) —
   bandes VERTICALES 12px (6 teintes) sur un ::before ROTATÉ 35° qui défile de 72px (1
   période) → seamless trivial. L'entrée (slam) reste pilotée par .slam (transform sur la
   pastille), indépendante du scroll du fond (sur ::before). */
.rvd-mchip.is-holo {
  background: var(--ink);
  overflow: hidden;                 /* clippe le ::before rotaté au bord de la pastille */
}
.rvd-mchip.is-holo::before {
  content: '';
  position: absolute;
  inset: -120%;                     /* surdimensionné : couvre la pastille une fois rotaté 35° */
  background: repeating-linear-gradient(90deg,
    var(--hue-1) 0 12px, var(--hue-2) 12px 24px, var(--hue-3) 24px 36px,
    var(--hue-4) 36px 48px, var(--hue-5) 48px 60px, var(--hue-6) 60px 72px);
  transform: rotate(35deg);
  animation: holo-barbershop-bg 2.4s linear infinite;
  pointer-events: none;
  z-index: 0;
}
.rvd-mchip.is-holo .mchip-name,
.rvd-mchip.is-holo .mchip-mult { position: relative; z-index: 1; }
/* (item 10) BRILLANTE : corps or + balayage shine (mêmes valeurs que la carte/float). */
.rvd-mchip.is-shiny {
  background: var(--yellow);
  overflow: hidden;
}
.rvd-mchip.is-shiny::after {
  content: ''; position: absolute; top: -40%; height: 180%; width: 16px;
  left: -30%; background: rgba(255, 255, 255, 0.92);
  transform: skewX(-16deg); pointer-events: none; z-index: 1;
  animation: rare-shine-sweep 2.8s ease-in-out infinite;
}
.rvd-mchip.is-shiny .mchip-name,
.rvd-mchip.is-shiny .mchip-mult { position: relative; z-index: 2; }
/* (refonte chrono 2026-06-16) CHRONO = COPIE CONFORME du post-it #speed-timer du jeu :
   post-it jaune (bordure + ombre déjà portées par .rvd-mchip), sablier qui flip, et
   SECONDES.CENTIÈMES À L'INTÉRIEUR (speed-num + speed-dec petits + unité « s »). */
.rvd-mchip.is-chrono {
  background: var(--yellow);
  color: var(--ink);
  flex-direction: column;         /* légende « Temps restant » AU-DESSUS de la rangée sablier+temps */
  align-items: center;
  gap: 2px;
}
.rvd-mchip.is-chrono .mchip-cap {
  font-family: var(--font-display);
  font-size: 9px;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.72;
}
.rvd-mchip.is-chrono .chrono-row {
  display: flex;
  align-items: center;
  gap: 4px;
}
.rvd-mchip.is-chrono .ico {
  width: 16px; height: 16px; flex: 0 0 16px;
  display: grid; place-items: center;
  animation: speedTimerFlip 2s linear infinite;   /* même retournement que le vrai sablier */
}
.rvd-mchip.is-chrono .ico > svg { width: 16px; height: 16px; display: block; }
.rvd-mchip.is-chrono .speed-num {
  font-family: 'Archivo Black', var(--font-display), sans-serif;
  font-weight: 900; font-size: 24px; font-style: normal; line-height: 1;
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
  min-width: 60px; text-align: right;
}
.rvd-mchip.is-chrono .speed-dec { font-size: 0.5em; opacity: 0.6; font-weight: 700; }
.rvd-mchip.is-chrono .speed-unit { font-size: 0.55em; opacity: 0.6; margin-left: -1px; align-self: center; }
.rvd-mchip .mchip-above {
  position: absolute;
  bottom: 100%;                 /* juste au-dessus de la pastille */
  left: 0; right: 0;
  margin-bottom: 5px;
  text-align: center;
  font-family: var(--font-display);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  pointer-events: none;
}
.rvd-mchip .mchip-name {
  font-family: var(--font-display);
  font-size: 11px;
  line-height: 1.1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.rvd-mchip .mchip-mult {
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 22px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* (correction E) Compteur de SÉRIE qui décompte → 0 : chiffre DROIT et un peu plus gros
   pour bien lire le décompte (esprit chrono), tabular-nums pour ne pas « danser ». */
.rvd-mchip .mchip-streak {
  font-style: normal;
  font-size: 26px;
}
/* SURGISSEMENT : claque au-dessus de la case ×MULT (sticker nerveux). */
.rvd-mchip.mchip-in { animation: rvd-mchip-in 240ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
@keyframes rvd-mchip-in {
  0%   { opacity: 0; transform: translateY(-10px) scale(0.5) rotate(-7deg); }
  60%  { opacity: 1; transform: translateY(0) scale(1.06) rotate(2deg); }
  100% { opacity: 1; transform: translateY(0) scale(1) rotate(-2deg); }
}
/* SLAM BALISTIQUE (refonte retour user 2026-06-15) : MÊME grammaire que le verdict
   (revealBallistic, style.css) — la pastille surgit petite/loin, GROSSIT vers l'apex
   où elle reste suspendue, puis RETOMBE SEC à 78% (≈468ms sur 600ms) = IMPACT. Le JS
   déclenche AU MÊME INSTANT le shake du cadre (shakeFiche) + le décompte (countTo).
   On ajoute un translateY pour appuyer la « chute ». */
@keyframes rvd-chip-slam {
  0%   { opacity: 0; transform: translateY(-16px) scale(0.30) rotate(-3deg);
         animation-timing-function: cubic-bezier(0.12, 0.72, 0.22, 1); }       /* montée bien décélérée */
  40%  { opacity: 1; transform: translateY(-38px) scale(2.15) rotate(-3deg);   /* APEX plus GRAND */
         animation-timing-function: cubic-bezier(0.45, 0, 0.65, 1); }
  66%  { opacity: 1; transform: translateY(-38px) scale(2.15) rotate(-3deg);   /* SUSPENDU plus LONGTEMPS (40→66 %) */
         animation-timing-function: cubic-bezier(0.82, 0.04, 0.92, 0.5); }     /* puis chute accélérée (slam sec) */
  84%  { opacity: 1; transform: translateY(0) scale(1.0) rotate(-2deg);        /* IMPACT */
         animation-timing-function: cubic-bezier(0.3, 0.65, 0.5, 1); }
  91%  { transform: translateY(3px) scale(0.87) rotate(-2deg); }               /* squash rebond */
  100% { transform: translateY(0) scale(1.0) rotate(-2deg); }
}
/* Le slam anime le TRANSFORM de la pastille ; le scroll holo vit sur ::before (background-
   position), donc une seule règle suffit pour toutes les variantes. */
.rvd-mchip.slam { animation: rvd-chip-slam 600ms both; }
/* ── FAST-FORWARD ×2 (tap pendant la séquence, façon Balatro) ───────────────────────────────
   La classe .rvd-fast est posée par fastForward() (depouillement.js) sur l'overlay ET la fiche.
   Le scheduler (at) et les count-ups (countTo) sont déjà accélérés par _rate ; ici on halve les
   keyframes dont l'IMPACT est calé sur un callback JS (sinon l'apex visuel tombe à contretemps) :
   windup (var --rvd-wind), tampon verdict (impact ~46 % ≈ at(110)), slam des pastilles mult
   (impact à 84 % ≈ SLAM_IMPACT 504), et le shake de fiche. */
.rvd-fast .rvd-clone.windup .rvd-body { animation-duration: calc(var(--rvd-wind, 420ms) / 2) !important; }
.rvd-fast .rvd-stamp                  { animation-duration: 125ms !important; }   /* 250 → 125 */
.rvd-fast .rvd-mchip.slam             { animation-duration: 300ms !important; }   /* 600 → 300 (impact 252 ≈ SLAM_IMPACT/2) */
.reveal-screen.rvd-fast.rvd-shake     { animation-duration: 140ms !important; }   /* 280 → 140 */
/* CHRONO (refonte ultracode 2026-06-14) : la VRAIE pastille #speed-timer est le
   mult chrono. Elle reste ENFANT de #game-panel à sa place campagne (top:44/left:-8),
   JAMAIS déplacée. Le SEUL z-index 99990 + le stacking context de #game-panel
   (z 500 !important pendant le reveal) la lèvent au-dessus de la fiche sans la
   bouger. On tue son sway ; seul transform:scale est posé inline (grow/settle),
   et la transition le porte. */
.speed-timer.rvd-chrono {
  z-index: 99990;                         /* au-dessus de TOUT le reveal (via stacking #game-panel) */
  font-size: 20px;                        /* dépouillement : garde la taille réglée (le chrono de JEU, lui, a été réduit) */
  animation: none !important;             /* tue le sway/breathe du gameplay */
  transform-origin: center center;
  /* Le sway étant tué, on garde un tilt STATIQUE (dans la fourchette -5/-3deg du
     sway) pour que la pastille ne se redresse pas (retour user). grow/settle le
     composent au scale inline ; restoreChrono retombe dessus. */
  transform: rotate(-4deg);
  transition: transform 380ms cubic-bezier(0.2, 1.45, 0.4, 1);
}
/* SABLIER RESET À L'ENDROIT pendant le dépouillement (retour user 2026-06-15) : on ne se
   contente PAS de pauser le flip (`.paused` le fige mid-flip, souvent tête en bas) — on TUE
   l'animation ET on force rotate(0) pour que le sablier soit DROIT tout du long. */
.speed-timer.rvd-chrono .ico {
  animation: none !important;
  transform: rotate(0deg) !important;
}
/* Le chrono DESCEND au coin BAS-DROITE du VIEWPORT DÈS LE DÉBUT du dépouillement
   — il couvrait le nom de la fiche (retour user 2026-06-14). Le translate inline
   est posé par chronoBringIn (depouillement.js) et composé au
   grow/settle ; cette transition adoucit la descente puis les pulsations du beat
   (courbe avec léger settle, plus longue que la bring-in). */
.speed-timer.rvd-chrono.park-bl {
  transition: transform 520ms cubic-bezier(0.34, 1.1, 0.5, 1);
}
/* Fin du beat chrono : la pastille s'efface (fondu) une fois le ×MULT rempli —
   chronoFadeOut pose .rvd-chrono-out, restoreChrono la nettoie (retour user 2026-06-14). */
.speed-timer.rvd-chrono.rvd-chrono-out {
  opacity: 0;
  transition: opacity 300ms ease-in;
}
/* FUSION dans ×MULT : transform inline (translate vers la cellule + scale) posé
   par JS (mergeChip) ; la carte descend dans la case et se fond. */
.rvd-mchip.mchip-merge {
  transition: transform 360ms cubic-bezier(0.4, -0.05, 0.3, 1), opacity 200ms ease-in 160ms;
  opacity: 0;
}

/* Flash « match » quand le choix du joueur était LE bon (pilule mouvement). */
.reveal-screen.reveal-buildup .bu-on.rvd-match {
  animation: bu-pop 260ms cubic-bezier(0.2, 1.4, 0.4, 1) backwards,
             rvd-match-pulse 700ms ease-out 180ms;
}
@keyframes rvd-match-pulse {
  0%   { box-shadow: var(--sh-md); }
  30%  { box-shadow: var(--sh-md), 0 0 24px 6px rgba(56, 211, 159, 0.85); }
  100% { box-shadow: var(--sh-md); }
}

/* Reduced-motion : le module n'anime déjà rien (état final direct), ceinture
   CSS au cas où des éléments .rvd-* traîneraient le temps d'un stop(). */
@media (prefers-reduced-motion: reduce) {
  .rvd-overlay * { animation: none !important; transition: none !important; }
  .reveal-screen.rvd-shake { animation: none !important; }
}
html.reduce-motion .rvd-overlay * { animation: none !important; transition: none !important; }
html.reduce-motion .reveal-screen.rvd-shake { animation: none !important; }

/* ── « +N pts » : post-it gain sur la fiche reveal (modes à paliers) ────── */
/* Le total du dépouillement n'apparaît qu'ici, avec la fiche — post-it jaune
   Syne italic qui pop en débordant du coin haut-droit (peuplé par showReveal). */
/* Quand un gain est affiché, le verdict balistique lui RÉSERVE le coin
   haut-droit (retour user : « le nombre de points final se fait souvent
   recouvrir par le verdict ») — fitRevealVerdict réduit la police pour tenir
   dans cette max-width. !important + spécificité 0-6-0 : la règle de base
   game-v3.css:2003 (calc(100% - 32px) !important, 0-3-0) doit perdre. */
.reveal-screen.visible:has(.reveal-gain:not([hidden])) .reveal-verdict.show {
  /* --gain-w = largeur RÉELLE du « +N pts » (mesurée par showReveal) ;
     + 42 = margin-left 16 du verdict + débord −10 du gain + 16 d'air. */
  max-width: calc(100% - var(--gain-w, 150px) - 42px) !important;
}
/* Très petits écrans (audit 2, B2) : la réserve étranglait le verdict
   (« Sans faute ! » compressé en 2 lignes de 69 px à 320 px) — on lui rend
   sa largeur, le gain (z 530) reste PAR-DESSUS et lisible. */
@media (max-width: 380px) {
  .reveal-screen.visible:has(.reveal-gain:not([hidden])) .reveal-verdict.show {
    max-width: calc(100% - 32px) !important;
  }
}
/* (Réserve .bull-on RETIRÉE 2026-06-14 : la pilule « +N bulletins » se place
   désormais SOUS le verdict — cf. showBulletinsFloat — donc plus de chevauchement
   horizontal à compenser, le verdict garde sa pleine largeur/taille.) */
.reveal-gain {
  position: absolute;
  top: -14px;
  right: -10px;
  /* AU-DESSUS du verdict balistique (z 520, flex item) : le « +N pts » arrive
     AVANT le verdict — il doit rester lisible au coin haut-droit quand un
     verdict large (« SANS FAUTE ! ») s'étale dessous. */
  z-index: 530;
  /* Couleur de la stat-card POINTS (#4b3cd1, cf. style.css) — le gain est en
     VOIX, il doit lire « points », pas « bulletins » (qui sont jaunes). */
  background: #4b3cd1;
  border: 3px solid var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
  padding: 6px 12px 8px;
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 24px;
  color: var(--paper);
  transform: rotate(2.5deg) scale(0);
  opacity: 0;
  pointer-events: none;
}
.reveal-gain.pop { animation: gain-pop 300ms cubic-bezier(0.2, 1.5, 0.4, 1) forwards; }
/* 0 pt : post-it éteint — papier passé, encre délavée, jamais de halo. */
.reveal-gain.zero { background: var(--paper-2, #f5ecc8); color: rgba(10, 10, 10, 0.55); }
.reveal-gain.fire {
  animation:
    gain-pop 300ms cubic-bezier(0.2, 1.5, 0.4, 1) forwards,
    gain-fire 900ms ease-in-out 300ms infinite alternate;
}
@keyframes gain-pop { to { transform: rotate(2.5deg) scale(1); opacity: 1; } }
@keyframes gain-fire {
  from { box-shadow: 5px 5px 0 var(--ink), 0 0 12px 2px rgba(255, 154, 60, 0.75); }
  to   { box-shadow: 5px 5px 0 var(--ink), 0 0 26px 7px rgba(255, 59, 59, 0.85); }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-gain.pop, .reveal-gain.fire { animation: none; transform: rotate(2.5deg) scale(1); opacity: 1; }
}
@media (max-width: 560px) {
  .reveal-gain { font-size: 19px; top: -8px; right: 2px; }
}

/* 2e temps du crédit : float « +N bulletins » distinct (jaune = couleur de la
   stat-card Bulletins). Standalone (PAS .reveal-gain) pour ne PAS déclencher la
   réserve --gain-w du verdict — il apparaît bien après. C'est lui la SOURCE des
   enveloppes (flyBulletins). */
/* La pilule est RENDUE dans l'overlay racine (#bump-overlay) et ANCRÉE au cadre
   #reveal-screen par showBulletinsFloat (JS) — pour échapper au `perspective` de
   #reveal-screen qui la faisait clignoter. Le top/right ci-dessous n'est qu'un
   FALLBACK (coin haut-droit) si le JS n'a pas posé d'inline. */
.reveal-gain-bull {
  position: absolute;
  top: -12px;
  right: 6px;
  z-index: 531;
  background: var(--yellow);
  color: var(--ink);
  border: 3px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink);
  padding: 4px 8px 5px;
  font-family: 'Syne', var(--font-body, sans-serif);
  font-style: italic;
  font-weight: 800;
  font-size: 15px;                /* pilule bulletins réduite (retour user 2026-06-14) */
  transform: rotate(2.5deg) scale(0);
  opacity: 0;
  pointer-events: none;
}
/* Entrée « TAMPON » identique au verdict (mêmes paliers que revealPop : slam lourd
   scale 0.3→1.3, rebond 0.92, micro-bounce 1.05, settle ; tilt 2.5deg conservé).
   SORTIE en rétraction douce. La pilule est positionnée par le CSS ci-dessus ;
   ces classes ne font QU'animer sur place. */
/* IMPORTANT : opacity:1 sur TOUS les frames ≥42% (et pas seulement à 42%). Sinon,
   avec fill-mode:both, l'élément tient le frame 100% à la fin de l'anim — et comme
   100% n'aurait pas d'opacity, il retomberait sur la base .reveal-gain-bull { opacity:0 }
   → la pilule DISPARAÎTRAIT juste après être apparue (bug « apparaît/disparaît »). */
@keyframes bull-stamp-in {
  0%   { opacity: 0; transform: rotate(2.5deg) scale(0.3); }
  42%  { opacity: 1; transform: rotate(2.5deg) scale(1.3); }
  62%  { opacity: 1; transform: rotate(2.5deg) scale(0.92); }
  78%  { opacity: 1; transform: rotate(2.5deg) scale(1.05); }
  100% { opacity: 1; transform: rotate(2.5deg) scale(1); }
}
@keyframes bull-sb-out {
  0%   { transform: scale(1)    rotate(2.5deg); opacity: 1; }
  100% { transform: scale(0.32) rotate(9deg);   opacity: 0; }
}
.reveal-gain-bull.stamp-in { animation: bull-stamp-in 460ms var(--ease-pop, cubic-bezier(0.2, 1.4, 0.4, 1)) both; }
.reveal-gain-bull.sb-out   { animation: bull-sb-out 240ms cubic-bezier(0.5, 0, 0.75, 0) both; }
@media (prefers-reduced-motion: reduce) {
  .reveal-gain-bull.stamp-in { animation: none; transform: rotate(2.5deg) scale(1); opacity: 1; }
  .reveal-gain-bull.sb-out   { animation: none; opacity: 0; }
}
@media (max-width: 560px) {
  .reveal-gain-bull { font-size: 13px; top: -8px; right: 4px; }
}

/* ── Bouton Passer à stock épuisé (campagne) ────────────────────────────── */
.skip-rp.skip-out { opacity: 0.45; pointer-events: none; }

/* ── Game over Campagne — accent (même pattern que style.css .go-card--*) ── */
.go-card--campagne { --go-accent: var(--blue); --go-accent-ink: var(--paper); }

/* ── Mobile : post-its compactés, urne pleine largeur ───────────────────── */
@media (max-width: 560px) {
  .stage-postit { font-size: 12px; max-width: 150px; padding: 5px 9px; }
  .urne-btn { text-align: center; }
  /* (Le chrono dans .gm-cta-row se dimensionne via clamp(14px,4.2vw,18px) — plus
     de règle #hemicycle-container .speed-timer, il a quitté le conteneur.) */
}
/* Très petits écrans (audit 2, A3) : « Nouvelle partie » + « Modes »
   débordaient de l'écran à 320 px — CTA compactés. */
@media (max-width: 360px) {
  .btn-new-game { font-size: 14px; padding: 10px 14px; }
  .btn-options  { font-size: 12px; padding: 8px 10px; }
}

/* ════════════════════════════════════════════════════════════════════════
   QG DE CAMPAGNE — boutique néo-brut (Plan B). Pause tous les 5 RP, chrono
   coupé. Même plomberie d'ouverture que .options-backdrop (.show + 220 ms).
   ════════════════════════════════════════════════════════════════════════ */
.qg-backdrop {
  position: fixed; inset: 0;
  z-index: var(--z-overlay, 1000);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(10, 10, 10, 0.55);
  opacity: 0;
  transition: opacity var(--dur-base, 220ms) var(--ease-out, ease);
}
.qg-backdrop[hidden] { display: none; }
.qg-backdrop.show { opacity: 1; }
.qg-modal {
  position: relative;
  width: min(440px, 100%);
  max-height: 90vh; overflow-y: auto; box-sizing: border-box;
  background: var(--paper);
  border: var(--stroke, 3px solid var(--ink));
  box-shadow: var(--sh-hover, 8px 9px 0 var(--ink));
  padding: 22px 18px 18px;
  transform: rotate(-1.2deg);   /* léger tilt néo-brut au repos */
}
/* ENTRÉE (le menu surgit du bas, dépasse, rebondit) et SORTIE (élan puis plongée tournante) du
   menu QG : désormais pilotées par la Web Animations API dans openQG/closeQG (modal.animate),
   PAS par une classe CSS — sinon WebKit/iOS sautait l'animation au passage display:none→flex
   (retour user répété : « pas d'animation d'arrivée/sortie »). Ici on ne garde que le fond qui
   s'estompe (.qg-backdrop opacity). Le tilt au repos reste sur .qg-modal (transform ci-dessus). */
/* Cocarde tricolore (rosette de campagne) épinglée par son CENTRE sur le coin haut-gauche du
   drapeau. Positions en % du flagwrap (= boîte du SVG, viewBox 224×68) → calée sur le coin du
   ruban au repos (x≈8/224, y≈15/68) quelle que soit la taille ; translate(-50%) la centre sur
   ce point. Fixe pendant que le drapeau ondule (le coin au mât a une amplitude nulle). */
.qg-cocarde {
  position: absolute; left: 3.6%; top: 22%; z-index: 3;
  width: 30px; height: 30px; border-radius: 50%;
  background: radial-gradient(circle, var(--red) 0 30%, var(--paper) 30% 58%, var(--blue) 58% 100%);
  border: 3px solid var(--ink); box-shadow: 2px 2px 0 var(--ink);
  transform: translate(-50%, -50%) rotate(-12deg);
}
.qg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 16px;
}
/* Le titre prend toute la largeur dispo à GAUCHE de la pastille bulletins → le drapeau
   flex-fill (retour user : « grand vide entre le drapeau et les bulletins ; titre plus grand »). */
.qg-titlewrap { flex: 1 1 auto; min-width: 0; }
/* TITRE en DRAPEAU qui ONDULE pour de vrai (vraies vagues sinusoïdales, sans crénelage). Le
   drapeau est un RUBAN VECTORIEL (<svg class="qgf-svg"> injecté par buildQGFlag) dont l'attribut
   `d` est morphé en SMIL à travers des phases décalées → la vague voyage du mât vers le bord
   libre. .qgf-bg = tissu bleu bord noir, .qgf-shadow = copie décalée (ombre dure néo-brut),
   .qgf-txt = texte qui chevauche la médiane animée (centrage par baseline + textLength anti-débord).
   Cocarde épinglée au coin, pas de mât. Le SVG remplit 100 % de la largeur (titre plus grand) ;
   vectoriel → net à toute échelle ; SMIL sur `d` → OK iOS (aucun filter:url). */
.qg-flagwrap { position: relative; display: flex; width: 100%; align-items: center; }
.qg-flag { display: block; width: 100%; margin: 0; }
.qg-flag .qgf-svg { display: block; width: 100%; height: auto; overflow: visible; }
.qg-flag .qgf-bg { fill: var(--blue); stroke: var(--ink); stroke-width: 3; stroke-linejoin: round; }
.qg-flag .qgf-shadow { fill: var(--ink); }
.qg-flag .qgf-txt {
  fill: var(--paper); font-family: 'Archivo Black', var(--font-display), sans-serif;
}
.qg-balance {
  flex-shrink: 0; display: flex; flex-direction: column; align-items: center;
  background: var(--yellow); color: var(--ink);
  border: 3px solid var(--ink); box-shadow: 3px 3px 0 var(--ink);
  padding: 4px 13px 6px; transform: rotate(2deg);
}
.qg-bal-num {
  font-family: 'Syne', var(--font-body, sans-serif); font-style: italic;
  font-weight: 800; font-size: 28px; line-height: 1; font-variant-numeric: tabular-nums;
}
.qg-bal-lbl {
  font-family: var(--font-display); font-size: 8px; letter-spacing: 0.1em;
  text-transform: uppercase; margin-top: 1px;
}
.qg-articles { display: flex; flex-direction: column; gap: 9px; }
.qg-article {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: var(--paper); border: 3px solid var(--ink);
  box-shadow: 4px 4px 0 var(--ink); padding: 9px 12px;
  cursor: pointer; text-align: left; color: var(--ink);
  transition: transform 110ms var(--ease-out, ease), box-shadow 110ms var(--ease-out, ease);
}
.qg-article:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 6px 6px 0 var(--ink); }
.qg-article:active:not(:disabled) { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--ink); }
.qg-article:disabled { opacity: 0.5; cursor: default; box-shadow: 2px 2px 0 var(--ink); }
/* Article disponible mais cagnotte insuffisante : reste cliquable (pour le shake
   de refus) mais grisé, prix en rouge — etat visuel manquant avant (audit). */
.qg-article.qg-cant { opacity: 0.6; cursor: not-allowed; box-shadow: 2px 2px 0 var(--ink); }
.qg-article.qg-cant:hover { transform: none; box-shadow: 2px 2px 0 var(--ink); }
.qg-article.qg-cant .qg-art-price { color: var(--red); }
.qg-art-icon {
  flex-shrink: 0; width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 3px solid var(--ink); font-size: 21px; line-height: 1;
  background: var(--art-bg, var(--yellow)); color: var(--ink);
  transform: rotate(-2deg);
}
.qg-art-text { flex: 1; display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.qg-art-label { font-family: var(--font-display); font-size: 15px; text-transform: uppercase; line-height: 1.05; }
.qg-art-desc { font-family: var(--font-body, sans-serif); font-size: 11px; opacity: 0.65; line-height: 1.15; }
.qg-art-price {
  flex-shrink: 0; display: inline-flex; align-items: baseline; gap: 3px;
  font-family: 'Syne', var(--font-body, sans-serif); font-style: italic; font-weight: 800;
  font-size: 21px; line-height: 1; font-variant-numeric: tabular-nums;
}
.qg-art-price-u { font-size: 11px; opacity: 0.6; font-style: normal; font-family: var(--font-display); }
.qg-art-price.sold {
  font-size: 12px; font-style: normal; font-family: var(--font-display);
  text-transform: uppercase; opacity: 0.55; letter-spacing: 0.06em;
}
.qg-resume {
  display: block; width: 100%; margin-top: 16px;
  background: var(--green-2); color: var(--paper);
  border: 3px solid var(--ink); box-shadow: 5px 5px 0 var(--ink);
  padding: 13px 14px; font-family: var(--font-display); font-size: 16px;
  text-transform: uppercase; cursor: pointer;
  transition: transform 110ms var(--ease-out, ease), box-shadow 110ms var(--ease-out, ease);
}
.qg-resume:hover { transform: translate(-1px, -1px); box-shadow: 7px 7px 0 var(--ink); }
.qg-resume:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--ink); }
/* Refus (cagnotte insuffisante) : petit shake horizontal. */
.qg-article.qg-deny { animation: qg-deny 280ms cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes qg-deny {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-5px, 0); }
  40% { transform: translate(5px, 0); }
  60% { transform: translate(-3px, 0); }
  80% { transform: translate(3px, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .qg-backdrop, .qg-modal, .qg-article, .qg-resume { transition: none !important; }
  .qg-article.qg-deny { animation: none !important; }
  /* Drapeau + arrivée/sortie du menu : déjà neutralisés en sobriété côté JS
     (buildQGFlag n'émet aucun <animate> ; openQG/closeQG ne lancent pas modal.animate). */
}

/* ═══ (item 9) SYSTÈME DE COMBO DÉSACTIVÉ TOTALEMENT (obsolète) ═══
   Masque le pin ×N (floats x2/x7 sur la stat-card Points), le bumper central
   « COMBO ×N », le bumper « Combo perdu » et le pulse de cadre. Les flammes du
   dépouillement sont indépendantes (moteur SVG interne par pastille, cf. .dlp-afire). */
.combo-pin, .combo-intro, .combo-lost { display: none !important; }
.game-panel.combo-pulse { animation: none !important; }
