  /* Police d'affichage (chiffres, titres, PLAY) : Mont Heavy, la même que le portage
     Unity (qui a remplacé Merkin). Une seule graisse, déclarée à sa vraie valeur 900
     (usWeightClass de la police) : les règles en font-weight 900 la prennent telle quelle,
     sans faux gras synthétisé — y compris sur les vieux WebView (la syntaxe plage
     « 100 900 » n'existe qu'à partir de Chrome 62 / Safari 11, sous notre plancher).
     ⚠ C'est la version DEMO de Mont : son cmap annonce ~490 caractères, mais beaucoup sont
     des FILIGRANES « DEMO » (vérifié au rendu) — TOUTES les lettres accentuées et marques
     combinantes, et en ASCII # % / < > \ ^ _ ` | ~. unicode-range limite donc Mont à
     ses glyphes réels (espace ! " $ & ' ( ) * + , - . 0-9 : ; = ? @ A-Z [ ] a-z { }) ;
     tout autre caractère (le « / » des compteurs, les accents ES/PT des titres) tombe
     sur la police suivante de --font-display, en graisse 900. */
  @font-face {
    font-family: "Mont";
    src: url("fonts/Mont-HeavyDEMO.otf") format("opentype");
    font-weight: 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0020-0022, U+0024, U+0026-002E, U+0030-003B, U+003D, U+003F-005B, U+005D, U+0061-007B, U+007D;
  }
  :root {
    /* Police d'affichage + repli système « Black » pour les glyphes hors Mont DEMO. */
    --font-display: "Mont", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --panel: #fffaea;
    --accent: #f5b04a;
    --accent2: #ee7a3e;
    --text: #3a2a1e;
    /* Boutons « pilule » du rail (HINT / NEXT) : taille FIXE (le libellé est fitté
       dedans, fitText). PORTRAIT : pilule couchée --btn-h × --btn-w, rangée sous le
       plateau (--rail = sa hauteur). PAYSAGE : la même pilule DEBOUT --btn-vw × --btn-vh
       (pastille en haut, libellé dessous), colonne à droite (--rail-w = sa largeur) :
       couchée, elle prenait 3,3 × sa hauteur de large à côté du plateau. Au-delà de ~4:3
       la largeur ne borne pas le plateau (c'est la hauteur) ; entre 1:1 et 4:3 le rail la
       rogne, d'autant moins qu'il est étroit (voir la media query portrait plus bas). */
    --btn-h: clamp(50px, 9vmin, 62px);
    --btn-w: calc(var(--btn-h) * 3.3);
    --btn-vw: calc(var(--btn-h) * 1.5);
    --btn-vh: calc(var(--btn-vw) * 1.5);
    --rail: calc(var(--btn-h) + 16px);
    --rail-w: calc(var(--btn-vw) + 12px);
    /* Puces de progression (en-tête) : taille normale / courante. */
    --pip: clamp(18px, 5.2vmin, 28px);
    --pip-cur: clamp(23px, 6.6vmin, 35px);
    /* Boutons ronds de l'en-tête (maison, réglages) : c'est eux qui fixent la hauteur de
       la ligne du titre. Agrandis (retour Sébastien : « vraiment petits » à 28-36 px) →
       42-54 px, cible tactile confortable, un poil au-dessus de l'engrenage du menu. */
    --hdr-btn: clamp(42px, 7.5vmin, 54px);
    /* Hauteur EN PLUS quand l'en-tête passe sur deux lignes (puces sous le titre,
       voir fitHeader) : ligne de puces + place des badges qui débordent en haut. */
    --hdr-extra: 0px;
    /* Défaut = PAYSAGE : le rail est à DROITE du plateau. Le board doit tenir en
       hauteur (100vh − en-tête) ET laisser à sa droite la place du rail + gap.
       En-tête = 68 px (marges de #app 24 + écart 14 + ~30 d'air pour les badges des puces
       et l'ombre du plateau) + la ligne du titre (--hdr-btn). */
    --board-size: min(600px, calc(100vh - 68px - var(--hdr-btn) - var(--hdr-extra)), calc(100vw - var(--rail-w) - 44px));
  }
  /* Deux lignes en PAYSAGE (repli quand une ligne ne tient pas) : puces à leur taille
     normale, pour ne prendre au plateau (borné par la hauteur) que le strict minimum. */
  :root.hdr-2l { --hdr-extra: calc(var(--pip-cur) * 1.5 + 6px); }
  /* Deux lignes en PORTRAIT (toujours) : les puces ont toute la largeur → plus grosses
     (comme le portage Unity, lisibles au doigt ; la hauteur y est libre), plafonnées par
     la largeur du plateau (7 puces + rails ≈ 9,3 puces) pour toujours tenir. La réserve
     de hauteur utilise la taille NON plafonnée (--pip-2l-cur) : --board-size dépend de
     --hdr-extra, qui ne doit donc pas dépendre de --board-size (cycle = tout invalide). */
  @media (max-aspect-ratio: 1/1) {
    :root.hdr-2l {
      --pip-2l-cur: clamp(30px, 9vmin, 42px);
      --hdr-extra: calc(var(--pip-2l-cur) * 1.5 + 6px);
      --pip: min(clamp(24px, 7.2vmin, 34px), calc(var(--board-size) / 10.5));
      --pip-cur: min(var(--pip-2l-cur), calc(var(--board-size) / 8.4));
    }
  }
  /* PORTRAIT / carré (largeur ≤ hauteur) : le rail passe SOUS le plateau. Le board est
     borné en largeur (92vw) et en hauteur on réserve la hauteur du rail + gap →
     le rail (donc le bouton HINT) a toujours sa place, jamais hors écran.
     Critère = RATIO seul, seuil 1:1 (retour QA Poki « scaling off ») : l'ancienne règle
     « max-width: 760px, max-aspect-ratio: 4/3 » envoyait ici les PETITS PAYSAGES (cadre
     Poki desktop réduit, mobile couché : plateau 131 px à 663×373, en-tête sur 3 lignes)
     et le 4:3 (iPad couché : 501 px au lieu de 600). Dès que l'écran est plus large que
     haut, le rail à droite donne un plateau au moins aussi grand : il prend de la
     largeur, que le paysage a en trop, au lieu de la hauteur qui borne le plateau. */
  @media (max-aspect-ratio: 1/1) {
    :root { --board-size: min(600px, 92vw, calc(100vh - 68px - var(--hdr-btn) - var(--rail) - 16px - var(--hdr-extra))); }
  }
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  html, body {
    margin: 0; padding: 0; height: 100%;
    font-family: "Nunito", "Segoe UI", system-ui, sans-serif;
    background: radial-gradient(circle at 50% 20%, var(--bg1, #fff3c0) 0%, var(--bg2, #f8d97b) 100%);
    color: var(--text);
    overflow: hidden;
    touch-action: manipulation;
  }
  /* Fond par world (Forêt vert, Mer bleu clair, Sport orange, Ville bleu nuit).
     Les mêmes variables alimentent l'écran interstitiel NEW WORLD. */
  body[data-world="1"] { --bg1: #d9f6b3; --bg2: #7ec96a; }
  body[data-world="2"] { --bg1: #cdf2ff; --bg2: #74c6ec; }
  body[data-world="3"] { --bg1: #ffe0a6; --bg2: #ff9239; }
  body[data-world="4"] { --bg1: #3f5285; --bg2: #18203c; }
  body[data-world="5"] { --bg1: #fff4b0; --bg2: #f5b81e; }
  #app {
    width: 100%; height: 100%;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 12px;
  }
  /* #stage = plateau + rail de boutons. PAYSAGE (défaut) : en ligne, rail à droite.
     PORTRAIT (media query plus bas) : en colonne, rail dessous. Comme le rail est
     un vrai enfant flex (pas un overlay absolu), il occupe une place réelle et le
     centrage de #app en tient compte → le rail ne sort jamais de l'écran. */
  #stage {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: clamp(8px, 1.6vmin, 16px);
  }
  /* Colonne en-tête + plateau : garde le WORLD aligné pile au-dessus du board. */
  #main-col {
    display: flex; flex-direction: column;
    align-items: center;
    gap: 14px;
  }
  /* Rail de boutons : colonne en paysage (largeur = --rail-w), rangée en portrait
     (hauteur = --rail). Prévu pour accueillir d'autres boutons à l'avenir. */
  #button-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(8px, 1.6vmin, 14px);
    width: var(--rail-w);
    flex: 0 0 auto;
  }
  @media (max-aspect-ratio: 1/1) {
    #stage { flex-direction: column; }
    #button-rail { flex-direction: row; width: auto; height: var(--rail); }
  }
  #world-label {
    display: flex;
    align-items: center;
    gap: 7px;
  }
  #world-icon {
    height: clamp(20px, 4.4vmin, 30px);
    width: auto;
    display: block;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.25));
  }
  #world-img {
    height: clamp(13px, 2.8vmin, 20px);
    width: auto;
    display: block;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.22));
  }
  #world-num {
    font-family: var(--font-display); font-weight: 900;
    font-size: clamp(15px, 3.1vmin, 22px);
    line-height: 1;
    color: #fff;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.22));
  }

  /* Progression du world — style du portage Unity : un DISQUE PLAT par niveau (gris =
     à venir, vert = fini, orange et plus gros = courant) reliés par un RAIL continu
     (vert jusqu'à la puce courante, gris translucide ensuite). Plus de billes glossy
     « Circle Button » qui passaient pour des boutons cliquables. Numéro (position dans
     le world) en Mont avec ombre ; ombre portée sous le disque pour compenser la perte
     du biseau. Badge Hard/Bonus plus petit (70 %) et remonté (bas à 79 % du disque)
     pour ne plus mordre sur le chiffre (géométrie Unity, PARITY_WEB §D).
     Mise en page (décidée en JS par fitHeader) : en PORTRAIT toujours sur DEUX lignes
     (#title-row.two-line : titre + boutons en haut, puces dessous, plus grosses) ; en
     PAYSAGE sur UNE ligne, centrée, tant qu'elle ne touche ni « WORLD n » ni les
     boutons, sinon deux lignes → jamais de chevauchement. Construite dans
     buildWorldProgress(). */
  #world-progress {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .pip {
    position: relative; z-index: 1; /* au-dessus des rails qu'il chevauche */
    flex: 0 0 auto;
    width: var(--pip); height: var(--pip);
    border-radius: 50%;
    background: #adbccb;             /* à venir */
    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.18);
    display: flex; align-items: center; justify-content: center;
  }
  .pip.done    { background: #54e26d; }
  .pip.current { background: #ff9e3c; width: var(--pip-cur); height: var(--pip-cur); }
  /* Segment de rail entre deux puces : c'est lui qui les écarte (pas de gap). Plein
     (vert) s'il mène à une puce finie ou à la courante, translucide sinon. */
  .pip-rail {
    flex: 0 0 auto;
    width: calc(var(--pip) * 0.34);
    height: max(3px, calc(var(--pip) * 0.13));
    margin: 0 -1px;                  /* mord un poil sous les disques → trait continu */
    background: rgba(173, 188, 203, 0.55);
  }
  .pip-rail.filled { background: #54e26d; }
  .pip-num {
    font-family: var(--font-display); font-weight: 900;
    font-size: calc(var(--pip) * 0.54);
    line-height: 1;
    color: #fff;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.45);
    pointer-events: none;
  }
  .pip.current .pip-num { font-size: calc(var(--pip-cur) * 0.54); }
  /* Badge de type (crâne = Hard, chat à flèches = Bonus/rotation), au-dessus du disque. */
  .pip-icon {
    position: absolute;
    left: 50%;
    bottom: 79%;
    transform: translateX(-50%);
    width: 70%; height: 70%;
    object-fit: contain;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4));
  }
  /* En-tête sur deux lignes : puces sous le titre, sur toute la largeur. La marge du
     haut laisse la place aux badges qui débordent (bas à 79 % + 70 % de haut). */
  #title-row.two-line { flex-wrap: wrap; }
  #title-row.two-line #world-progress {
    position: static;
    transform: none;
    order: 3;
    flex: 0 0 100%;
    margin-top: calc(var(--pip-cur) * 0.5 + 4px);
  }
  #title-row.two-line #title-actions { position: static; transform: none; margin-left: auto; }

  #board-wrap {
    position: relative;
    width: var(--board-size); height: var(--board-size);
    background: var(--panel);
    border-radius: 18px;
    padding: 6px;
    box-shadow: 0 10px 24px rgba(0,0,0,0.12), inset 0 0 0 4px rgba(0,0,0,0.04);
  }
  /* Canvas PLEIN ÉCRAN (couvre tout le viewport) : le plateau y est dessiné à sa
     position réelle et une pièce draguée peut aller n'importe où sans être rognée
     par le raster. Fixed → indépendant du flux ; z-index au-dessus du cadre/en-tête
     (la pièce flotte par-dessus) mais SOUS les overlays plein écran (intros,
     NEW WORLD, Settings en z 50-70). pointer-events: none → les clics passent à
     #board-input et aux boutons. Reste UN seul canvas → contrainte Poki préservée
     (seul le plateau est peint, le décor DOM autour n'est pas capturé par le
     recorder — assumé). */
  #board {
    position: fixed;
    left: 0; top: 0;
    width: 100%; height: 100%;
    display: block;
    background: transparent;
    user-select: none;
    pointer-events: none;
    z-index: 30;
  }
  /* Couche d'entrée (DOM, pas un 2e canvas) posée pile sur la zone plateau :
     reçoit les événements pointeur. Transparente. */
  #board-input {
    position: absolute;
    left: 6px; top: 6px;
    width: calc(100% - 12px);
    height: calc(100% - 12px);
    border-radius: 10px;
    touch-action: none;
    cursor: grab;
    z-index: 2;
  }
  #board-input.drag-active { cursor: grabbing; }
  button {
    font: inherit; font-weight: 700;
    background: var(--accent);
    color: #fff;
    border: none; border-radius: 12px;
    padding: 10px 18px; cursor: pointer;
    box-shadow: 0 3px 0 rgba(0,0,0,0.12);
    transition: transform 0.08s ease, box-shadow 0.08s ease, opacity 0.2s ease;
    font-size: clamp(13px, 3vmin, 16px);
  }
  button:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.12); }

  /* Main de tutoriel (niveau 1 et 1er bonus). Overlay DOM (pas un 2e canvas)
     → invisible dans le replay playtest Poki, comme le reste du décor.
     left/top positionnent le bout du doigt (≈27%/8% de l'image) sur le centre
     d'une case ; transform-origin calé à ce même point pour le « press ».
     Deux gestes : .slide (niveau 1, glisse haut-centre → haut-gauche) et .tap
     (1er bonus, scale down/up sur la case haut-centre). Calé pour une grille 3×3.
     Le retrait (fondu de sortie) est piloté en JS — voir setTutorHand. */
  #tutor-hand {
    position: absolute;
    width: 24%;
    height: auto;
    left: 43%;
    top: 14.5%;
    z-index: 32; /* au-dessus du canvas plein écran (z 30) : la main pointe sur les pièces */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform-origin: 27% 8%;
    filter: drop-shadow(0 4px 6px rgba(0,0,0,0.3));
  }
  #tutor-hand.show { visibility: visible; opacity: 1; }
  #tutor-hand.show.slide { animation: tutorSlide 2800ms cubic-bezier(0.45, 0, 0.3, 1) infinite; }
  #tutor-hand.show.tap   { animation: tutorTap 1150ms ease-in-out infinite; }
  /* Slide : left 43% → doigt au centre de la case haut-centre (col1) ; left 9.5%
     → centre de la case haut-gauche (col0). Press, glissé à gauche, relâche, hold,
     fondu, boucle. */
  @keyframes tutorSlide {
    0%   { left: 43%;  opacity: 0; transform: scale(1.12); }
    7%   { left: 43%;  opacity: 1; transform: scale(1); }
    16%  { left: 43%;  opacity: 1; transform: scale(0.86); }
    58%  { left: 9.5%; opacity: 1; transform: scale(0.86); }
    66%  { left: 9.5%; opacity: 1; transform: scale(1); }
    85%  { left: 9.5%; opacity: 1; transform: scale(1); }
    100% { left: 9.5%; opacity: 0; transform: scale(1); }
  }
  /* Tap : reste sur la case haut-centre, scale down (press) puis up (release),
     hold, boucle. L'opacité reste pleine (vient de .show). */
  @keyframes tutorTap {
    0%   { transform: scale(1); }
    22%  { transform: scale(0.8); }
    42%  { transform: scale(1); }
    100% { transform: scale(1); }
  }
  #hidden-video {
    position: absolute; width: 1px; height: 1px; opacity: 0;
    pointer-events: none; left: -9999px; top: -9999px;
  }
  #title-row {
    position: relative;
    width: var(--board-size);
    /* Au moins la hauteur des boutons : en une ligne ils sont en absolu (hors flux) et
       déborderaient sinon sur le plateau — la réserve de --board-size compte --hdr-btn. */
    min-height: var(--hdr-btn);
    display: flex; align-items: center; justify-content: flex-start;
  }
  /* Bouton réglages dans l'en-tête (remplace l'ancien bouton mute, qui vit
     désormais dans la popup #settings-modal). */
  /* Actions à droite de l'en-tête : bouton maison (ouvre le menu de sélection) puis
     réglages. Skin pilule RONDE et BLEUE (.pill .pill-blue .pill-round, comme le PLAY)
     + picto blanc (Layer Lab PictoIcon_Home_1 / Setting_1). */
  #title-actions {
    position: absolute;
    right: 0; top: 50%;
    transform: translateY(-50%);
    display: flex; align-items: center;
    gap: clamp(6px, 1.4vmin, 10px);
  }
  #settings-btn, #home-btn {
    --cta-h: var(--hdr-btn);
    --cta-b: 3px;
    transition: transform 0.1s ease;
  }
  #settings-icon, #home-icon { width: 60%; height: 60%; object-fit: contain; pointer-events: none; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25)); }

  /* Popup réglages : overlay DOM plein écran (invisible dans le replay playtest
     Poki, comme le reste du décor). Contient le bouton mute + les crédits audio. */
  #settings-modal {
    position: fixed; inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 70;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.2s ease, visibility 0.2s ease;
  }
  #settings-modal.show { opacity: 1; visibility: visible; pointer-events: auto; }
  #settings-backdrop {
    position: absolute; inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    background: rgba(0,0,0,0.5);
  }
  #settings-panel {
    position: relative;
    width: min(360px, 84vw);
    background: var(--panel);
    border-radius: 22px;
    padding: 26px 24px 22px;
    box-shadow: 0 14px 40px rgba(0,0,0,0.35), inset 0 0 0 4px rgba(0,0,0,0.04);
    display: flex; flex-direction: column; align-items: center;
    gap: 18px;
    transform: scale(0.9);
    transition: transform 0.2s cubic-bezier(0.2, 0.9, 0.3, 1.3);
  }
  #settings-modal.show #settings-panel { transform: scale(1); }
  #settings-close {
    position: absolute;
    top: 10px; right: 12px;
    width: 34px; height: 34px;
    padding: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; line-height: 1; font-weight: 900;
    color: var(--accent2);
    background: none;
    border-radius: 0;
    box-shadow: none;
  }
  /* Bouton mute (dans la popup) : picto son seul. */
  #mute-btn {
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
    background: var(--accent);
    border-radius: 18px;
  }
  #sound-icon { width: clamp(34px, 9vmin, 48px); height: clamp(34px, 9vmin, 48px); object-fit: contain; pointer-events: none; }
  #settings-credits {
    font-size: clamp(10px, 2.4vmin, 12px);
    line-height: 1.5;
    color: var(--text);
    text-align: center;
    opacity: 0.85;
    word-break: break-word;
  }
  #settings-credits a { color: var(--accent2); }
  #settings-credits-icons {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid rgba(0,0,0,0.1);
    font-size: 0.92em;
  }

  /* ===== Bouton « pilule » : skin COMMUN des actions principales =====
     PLAY du menu (CTA collant + PLAY des cartes), HINT et NEXT (rail). Pilule verte
     à bord blanc, lèvre intérieure en bas, ombre colorée, libellé Mont. Tout est
     proportionnel à la hauteur --cta-h (bordure --cta-b, écart --cta-g). Pastille
     ronde optionnelle à gauche (.has-slot + .pill-slot : vignette ou icône sur fond
     blanc), CONCENTRIQUE à l'arrondi : diamètre = --cta-h − 2×--cta-b − 2×--cta-g et
     même écart à gauche qu'en haut/bas → son centre = celui du demi-cercle. */
  .pill {
    --cta-h: 56px;
    --cta-b: 3px;
    --cta-g: 5px;
    /* Couleurs (vert par défaut ; .pill-orange pour le HINT rewarded). */
    --pill-bg: linear-gradient(180deg, #6ff59a 0%, #2fdc6e 55%, #12b981 100%);
    --pill-lip: rgba(0, 120, 80, 0.28);
    --pill-glow: rgba(18, 185, 129, 0.45);
    --pill-ring: rgba(0, 110, 70, 0.3);
    display: flex; align-items: center; justify-content: center;
    height: var(--cta-h);
    padding: 0 calc(var(--cta-h) * 0.4);
    /* Bord blanc = calque de fond border-box SOUS le dégradé (padding-box), pas une
       `border` peinte : Firefox (WebRender ≥ 154) re-rastérise à chaque frame un contenu
       dont le transform est animé, et une bordure arrondie y fuit sa couleur le long de
       ses segments de coin → fines lignes « 9-grid » une frame par cycle (pulse du CTA,
       rebond du PLAY, NEXT). Mesuré sur Firefox 156 : ~2 % des frames avec la bordure,
       0 avec les 2 fonds ; Chrome n'est pas touché. La bordure reste, transparente, pour
       la géométrie (padding-box, rayon intérieur, lèvre inset, pastille concentrique). */
    border: var(--cta-b) solid transparent;
    border-radius: 999px;
    background: var(--pill-bg) padding-box, rgba(255, 255, 255, 0.95) border-box;
    box-shadow: inset 0 calc(var(--cta-h) * -0.08) 0 var(--pill-lip),
                0 calc(var(--cta-h) * 0.08) 0 rgba(0, 0, 0, 0.14),
                0 calc(var(--cta-h) * 0.15) calc(var(--cta-h) * 0.34) var(--pill-glow);
    font-family: var(--font-display); font-weight: 900;
    font-size: calc(var(--cta-h) * 0.44);
    line-height: 1;
    white-space: nowrap;
    color: #fff;
    text-shadow: 0 2px 2px rgba(0, 0, 0, 0.22);
  }
  /* Variante orange : le HINT (pub récompensée) garde sa couleur « pub », même forme. */
  .pill.pill-orange {
    --pill-bg: linear-gradient(180deg, #ffd27a 0%, #f5b04a 55%, #ee7a3e 100%);
    --pill-lip: rgba(170, 70, 20, 0.3);
    --pill-glow: rgba(238, 122, 62, 0.45);
    --pill-ring: rgba(170, 80, 20, 0.3);
  }
  /* Variante bleue : boutons secondaires (maison, réglages), même skin que PLAY. */
  .pill.pill-blue {
    --pill-bg: linear-gradient(180deg, #7fcbff 0%, #3a9bf0 55%, #1f6fd6 100%);
    --pill-lip: rgba(10, 60, 150, 0.3);
    --pill-glow: rgba(40, 120, 230, 0.45);
    --pill-ring: rgba(10, 60, 150, 0.3);
  }
  /* Pilule ronde (bouton icône) : largeur = hauteur. */
  .pill.pill-round { width: var(--cta-h); padding: 0; }
  .pill.has-slot { padding-left: var(--cta-g); gap: calc(var(--cta-h) * 0.18); }
  button.pill { cursor: pointer; }
  button.pill:active {
    transform: translateY(calc(var(--cta-h) * 0.05));
    box-shadow: inset 0 calc(var(--cta-h) * -0.05) 0 var(--pill-lip),
                0 calc(var(--cta-h) * 0.03) 0 rgba(0, 0, 0, 0.14),
                0 calc(var(--cta-h) * 0.08) calc(var(--cta-h) * 0.18) var(--pill-glow);
  }
  button.pill:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
  .pill-slot {
    flex: 0 0 auto;
    width: calc(var(--cta-h) - 2 * var(--cta-b) - 2 * var(--cta-g));
    height: calc(var(--cta-h) - 2 * var(--cta-b) - 2 * var(--cta-g));
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 0 0 2px var(--pill-ring), 0 2px 4px rgba(0, 0, 0, 0.18);
    display: flex; align-items: center; justify-content: center;
    object-fit: cover; /* quand la pastille est elle-même une <img> (vignette du CTA) */
    pointer-events: none;
  }
  /* Libellé : prend la place restante (min-width 0 → rétrécissable) et fitText le
     réduit s'il déborde (langues longues : SIGUIENTE, OVERSLAAN…). */
  .pill-label { flex: 1 1 auto; min-width: 0; text-align: center; pointer-events: none; }

  /* Pilules du rail (HINT / NEXT). PAYSAGE (défaut) : pilule DEBOUT --btn-vw × --btn-vh
     (voir :root). --cta-h = sa LARGEUR : la pastille (--cta-h − 2×b − 2×g) reste
     concentrique au demi-cercle du haut (même écart --cta-g en haut et sur les côtés) ;
     le libellé occupe le bas, centré, fitté à la largeur (fitText). PORTRAIT : pilule
     couchée --btn-h × --btn-w (media query juste en dessous). Sélecteur .pill.rail-pill
     (2 classes) pour passer devant .pill.has-slot (padding-left, gap). */
  .pill.rail-pill {
    --cta-h: var(--btn-vw);
    position: relative;  /* contexte du reflet ::after du HINT */
    overflow: hidden;    /* clippe le reflet à la pilule */
    flex-direction: column;
    justify-content: flex-start;
    width: var(--btn-vw);
    height: var(--btn-vh);
    padding: var(--cta-g) var(--cta-g) calc(var(--btn-h) * 0.14);
    gap: 0;
    font-size: calc(var(--btn-h) * 0.36);
    transition: transform 0.1s ease, opacity 0.2s ease;
  }
  .pill.rail-pill .pill-label {
    align-self: stretch; /* toute la largeur → clientWidth = place réelle pour fitText */
    display: flex; align-items: center; justify-content: center;
  }
  @media (max-aspect-ratio: 1/1) {
    .pill.rail-pill {
      --cta-h: var(--btn-h);
      flex-direction: row;
      justify-content: center;
      width: var(--btn-w);
      height: var(--cta-h);
      padding: 0 calc(var(--cta-h) * 0.28) 0 var(--cta-g);
      gap: calc(var(--cta-h) * 0.18);
      font-size: calc(var(--cta-h) * 0.38);
    }
    .pill.rail-pill .pill-label { align-self: auto; display: block; }
  }

  /* Emplacement du bouton d'action du rail : HINT pendant le jeu, NEXT une fois le
     niveau résolu. Jamais visibles ensemble → superposés dans la même cellule de
     grille : NEXT apparaît pile à la place du HINT, sans décaler le rail. */
  #cta-slot { display: grid; place-items: center; }
  #cta-slot > button { grid-area: 1 / 1; }

  /* HINT (indice, rewarded ad) : pilule du rail en ORANGE (.pill-orange, couleur « pub »),
     clap dans la pastille (= « regarder une pub », comme les cartes verrouillées du menu).
     Masqué pendant la victoire / une pub (.hidden) en restant dans le flux (opacity 0)
     → le rail garde sa place, pas de saut. */
  #hint-icon { width: 78%; height: 78%; object-fit: contain; pointer-events: none; }
  /* Reflet « shling » qui balaie le bouton toutes les 3 s (sweep rapide ~0,6 s puis
     pause). Clippé par overflow:hidden, pointer-events:none → n'intercepte rien. */
  #hint-btn::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0;
    left: -60%;
    width: 45%;
    background: linear-gradient(100deg, transparent 0%, rgba(255,250,222,0.85) 50%, transparent 100%);
    transform: translateX(0) skewX(-16deg);
    pointer-events: none;
    animation: hintShine 3s ease-in-out infinite;
  }
  /* translateX (composité GPU) plutôt que left → pas de repaint par frame.
     L'opacité est mise à 0 hors du balayage : au repos, rien n'est visible (sinon
     un coin du reflet incliné dépasse en bas à droite à la position garée). */
  @keyframes hintShine {
    0%   { transform: translateX(0) skewX(-16deg);    opacity: 0; }
    3%   { opacity: 1; }
    17%  { opacity: 1; }
    20%  { transform: translateX(360%) skewX(-16deg); opacity: 0; }
    100% { transform: translateX(360%) skewX(-16deg); opacity: 0; }
  }
  /* Apparition (après 10 s) : scale up + bounce. Le rail gère le placement, l'anim
     ne fait donc que le scale. */
  #hint-btn.reveal { animation: railReveal 520ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards; }
  @keyframes railReveal {
    0%   { opacity: 0; transform: scale(0); }
    100% { opacity: 1; transform: scale(1); }
  }
  #hint-btn.hidden { opacity: 0; pointer-events: none; transform: scale(0.85); }
  /* Pub récompensée abandonnée / indisponible : petit « non » (secousse horizontale). */
  #hint-btn.nudge { animation: pillNudge 380ms ease; }
  @keyframes pillNudge {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(-7px); }
    45%      { transform: translateX(6px); }
    70%      { transform: translateX(-3px); }
  }

  /* NEXT : pilule du rail, flèche verte dans la pastille. Entrée = même scale up +
     bounce que le HINT, puis pulse. Le pulse anime la propriété `scale` (indépendante
     de `transform`) → il se compose avec l'anim d'entrée et l'enfoncement :active au
     lieu de les écraser (sans support, iOS < 14.5 : pas de pulse, rien d'autre ne change). */
  #next-btn { animation: nextPulse 1.4s ease-in-out infinite; }
  @keyframes nextPulse {
    0%, 100% { scale: 1; }
    50%      { scale: 1.06; }
  }
  #next-btn.reveal {
    animation: railReveal 520ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards,
               nextPulse 1.4s ease-in-out 520ms infinite;
  }
  #next-btn.hidden { opacity: 0; pointer-events: none; transform: scale(0.85); animation: none; }
  #next-icon { width: 62%; height: 62%; color: #12b981; pointer-events: none; }

  /* Bandeau d'intro plein écran à l'arrivée sur un niveau Bonus ou Hard. */
  #level-intro {
    position: fixed; inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 50;
    pointer-events: none;
    visibility: hidden;
  }
  #level-intro.show { visibility: visible; }
  #level-intro-backdrop {
    position: absolute; inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    background: radial-gradient(circle at 50% 45%, rgba(0,0,0,0.2), rgba(0,0,0,0.6));
    opacity: 0;
  }
  #level-intro.show #level-intro-backdrop { animation: introBackdrop 2400ms ease forwards; }
  #level-intro-banner {
    position: relative;
    display: flex; flex-direction: column; align-items: center;
    gap: 10px;
    text-align: center;
    opacity: 0;
    will-change: transform, opacity;
  }
  #level-intro.show #level-intro-banner {
    animation: introSeq 2400ms cubic-bezier(0.2, 0.9, 0.3, 1.25) forwards;
  }
  #level-intro-icon {
    height: clamp(140px, 38vmin, 240px);
    width: auto;
    display: block;
    filter: drop-shadow(0 4px 10px rgba(0,0,0,0.5)) drop-shadow(0 0 14px rgba(255,255,255,0.35));
  }
  #level-intro-title {
    font-family: var(--font-display); font-weight: 900;
    font-size: clamp(38px, 11vmin, 80px);
    letter-spacing: 3px;
    color: #fff;
    padding: 12px 40px;
    border-radius: 16px;
    white-space: nowrap;
    text-shadow: 0 3px 0 rgba(0,0,0,0.28);
  }
  #level-intro.hard #level-intro-title {
    background: linear-gradient(180deg, #ff7a4d 0%, #e6361f 58%, #aa180c 100%);
    box-shadow: 0 6px 0 rgba(0,0,0,0.28), 0 14px 34px rgba(226,52,32,0.6);
  }
  #level-intro.bonus #level-intro-title {
    background: linear-gradient(180deg, #ffe07a 0%, #f5a623 58%, #e07d12 100%);
    box-shadow: 0 6px 0 rgba(0,0,0,0.22), 0 14px 34px rgba(245,166,35,0.6);
  }
  @keyframes introBackdrop {
    0%   { opacity: 0; }
    14%  { opacity: 1; }
    72%  { opacity: 1; }
    100% { opacity: 0; }
  }
  @keyframes introSeq {
    0%   { opacity: 0; transform: scale(0.3); }
    12%  { opacity: 1; transform: scale(1.12); }
    20%  { transform: scale(1); }
    70%  { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.3); }
  }

  /* Écran interstitiel « NEW WORLD » à l'entrée d'un nouveau world. Overlay DOM
     plein écran, opaque, aux couleurs du world (mêmes variables --bg1/--bg2 que
     le fond). L'icône puis le texte arrivent, hold, puis fondu de sortie. */
  #new-world {
    position: fixed; inset: 0; top: 0; right: 0; bottom: 0; left: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: clamp(10px, 3vmin, 26px);
    z-index: 60;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    background: radial-gradient(circle at 50% 38%, var(--bg1, #fff3c0) 0%, var(--bg2, #f8d97b) 100%);
  }
  #new-world.show {
    visibility: visible;
    pointer-events: auto;
    animation: newWorldFade 2600ms ease forwards;
  }
  #new-world-icon {
    height: clamp(96px, 28vmin, 200px);
    width: auto;
    display: block;
    filter: drop-shadow(0 6px 12px rgba(0,0,0,0.3));
  }
  #new-world-text {
    font-family: var(--font-display); font-weight: 900;
    font-size: clamp(40px, 12vmin, 92px);
    letter-spacing: 3px;
    color: #fff;
    text-shadow: 0 4px 0 rgba(0,0,0,0.18), 0 0 22px rgba(255,255,255,0.35);
  }
  #new-world.show #new-world-icon { animation: nwIcon 2600ms cubic-bezier(0.2,0.9,0.3,1.3) forwards; }
  #new-world.show #new-world-text { animation: nwText 2600ms cubic-bezier(0.2,0.9,0.3,1.3) forwards; }
  @keyframes newWorldFade {
    0%   { opacity: 0; }
    6%   { opacity: 1; }
    82%  { opacity: 1; }
    100% { opacity: 0; }
  }
  @keyframes nwIcon {
    0%   { opacity: 0; transform: scale(0.2) translateY(12px); }
    14%  { opacity: 1; transform: scale(1.12) translateY(0); }
    22%  { transform: scale(1); }
    82%  { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.12); }
  }
  @keyframes nwText {
    0%   { opacity: 0; transform: translateY(50px); }
    18%  { opacity: 0; transform: translateY(50px); }
    36%  { opacity: 1; transform: translateY(0); }
    82%  { opacity: 1; transform: translateY(0); }
    100% { opacity: 0; transform: translateY(-22px); }
  }

  /* ===== Menu de sélection des niveaux =====
     Base : maquette Figma « Selection menu » (carte 304, marge 16, arrondi 32,
     illustration arrondie 16, écart 24, pastille 80, clap 112, PLAY 186×64), cotes en
     unités --u (largeur du menu / grille). Polish inspiré des saga maps (Royal Match,
     Candy Crush) et des apps de jigsaw :
       - un PANNEAU COLORÉ par world (palette du biome, comme le fond en jeu) avec sa
         barre de progression, + compteur global en haut de page ;
       - carte courante VIVANTE : la scénette tourne en vidéo, halo pulsé, reflet, PLAY
         qui rebondit ; cartes finies en couleur avec une coche « tampon » en coin (on
         voit la collection de scènes débloquées) ; cartes à venir voilées + clap ;
       - gros CTA PLAY collant en bas (l'action principale toujours à portée de pouce) ;
       - entrée en vague qui part de la carte courante ;
       - au retour d'une victoire : séquence animée « niveau fini → coche tamponnée →
         coche qui vole au compteur → carte suivante qui se débloque ».
     Perf : animations en transform/opacity uniquement (composité GPU), fond statique,
     UNE seule vidéo décodée (plateau en pause + rendu canvas suspendu tant que le menu
     le couvre, #stage masqué une fois le fondu d'entrée fini). */
  #level-menu {
    --cols: 4;
    /* 140vh : en paysage (desktop, mobile couché) on borne la largeur par la hauteur
       pour voir un world entier + le début du suivant au lieu d'une seule rangée. */
    --menu-w: min(1180px, 90vw, 130vh);
    --u: calc(var(--menu-w) / 1364); /* 4×304 + 3×28 (écart) + 2×32 (padding du panneau) */
    position: fixed; inset: 0;
    z-index: 65; /* au-dessus des intros / NEW WORLD (50-60), sous Settings (70) */
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
    /* Fond statique (bulles douces + dégradé Figma). Attaché à la boîte du scroller :
       il ne défile pas, les cartes glissent dessus (léger effet de profondeur, gratuit). */
    background:
      radial-gradient(circle at 7% 12%, rgba(255,255,255,0.55) 0 7vmin, transparent calc(7vmin + 1px)),
      radial-gradient(circle at 94% 26%, rgba(255,255,255,0.32) 0 12vmin, transparent calc(12vmin + 1px)),
      radial-gradient(circle at 10% 74%, rgba(255,255,255,0.28) 0 10vmin, transparent calc(10vmin + 1px)),
      radial-gradient(circle at 88% 90%, rgba(255,255,255,0.42) 0 6vmin, transparent calc(6vmin + 1px)),
      linear-gradient(180deg, #a7e4f7 0%, #e0f7d4 100%);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity 0.22s ease, visibility 0.22s;
    user-select: none; -webkit-user-select: none;
  }
  #level-menu.show { opacity: 1; visibility: visible; pointer-events: auto; }
  #level-menu img { -webkit-user-drag: none; }
  /* Souris : on peut glisser le menu pour le faire défiler (voir menuDrag, index.html). */
  @media (hover: hover) and (pointer: fine) { #level-menu { cursor: grab; } }
  #level-menu.dragging, #level-menu.dragging * { cursor: grabbing !important; }
  /* Écran étroit : 3 colonnes (3×304 + 2×28 + 2×32 = 1032) */
  @media (max-width: 600px) {
    #level-menu { --cols: 3; --u: calc(var(--menu-w) / 1032); }
  }
  /* Menu ouvert (fondu d'entrée fini) : le jeu dessous est entièrement couvert → on le
     masque et on gèle ses animations (pulse NEXT, reflet HINT) : plus rien à composer. */
  body.menu-open #stage { visibility: hidden; }
  body.menu-open #stage * { animation-play-state: paused !important; }

  /* --- Barre du haut : progression globale + réglages. Elle DÉFILE avec le contenu
     (pas collante, pas de bandeau) : seul le CTA reste fixe → le menu respire. --- */
  #lm-top {
    padding: max(18px, env(safe-area-inset-top)) 0 4px;
  }
  #lm-top-inner {
    width: var(--menu-w); margin: 0 auto;
    display: flex; align-items: center; justify-content: space-between;
  }
  #lm-top-inner > * { pointer-events: auto; }
  #lm-total {
    display: flex; align-items: center; gap: 6px;
    height: clamp(34px, 6.2vmin, 46px);
    padding: 0 16px 0 6px;
    border-radius: 999px;
    background: rgba(255,255,255,0.96);
    box-shadow: 0 3px 0 rgba(31,48,79,0.10), 0 6px 14px rgba(31,48,79,0.10);
    font-family: var(--font-display); font-weight: 900;
    font-size: clamp(16px, 3.1vmin, 22px); line-height: 1;
    color: #3a2a1e;
  }
  #lm-total img { height: 78%; width: auto; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.2)); }
  #lm-total-of { opacity: 0.5; }
  #lm-total.bump { animation: lmBump 460ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  /* Engrenage du menu : même skin rond et bleu que les boutons de l'en-tête en jeu. */
  #lm-settings { --cta-h: clamp(40px, 7vmin, 50px); --cta-b: 3px; }
  #lm-settings img { width: 58%; height: 58%; object-fit: contain; pointer-events: none; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.25)); }

  #level-menu-inner {
    width: var(--menu-w);
    margin: 0 auto;
    padding: 12px 0 calc(56 * var(--u));
    display: flex; flex-direction: column;
    gap: calc(56 * var(--u));
  }

  /* --- Panneau d'un world (palette du biome, mêmes couleurs que le fond en jeu) --- */
  .lm-world {
    --w1: #fff; --w2: #eee;
    display: flex; flex-direction: column;
    gap: calc(22 * var(--u));
    padding: calc(22 * var(--u)) calc(32 * var(--u)) calc(34 * var(--u));
    border-radius: calc(52 * var(--u));
    background: linear-gradient(165deg, var(--w1) 0%, var(--w2) 100%);
    box-shadow: 0 calc(14 * var(--u)) calc(30 * var(--u)) rgba(31, 48, 79, 0.16),
                inset 0 0 0 calc(4 * var(--u)) rgba(255, 255, 255, 0.35);
  }
  .lm-world[data-world="1"] { --w1: #d9f6b3; --w2: #7ec96a; }
  .lm-world[data-world="2"] { --w1: #cdf2ff; --w2: #74c6ec; }
  .lm-world[data-world="3"] { --w1: #ffe0a6; --w2: #ff9239; }
  .lm-world[data-world="4"] { --w1: #4d62a0; --w2: #18203c; }
  .lm-world[data-world="5"] { --w1: #fff4b0; --w2: #f5b81e; }
  .lm-header {
    display: flex; align-items: center; justify-content: space-between;
    gap: calc(16 * var(--u));
    min-height: calc(84 * var(--u));
  }
  .lm-title { display: flex; align-items: center; gap: calc(12 * var(--u)); }
  .lm-world-icon {
    width: calc(76 * var(--u)); height: calc(76 * var(--u)); object-fit: contain;
    filter: drop-shadow(0 calc(3 * var(--u)) calc(4 * var(--u)) rgba(0,0,0,0.25));
  }
  .lm-world.wake .lm-world-icon { animation: lmWake 700ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  .lm-world-label { height: calc(46 * var(--u)); width: auto; filter: drop-shadow(0 2px 2px rgba(0,0,0,0.22)); }
  .lm-world-num {
    font-family: var(--font-display); font-weight: 900;
    font-size: calc(54 * var(--u));
    line-height: 1;
    color: #fff;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.25));
  }
  /* Progression du world : mini barre + « 2/4 ». Or quand le world est bouclé. */
  .lm-wprog {
    display: flex; align-items: center; gap: calc(12 * var(--u));
    padding: calc(10 * var(--u)) calc(20 * var(--u));
    border-radius: 999px;
    background: rgba(255,255,255,0.94);
    box-shadow: 0 calc(4 * var(--u)) 0 rgba(31,48,79,0.10);
  }
  .lm-wbar {
    position: relative;
    width: calc(150 * var(--u)); height: calc(18 * var(--u));
    border-radius: 999px;
    background: rgba(58, 42, 30, 0.14);
    overflow: hidden;
  }
  .lm-wbar i {
    position: absolute; inset: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, #5ef58f, #13c98a);
    transform-origin: 0 50%;
    transform: scaleX(var(--p, 0));
    transition: transform 0.6s cubic-bezier(0.2, 0.9, 0.3, 1.15);
  }
  .lm-wcount {
    font-family: var(--font-display); font-weight: 900;
    font-size: calc(34 * var(--u)); line-height: 1;
    color: #3a2a1e;
    min-width: 2.2em; text-align: right;
  }
  .lm-wprog.bump { animation: lmBump 460ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  .lm-world.complete .lm-wprog { background: linear-gradient(180deg, #fff0a8 0%, #ffcf3f 100%); }
  .lm-world.complete .lm-wbar i { background: linear-gradient(180deg, #ffe066, #f39c12); }

  .lm-grid {
    display: grid;
    grid-template-columns: repeat(var(--cols), calc(304 * var(--u)));
    gap: calc(28 * var(--u));
  }

  /* --- Carte de niveau --- */
  .lm-card {
    position: relative;
    width: calc(304 * var(--u)); height: calc(304 * var(--u));
    padding: calc(16 * var(--u));
    border: 0; margin: 0;
    border-radius: calc(32 * var(--u));
    background: rgba(255, 255, 255, 0.98);
    box-shadow: 0 calc(10 * var(--u)) calc(20 * var(--u)) rgba(31, 48, 79, 0.14);
    cursor: pointer;
    transition: transform 0.14s ease;
  }
  /* Entrée en vague depuis la carte courante (--d posé en JS selon la distance).
     Classe retirée ensuite : sinon un autre `animation` (nudge) la relancerait en
     revenant à la valeur de base. */
  .lm-card.enter {
    animation: lmIn 440ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
    animation-delay: var(--d, 0ms);
  }
  /* Calque orange de la carte courante : un fondu d'opacité (un dégradé ne s'anime pas). */
  .lm-card::before {
    content: "";
    position: absolute; inset: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, #ff9d4a 0%, #e65100 100%);
    opacity: 0;
    transition: opacity 0.35s ease;
    pointer-events: none;
  }
  /* Halo pulsé de la carte courante. Anneau en box-shadow inset, pas en `border` : même
     fuite de la bordure arrondie animée dans Firefox que sur les pilules (lignes orange
     dans les coins de la carte), voir .pill. */
  .lm-card::after {
    content: "";
    position: absolute; inset: calc(-8 * var(--u));
    border-radius: calc(40 * var(--u));
    box-shadow: inset 0 0 0 calc(7 * var(--u)) rgba(255, 196, 92, 0.95);
    opacity: 0;
    pointer-events: none;
  }
  .lm-card:active { transform: scale(0.95); box-shadow: 0 calc(6 * var(--u)) calc(14 * var(--u)) rgba(31, 48, 79, 0.14); }
  @media (hover: hover) {
    .lm-card:hover { transform: translateY(calc(-6 * var(--u))); }
    .lm-card:hover .lm-ill { transform: scale(1.06); }
  }
  .lm-card:focus-visible { outline: 3px solid #fff; outline-offset: 3px; }
  @media (hover: hover) {
    #level-menu.dragging .lm-card:hover { transform: none; }
    #level-menu.dragging .lm-card:hover .lm-ill { transform: none; }
  }
  .lm-card.current { box-shadow: 0 calc(12 * var(--u)) calc(26 * var(--u)) rgba(230, 81, 0, 0.35); }
  .lm-card.current::before { opacity: 1; }
  .lm-card.current::after { animation: lmRing 1.8s ease-out infinite; }
  .lm-card.nudge { animation: lmNudge 380ms ease; }
  .lm-card.lift { z-index: 2; } /* éclats au-dessus des cartes voisines */

  /* Illustration (vignette, ou vidéo sur la carte courante), arrondie et clippée. */
  .lm-frame {
    position: relative; display: block;
    width: 100%; height: 100%;
    border-radius: calc(16 * var(--u));
    overflow: hidden;
    background: #dfeef2;
  }
  /* border-radius aussi sur l'image/la vidéo : vieux Safari ne clippe pas toujours un
     enfant composité (vidéo, zoom au survol) via l'overflow arrondi du parent. */
  .lm-ill, .lm-vid {
    position: absolute; inset: 0;
    display: block; width: 100%; height: 100%;
    object-fit: cover;
    border-radius: inherit;
  }
  .lm-ill { transition: transform 0.3s ease, opacity 0.45s ease, filter 0.45s ease; }
  .lm-vid { opacity: 0; transition: opacity 0.3s ease; }
  .lm-vid.on { opacity: 1; }
  .lm-card.locked .lm-ill { opacity: 0.5; filter: saturate(0.35); }
  /* Reflet qui balaie la carte courante toutes les 3 s (comme le HINT). */
  .lm-frame::after {
    content: "";
    position: absolute; top: 0; bottom: 0; left: -60%;
    width: 42%;
    background: linear-gradient(100deg, transparent 0%, rgba(255,255,255,0.55) 50%, transparent 100%);
    transform: translateX(0) skewX(-16deg);
    opacity: 0;
    pointer-events: none;
  }
  .lm-card.current .lm-frame::after { animation: lmShine 3.2s ease-in-out 0.8s infinite; }

  /* Numéro du niveau dans le world (comme les puces de l'en-tête). */
  .lm-num {
    position: absolute;
    top: calc(4 * var(--u)); left: calc(4 * var(--u));
    min-width: calc(64 * var(--u)); height: calc(64 * var(--u));
    padding: 0 calc(12 * var(--u));
    border-radius: 999px;
    background: #fff;
    box-shadow: 0 calc(4 * var(--u)) calc(8 * var(--u)) rgba(31, 48, 79, 0.16);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-display); font-weight: 900;
    font-size: calc(36 * var(--u)); line-height: 1;
    color: #3a2a1e;
    pointer-events: none;
  }
  .lm-card.current .lm-num { color: #e65100; }

  /* Centre de la carte : PLAY (courante) ou clap (à venir). Centré en flex pour laisser
     `transform` libre aux animations. */
  .lm-mid {
    position: absolute; inset: 0;
    display: grid; place-items: center;
    pointer-events: none;
  }
  .lm-mid > * { grid-area: 1 / 1; } /* PLAY et clap superposés (un seul visible) */
  .lm-play, .lm-clap {
    opacity: 0; transform: scale(0.4);
    transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  /* PLAY de la carte courante : skin .pill (comme le CTA), sans pastille. */
  .lm-play {
    --cta-h: calc(64 * var(--u));
    --cta-b: calc(5 * var(--u));
    width: calc(196 * var(--u));
    padding: 0 calc(14 * var(--u));
    font-size: calc(38 * var(--u));
  }
  .lm-clap { width: calc(112 * var(--u)); height: calc(112 * var(--u)); filter: drop-shadow(0 calc(4 * var(--u)) calc(6 * var(--u)) rgba(0,0,0,0.25)); }
  .lm-card.current .lm-play { opacity: 1; transform: scale(1); animation: lmBob 1.5s ease-in-out 0.6s infinite; }
  .lm-card.locked .lm-clap { opacity: 1; transform: scale(1); }
  /* Débloquée (par pub, ou prochain niveau quand ce n'est pas la courante) : vignette en
     couleur + PLAY fixe (sans le fond orange ni le rebond de la courante) → jouable. */
  .lm-card.unlocked .lm-play { opacity: 1; transform: scale(0.92); }
  .lm-card.nudge .lm-clap { animation: lmWiggle 380ms ease; }
  .lm-card.unlocking .lm-clap { animation: lmWiggle 420ms ease 2; }

  /* Coche « tampon » en coin : la vignette reste visible (collection de scènes). */
  .lm-check {
    position: absolute;
    right: calc(-10 * var(--u)); bottom: calc(-12 * var(--u));
    width: calc(104 * var(--u)); height: calc(104 * var(--u));
    opacity: 0; transform: rotate(-8deg) scale(0.5);
    filter: drop-shadow(0 calc(4 * var(--u)) calc(4 * var(--u)) rgba(0,0,0,0.22));
    pointer-events: none;
  }
  .lm-card.done .lm-check { opacity: 1; transform: rotate(-8deg) scale(1); }
  .lm-card.stamp .lm-check { animation: lmStamp 560ms cubic-bezier(0.34, 1.56, 0.64, 1); }

  /* Pastille Hard (tête de mort) / Bonus (étoile). */
  .lm-badge {
    position: absolute;
    top: calc(4 * var(--u)); right: calc(4 * var(--u));
    width: calc(80 * var(--u)); height: calc(80 * var(--u));
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 calc(4 * var(--u)) calc(8 * var(--u)) rgba(31, 48, 79, 0.16);
    display: flex; align-items: center; justify-content: center;
    pointer-events: none;
  }
  .lm-badge img { width: 75%; height: 75%; object-fit: contain; }
  .lm-card.current .lm-badge img { animation: lmWiggle 2.4s ease-in-out 1s infinite; }

  /* Éclats (tampon de la coche / déblocage) et coche volante vers le compteur. */
  .lm-spark {
    position: absolute; left: 50%; top: 50%;
    width: calc(26 * var(--u)); height: calc(26 * var(--u));
    margin: calc(-13 * var(--u)) 0 0 calc(-13 * var(--u));
    border-radius: 50%;
    background: radial-gradient(circle, #fff 0 30%, #ffe27a 45%, rgba(255,190,60,0) 70%);
    pointer-events: none;
    animation: lmSpark 620ms cubic-bezier(0.15, 0.7, 0.3, 1) forwards;
  }
  .lm-flyer {
    position: fixed; left: 0; top: 0;
    width: 44px; height: 44px;
    z-index: 4;
    pointer-events: none;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.25));
  }

  /* --- CTA PLAY collant en bas --- */
  #lm-cta-bar {
    position: sticky; bottom: 0; z-index: 3;
    display: flex; justify-content: center;
    /* Décollé du bas (+ zone sûre iPhone) ; voile léger plutôt qu'un bandeau opaque. */
    padding: 28px 0 max(26px, calc(env(safe-area-inset-bottom) + 16px));
    background: linear-gradient(0deg, rgba(224,247,212,0.88) 0%, rgba(224,247,212,0.5) 55%, rgba(224,247,212,0));
    pointer-events: none;
  }
  /* CTA : skin .pill + pastille = vignette ronde du niveau (concentrique à l'arrondi,
     quelle que soit la langue : seule la largeur suit le libellé, bornée par min-width). */
  #lm-cta {
    --cta-h: clamp(56px, 9.6vmin, 72px);
    pointer-events: auto;
    min-width: calc(var(--cta-h) * 3.1);
    padding-right: calc(var(--cta-h) * 0.48);
    gap: calc(var(--cta-h) * 0.2);
    font-size: clamp(22px, 4.6vmin, 34px);
    animation: lmCtaPulse 1.6s ease-in-out infinite;
  }
  #lm-cta.bump { animation: lmBump 460ms cubic-bezier(0.34, 1.56, 0.64, 1), lmCtaPulse 1.6s ease-in-out 0.5s infinite; }
  #lm-cta-thumb { background: #dfeef2; }

  /* --- Keyframes (transform/opacity uniquement) --- */
  @keyframes lmIn {
    0%   { opacity: 0; transform: translateY(calc(26 * var(--u))) scale(0.86); }
    100% { opacity: 1; transform: none; }
  }
  @keyframes lmRing {
    0%   { opacity: 0.95; transform: scale(0.97); }
    75%  { opacity: 0; transform: scale(1.1); }
    100% { opacity: 0; transform: scale(1.1); }
  }
  @keyframes lmShine {
    0%   { transform: translateX(0) skewX(-16deg);    opacity: 0; }
    4%   { opacity: 1; }
    18%  { opacity: 1; }
    22%  { transform: translateX(380%) skewX(-16deg); opacity: 0; }
    100% { transform: translateX(380%) skewX(-16deg); opacity: 0; }
  }
  @keyframes lmBob {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.08); }
  }
  @keyframes lmCtaPulse {
    0%, 100% { scale: 1; }
    50%      { scale: 1.05; }
  }
  @keyframes lmBump {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.22); }
    100% { transform: scale(1); }
  }
  @keyframes lmStamp {
    0%   { opacity: 0; transform: rotate(-38deg) scale(2.6); }
    55%  { opacity: 1; transform: rotate(-4deg) scale(0.86); }
    100% { opacity: 1; transform: rotate(-8deg) scale(1); }
  }
  @keyframes lmWiggle {
    0%, 100% { transform: rotate(0) scale(1); }
    25%      { transform: rotate(-12deg) scale(1.06); }
    50%      { transform: rotate(10deg) scale(1.06); }
    75%      { transform: rotate(-6deg) scale(1.03); }
  }
  @keyframes lmNudge {
    0%, 100% { transform: translateX(0); }
    20%      { transform: translateX(calc(-10 * var(--u))); }
    45%      { transform: translateX(calc(8 * var(--u))); }
    70%      { transform: translateX(calc(-5 * var(--u))); }
  }
  @keyframes lmWake {
    0%   { transform: scale(1) rotate(0); }
    35%  { transform: scale(1.35) rotate(-10deg); }
    65%  { transform: scale(0.95) rotate(6deg); }
    100% { transform: scale(1) rotate(0); }
  }
  @keyframes lmSpark {
    0%   { opacity: 1; transform: translate(0, 0) scale(0.4); }
    60%  { opacity: 1; }
    100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1); }
  }
  /* Confort : mouvements réduits → pas d'animations continues ni de vague d'entrée. */
  @media (prefers-reduced-motion: reduce) {
    .lm-card.enter, .lm-card::after, .lm-frame::after, .lm-play, .lm-badge img, #lm-cta, .lm-world-icon {
      animation: none !important;
    }
  }

