:root {
  --bg: #020110;
  --pink: #ff4fa0;
  --purple: #9b5cff;
  --blue: #5b8cff;
  --text: #ffffff;
  --text-dim: rgba(255, 255, 255, 0.65);
  --border: rgba(255, 255, 255, 0.14);
  --glass: rgba(255, 255, 255, 0.045);
  --easy: #6fe3a6;
  --medium: #ffd76a;
  --hard: #ff9f5a;
  --veryhard: #ff5a7a;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background:
    radial-gradient(ellipse 60% 40% at 20% 8%, rgba(155, 92, 255, 0.32), transparent 60%),
    radial-gradient(ellipse 55% 40% at 88% 85%, rgba(91, 140, 255, 0.24), transparent 55%),
    var(--bg);
  color: var(--text);
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  position: relative;
}

.stars {
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: 0.55;
  pointer-events: none;
  background-image:
    radial-gradient(1.4px 1.4px at 40px 60px, #fff, transparent),
    radial-gradient(1px 1px at 160px 200px, rgba(255, 255, 255, 0.8), transparent),
    radial-gradient(1.6px 1.6px at 300px 90px, #fff, transparent),
    radial-gradient(1px 1px at 380px 320px, rgba(255, 255, 255, 0.7), transparent),
    radial-gradient(1.2px 1.2px at 90px 340px, #fff, transparent);
  background-size: 420px 420px;
}

#app { width: 100%; max-width: 460px; position: relative; z-index: 1; }

.screen { display: none; }
.screen.active { display: block; }

.card {
  background: var(--glass);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 26px 22px;
  backdrop-filter: blur(6px);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 12px;
  color: var(--pink);
  background: rgba(255, 79, 160, 0.1);
  border: 1px solid rgba(255, 79, 160, 0.55);
  font-weight: 800;
  padding: 5px 12px;
  border-radius: 999px;
  margin: 0 0 14px;
}
.eyebrow-check { color: var(--pink); }
.h2-spark {
  display: inline-block;
  color: var(--purple);
  text-shadow: 0 0 8px rgba(155, 92, 255, 0.7);
  font-size: 0.8em;
}

h1 {
  font-family: "Baloo 2", sans-serif;
  font-size: 27px;
  margin: 0 0 12px;
  line-height: 1.24;
  font-weight: 700;
}
h2 { font-family: "Baloo 2", sans-serif; font-size: 21px; margin: 0 0 16px; line-height: 1.28; font-weight: 700; }

.subtitle { color: var(--text-dim); font-size: 15px; margin: 0 0 8px; line-height: 1.5; }
.subtitle strong { color: var(--pink); }
.meta { color: var(--text-dim); font-size: 13px; margin: 0 0 20px; }

.btn-primary {
  width: 100%;
  padding: 16px 20px;
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  clip-path: polygon(22px 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%, 0 22px);
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 0 1px rgba(255, 79, 160, 0.25), 0 6px 24px rgba(155, 92, 255, 0.45);
  color: #ffffff;
  font-family: "Manrope", sans-serif;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-weight: 800;
  cursor: pointer;
  margin-top: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  transition: transform 0.15s ease, filter 0.15s ease;
}
.btn-primary::before {
  content: "";
  width: 18px;
  height: 18px;
  flex: none;
  background-color: #fff;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 2 4 14h6l-1 8 9-12h-6l1-8z'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 2 4 14h6l-1 8 9-12h-6l1-8z'/></svg>") center / contain no-repeat;
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-primary:active { transform: scale(0.98); filter: brightness(0.92); }
.btn-primary:disabled { opacity: 0.5; cursor: default; }

.btn-secondary {
  width: 100%;
  padding: 14px 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  margin-top: 10px;
}

.btn-link {
  display: block;
  width: 100%;
  text-align: center;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 13px;
  margin-top: 14px;
  cursor: pointer;
  text-decoration: underline;
}

/* Quiz — même technique de fond cosmique que hook/reveal. Le numéro de
   question, le texte de la question (et l'image le cas échéant), le badge
   de difficulté, les barres de progression/temps et les 4 options sont
   posés en overlay sur assets/quiz-bg.png.
   v6 (canevas refait par Sav en 1080×1920, format 9:16 standard) : seul ce
   ratio garantit une largeur 100% sur la quasi-totalité des téléphones
   réels sans bandes latérales (cf. historique plus bas dans le fichier,
   section "Plein écran mobile") — l'ancien 1080×2340 (ratio ~19.5:9) était
   plus étiré que l'écran visible réel de nombreux téléphones, d'où les
   bandes. Seuls "QUESTION"/"/20" et les 4 pastilles de réponse sont bakés ;
   badge, barres de progression/temps et texte de question restent du vrai
   HTML positionné en %, comme avant. Toutes les coordonnées ci-dessous ont
   été remesurées au pixel sur ce nouveau fichier (canevas plus court de
   18% qu'avant : la zone de texte disponible entre la ligne "QUESTION/20"
   et la 1ère pastille est plus réduite en absolu, mais fitQuestionText
   dans app.js réduit déjà dynamiquement la police des questions longues —
   c'est ce mécanisme existant qui absorbe la différence).
   v6.1 — Sav a retiré le cadre décoratif extérieur (bordure néon, coins,
   losanges) : même avec un ratio 9:16, un résidu de quelques % de bande
   reste mathématiquement possible quand le navigateur affiche à la fois
   sa barre d'adresse ET sa barre d'outils (aucun ratio fixe ne peut
   couvrir 100% des configurations de chrome navigateur). Sans cadre, ce
   résidu se fond dans le fond du body au lieu de créer une ligne visible
   qui s'arrête net. Couleur de fond du body alignée sur celle du nouveau
   fichier (#020110, cf. --bg) pour qu'il n'y ait plus aucune différence
   de teinte perceptible. Coordonnées des pastilles/QUESTION-index
   inchangées (mesurées identiques sur ce fichier, seul le cadre a changé). */
.quiz-card {
  container-type: inline-size;
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/quiz-bg.png");
  background-size: 100% 100%;
}
/* Numéro de question : centré dans le vide laissé entre "QUESTION" et
   "/20" sur la même ligne (mesuré sur le nouveau fichier : bbox texte
   y=214→264px soit top ~12.45%, milieu du vide entre les deux mots
   ~56.2% en largeur). */
.qz-index {
  left: 56.2%; top: 12.45%; transform: translate(-50%, -50%);
  font-family: "Manrope", sans-serif; font-weight: 800; font-size: 5cqw;
  color: #ffffff; white-space: nowrap;
}
/* Pastille de niveau de difficulté — centrée horizontalement, posée juste
   sous la ligne "QUESTION x/20" (demande Sav : "centre la pour qu'elle
   soit sous la ligne du nombre de question, bien au milieu"). Couleur/
   emoji/label viennent du serveur (difficulty.json). Position recalée pour
   le nouveau canevas : même écart en pixels sous qz-index qu'avant
   (82,8px), converti en % de la nouvelle hauteur (1920). */
.qz-difficulty {
  position: absolute; left: 50%; top: 16.76%; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 0.5cqw;
  padding: 0.8cqw 1.9cqw;
  border-radius: 999px;
  font-family: "Manrope", sans-serif; font-weight: 800; font-size: 2.3cqw;
  white-space: nowrap;
  background: color-mix(in srgb, var(--qz-diff-color, #6fe3a6) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--qz-diff-color, #6fe3a6) 55%, transparent);
  color: var(--qz-diff-color, #6fe3a6);
}
.qz-difficulty[data-level="easy"]     { --qz-diff-color: var(--easy); }
.qz-difficulty[data-level="medium"]   { --qz-diff-color: var(--medium); }
.qz-difficulty[data-level="hard"]     { --qz-diff-color: var(--hard); }
.qz-difficulty[data-level="veryhard"] { --qz-diff-color: var(--veryhard); }
.qz-question {
  /* Zone recalée pour le canevas v6 (1080×1920) : du bas de la barre de
     temps (~22.4%) jusqu'à juste au-dessus de la 1ère pastille mesurée sur
     le nouveau fond (52.24%, marge de ~1% gardée). Zone plus courte en
     absolu qu'avant (canevas 18% plus court) : fitQuestionText (app.js)
     rétrécit déjà dynamiquement les questions longues, c'est ce mécanisme
     qui absorbe la différence plutôt qu'un recalcul de la police ici. */
  left: 7.41%; top: 24.44%; width: 85.19%; height: 26.76%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1cqw;
  text-align: center;
  /* Oswald (condensée) à la demande de Sav — colle au style bâké
     "QUESTION /20" du fond, contrairement à Manrope utilisée avant. */
  font-family: "Oswald", sans-serif; font-weight: 700; line-height: 1.2;
  color: #ffffff;
}
/* Taille du texte de la question : 9.1cqw par défaut (paliers réduits de
   30% à la demande de Sav, depuis un doublement initial à 13cqw) — app.js
   (fitQuestionText) réduit dynamiquement #qz-question-text via un style
   inline pour les questions les plus longues de la banque si besoin,
   cette valeur n'est que le point de départ/repli. */
#qz-question-text { font-size: 9.1cqw; }
/* Question-image : image en premier (taille réduite de 10%, hauteur fixe
   plutôt que flex:1 pour libérer un espace constant en dessous), puis le
   texte de la question centré verticalement dans l'espace restant entre
   l'image et les pastilles de réponse (flex:1 + centrage interne) — texte
   agrandi de 50% au total depuis la version d'origine (3.6cqw → 7.02cqw)
   à la demande de Sav. */
.qz-question.has-image { justify-content: flex-start; }
.qz-question.has-image #qz-question-image { order: -1; flex: 0 0 auto; height: 80%; margin-bottom: 0; }
.qz-question.has-image #qz-question-text {
  order: 1; flex: 1; min-height: 0; width: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 7.02cqw; line-height: 1.2;
}
.qz-image {
  flex: 1;
  min-height: 0;
  max-width: 100%;
  margin-bottom: 1.2cqw;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 3px 16px rgba(0, 0, 0, 0.55);
}

/* Progression dans le quiz (question X/20) — sous la ligne "QUESTION x/20",
   avec un drapeau fixe à l'arrivée. Top recalé pour v6 (même écart en
   pixels sous le badge de difficulté qu'avant, converti en % de 1920). */
.qz-progress-track {
  position: absolute; left: 7.41%; width: 82.5%; top: 19.2%; height: 1.4%;
  background: rgba(255, 255, 255, 0.12); border-radius: 999px; overflow: visible;
}
.qz-progress-fill {
  position: relative; height: 100%; width: 0%; border-radius: 999px;
  background: linear-gradient(90deg, #ff4fa0, #9b5cff, #5b8cff);
  box-shadow: 0 0 3px rgba(155, 92, 255, 0.55);
  transition: width 0.4s ease;
}
.qz-progress-flag {
  position: absolute; right: -3.2cqw; top: 50%; transform: translateY(-50%);
  width: 3cqw; height: 3cqw; color: #ffffff;
  filter: drop-shadow(0 0 0.6cqw rgba(91, 140, 255, 0.85));
}

/* Chrono de la question en cours — sous le badge de difficulté. Vire à
   l'orange/rouge dans les 3 dernières secondes. Top recalé pour v6 (même
   écart en pixels sous la barre de progression qu'avant). */
.qz-timer-track {
  position: absolute; left: 7.41%; top: 22.37%; width: 85.19%; height: 1.4%;
  background: rgba(255, 255, 255, 0.12); border-radius: 999px; overflow: visible;
}
.qz-timer-fill {
  position: relative; height: 100%; width: 100%; border-radius: 999px;
  background: linear-gradient(90deg, #ffd9a0, #ff9f5a);
  box-shadow: 0 0 3px rgba(255, 159, 90, 0.55);
  transition: width 0.1s linear, background 0.2s;
}
.qz-timer-fill.low {
  background: linear-gradient(90deg, #ff9f5a, #ff5a7a);
  box-shadow: 0 0 3px rgba(255, 90, 122, 0.6);
}
.qz-option {
  /* left/width mesurés sur le fond v6 (1080×1920) : quasi identiques à
     l'ancien fond (9.35%/81.76% vs 9.17%/81.67%), la largeur de référence
     1080px n'ayant pas changé. */
  position: absolute;
  left: 9.35%; width: 81.76%;
  display: flex; align-items: center;
  padding: 0 12% 0 8%;
  background: transparent; border: none; cursor: pointer;
  text-align: left;
  /* Oswald à la demande de Sav, pour matcher le texte de la question.
     Taille +30% à la demande de Sav (4.2cqw → 5.46cqw). */
  font-family: "Oswald", sans-serif; font-weight: 600; font-size: 5.46cqw; line-height: 1.28;
  color: #ffffff;
  overflow: hidden;
}
/* Exception ciblée (question "homma", id v29 dans questions.json) : police
   un peu plus petite pour cette question precise, demandee par Sav, sans
   affecter les autres questions (voir toggle qz-option-compact dans
   renderQuestion() dans app.js). */
.qz-option.qz-option-compact { font-size: 4.6cqw; }
.qz-option:disabled { cursor: default; }
.qz-option.selected { color: #ffd76a; }
/* Positions des 4 pastilles mesurées au pixel sur le nouveau fond
   quiz-bg.png (1080×1920, détection du contour lumineux, cf. commit v6). */
.opt-0 { top: 52.24%; height: 7.76%; }
.opt-1 { top: 62.97%; height: 7.66%; }
.opt-2 { top: 73.65%; height: 7.76%; }
.opt-3 { top: 84.38%; height: 7.66%; }

/* Gate */
.teaser-line { color: var(--text-dim); font-size: 14px; margin-bottom: 18px; }

.locked-block {
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 14px;
  padding: 6px 16px;
  margin-bottom: 22px;
}
.locked-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  padding: 10px 0;
}
.locked-row + .locked-row { border-top: 1px solid rgba(255, 255, 255, 0.08); }
.locked-icon {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.locked-icon svg { width: 15px; height: 15px; }
.locked-icon-score { color: var(--pink); background: rgba(255, 79, 160, 0.14); }
.locked-icon-niveau { color: var(--purple); background: rgba(155, 92, 255, 0.14); }
.locked-icon-rang { color: var(--blue); background: rgba(91, 140, 255, 0.14); }
.locked-label { font-weight: 700; }
.locked-sep { color: var(--text-dim); margin: 0 -2px; }
.locked-value { margin-left: auto; font-weight: 800; }
.locked-value-score { color: var(--pink); }
.locked-value-niveau { color: var(--purple); }
.locked-value-rang { color: var(--blue); }
.blur { filter: blur(5px); user-select: none; }

.field-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text);
  margin: 16px 0 7px;
  font-weight: 700;
}
.field-label em { color: var(--text-dim); font-weight: 500; font-style: normal; }
.field-icon { flex: none; width: 17px; height: 17px; color: var(--purple); }
input[type="text"], input[type="email"], select {
  width: 100%;
  padding: 13px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255, 79, 160, 0.3);
  background: rgba(255, 255, 255, 0.045);
  color: var(--text);
  font-size: 15px;
  font-family: "Manrope", sans-serif;
  transition: border-color 0.15s ease;
}
input[type="text"]:focus, input[type="email"]:focus, select:focus {
  outline: none;
  border-color: var(--pink);
}
/* Le <select> lui-même hérite du fond sombre défini ci-dessus, mais la
   liste déroulante d'options est rendue par l'OS/le navigateur et ignore
   ce fond par défaut (blanc sur Windows) — sans ceci le texte blanc de
   var(--text) devient illisible en blanc sur blanc. */
select option {
  background: #16122b;
  color: var(--text);
}
.error-text { color: var(--veryhard); font-size: 15px; margin-top: 10px; min-height: 16px; }

/* ==== Wizard gate (3 étapes : pseudo -> groupe favori -> email/optin) ==== */

/* Étape 1 : reprise à l'identique du mockup fourni — tout le visuel (fond
   étoilé, cadre, header KPOP-FRANCE, trophée, titre, sous-titre, cadre du
   champ, bouton) est dans l'image assets/wizard-step1-bg.png. Seuls le
   champ de saisie et le bouton sont de vrais éléments HTML, posés en
   overlay aux coordonnées calées au pixel sur l'image (même technique que
   .hook-card / .quiz-card / .reveal-card). */
.wizard-step1-card {
  position: relative;
  width: 100%;
  container-type: inline-size;
  aspect-ratio: 1080 / 2340;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/wizard-step1-bg.png");
  background-size: 100% 100%;
}
/* Sélecteur combiné input.wizard-step1-input : la règle générique
   "input[type=\"text\"]" a une spécificité element+attribut plus élevée
   qu'une simple classe — sans le "input" ici, elle repasse par-dessus
   width/border/background/padding malgré l'ordre des règles.
   Coordonnées recalées au pixel sur le nouveau fond 1080×2340 (icône
   silhouette bakée à gauche de la pastille : padding-left dégage l'icône). */
input.wizard-step1-input {
  position: absolute;
  left: 12.5%; top: 69.23%; width: 75.19%; height: 5.39%;
  padding: 0 4% 0 10.8%;
  border: none;
  border-radius: 0;
  background: transparent;
  color: #ffffff;
  font-family: "Manrope", sans-serif;
  font-size: 3.3cqw;
}
input.wizard-step1-input:focus { outline: none; border-color: transparent; }
/* Curseur clignotant factice affiché à la place d'un placeholder texte
   (demande Sav) : indique où écrire tant que le champ est vide et pas
   encore focus — app.js le masque au focus (le vrai curseur du navigateur
   prend le relais) ou dès qu'il y a du texte. Même position que le début
   du texte tapé (left = left+padding-left du champ). */
.wizard-step1-caret {
  position: absolute;
  left: 23.3%; top: 71.93%;
  width: 2px; height: 3.6cqw;
  background: #ffffff;
  transform: translateY(-50%);
  animation: caret-blink 1s steps(1) infinite;
  pointer-events: none;
}
@keyframes caret-blink { 50% { opacity: 0; } }
.wizard-step1-error {
  position: absolute;
  left: 12.5%; top: 76.4%; width: 75.19%;
  text-align: center;
  margin: 0;
  font-size: 2.4cqw;
}
.wizard-step1-btn {
  left: 9.17%; top: 82.27%; width: 84.26%; height: 5.43%;
}

.wizard-step { display: none; }
.wizard-step.active { display: block; }

/* Étape 2 : même principe que l'étape 1 — tout le visuel (header, titre,
   sous-titre, les 15 tuiles avec photo + nom baké dedans) vient de
   assets/wizard-step2-bg.png. v2 (canevas 1080×1920, refonte par Sav) :
   plus de bouton "Continuer" ni de lien "Retour" bakés — cliquer une tuile
   sélectionne ET avance direct à l'étape 3 (cf. app.js). Seules les 15
   zones cliquables (une par tuile) restent en overlay HTML, aux
   coordonnées calées au pixel sur l'image. Retour vers cette étape
   toujours possible depuis le bouton Retour de l'étape 3. */
.wizard-step2-card {
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/wizard-step2-bg.png");
  background-size: 100% 100%;
}
.tile-hit { border-radius: 12px; }
/* Glow de sélection : outline (jamais dans le flux, n'affecte pas la
   boîte) + outline-offset positif pour garantir que l'anneau reste
   entièrement à l'EXTÉRIEUR du cadre bâké de la tuile, jamais par-dessus
   la photo — demande explicite de Sav. Le box-shadow (flou seul, pas de
   spread négatif) ajoute la lueur diffuse tout autour, également vers
   l'extérieur. Anim "pop" + checkmark qui apparaît en grossissant : retour
   visuel immédiat pour que Sav (et les joueurs) voient qu'il n'y a pas eu
   de mis-clic avant l'avance auto vers l'étape 3 (délai dans app.js calé
   pour laisser le temps de voir cette anim en entier). */
.tile-hit.selected {
  outline: 3px solid var(--pink);
  outline-offset: 3px;
  box-shadow: 0 0 18px 2px rgba(255, 79, 160, 0.6);
  animation: tile-select-pop 0.35s ease;
}
@keyframes tile-select-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(0.94); }
  70%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}
.tile-hit.selected::after {
  content: "✓";
  position: absolute;
  top: -10px;
  right: -8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--pink);
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 8px rgba(255, 79, 160, 0.7);
  animation: tile-check-in 0.25s ease 0.05s both;
}
@keyframes tile-check-in {
  from { transform: scale(0); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
/* Grille 3 colonnes x 5 lignes, coordonnées relevées au pixel sur le
   nouveau fond wizard-step2-bg.png (1080×1920, détection des bandes de
   fond entre tuiles). Colonnes quasi uniformes (~26.8% chacune) ; lignes
   de hauteur quasi uniforme (~10.6%), mesurées indépendamment par ligne. */
.tile-hit[data-value="aespa"]        { left: 9.35%;  top: 37.92%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="ateez"]        { left: 36.85%; top: 37.92%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="babymonster"]  { left: 64.35%; top: 37.92%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="blackpink"]    { left: 9.35%;  top: 49.22%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="bts"]          { left: 36.85%; top: 49.22%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="enhypen"]      { left: 64.35%; top: 49.22%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="ive"]          { left: 9.35%;  top: 60.52%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="le-sserafim"]  { left: 36.85%; top: 60.52%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="nct"]          { left: 64.35%; top: 60.52%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="newjeans"]     { left: 9.35%;  top: 71.77%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="seventeen"]    { left: 36.85%; top: 71.77%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="stray-kids"]   { left: 64.35%; top: 71.77%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="twice"]        { left: 9.35%;  top: 83.28%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="txt"]          { left: 36.85%; top: 83.28%; width: 26.8%; height: 10.6%; }
.tile-hit[data-value="autre"]        { left: 64.35%; top: 83.28%; width: 26.8%; height: 10.6%; }

/* Étape 3 — refonte du 15/09 (retour Sav : nouveau mockup "OBTIENS TON
   RÉSULTAT / ET DÉBLOQUE DES BONUS", champ email + carte opt-in bordée +
   3 perks). Sav n'a fourni qu'un fond quasi vide pour ce mockup (starfield +
   bandeau "KPOP-FRANCE / K-POP IQ ✨" mesuré par analyse de luminosité :
   bandeau + filet dégradé dans les ~7.7% hauts d'un canevas 1080×1920) :
   plutôt que d'en faire une image de fond (imposerait un ratio fixe, mal
   adapté à la hauteur variable de ce contenu), le bandeau est reproduit en
   CSS pur (.gate-header ci-dessous) et tout le reste du contenu (titre,
   formulaire, carte bonus) est du vrai HTML/CSS, dans le même langage
   visuel que les écrans "non-bakés" (.mini-panel, voir plus bas) — reste
   net à toutes les tailles d'écran, pas de coordonnées calées au pixel à
   maintenir. L'ancienne image wizard-step3-bg-v2.png (et son overlay calé
   au pixel) est remplacée. */
.wizard-step3-card {
  position: relative;
  width: 100%;
  margin: 0 auto;
}

/* Bandeau "KPOP-FRANCE / K-POP IQ" — reproduit le fond fourni par Sav en
   direct CSS (2 libellés + filet dégradé) plutôt qu'une image, pour rester
   net sur toutes les résolutions et s'intégrer à la hauteur variable de
   .mini-panel (une image de fond figée imposerait un ratio fixe). */
.gate-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: min(100%, 420px);
  margin: 0 auto 18px;
  padding-bottom: 10px;
  border-bottom: 1px solid transparent;
  border-image: linear-gradient(90deg, var(--pink), var(--purple), var(--blue)) 1;
}
.gate-header-logo {
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 4.6cqw;
  color: #ffffff;
  letter-spacing: 0.01em;
}
.gate-header-logo span { color: var(--pink); }
.gate-header-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  font-size: 3.4cqw;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}
.gate-header-tag .mini-sparkle { width: 12px; height: 12px; }

/* Indicateur d'étapes (1-2-3, points reliés) — décoratif, toujours affiché
   "rempli" sur cette 3ᵉ et dernière étape (pas de logique JS nécessaire, un
   seul état à représenter ici). */
.gate-progress {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0 0 14px;
}
.gate-progress-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
}
.gate-progress-dot--pink   { background: var(--pink);   box-shadow: 0 0 8px rgba(255, 79, 160, 0.8); }
.gate-progress-dot--purple { background: var(--purple); box-shadow: 0 0 8px rgba(155, 92, 255, 0.8); }
.gate-progress-dot--blue   { background: var(--blue);   box-shadow: 0 0 8px rgba(91, 140, 255, 0.8); }
.gate-progress-line {
  width: 28px;
  height: 1.5px;
  background: linear-gradient(90deg, var(--pink), var(--blue));
  opacity: 0.6;
}

.gate-title {
  text-transform: uppercase;
  letter-spacing: 0.01em;
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 7.6cqw;
  line-height: 1.25;
  margin: 0 0 16px;
  color: #ffffff;
}
.gate-title-line2 {
  display: block;
}

.gate-envelope {
  position: relative;
  display: inline-flex;
  margin: 0 0 16px;
}
.gate-envelope-icon {
  width: 48px;
  height: 48px;
  fill: none;
  stroke: var(--purple);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 10px rgba(155, 92, 255, 0.55));
}
.gate-envelope-sparkle {
  position: absolute;
  top: -6px;
  right: -10px;
  width: 20px;
  height: 20px;
}

/* Carte "REJOINS KPOP FRANCE" — cadre néon à coins coupés (octogone),
   même trick que .mini-input-wrap (dégradé posé en fond, "percé" par le
   padding) mais avec un clip-path partagé entre le cadre et son contenu
   pour obtenir les coins coupés plutôt qu'arrondis, comme le mockup. */
.gate-bonus-card {
  --gate-cut: 18px;
  display: block;
  position: relative;
  margin: 4px 0 18px;
  padding: 2px;
  border-radius: 0;
  clip-path: polygon(
    var(--gate-cut) 0, calc(100% - var(--gate-cut)) 0,
    100% var(--gate-cut), 100% calc(100% - var(--gate-cut)),
    calc(100% - var(--gate-cut)) 100%, var(--gate-cut) 100%,
    0 calc(100% - var(--gate-cut)), 0 var(--gate-cut)
  );
  background: linear-gradient(135deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 24px rgba(155, 92, 255, 0.3);
  cursor: pointer;
  text-align: left;
}
.gate-bonus-card-inner {
  /* display:block obligatoire : c'est un <span> dans le HTML (élément
     interactif <label> qui ne peut contenir un <div>), et sans display
     explicite un span reste "inline" par défaut — position/padding/
     clip-path/background sur une boîte inline ne s'appliquent pas comme
     attendu (tout le contenu se retrouvait invisible, carte vide en test). */
  display: block;
  position: relative;
  padding: 18px 16px;
  background: #0c0a1e;
  clip-path: polygon(
    calc(var(--gate-cut) - 2px) 0, calc(100% - var(--gate-cut) + 2px) 0,
    100% calc(var(--gate-cut) - 2px), 100% calc(100% - var(--gate-cut) + 2px),
    calc(100% - var(--gate-cut) + 2px) 100%, calc(var(--gate-cut) - 2px) 100%,
    0 calc(100% - var(--gate-cut) + 2px), 0 calc(var(--gate-cut) - 2px)
  );
}
.gate-bonus-eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 3.4cqw;
  color: var(--text-dim);
  margin: 0 0 12px;
}
.gate-bonus-consent {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px;
}
.gate-checkbox {
  position: relative;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  border: 1.5px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.04);
}
.gate-checkbox input[type="checkbox"] {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.gate-checkbox-tick {
  position: absolute;
  inset: 3px;
  fill: none;
  stroke: #ffffff;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
  pointer-events: none;
}
.gate-checkbox input[type="checkbox"]:checked ~ .gate-checkbox-tick {
  opacity: 1;
  transform: scale(1);
}
.gate-checkbox input[type="checkbox"]:checked ~ .gate-checkbox-tick,
.gate-checkbox:has(input[type="checkbox"]:checked) {
  border-color: var(--pink);
}
.gate-bonus-consent-text {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: "Manrope", sans-serif;
  font-weight: 800;
  font-size: 4.4cqw;
  line-height: 1.3;
  background: linear-gradient(90deg, var(--pink), var(--blue));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.gate-bonus-consent-text .mini-btn-icon { fill: var(--pink); -webkit-text-fill-color: initial; }

.gate-perks {
  display: flex;
  gap: 10px;
  padding: 14px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  margin: 0 0 12px;
}
.gate-perk {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}
.gate-perk-icon {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: var(--purple);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.gate-perk-icon--fill {
  fill: var(--purple);
  stroke: none;
}
.gate-perk span {
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 3cqw;
  line-height: 1.3;
  color: var(--text-dim);
}
.gate-bonus-fineprint {
  /* display:block nécessaire pour que text-align:center s'applique — même
     raison que .gate-bonus-card-inner ci-dessus (span inline par défaut). */
  display: block;
  margin: 0;
  text-align: center;
  font-family: "Manrope", sans-serif;
  font-size: 3cqw;
  color: var(--text-dim);
}

/* Message d'erreur (panne réseau / pseudo déjà pris entre-temps) : rare,
   aucune place dédiée dans le mockup — posé simplement sous la carte,
   au-dessus de la mention légale. */
.wizard-step3-error {
  margin: 10px auto 0;
  max-width: 420px;
  text-align: center;
}

.wizard-step3-legal {
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
  line-height: 1.5;
  margin: 10px auto 0;
  max-width: 320px;
}
.wizard-step3-legal a { color: var(--text-dim); text-decoration: underline; }
.wizard-step3-legal a:hover { color: var(--text); }
/* .wizard-step3-back-link supprimée (étape 5) : le lien retour de l'étape 3
   utilise désormais .mini-link comme les autres écrans non-bakés. */

/* Lien retour accueil sous la carte reveal — même technique que .mini-link
   (rien de bakée à cet endroit dans reveal-bg.png).
   Un peu plus visible que le lien retour du wizard (c'est une action
   principale ici, pas un simple rattrapage d'erreur), largeur bornée comme
   la carte pour rester centré sur mobile. */
.reveal-home-link {
  display: block;
  margin: 14px auto 0;
  max-width: min(100%, 100dvh * 1080 / 1920);
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: "Manrope", sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 10px 20px;
}
.reveal-home-link:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.5); }

/* Hook — écran de démarrage, même technique que la carte de résultat :
   fond image avec une hitbox invisible sur chaque bouton bakée dans l'art.
   v2 (canevas 1080×1920, même refonte 9:16 que .quiz-card) : nouveau
   visuel complet par Sav (crown/logo, "LE QUIZ ULTIME DES VRAIS STAN",
   panneau 3 stats, 2 boutons), sans cadre extérieur. Coordonnées
   remesurées au pixel sur ce nouveau fichier. */
.hook-card {
  container-type: inline-size;
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/hook-bg.png");
  background-size: 100% 100%;
}
/* Les 2 boutons ("COMMENCER LE QUIZ" et "VOIR LE CLASSEMENT") sont bakés
   dans hook-bg.png ; .start-hit/.leaderboard-hit ne sont que des hitbox
   invisibles cliquables par-dessus, mesurées au pixel sur le nouveau
   fichier. L'ancien effet "grow" au survol (chaque bouton avait sa propre
   copie en image via background-image, cf. assets/btn-start.png et
   btn-leaderboard.png) est abandonné ici : ces 2 assets correspondaient au
   style de l'ancien bouton et ne matchent plus ce nouveau design — un
   simple scale() sur la hitbox transparente suffit pour l'effet de survol
   sans avoir besoin de nouveaux fichiers dédiés.
   Recalées le 08/09 (retour Sav : boutons agrandis + repositionnés dans le
   même visuel — mesuré par analyse pixel du nouveau hook-bg.png, seules ces
   2 zones diffèrent de l'ancien fichier, confirmé par diff d'image avant de
   toucher au code). */
.start-hit {
  left: 12.04%; top: 71.88%; width: 75.74%; height: 8.28%;
  border-radius: 999px;
  cursor: pointer;
  transition: transform 0.2s ease, filter 0.2s ease;
}
.leaderboard-hit {
  left: 19.35%; top: 84.53%; width: 61.11%; height: 4.43%;
  border-radius: 999px;
  cursor: pointer;
  transition: transform 0.2s ease, filter 0.2s ease;
}
/* 3e bouton, ajouté par Sav le 15/09 dans le même fichier hook-bg.png (voir
   commentaire dans index.html) — mesuré au pixel comme les 2 autres. */
.all-quizzes-hit {
  left: 19.35%; top: 90.89%; width: 61.11%; height: 4.22%;
  border-radius: 999px;
  cursor: pointer;
  transition: transform 0.2s ease, filter 0.2s ease;
}
.start-hit:hover, .leaderboard-hit:hover, .all-quizzes-hit:hover {
  transform: scale(1.04);
  filter: brightness(1.08);
}
.start-hit:active, .leaderboard-hit:active, .all-quizzes-hit:active {
  transform: scale(0.98);
}

/* Tous les quiz — écran de liste (15/09, retour Sav), même technique que
   .hook-card : fond baké (assets/all-quizzes-bg.png), une seule carte
   interactive pour l'instant (KPOP IQ). Les 5 autres ("BIENTÔT") sont
   entièrement dans l'image, sans hitbox — rien à cliquer tant qu'elles ne
   sont pas livrées. */
.all-quizzes-card {
  container-type: inline-size;
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/all-quizzes-bg.png");
  background-size: 100% 100%;
}
.all-quizzes-play-hit {
  left: 10.00%; top: 40.31%; width: 35.37%; height: 3.02%;
  border-radius: 999px;
  cursor: pointer;
  transition: transform 0.2s ease, filter 0.2s ease;
}
.all-quizzes-play-hit:hover { transform: scale(1.04); filter: brightness(1.08); }
.all-quizzes-play-hit:active { transform: scale(0.98); }

/* Lien retour sous la carte — même technique que .reveal-home-link (rien de
   bakée à cet endroit dans all-quizzes-bg.png). */
.all-quizzes-home-link {
  display: block;
  margin: 14px auto 0;
  max-width: min(100%, 100dvh * 1080 / 1920);
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: "Manrope", sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 10px 20px;
}
.all-quizzes-home-link:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.5); }

/* Reveal — carte cosmique finale : fond image (assets/reveal-bg.png) avec les
   champs dynamiques posés en overlay par-dessus, aux coordonnées calibrées
   au pixel près. v2 (canevas 1080×1920, même refonte 9:16 que
   quiz/hook/step2/step3) : nouveau visuel par Sav, anneau néon (score) +
   couronne + guirlande de lauriers en haut, encart hexagonal pour le
   niveau juste dessous, "TU BATS x% DES FANS" + barre percentile, 4 tuiles
   stat (RANG/VITESSE/PRÉCISION/SÉRIE) avec icône et rail bakés, panneau
   "TON PROFIL K-POP" (4 lignes, rail vide baké — seuls le remplissage et le
   "%" sont posés ici), et 2 boutons pleins "PARTAGER"/"CLASSEMENT" en bas
   (au lieu des 5 icônes + lien + téléchargement de la v1 — même flux
   technique unique derrière, voir shareCard() dans app.js). Coordonnées
   remesurées au pixel sur ce nouveau fichier (fond non uni, détection par
   scan de la couleur/luminosité des traits néon plutôt que seuil global).
   Le bandeau de niveau reste animé image par image (wordmarks PNG), même
   logique que la v1 — voir playNiveauClimb() dans app.js et le commentaire
   sur .niveau plus bas. */
.already-note { color: var(--text-dim); font-size: 13px; text-align: center; margin-bottom: 8px; }
.hidden { display: none !important; }

.reveal-card {
  container-type: inline-size;
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/reveal-bg.png");
  background-size: 100% 100%;
  /* Rejoue à chaque fois que l'écran passe de display:none à visible
     (donc à chaque déblocage), pas besoin de le déclencher en JS. */
  animation: reveal-card-in 0.7s cubic-bezier(0.22, 0.85, 0.28, 1) both;
}
@keyframes reveal-card-in {
  from { opacity: 0; transform: scale(0.92) translateY(14px); filter: blur(8px); }
  to { opacity: 1; transform: scale(1) translateY(0); filter: blur(0); }
}

/* Écran de transition "suspense" entre le formulaire gate et le reveal —
   refonte du 06/09 (brief Sav) : mini-séquence en 4 temps ("ANALYSE DU
   KPOP IQ DE {PSEUDO}" -> 3 barres de "catégories" qui se remplient l'une
   après l'autre -> "RANG FINAL — CALCUL EN COURS..." -> message final
   personnalisé), minutage exact et enchaînement joués en JS par
   playUnlockSequence() (voir app.js). Purement cosmétique : le calcul réel
   (fetch /api/quiz/submit) tourne en parallèle dès la soumission du
   formulaire et n'est attendu qu'à la toute fin — si le réseau est plus
   lent que l'animation, on reste sur "CALCUL EN COURS..." le temps qu'il
   faut. Même palette/typos que le reste de l'app (pas de nouvelle DA) :
   Baloo 2 pour le pseudo (même famille que .pseudo du reveal), Orbitron
   pour les valeurs "display", dégradé rose/violet/bleu commun à toute
   l'appli pour les remplissages et le glow final.
   container-type: inline-size posé ici (comme les autres cartes) pour que
   les cqw des tailles de police restent proportionnelles à la largeur du
   panneau plutôt qu'au viewport — cf. fitUnlockPseudo() dans app.js pour
   le pseudo, qui peut aller jusqu'à 24 caractères (maxlength input-pseudo)
   et ne doit jamais déborder de son conteneur sur mobile. */
.unlock-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 55vh;
  width: min(100%, 420px);
  margin: 0 auto;
  padding: 28px 22px;
  gap: 16px;
  text-align: center;
  container-type: inline-size;
  overflow: hidden;
}

/* Ligne de scan holographique qui balaie le panneau en continu — pur
   habillage décoratif (cosmos/holographique), cohérent avec le reste de
   la DA, indépendant du minutage de la séquence. */
.unlock-scan {
  position: absolute;
  left: 4%; right: 4%; top: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.85), transparent);
  box-shadow: 0 0 12px 2px rgba(155, 92, 255, 0.65);
  opacity: 0;
  animation: unlock-scan-sweep 2.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes unlock-scan-sweep {
  0% { top: 0%; opacity: 0; }
  8% { opacity: 1; }
  92% { opacity: 1; }
  100% { top: 100%; opacity: 0; }
}

/* Logo "KPOP IQ" en tête de panneau (retour Sav 08/09, mockup fourni pour
   cet écran) — diviseur + couronne + wordmark dégradé, purement décoratif
   (aucune donnée dynamique), même vocabulaire que .mini-sparkle/.rules-line
   déjà utilisés ailleurs. */
.unlock-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-bottom: -4px;
}
.unlock-logo-line {
  flex: 1 1 60px;
  max-width: 90px;
  height: 1.5px;
  background: linear-gradient(90deg, transparent, rgba(255, 79, 160, 0.6), rgba(91, 140, 255, 0.6), transparent);
}
.unlock-logo-crown {
  flex: none;
  width: 5.2cqw;
  min-width: 20px;
  max-width: 26px;
  height: 5.2cqw;
  min-height: 20px;
  max-height: 26px;
  fill: var(--pink);
  filter: drop-shadow(0 0 6px rgba(255, 79, 160, 0.8));
}
.unlock-logo-title {
  margin: 0;
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 6.4cqw;
  letter-spacing: 0.02em;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 10px rgba(155, 92, 255, 0.45));
}
.unlock-logo-title-accent { color: inherit; }

.unlock-eyebrow {
  margin: 0;
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 3.4cqw;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.5s ease, transform 0.5s ease;
}
/* Encadré lumineux autour du pseudo (mockup : rectangle à bordure/glow,
   plutôt que le texte "flottant" d'origine). fitUnlockPseudo() (app.js)
   continue de mesurer #unlock-pseudo lui-même (scrollWidth/clientWidth) :
   le cqw reste basé sur .unlock-panel (container-type posé là, pas ici),
   donc le calcul de taille n'est pas affecté par cet encadré. */
.unlock-pseudo-box {
  display: inline-flex;
  max-width: 100%;
  padding: 8px 22px;
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  border-radius: 12px;
  background: rgba(155, 92, 255, 0.06);
  box-shadow: 0 0 22px rgba(155, 92, 255, 0.3), inset 0 0 14px rgba(255, 255, 255, 0.04);
}
.unlock-pseudo {
  margin: 0;
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 9cqw;
  line-height: 1.25;
  max-width: 100%;
  color: #ffffff;
  text-shadow:
    0 0 1cqw rgba(255, 255, 255, 0.5),
    0 0 3cqw rgba(255, 79, 160, 0.6),
    0 0 5cqw rgba(155, 92, 255, 0.4);
  white-space: nowrap;
  overflow: hidden;
  opacity: 0;
  transform: scale(0.85);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.unlock-panel.is-in .unlock-eyebrow { opacity: 1; transform: translateY(0); }
.unlock-panel.is-in .unlock-pseudo {
  opacity: 1;
  transform: scale(1);
  animation: unlock-pseudo-pulse 1.8s ease-in-out 0.55s 1;
}
@keyframes unlock-pseudo-pulse {
  0%, 100% {
    text-shadow:
      0 0 1cqw rgba(255, 255, 255, 0.5),
      0 0 3cqw rgba(255, 79, 160, 0.6),
      0 0 5cqw rgba(155, 92, 255, 0.4);
  }
  50% {
    text-shadow:
      0 0 1.5cqw rgba(255, 255, 255, 0.85),
      0 0 4.5cqw rgba(255, 79, 160, 0.9),
      0 0 7cqw rgba(155, 92, 255, 0.65);
  }
}

.unlock-steps {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 6px;
}
/* Rangée icône + (label/barre) + coche — remplace l'ancienne disposition en
   colonne (label au-dessus de la barre) par la rangée du mockup (icône
   ronde à gauche, coche ronde à droite). */
.unlock-step {
  display: flex;
  align-items: center;
  gap: 10px;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.unlock-step.is-in { opacity: 1; transform: translateY(0); }
.unlock-step-icon-wrap {
  flex: none;
  width: 9cqw;
  min-width: 32px;
  max-width: 40px;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1.5px solid rgba(155, 92, 255, 0.55);
  background: rgba(155, 92, 255, 0.1);
  box-shadow: 0 0 10px rgba(155, 92, 255, 0.35);
}
.unlock-step-icon {
  width: 46%;
  height: 46%;
  fill: none;
  stroke: var(--purple);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.unlock-step-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.unlock-step-label {
  text-align: left;
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 2.9cqw;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.unlock-step-track {
  position: relative;
  width: 100%;
  height: 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
.unlock-step-fill {
  position: absolute; inset: 0;
  width: 0%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 8px rgba(155, 92, 255, 0.65);
  transition: width 0.4s ease;
}
.unlock-step.is-filled .unlock-step-fill { width: 100%; }
.unlock-step-check {
  flex: none;
  width: 7cqw;
  min-width: 26px;
  max-width: 32px;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  font-size: 3.4cqw;
  font-weight: 800;
  line-height: 1;
  color: #ffffff;
  background: rgba(255, 79, 160, 0.16);
  border: 1.5px solid rgba(255, 79, 160, 0.6);
  box-shadow: 0 0 10px rgba(255, 79, 160, 0.5);
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.unlock-step.is-checked .unlock-step-check { opacity: 1; transform: scale(1); }

/* Diviseur avant "Rang final" (mockup) — mêmes traits dégradés + sparkle
   que .rules-line/.mini-sparkle--title, réutilisés tels quels. */
.unlock-divider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 2px;
}

.unlock-final {
  width: 100%;
  margin-top: 0;
  padding-top: 0;
  /* Le trait de séparation vient maintenant de .unlock-divider (juste
     au-dessus dans le HTML, sparkle inclus) plutôt que d'une bordure ici. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.unlock-final.is-in { opacity: 1; transform: translateY(0); }
.unlock-final-label {
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 2.9cqw;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: opacity 0.3s ease;
}
.unlock-final-value {
  min-height: 2.6em;
  line-height: 1.3;
  font-family: "Orbitron", sans-serif;
  font-weight: 700;
  font-size: 3.6cqw;
  color: #ffffff;
}
/* Points clignotants de "Calcul en cours..." — même langage que les autres
   micro-animations de l'appli (rien de nouveau à charger). */
.unlock-dots span {
  animation: unlock-dot 1.2s infinite;
  opacity: 0;
}
.unlock-dots span:nth-child(2) { animation-delay: 0.2s; }
.unlock-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes unlock-dot {
  0%, 80%, 100% { opacity: 0; }
  40% { opacity: 1; }
}
/* Étape finale : le message générique "calcul en cours" est remplacé par
   "{PSEUDO}, TON RÉSULTAT EST PRÊT." (voir app.js) — bascule visuelle vers
   un texte plus grand, en dégradé, avec un petit flash (brief : "flash /
   glow / transition" avant l'ouverture de la fiche résultat). */
.unlock-final.is-ready .unlock-final-label { opacity: 0; }
.unlock-final.is-ready .unlock-final-value {
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 4.3cqw;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: unlock-ready-flash 0.6s ease both;
}
@keyframes unlock-ready-flash {
  0% { filter: brightness(1) drop-shadow(0 0 0 transparent); transform: scale(0.92); opacity: 0.3; }
  55% { filter: brightness(1.7) drop-shadow(0 0 1.2cqw rgba(255, 255, 255, 0.8)); }
  100% { filter: brightness(1) drop-shadow(0 0 0 transparent); transform: scale(1); opacity: 1; }
}

/* Phrase easter egg K-pop (liste UNLOCK_EASTER_LINES dans app.js), tirée au
   hasard à chaque partie — volontairement secondaire (petite, discrète,
   en italique) pour ne pas concurrencer le pseudo ni la progression. */
.unlock-easter {
  margin: 2px 0 0;
  min-height: 1.4em;
  font-family: "Manrope", sans-serif;
  font-weight: 500;
  font-style: italic;
  font-size: 2.5cqw;
  color: var(--text-dim);
  opacity: 0;
  transition: opacity 0.6s ease 0.15s;
}
.unlock-panel.is-in .unlock-easter { opacity: 0.85; }

/* ==== Écrans "non-bakés" (étape 5 du plan Projet Claude) ====
   screen-prequiz (email avant le quiz), screen-cooldown (blocage 24h) et la
   3ᵉ étape du wizard post-quiz (confirmation email + opt-in newsletter,
   remplace l'ancienne étape image wizard-step3-bg.png) — trois écrans sans
   image de fond dédiée, ajoutés après coup, aucun mockup fourni pour eux.
   Repris du même langage visuel que .unlock-panel plutôt qu'une nouvelle DA :
   container-type: inline-size (cqw proportionnels à la largeur du panneau,
   pas au viewport, comme partout ailleurs dans l'appli), palette/typos
   communes (Manrope/Baloo 2, --pink/--purple/--blue), léger cadre "glass"
   repris de l'ancien .card (jamais utilisé ailleurs dans l'appli actuelle)
   pour rester lisible sans le support d'une image. */
.mini-panel {
  position: relative;
  width: min(100%, 420px);
  margin: 0 auto;
  padding: 30px 24px;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: var(--glass);
  backdrop-filter: blur(6px);
  text-align: center;
  container-type: inline-size;
  /* Même animation d'entrée que .reveal-card (déjà définie plus haut,
     réutilisée telle quelle) : rejoue à chaque passage à display:block. */
  animation: reveal-card-in 0.5s cubic-bezier(0.22, 0.85, 0.28, 1) both;
}

.mini-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-family: "Manrope", sans-serif;
  /* Polices des écrans .mini-panel agrandies sur toute la ligne (retour Sav,
     07/09) : trop petites comparées au reste de l'appli (questions du quiz,
     options...), qui utilise des tailles bien plus généreuses. */
  font-size: 4cqw;
  color: var(--pink);
  background: rgba(255, 79, 160, 0.1);
  border: 1px solid rgba(255, 79, 160, 0.55);
  font-weight: 800;
  padding: 5px 14px;
  border-radius: 999px;
  margin: 0 0 14px;
}
/* Cooldown : variante orangée ("attention"), même famille que --hard
   (déjà utilisé pour la difficulté "difficile" du quiz) plutôt qu'une
   nouvelle couleur — pas d'alarme rouge (--veryhard, réservée aux erreurs). */
.mini-eyebrow--warn {
  color: var(--hard);
  background: rgba(255, 159, 90, 0.12);
  border-color: rgba(255, 159, 90, 0.55);
}

.mini-title {
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 8.4cqw;
  line-height: 1.25;
  margin: 0 0 10px;
  color: #ffffff;
}

.mini-subtitle {
  font-family: "Manrope", sans-serif;
  font-weight: 500;
  font-size: 4.6cqw;
  line-height: 1.5;
  color: var(--text-dim);
  margin: 0 0 22px;
}
.mini-subtitle strong { color: #ffffff; font-weight: 800; }

/* Emphase couleur dans le sous-titre (retour Sav 08/09, distinct de
   .mini-subtitle strong déjà utilisé ailleurs pour une valeur neutre comme
   le compte à rebours du cooldown — ici l'accent est volontairement rose,
   façon "argument clé" plutôt que juste un gras blanc). */
.mini-highlight {
  color: var(--pink);
  font-weight: 800;
}

/* Icône "sparkle" (chemin étoile à 4 branches) réutilisée dans le badge et
   le titre — SVG inline plutôt qu'un emoji "✨"/"✦" pour un rendu net,
   identique sur tous les appareils/OS (un emoji dépend de la police système
   installée, Windows notamment). currentColor via fill hérite de la couleur
   CSS posée sur chaque usage. */
.mini-sparkle {
  flex: none;
  width: 13px;
  height: 13px;
  fill: var(--pink);
  filter: drop-shadow(0 0 4px rgba(255, 79, 160, 0.8));
}
.mini-sparkle--title {
  width: 18px;
  height: 18px;
  fill: #ffffff;
  filter: drop-shadow(0 0 6px rgba(155, 92, 255, 0.7));
}

/* Glow néon du panneau/badge/titre (retour Sav 08/09, mockup fourni pour
   l'écran prequiz) — classes modificatrices ajoutées en plus des classes de
   base .mini-panel/.mini-eyebrow/.mini-title, qui restent inchangées pour
   les autres écrans (règles, cooldown, wizard étape 3). */
.mini-panel--glow {
  box-shadow: 0 0 50px rgba(155, 92, 255, 0.18);
}
.mini-eyebrow--glow {
  box-shadow: 0 0 18px rgba(255, 79, 160, 0.4), inset 0 0 12px rgba(255, 79, 160, 0.08);
}
.mini-title--glow {
  text-transform: uppercase;
  letter-spacing: 0.01em;
  /* Dégradé blanc -> bleu pâle "clippé" au texte (façon verre/chrome) +
     glow en drop-shadow (text-shadow ne fonctionne pas sur un texte rendu
     transparent par background-clip). */
  background: linear-gradient(180deg, #ffffff 0%, #ffffff 55%, #a9c3ff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 10px rgba(155, 92, 255, 0.55)) drop-shadow(0 0 22px rgba(255, 79, 160, 0.3));
}
.mini-title-line2 {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

input.mini-input {
  width: 100%;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1.5px solid var(--border);
  background: rgba(255, 255, 255, 0.04);
  color: #ffffff;
  font-family: "Manrope", sans-serif;
  /* Fixe (pas cqw) et >=16px délibérément : évite le zoom-auto-au-focus sur
     mobile, même filet de sécurité que le viewport meta (voir index.html)
     et déjà appliqué à input.wizard-step1-input en @media (l'étape 3 utilise
     ce champ .mini-input depuis la refonte du 15/09, plus de classe dédiée).
     Relevé à 18px (retour Sav 07/09, polices trop petites) : reste bien
     au-dessus du seuil de 16px qui déclenche le zoom automatique. */
  font-size: 18px;
  text-align: center;
  margin: 0 0 4px;
}
input.mini-input::placeholder { color: rgba(255, 255, 255, 0.35); }
input.mini-input:focus { outline: none; border-color: var(--pink); }

/* Champ email à bordure dégradée + icône (retour Sav 08/09, écran prequiz) :
   dégradé posé en fond du wrapper, "percé" par le padding pour ne laisser
   dépasser qu'un liseré de 2px — plus fiable qu'un border-image pour un
   rayon arrondi. Optionnel : un .mini-input sans ce wrapper garde son
   apparence normale (bordure unie), donc n'affecte pas les autres écrans. */
.mini-input-wrap {
  position: relative;
  border-radius: 14px;
  padding: 2px;
  background: linear-gradient(120deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 20px rgba(155, 92, 255, 0.35);
  margin-bottom: 4px;
}
.mini-input-wrap input.mini-input {
  margin: 0 0 0 0;
  border: none;
  border-radius: 12px;
  background: #0c0a1e;
  padding-left: 48px;
}
.mini-input-icon {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  fill: none;
  stroke: var(--pink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
  z-index: 1;
}

.mini-btn-primary {
  width: 100%;
  padding: 15px 20px;
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  border-radius: 12px;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 0 1px rgba(255, 79, 160, 0.25), 0 6px 24px rgba(155, 92, 255, 0.45);
  color: #ffffff;
  font-family: "Manrope", sans-serif;
  font-size: 4.6cqw;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-weight: 800;
  cursor: pointer;
  margin-top: 14px;
  transition: transform 0.15s ease, filter 0.15s ease;
  /* flex plutôt que le centrage par défaut du bouton : accueille les icônes
     optionnelles (.mini-btn-icon, retour Sav 08/09) de part et d'autre du
     texte sans rien changer visuellement sur les boutons qui n'en ont pas. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.mini-btn-primary:hover { filter: brightness(1.08); }
.mini-btn-primary:active { transform: scale(0.98); filter: brightness(0.92); }
.mini-btn-primary:disabled { opacity: 0.5; cursor: default; }
.mini-btn-icon {
  flex: none;
  width: 18px;
  height: 18px;
  fill: #ffffff;
}
.mini-btn-icon--arrow {
  fill: none;
  stroke: #ffffff;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mini-btn-secondary {
  width: 100%;
  padding: 13px 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  font-family: "Manrope", sans-serif;
  font-size: 4.6cqw;
  font-weight: 700;
  cursor: pointer;
  margin-top: 10px;
}

.mini-link {
  display: block;
  width: 100%;
  text-align: center;
  background: none;
  border: none;
  color: var(--text-dim);
  font-family: "Manrope", sans-serif;
  font-size: 15px;
  margin-top: 16px;
  cursor: pointer;
  text-decoration: underline;
}
.mini-link:hover { color: var(--text); }

.mini-actions { display: flex; flex-direction: column; }

.mini-disclosure {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.mini-disclosure p {
  font-family: "Manrope", sans-serif;
  font-size: 4.4cqw;
  color: var(--text-dim);
  line-height: 1.5;
  margin: 0 0 12px;
}
.mini-disclosure strong { color: #ffffff; }

.mini-readonly-email {
  width: 100%;
  padding: 12px 16px;
  border-radius: 12px;
  border: 1.5px dashed var(--border);
  background: rgba(255, 255, 255, 0.03);
  color: #ffffff;
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  font-size: 4.4cqw;
  word-break: break-word;
  margin: 0 0 20px;
}

/* Case à cocher opt-in newsletter (étape 3 du wizard post-quiz) : vrai
   <input type="checkbox"> visible + libellé cliquable (via <label for>),
   plus de piste bakée à caler au pixel comme dans l'ancienne étape 3 image
   (cf. historique de wizard-step3-consent-hit ci-dessus dans ce fichier). */
.mini-consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  margin: 0 0 4px;
  cursor: pointer;
}
.mini-consent input[type="checkbox"] {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--pink);
  cursor: pointer;
}
.mini-consent span {
  font-family: "Manrope", sans-serif;
  font-size: 4cqw;
  color: var(--text-dim);
  line-height: 1.4;
}

/* Écran règles (retour Sav, 07/09, remaquetté au plus proche du 2ᵉ mockup
   fourni le 08/09) — badge/titre néon (mêmes classes que l'écran prequiz,
   voir plus haut) + 3 cartes de règles à bordure/icône colorée, puis
   décompte 5→0. Voir #screen-rules dans index.html et
   playCountdownThenStart() dans app.js. */

/* Titre en 2 lignes : "Les règles" (dégradé rose->violet->bleu) puis
   "du jeu" flanqué de sparkles + petits traits dégradés (comme le mockup),
   dans le même blanc->bleu pâle que .mini-title--glow (prequiz) sans
   dépendre de cette classe pour rester indépendant écran par écran. */
.rules-title { text-transform: uppercase; letter-spacing: 0.01em; }
.rules-title-line1 {
  display: block;
  background: linear-gradient(90deg, var(--pink), var(--purple) 55%, var(--blue));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 12px rgba(255, 79, 160, 0.45)) drop-shadow(0 0 20px rgba(91, 140, 255, 0.35));
}
.rules-title-line2-text {
  background: linear-gradient(180deg, #ffffff 0%, #ffffff 55%, #a9c3ff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 10px rgba(155, 92, 255, 0.55));
}
.rules-line {
  flex: 1 1 32px;
  max-width: 40px;
  height: 1.5px;
}
.rules-line--left { background: linear-gradient(90deg, transparent, rgba(255, 79, 160, 0.7)); }
.rules-line--right { background: linear-gradient(90deg, rgba(91, 140, 255, 0.7), transparent); }

.rules-kicker {
  font-family: "Manrope", sans-serif;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-size: 3cqw;
  color: var(--text-dim);
  text-align: center;
  margin: 0 0 22px;
}

.rules-cards {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0 0 20px;
}
.rules-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.02);
  text-align: left;
  overflow: hidden;
}
.rules-card--pink   { border: 1.5px solid rgba(255, 79, 160, 0.55); box-shadow: 0 0 16px rgba(255, 79, 160, 0.22); }
.rules-card--purple { border: 1.5px solid rgba(155, 92, 255, 0.55); box-shadow: 0 0 16px rgba(155, 92, 255, 0.22); }
.rules-card--blue   { border: 1.5px solid rgba(91, 140, 255, 0.55); box-shadow: 0 0 16px rgba(91, 140, 255, 0.22); }
/* Tache lumineuse décorative en coin bas-droit de chaque carte, comme le
   mockup — purement esthétique, sous le reste du contenu (z-index natif de
   l'ordre du DOM, la carte n'a pas de position autre que ses enfants). */
.rules-card::after {
  content: "";
  position: absolute;
  right: -22px;
  bottom: -22px;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  pointer-events: none;
}
.rules-card--pink::after   { background: radial-gradient(circle, rgba(255, 79, 160, 0.22), transparent 70%); }
.rules-card--purple::after { background: radial-gradient(circle, rgba(155, 92, 255, 0.22), transparent 70%); }
.rules-card--blue::after   { background: radial-gradient(circle, rgba(91, 140, 255, 0.22), transparent 70%); }

.rules-card-sparkle {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 13px;
  height: 13px;
}
.rules-card--pink .rules-card-sparkle   { fill: var(--pink); filter: drop-shadow(0 0 4px rgba(255, 79, 160, 0.8)); }
.rules-card--purple .rules-card-sparkle { fill: var(--purple); filter: drop-shadow(0 0 4px rgba(155, 92, 255, 0.8)); }
.rules-card--blue .rules-card-sparkle   { fill: var(--blue); filter: drop-shadow(0 0 4px rgba(91, 140, 255, 0.8)); }

.rules-card-icon-wrap {
  position: relative;
  flex: none;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: 2px solid;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rules-card--pink .rules-card-icon-wrap   { border-color: var(--pink); box-shadow: 0 0 14px rgba(255, 79, 160, 0.45); color: var(--pink); }
.rules-card--purple .rules-card-icon-wrap { border-color: var(--purple); box-shadow: 0 0 14px rgba(155, 92, 255, 0.45); color: var(--purple); }
.rules-card--blue .rules-card-icon-wrap   { border-color: var(--blue); box-shadow: 0 0 14px rgba(91, 140, 255, 0.45); color: var(--blue); }
.rules-card-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.rules-card-icon--bars, .rules-card-icon--bolt { fill: currentColor; stroke: none; }

.rules-card-num {
  position: absolute;
  left: -6px;
  bottom: -6px;
  min-width: 20px;
  height: 20px;
  padding: 0 4px;
  border-radius: 6px;
  background: #0c0a1e;
  border: 1.5px solid;
  color: #ffffff;
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rules-card--pink .rules-card-num   { border-color: var(--pink); }
.rules-card--purple .rules-card-num { border-color: var(--purple); }
.rules-card--blue .rules-card-num   { border-color: var(--blue); }

.rules-card-divider {
  flex: none;
  width: 1px;
  align-self: stretch;
  background: rgba(255, 255, 255, 0.14);
}

.rules-card-body { flex: 1; min-width: 0; }
.rules-card-title {
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: 5cqw;
  margin: 0 0 3px;
  color: #ffffff;
}
.rules-card--pink .rules-card-title   { background: linear-gradient(90deg, #ffffff, #ffc2e0); -webkit-background-clip: text; background-clip: text; color: transparent; }
.rules-card--purple .rules-card-title { background: linear-gradient(90deg, #ffffff, #d5c2ff); -webkit-background-clip: text; background-clip: text; color: transparent; }
.rules-card--blue .rules-card-title   { background: linear-gradient(90deg, #ffffff, #c2d3ff); -webkit-background-clip: text; background-clip: text; color: transparent; }
.rules-card-desc {
  font-family: "Manrope", sans-serif;
  font-size: 3.6cqw;
  color: var(--text-dim);
  line-height: 1.4;
  margin: 0;
}

/* Décompte 5→0 avant la 1ère question — retour Sav 08/09 : image mockup
   utilisée TELLE QUELLE (badge/titre/anneau néon bakés), même technique que
   .hook-card/.quiz-card (fond image + overlay HTML). Seul le chiffre est un
   vrai élément, posé dans l'anneau. Coordonnées de l'anneau mesurées par
   analyse de luminosité sur assets/rules-countdown-bg.png (941×1672) :
   centre de l'anneau ≈ (467px, 979px), rayon intérieur ≈ 199px →
   left/top/width/height ci-dessous. */
.rules-countdown-card {
  container-type: inline-size;
  position: relative;
  width: 100%;
  aspect-ratio: 941 / 1672;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 18px;
  background-image: url("assets/rules-countdown-bg.png");
  background-size: 100% 100%;
}
/* Boîte carrée inscrite dans l'anneau (centrée dessus), flex pour centrer le
   chiffre sans transform:translate — évite tout conflit avec le
   transform:scale() de l'animation "pop" (voir plus bas). */
.rules-countdown-ring {
  position: absolute;
  left: 28.48%; top: 46.65%;
  width: 42.3%; height: 23.8%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.rules-countdown-number {
  font-family: "Orbitron", sans-serif;
  font-weight: 900;
  font-size: 26cqw;
  line-height: 1;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 0 18px rgba(155, 92, 255, 0.5));
}
.rules-countdown-number.pop {
  animation: rules-countdown-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes rules-countdown-pop {
  0% { transform: scale(0.5); opacity: 0.3; }
  60% { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* Popup entre paliers de difficulté (retour Sav 09/09 : un écran plein
   "casse trop la dynamique" -> remplacée par une popup flottante PAR-DESSUS
   le quiz, qui reste visible/assombri dessous, plutôt qu'un écran qui le
   remplace complètement. Hors du flux .screen/showScreen() exprès (voir
   #tier-transition-overlay dans index.html) : #screen-quiz garde .active
   pendant tout l'affichage. 3 images bakées par Sav (carte bordée néon +
   croix déco intégrée à l'image, fond transparent hors du cadre) posées en
   <img> (pas en background-image, pour que le navigateur respecte le ratio
   intrinsèque de chaque image sans avoir à le mesurer/hardcoder par
   palier). Seule partie vivante : la barre de progression 5s, placée dans
   l'espace vide repéré sous la ligne FACILE/NORMAL/DIFFICILE/TRÈS DIFFICILE
   (mesuré par analyse de luminosité, quasi identique sur les 3 images). */
.tier-transition-overlay {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(2, 1, 16, 0.75);
  opacity: 0;
  transition: opacity 0.35s ease;
}
.tier-transition-overlay.showing { display: flex; }
.tier-transition-overlay.visible { opacity: 1; }

.tier-transition-card {
  position: relative;
  width: min(100%, 380px);
  transform: scale(0.92);
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.tier-transition-overlay.visible .tier-transition-card {
  transform: scale(1);
}
.tier-transition-img {
  display: block;
  width: 100%;
  height: auto;
}
.tier-transition-progress-track {
  position: absolute;
  left: 12.3%; top: 89.3%;
  width: 75.4%; height: 1.4%;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  overflow: hidden;
}
.tier-transition-progress-fill {
  width: 0%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
}
/* Chaque palier reprend la teinte dominante de son image plutôt que le
   dégradé générique ci-dessus (facile→normal). */
.tier-transition-card[data-tier="medium-to-hard"] .tier-transition-progress-fill {
  background: linear-gradient(90deg, var(--purple), var(--pink));
}
.tier-transition-card[data-tier="hard-to-veryhard"] .tier-transition-progress-fill {
  background: linear-gradient(90deg, var(--medium), var(--hard), var(--pink));
}

.ov { position: absolute; color: #fff; text-align: center; }

/* Score — centré par flexbox dans la boîte inscrite au cercle (immunisé
   contre les décalages de line-height), police display, blanc + glow
   multicolore (rose/violet/bleu) derrière, pas de remplissage dégradé.
   Boîte = zone sûre à l'intérieur de l'anneau (centre mesuré à 540/470px,
   rayon ~175px sur 1080×1920 ; boîte réduite pour rester sous le rayon en
   diagonale, donc bien à l'intérieur du trait néon). */
.score-box {
  left: 37.04%; top: 19.53%; width: 25.93%; height: 9.9%;
  display: flex; align-items: center; justify-content: center;
}
.score-box .num {
  font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 15.5cqw; line-height: 1;
  color: #ffffff;
  text-shadow:
    0 0 6cqw rgba(255, 79, 160, 0.85),
    0 0 11cqw rgba(155, 92, 255, 0.75),
    0 0 16cqw rgba(91, 140, 255, 0.6);
}
/* Petit "pop" joué une fois que le compteur a atteint sa valeur finale : il
   grossit puis se consolide (retombe) sur sa position — déclenché en JS en
   ajoutant cette classe (voir animateScoreCountUp() dans app.js). */
.num.is-landing { animation: score-land 0.5s cubic-bezier(0.32, 1.7, 0.4, 1) both; }
@keyframes score-land {
  0% { transform: scale(1); }
  45% { transform: scale(1.22); }
  100% { transform: scale(1); }
}

/* Pseudo — posé dans la bande vide entre le header et le haut du cercle
   (aucun encart baké à cet endroit sur le nouveau fond, comme sur la v1). */
.pseudo {
  left: 50%; top: 11%; width: 88%; transform: translate(-50%, -50%);
  font-family: "Baloo 2", sans-serif; font-weight: 800; font-size: 4.6cqw;
  letter-spacing: 0.02em; color: #ffffff;
  text-shadow: 0 0 1cqw rgba(255, 255, 255, 0.5), 0 0 3cqw rgba(255, 79, 160, 0.55), 0 0 5cqw rgba(155, 92, 255, 0.35);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Titre de niveau — encart hexagonal sous la couronne (mesuré : centre à
   540/752px, largeur utile ~600px, hauteur ~146px sur 1080×1920), ratio
   ~4,1:1. Toujours les wordmarks PNG recadrées (assets/niveau-*-band.png,
   ~3:1) en <img class="niveau-step">, object-fit:contain les cale par la
   hauteur (le nouvel encart étant plus "large" que ce ratio 3:1, comme
   l'ancien) — même montée palier par palier, voir playNiveauClimb() dans
   app.js. */
.niveau {
  left: 23.15%; top: 39.17%; width: 55.56%; height: 7.6%;
  transform: translateY(-50%);
  overflow: hidden;
  opacity: 0;
  transition: opacity 0.6s ease, transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.niveau.is-in { opacity: 1; }

.niveau-reel { position: absolute; inset: 0; }

/* Une <img> par palier traversé, créée puis détruite au fil de la montée
   (voir playNiveauClimb()). Position/opacité de repos posées ici ; les
   états "hors-champ" (avant l'entrée / après la sortie) sont sur les
   classes .niveau-step-off-right / -off-left ci-dessous — une simple
   transition CSS gère le mouvement, pas de calcul par frame. */
.niveau-step {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain;
  transform: translateX(0) scale(1);
  opacity: 1;
  transition: transform 0.22s cubic-bezier(0.22, 1.15, 0.36, 1), opacity 0.22s ease;
}
.niveau-step-off-right { transform: translateX(55%) scale(0.55); opacity: 0; }
.niveau-step-off-left { transform: translateX(-55%) scale(0.55); opacity: 0; }

/* Petit "clunk" de consolidation une fois arrivé sur le vrai palier — même
   langage que .num.is-landing (score) pour rester cohérent. */
.niveau-step.is-landing { animation: niveau-land 0.45s cubic-bezier(0.32, 1.7, 0.4, 1) both; }
@keyframes niveau-land {
  0% { transform: translateX(0) scale(1); }
  45% { transform: translateX(0) scale(1.12); }
  100% { transform: translateX(0) scale(1); }
}

/* % de victoire — dans le blanc entre "TU BATS" et "DES FANS", texte en
   dégradé rose→violet, cohérent avec le glow du score. */
.pct {
  left: 40.09%; top: 46.35%; width: 15.74%; height: 3.13%;
  display: flex; align-items: center; justify-content: center;
  font-family: "Baloo 2", sans-serif; font-weight: 800; font-size: 4cqw;
  background: linear-gradient(90deg, #ff4fa0, #c46bff);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.pct.is-in { opacity: 1; }

/* Barre — calée précisément à l'intérieur du contour blanc creux du fond. */
.bar-track { left: 13.33%; top: 50.68%; width: 72.87%; height: 1.67%; }
.bar-fill {
  position: relative;
  top: 50%; left: 0.5%; transform: translateY(-50%);
  height: 55%; width: 0%; max-width: 99%; border-radius: 999px;
  background: linear-gradient(90deg, #ff4fa0, #9b5cff, #5b8cff);
  box-shadow: 0 0 3px rgba(155, 92, 255, 0.55);
  transition: width 0.9s ease;
}
.bar-marker {
  position: absolute; right: 0; top: 50%; transform: translate(50%, -50%);
  width: 3.4cqw; height: 3.4cqw; border-radius: 50%;
  background: #ffffff;
  box-shadow:
    0 0 0.7cqw rgba(255, 255, 255, 0.95),
    0 0 2.4cqw 0.5cqw rgba(255, 255, 255, 0.55),
    0 0 4cqw rgba(155, 92, 255, 0.6);
}

.stat-value {
  font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 5cqw;
  display: flex; align-items: center; justify-content: center; height: 100%;
  line-height: 1;
  transform: translateY(1.6cqw);
  opacity: 0;
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.stat-value.is-in { opacity: 1; transform: translateY(0); }
/* 4 tuiles pilules (label vertical + icône bakés, reste vide) — mesurées
   sur le nouveau fond : tuiles RANG/VITESSE haut à 1065px, hauteur 98px ;
   PRÉCISION/SÉRIE plus bas à 1194px, hauteur 97px ; colonne gauche
   99-505px, colonne droite 567-977px sur 1080×1920. Le texte de valeur va
   dans l'espace libre après l'icône (offset +161px par rapport au bord
   gauche de la tuile, largeur ~245px). */
.rang { left: 24.07%; top: 55.47%; width: 22.69%; height: 5.1%; }
.vitesse { left: 67.41%; top: 55.47%; width: 23.06%; height: 5.1%; }
.precision { left: 24.07%; top: 62.19%; width: 22.69%; height: 5.05%; }
.serie { left: 67.41%; top: 62.19%; width: 23.06%; height: 5.05%; }

/* "TON PROFIL K-POP" — 4 barres de maîtrise par thème (voir
   computeKpopProfile() dans server.js). Le libellé + l'icône de chaque
   ligne sont bakés dans le fond, mais contrairement à la v1 il n'y a plus
   de rail creux distinct : toute la ligne (encart hexagonal) est vide, donc
   le fill sert lui-même de rail visuel, et le "%" est posé à droite,
   au-dessus de la zone qui reste non remplie tant que --pct < 100. Colonne
   x commune aux 4 lignes (460-900px), seul le top change (mesuré : rails
   1400/1470/1543/1613px, hauteur ~65px, sur 1080×1920).
   Sav a signalé (07/09) que le fill mordait sur le libellé baké dans le
   nouveau fond 1080×1920 (ex : "IDOLS" recouvert) : les 280px de départ
   venaient d'un ancien repère (fond 1080×2340), jamais re-mesurés pour ce
   fond-ci. Re-mesuré : le texte le plus long ("MUSIC KNOWLEDGE") se
   termine vers x=424px, le rail (bordure chanfreinée bakée) va jusqu'à
   x≈940px — départ du fill repoussé à 460px (marge ~35px après le texte).
   Sav a signalé une 2e fois (06/09, score 88%) que le fill empiète sur le
   "%" : la largeur du fill n'était pas capée assez tôt et le "%" est posé
   DANS la zone du fill (occupée dès que pct dépasse ~77%, courant pour un
   bon score). Comme il n'y a pas de rail visible à respecter (juste un
   encart vide, cf. plus bas), le fill et le "%" sont désormais deux zones
   x strictement séparées et non plus superposées : fill 460-825px (365px,
   pct 0-100% s'affiche sur toute cette largeur, jamais tronqué), pct
   840-940px (aligné sur la fin du rail bakée), 15px d'écart entre les deux
   — plus aucun chevauchement possible quel que soit le score. */
.profile-row { position: absolute; left: 42.59%; width: 33.80%; height: 3.39%; }
.profile-row .profile-fill {
  position: absolute; top: 50%; left: 0.6%; transform: translateY(-50%);
  height: 60%; width: calc(var(--pct, 0) * 1%);
  max-width: 98.8%;
  border-radius: 999px;
  box-shadow: 0 0 0.5cqw rgba(155, 92, 255, 0.6);
  transition: width 0.9s ease;
}
/* Un dégradé distinct par barre, assorti à la couleur de l'icône de sa ligne. */
.profile-row-1 .profile-fill { background: linear-gradient(90deg, #ff4fa0, #9b5cff); }
.profile-row-2 .profile-fill { background: linear-gradient(90deg, #5b8cff, #4fd8ff); }
.profile-row-3 .profile-fill { background: linear-gradient(90deg, #ff4f6f, #ffb84f); }
.profile-row-4 .profile-fill { background: linear-gradient(90deg, #9b5cff, #ffd24f); }
.profile-pct {
  position: absolute; left: 77.78%; width: 9.26%; height: 3.39%;
  display: flex; align-items: center; justify-content: center;
  font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 1.9cqw;
  color: #fff;
  text-shadow: 0 0 0.5cqw rgba(0, 0, 0, 0.8);
}
.profile-row-1 { top: 72.92%; }
.profile-pct-1 { top: 72.92%; }
.profile-row-2 { top: 76.56%; }
.profile-pct-2 { top: 76.56%; }
.profile-row-3 { top: 80.36%; }
.profile-pct-3 { top: 80.36%; }
.profile-row-4 { top: 83.99%; }
.profile-pct-4 { top: 83.99%; }

/* background-color (pas le raccourci "background") pour ne pas écraser le
   background-image posé par .start-hit / .leaderboard-hit (effet grow). */
/* color: transparent en plus du fond transparent — certains .hit reçoivent
   un textContent live via JS (ex: submitBtn.textContent = "Calcul en
   cours…" dans le handler form-gate) alors que leur libellé est déjà baké
   dans l'image ; sans cette règle ce texte JS redeviendrait visible
   (couleur par défaut du navigateur) par-dessus l'image. */
.hit { position: absolute; background-color: transparent; color: transparent; border: none; cursor: pointer; padding: 0; }
/* v2 : le nouveau fond revient à 2 boutons pleins "PARTAGER"/"CLASSEMENT"
   (texte + icône bakés dans l'image, comme .start-hit/.leaderboard-hit sur
   le hook) au lieu des 5 icônes + lien + téléchargement de la v1 — mêmes
   flux techniques derrière (shareCard() gère toujours le partage natif
   avec repli téléchargement, voir app.js), juste regroupés visuellement.
   Mesurés au pixel : mêmes top/height pour les deux, hauteur bouton 58px
   sur 1080×1920. */
.share-hit, .lb-hit {
  border-radius: 999px;
  transition: transform 0.2s ease, filter 0.2s ease;
}
.share-hit:hover, .lb-hit:hover { transform: scale(1.03); filter: brightness(1.1); }
.share-hit:active, .lb-hit:active { transform: scale(0.97); }
.share-hit { left: 8.8%; top: 91.35%; width: 36.11%; height: 3.02%; }
.lb-hit    { left: 54.63%; top: 91.35%; width: 36.57%; height: 3.02%; }

/* ==== Écran 5 : classement ====
   Refonte complète du 28/09 (retour Sav : "code entièrement la page de
   classement, trop cassé malgré nos tentatives"). L'ancienne version
   posait le texte dynamique (pseudo/score/temps/pastille) en overlay %
   calé au pixel par-dessus une image de fond bakée (assets/leaderboard-bg.png,
   1080×1920) : chaque pseudo un peu plus long que prévu (ex.
   "riize_shotaro_83") redevenait un nouveau cas limite à mesurer au pixel
   sur le PNG (3 allers-retours le 27/09 pour la même classe de bug sur 3
   endroits différents — record, podium 2/3, podium 1). Le fond n'est plus
   utilisé du tout : toute la carte (header, titre, filtres, barre record,
   podium, tableau) est du vrai HTML/CSS, même langage visuel que
   .lb-my-position/.gate-bonus-card déjà "non-bakés" plus bas dans ce
   fichier (cadre néon en dégradé, container-type: inline-size + unités
   cqw) — chaque pseudo/score/temps est dans sa propre boîte flex/grid avec
   min-width:0 + text-overflow:ellipsis, qui s'adapte à n'importe quelle
   longueur de texte au lieu d'un budget de quelques pixels contre une
   image figée. .leaderboard-bg.png reste dans assets/ (plus référencé
   nulle part) au cas où, mais n'est plus utilisé. */
.leaderboard-card {
  position: relative;
  width: min(100%, 420px);
  margin: 0 auto;
  padding: 18px 14px 22px;
  border-radius: 20px;
  border: 1px solid var(--border);
  background:
    radial-gradient(ellipse 70% 35% at 20% 0%, rgba(155, 92, 255, 0.30), transparent 55%),
    radial-gradient(ellipse 60% 30% at 95% 100%, rgba(91, 140, 255, 0.22), transparent 55%),
    #0c0a1e;
  box-shadow: 0 0 50px rgba(155, 92, 255, 0.16);
  container-type: inline-size;
  color: #fff;
}

/* --- Header + titre — réutilisent .gate-header/.gate-header-logo/
   .gate-header-tag/.mini-sparkle tels quels (déjà utilisés pour ce même
   bandeau "KPOP-FRANCE / K-POP IQ" sur l'étape 3, voir plus haut dans ce
   fichier), pour éviter de dupliquer ce composant. --- */
.lb-title {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 2px 0 16px;
  font-family: "Baloo 2", sans-serif;
  font-weight: 800;
  font-size: max(20px, 7cqw);
  letter-spacing: 0.03em;
  text-align: center;
  background: linear-gradient(90deg, var(--pink), var(--purple), var(--blue));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 10px rgba(155, 92, 255, 0.4));
}
.lb-title .mini-sparkle--title { fill: var(--pink); }

/* --- Filtre période (ALL TIME / CE MOIS / CETTE SEMAINE) --- */
.lb-filters { display: flex; gap: 6px; margin-bottom: 16px; }
.lb-filter-btn {
  flex: 1;
  min-width: 0;
  padding: 8px 2px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  border: 1.5px solid var(--blue);
  color: #fff;
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  font-size: max(9px, 2.6cqw);
  letter-spacing: 0.01em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: center;
}
.lb-filter-btn.active {
  border: 2px solid transparent;
  background:
    linear-gradient(rgba(20, 16, 42, 0.9), rgba(20, 16, 42, 0.9)) padding-box,
    linear-gradient(135deg, var(--pink), var(--purple)) border-box;
  box-shadow: 0 0 14px rgba(255, 79, 160, 0.35);
}

/* --- Barre RECORD À BATTRE : cadre néon en dégradé (même technique que
   .lb-my-position/.gate-bonus-card, padding 2px + fond dégradé dehors,
   fond plein dedans = bordure en dégradé), icônes en SVG inline (jamais
   d'emoji — dépend de la police système/OS, voir commentaire .mini-sparkle
   plus haut). --- */
.lb-record {
  padding: 2px;
  border-radius: 16px;
  margin-bottom: 18px;
  background: linear-gradient(135deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 20px rgba(155, 92, 255, 0.25);
}
.lb-record-inner {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  border-radius: 14px;
  background: #0c0a1e;
  min-width: 0;
}
.lb-icon-trophy {
  flex: none;
  width: max(22px, 7cqw); height: max(22px, 7cqw);
  fill: #ffd76a;
  filter: drop-shadow(0 0 6px rgba(255, 215, 106, 0.6));
}
.lb-record-text { flex: none; min-width: 0; }
.lb-record-title {
  font-family: "Oswald", sans-serif; font-weight: 700; font-size: max(10px, 3.1cqw);
  letter-spacing: 0.02em; color: #fff; white-space: nowrap;
}
.lb-record-subtitle {
  font-family: "Manrope", sans-serif; font-weight: 500; font-size: max(8px, 2.1cqw);
  color: var(--text-dim); white-space: nowrap;
}
/* Score/temps = les 2 seules valeurs vraiment dynamiques de cette barre :
   flex:1 + min-width:0 + ellipsis sur le score (au cas où un score à 3
   chiffres + "/100" serre l'espace sur une carte très étroite), flex:none
   sur le temps (toujours court, "M:SS"). */
.lb-record-score {
  flex: 1 1 auto; min-width: 0;
  text-align: right;
  font-family: "Oswald", sans-serif; font-weight: 700; font-size: max(15px, 4.6cqw);
  background: linear-gradient(135deg, var(--pink), var(--purple));
  -webkit-background-clip: text; background-clip: text; color: transparent;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lb-record-sep { flex: none; width: 1px; align-self: stretch; background: rgba(255, 255, 255, 0.2); }
.lb-record-time {
  flex: none; display: flex; align-items: center; gap: 4px;
  font-family: "Oswald", sans-serif; font-weight: 600; font-size: max(12px, 3.4cqw); color: var(--blue);
  white-space: nowrap;
}
.lb-icon-clock { width: max(13px, 3.6cqw); height: max(13px, 3.6cqw); stroke: var(--blue); fill: none; flex: none; }

/* --- Podium : généré par renderPodium() dans app.js (pas de HTML statique
   par place — voir plus bas), ordre visuel 2-1-3 via `order`. Chaque place
   est un vrai bloc flex (avatar + pseudo + score + socle), donc aucune
   coordonnée calée au pixel contre une image : un pseudo trop long
   tronque proprement (ellipsis) dans sa propre boîte au lieu de déborder
   d'un contour baké. --- */
.lb-podium { display: flex; align-items: flex-end; justify-content: center; gap: 3%; margin: 0 0 20px; }
.lb-podium-stand { flex: 1 1 0; max-width: 32%; min-width: 0; display: flex; flex-direction: column; align-items: center; }
.lb-podium-stand--1 { order: 2; --rank-color: #ffd76a; --rank-glow: rgba(255, 215, 106, 0.5); }
.lb-podium-stand--2 { order: 1; --rank-color: #8fb4ff; --rank-glow: rgba(91, 140, 255, 0.45); }
.lb-podium-stand--3 { order: 3; --rank-color: #ff8fc0; --rank-glow: rgba(255, 79, 160, 0.45); }

.lb-podium-crown {
  width: max(14px, 5.5cqw); height: max(14px, 5.5cqw);
  fill: #ffd76a;
  filter: drop-shadow(0 0 6px rgba(255, 215, 106, 0.7));
  margin-bottom: 2px;
}
.lb-podium-avatar {
  position: relative;
  width: max(52px, 20cqw); height: max(52px, 20cqw);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.10), rgba(255, 255, 255, 0.02));
  border: 2.5px solid var(--rank-color, var(--blue));
  box-shadow: 0 0 16px var(--rank-glow, rgba(91, 140, 255, 0.4));
  margin-bottom: 8px;
}
.lb-podium-stand--1 .lb-podium-avatar { width: max(64px, 24cqw); height: max(64px, 24cqw); }
/* Pas de vrai avatar utilisateur (V1) : silhouette générique en SVG,
   identique à toutes les places (déjà le cas dans l'ancienne version
   bakée). */
.lb-podium-avatar-icon { width: 55%; height: 55%; fill: rgba(255, 255, 255, 0.5); }
.lb-podium-rank-num {
  position: absolute; bottom: -3px; right: -3px;
  width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: "Oswald", sans-serif; font-weight: 800; font-size: 11px;
  background: var(--rank-color, var(--blue));
  color: #0a081a;
  border: 2px solid #0c0a1e;
}
.lb-podium-name {
  width: 100%;
  text-align: center;
  font-family: "Oswald", sans-serif; font-weight: 700;
  font-size: max(9px, 3cqw);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin-bottom: 4px;
  padding: 0 2px;
}
.lb-podium-score {
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--rank-color, var(--blue));
  font-family: "Oswald", sans-serif; font-weight: 700; font-size: max(9px, 2.8cqw);
  color: var(--rank-color, var(--blue));
  white-space: nowrap;
  margin-bottom: 8px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lb-podium-base {
  width: 100%;
  border-radius: 10px 10px 0 0;
  background: linear-gradient(180deg, var(--rank-color-a), var(--rank-color-b));
  display: flex; align-items: flex-start; justify-content: center;
  padding-top: 6px;
  font-family: "Baloo 2", sans-serif; font-weight: 800;
  font-size: max(14px, 6cqw);
  color: rgba(10, 8, 26, 0.55);
}
.lb-podium-stand--1 .lb-podium-base { height: max(60px, 26cqw); --rank-color-a: #ffe9a8; --rank-color-b: #ff9f5a; }
.lb-podium-stand--2 .lb-podium-base { height: max(44px, 19cqw); --rank-color-a: #dbe8ff; --rank-color-b: #5b8cff; }
.lb-podium-stand--3 .lb-podium-base { height: max(34px, 14cqw); --rank-color-a: #ffcfa0; --rank-color-b: #ff4fa0; }

/* --- Tableau : grid (pas de colonnes calées en %/px absolus) — chaque
   cellule a sa propre largeur de colonne définie une fois pour le header
   ET les lignes (mêmes grid-template-columns), donc toujours alignées
   entre elles quel que soit le contenu. --- */
.lb-table { border-radius: 14px; overflow: hidden; border: 1px solid var(--border); background: rgba(255, 255, 255, 0.03); }
.lb-table-head, .lb-row {
  display: grid;
  grid-template-columns: 12% 34% 18% 16% 20%;
  gap: 4px;
  align-items: center;
  padding: 7px 8px;
}
.lb-table-head {
  background: linear-gradient(90deg, rgba(255, 79, 160, 0.18), rgba(155, 92, 255, 0.18), rgba(91, 140, 255, 0.18));
  border-bottom: 1px solid var(--border);
}
.lb-th {
  font-family: "Oswald", sans-serif; font-weight: 600; font-size: max(8px, 2.1cqw);
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--text-dim);
  text-align: center;
}
.lb-th-player { text-align: left; }
.lb-row { border-bottom: 1px solid rgba(255, 255, 255, 0.06); }
.lb-row:last-child { border-bottom: none; }
.lb-row:nth-child(odd) { background: rgba(255, 255, 255, 0.02); }
.lb-row-rank { display: flex; align-items: center; justify-content: center; min-width: 0; gap: 2px; font-family: "Oswald", sans-serif; font-weight: 700; font-size: max(9px, 2.4cqw); color: var(--text-dim); }
.lb-row-crown { width: 15px; height: 15px; flex: none; }
.lb-row--top1 .lb-row-rank { color: #ffd76a; }
.lb-row--top1 .lb-row-crown { fill: #ffd76a; filter: drop-shadow(0 0 4px rgba(255, 215, 106, 0.6)); }
.lb-row--top2 .lb-row-rank { color: #8fb4ff; }
.lb-row--top2 .lb-row-crown { fill: #8fb4ff; filter: drop-shadow(0 0 4px rgba(91, 140, 255, 0.5)); }
.lb-row--top3 .lb-row-rank { color: #ff8fc0; }
.lb-row--top3 .lb-row-crown { fill: #ff8fc0; filter: drop-shadow(0 0 4px rgba(255, 79, 160, 0.5)); }
/* min-width:0 sur chaque cellule texte, obligatoire dans une grille pour
   que overflow/ellipsis fonctionnent (sinon la colonne s'élargit pour
   accueillir tout le texte au lieu de le tronquer) — c'est précisément ce
   filet de sécurité qui manquait dans l'ancienne version bakée. */
.lb-row-name { min-width: 0; font-family: "Oswald", sans-serif; font-weight: 600; font-size: max(9px, 2.3cqw); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-row-score { text-align: center; font-family: "Oswald", sans-serif; font-weight: 700; font-size: max(9px, 2.3cqw); color: #fff; }
.lb-row-time { text-align: center; font-family: "Oswald", sans-serif; font-weight: 500; font-size: max(8px, 2.1cqw); color: rgba(255, 255, 255, 0.75); }
.lb-row-niveau { display: flex; align-items: center; justify-content: center; min-width: 0; }
.lb-row-niveau .niveau-pill { font-size: max(7px, 1.7cqw); padding: 0.3em 0.55em; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- État vide (aucun score sur la période) — n'existait pas dans
   l'ancienne version (le gabarit baké affichait toujours 3 places podium
   + 10 lignes, vides ou non). --- */
.lb-empty {
  padding: 30px 10px;
  text-align: center;
  font-family: "Manrope", sans-serif;
  color: var(--text-dim);
  font-size: 14px;
}

/* --- Pastille niveau : réutilisée tableau + TA POSITION. Rampe de
   couleur froid → chaud sur les 11 paliers (shared/levels.json), dégradé
   rose-violet réservé au dernier ("K-POP Legend"), même traitement que le
   bouton CONTINUER de l'étape 3. --- */
.niveau-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  padding: 0.35em 0.9em;
  font-family: "Oswald", sans-serif;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  font-size: 1.6cqw;
  line-height: 1;
  white-space: nowrap;
  border: 1.5px solid var(--niveau-color, var(--blue));
  color: #fff;
  background: rgba(10, 8, 26, 0.4);
}
.niveau-pill--newbie   { --niveau-color: #8a94b3; }
.niveau-pill--rookie   { --niveau-color: #5b8cff; }
.niveau-pill--listener { --niveau-color: #4fd6ff; }
.niveau-pill--fan      { --niveau-color: #4fe0c0; }
.niveau-pill--stan     { --niveau-color: #6fe3a6; }
.niveau-pill--insider  { --niveau-color: #c3e35a; }
.niveau-pill--expert   { --niveau-color: #ffd76a; }
.niveau-pill--master   { --niveau-color: #ff9f5a; }
.niveau-pill--elite    { --niveau-color: #ff5a7a; }
.niveau-pill--icon     { --niveau-color: #ff4fa0; }
.niveau-pill--legend {
  border: 1.5px solid transparent;
  background:
    linear-gradient(rgba(10, 8, 26, 0.4), rgba(10, 8, 26, 0.4)) padding-box,
    linear-gradient(135deg, var(--pink), var(--purple)) border-box;
  box-shadow: 0 0 1.4cqw 0.15cqw rgba(255, 79, 160, 0.4);
}

/* --- Barre TA POSITION : instantané localStorage posé après le dernier
   quiz joué sur cet appareil (pas de compte utilisateur — voir
   saveMyLeaderboardPosition() dans app.js). Toute la barre est masquée
   tant qu'aucune partie n'a été jouée sur cet appareil. Refonte du 16/09:
   plus rien n'est baké pour cette partie (le nouveau fond ne prévoit plus
   cet espace) — carte "non-bakée" à part entière, sous .leaderboard-card,
   même langage visuel que .gate-bonus-card (cadre néon en double dégradé,
   fond sombre translucide) plutôt qu'un overlay positionné en % sur une
   image. --- */
.lb-my-position {
  display: none;
  /* Alignée sur la largeur de .leaderboard-card (refonte du 28/09, plus
     d'image de fond à ratio fixe 1080/1920 à faire correspondre — même
     largeur simple min(100%, 420px) que le reste des cartes "non-bakées"
     de l'appli, ex. .mini-panel/.wizard-step3-card). */
  width: min(100%, 420px);
  margin: 14px auto 0;
  padding: 2px;
  border-radius: 16px;
  background: linear-gradient(135deg, var(--pink), var(--purple), var(--blue));
  box-shadow: 0 0 24px rgba(155, 92, 255, 0.3);
}
.lb-my-position.has-data { display: block; }
/* Cliquable uniquement quand le résultat complet de la partie est encore
   en mémoire (state.result, cf. app.js) — le snapshot localStorage qui
   pilote cette barre ne contient pas le détail (vitesse/précision/profil)
   nécessaire pour réafficher l'écran reveal complet, donc pas de clic
   possible après un rechargement de page sans avoir rejoué. */
.lb-my-position.clickable { cursor: pointer; }
.lb-my-position.clickable:active { transform: scale(0.985); }
.lb-my-position-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: 14px;
  background: #0c0a1e;
  container-type: inline-size;
}
.lb-my-rank-badge {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1.5px solid var(--pink);
  font-family: "Oswald", sans-serif;
  font-weight: 800;
  font-size: 15px;
  color: var(--pink);
}
.lb-my-name {
  flex: 1 1 auto;
  min-width: 0;
  font-family: "Oswald", sans-serif;
  font-weight: 700;
  font-size: 4.6cqw;
  line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-my-stat {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
.lb-my-stat-label {
  font-family: "Manrope", sans-serif;
  font-weight: 600;
  font-size: 2.6cqw;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.lb-my-score {
  font-family: "Oswald", sans-serif;
  font-weight: 700;
  font-size: 4cqw;
  line-height: 1;
  color: var(--blue);
  white-space: nowrap;
}
.lb-my-time {
  font-family: "Oswald", sans-serif;
  font-weight: 500;
  font-size: 2.6cqw;
  line-height: 1;
  color: rgba(255, 255, 255, 0.75);
  white-space: nowrap;
}
.lb-my-niveau .niveau-pill { font-size: 3cqw; padding: 0.36em 0.9em; }

/* Lien retour sous la carte — même technique que .reveal-home-link/
   .all-quizzes-home-link. Largeur alignée sur .leaderboard-card/
   .lb-my-position (refonte du 28/09, voir plus haut). */
.lb-home-link {
  display: block;
  margin: 14px auto 0;
  max-width: min(100%, 420px);
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: "Manrope", sans-serif;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  padding: 10px 20px;
}
.lb-home-link:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.5); }

/* ==== Plein écran mobile pour les cartes "image de fond" ====
   Sur mobile (95% du trafic), .hook-card / .quiz-card / .reveal-card /
   .wizard-stepN-card sont des images BAKÉES à un ratio fixe (1080×2340,
   sauf .quiz-card, .hook-card, .wizard-step2-card et .reveal-card repassées
   à 1080×1920, cf. plus bas — .wizard-step3-card n'existe plus depuis
   l'étape 5, remplacée par .mini-panel, qui n'est pas une image bakée) :
   logo, titre, textes, tuiles, tout est dans l'image, seuls les
   boutons/hit-zones sont
   de vrais éléments HTML posés en % par-dessus. Une image à ratio fixe
   qui doit remplir un écran à un ratio différent n'a que 3 issues :
   l'étirer (déformation), la rogner (perte de contenu), ou laisser un
   peu d'espace libre (marge ou scroll) — pas de 4e option.
   Historique des essais (les 3 premiers abandonnés, cf. commits
   précédents pour le détail technique de chacun) :
   v1 — min(100vw, 100dvh*ratio) pour garder le ratio → bandes vides sur
        les côtés (barre d'adresse Safari qui fausse le calcul).
   v2 — width:100vw/height:100dvh + marges négatives → pire, carte plus
        grande que l'écran visible réel, scroll bloqué (bug Safari connu :
        100dvh mal synchronisé avec la zone visible réelle au 1er paint).
   v3 — position:fixed + inset:0, puis --vvw/--vvh via
        window.visualViewport (JS) pour forcer un remplissage exact au
        pixel → plus de scroll bloqué, MAIS ce remplissage forcé étire
        l'image hors de son ratio d'origine → rendu "écrasé" (confirmé
        par Sav sur les captures : logo et badges visiblement déformés).
   v4 : on abandonne l'idée de "remplir exactement l'écran sans scroll".
   La carte garde son ratio 1080/2340 intact (pas de position:fixed, pas
   de JS) — juste étalée en pleine largeur mobile (#app perd son
   max-width, body perd son padding, cf. plus bas). Sa hauteur se déduit
   du ratio, sans être plafonnée : si elle dépasse l'écran, la PAGE
   défile normalement (scroll natif du navigateur, sans aucun élément
   fixed qui lui fait obstacle comme en v2/v3). Zéro déformation, zéro
   bande morte latérale, un poil de scroll vertical selon le téléphone.
   Très bien pour hook/reveal/wizard/leaderboard : ce sont des écrans
   "one-shot" (on les regarde, on appuie un bouton), un peu de scroll
   n'y gêne pas.
   MAIS sur .quiz-card spécifiquement, v4 posait un vrai problème
   fonctionnel (signalé par Sav) : le chrono par question et les 4
   réponses passaient sous la ligne de flottaison, invisibles sans
   scroller — inacceptable pour un écran avec un compte à rebours actif
   qu'il faut pouvoir surveiller sans interaction. .quiz-card revient
   donc au principe "contain" (comme l'ancien v1, mais seulement pour
   cet écran, et en dvh plutôt que vh pour coller à la zone vraiment
   visible plutôt qu'au viewport "barre d'adresse cachée" — cette carte
   n'est pas en position:fixed comme le v2 qui avait posé problème,
   donc dvh se comporte normalement ici, sans piège de scroll bloqué) :
   largeur plafonnée par min(100%, 100dvh*ratio) pour garantir qu'elle
   tient TOUJOURS dans la hauteur d'écran sans scroll, quitte à laisser
   une fine bande de fond de chaque côté sur les téléphones plus "plats"
   que le ratio du fond.

   v5 — cause réelle du scroll extérieur qui persistait sur .quiz-card
   malgré le "contain" ci-dessus : ce n'est PAS .quiz-card qui débordait,
   c'est <body> (règle de base, tout en haut du fichier) qui a
   `min-height: 100vh`. Sur Safari iOS, 100vh = hauteur du viewport
   "large" (barre d'adresse rétractée), alors que .quiz-card est calculée
   en 100dvh (viewport réellement visible, barre d'adresse comprise).
   Quand la barre d'adresse est affichée, 100vh > 100dvh (l'écart observé
   correspond exactement à la hauteur de cette barre) : body est donc
   forcé, par son propre min-height, à occuper une boîte plus haute que
   la zone visible, même si la carte qu'il contient tient exactement
   dans 100dvh — d'où un fond de page vide qui dépasse et reste
   scrollable. Fix : aligner le min-height de body sur la même unité
   dvh que .quiz-card, uniquement sur mobile (desktop n'a pas de barre
   d'adresse rétractable donc vh/dvh y sont déjà identiques).

   v6 — quiz-bg.png refait par Sav en 1080×1920 (ratio 9:16, cf. v5) au
   lieu de 1080×2340 : ce ratio est plus plat que la quasi-totalité des
   téléphones réels (le plus plat courant, iPhone SE, est à peine plus
   plat : 1,779 vs 1,778 ici), donc min(100%, 100dvh*ratio) résout presque
   toujours à 100% — les bandes latérales du v1-v4 disparaissent dans la
   pratique. La formule "contain" est gardée telle quelle (juste le ratio
   changé) : elle reste la garantie mathématique qu'aucun appareil, même
   un cas extrême non prévu, ne fasse dépasser la carte de 100dvh. */
@media (max-width: 640px) {
  /* Sur mobile, .quiz-card laisse parfois un résidu de quelques % de fond
     visible sur les côtés (cf. commentaire "Plein écran mobile" plus bas) —
     sans cadre, ce résidu doit rester uni. Les deux radial-gradient de la
     règle de base (violet en haut-gauche à 20%/8%, bleu en bas-droite à
     88%/85%) sont désactivés ici : sur desktop ils restent (ambiance
     autour de l'app centrée à 460px), mais sur mobile ils créaient
     exactement les deux taches de couleur signalées par Sav aux deux
     coins où pointent ces gradients. */
  body { padding: 0; min-height: 100vh; min-height: 100dvh; background: var(--bg); }
  #app { max-width: 100%; }
  .hook-card,
  .all-quizzes-card,
  .quiz-card,
  .reveal-card,
  .wizard-step1-card,
  .wizard-step2-card,
  .rules-countdown-card {
    border-radius: 0;
  }
  .hook-card,
  .all-quizzes-card,
  .quiz-card,
  .reveal-card,
  .wizard-step2-card {
    width: min(100%, 100dvh * 1080 / 1920);
    margin: 0 auto;
  }
  /* .leaderboard-card ne fait plus partie de ce groupe depuis la refonte
     du 28/09 : ce n'est plus une image bakée à ratio fixe qu'il faut faire
     déborder en plein écran, mais une carte à hauteur libre (comme
     .mini-panel/.wizard-step3-card) — elle garde ses coins arrondis et sa
     largeur/marge normales (min(100%, 420px), déjà posé plus haut) même
     sur mobile. */
  /* Ratio propre à l'image du compte à rebours (941/1672, quasi identique
     à 1080/1920 mais mesuré précisément sur ce fichier plutôt qu'approximé).
     .wizard-step3-card n'en fait plus partie depuis la refonte du 15/09
     (contenu flexible, plus une image bakée à ratio fixe). */
  .rules-countdown-card {
    width: min(100%, 100dvh * 941 / 1672);
    margin: 0 auto;
  }

  /* iOS Safari (et Chrome Android dans une moindre mesure) zoome
     automatiquement la page au focus d'un champ dont la font-size
     calculée est < 16px, et ne dézoome jamais tout seul ensuite (l'user
     doit pincer pour revenir). input.wizard-step1-input est en cqw
     (3.3cqw) : sur mobile, avec un container plus étroit que sur desktop,
     ça tombe largement sous 16px. Fix : forcer 16px fixes sur mobile
     uniquement pour ce champ, sans toucher au viewport meta (donc le
     pincer-zoomer manuel de l'user reste disponible, seul le
     zoom-auto-au-focus est supprimé). input.mini-input (écrans non-bakés,
     étape 5) est déjà fixé à 16px en toutes circonstances, pas besoin de
     règle mobile dédiée ici. */
  input.wizard-step1-input,
  select {
    font-size: 16px;
  }
}
