/* Système de design « magazine imprimé » — DESIGN-SYSTEM.md (2026-07-22).
 * Le chrome recule pour que l'illustration porte la valeur : papier crème,
 * encre, UNE SEULE encre d'accent (vermillon). Fraunces (display) + Public Sans
 * (texte/UI). Règles non négociables : aucune ombre portée (filets 1px et
 * valeur de fond uniquement), coins ≤4px (2px sur les cases de grille), marge
 * unique 20px en fer à gauche, pas de couleurs sémantiques.
 * Accessibilité (héritée) : cibles ≥48px, contraste soigné, tailles en rem,
 * prefers-reduced-motion coupe les animations. */
:root {
  /* papier */
  --bg: #F4EDE0;           /* fond global, crème imprimé (ex-lavande) */
  --paper-deep: #EBE1CF;   /* zones creusées : clavier, tiroir */
  --surface: #FBF7EF;      /* carte, grille — plus clair que le fond */
  --line: #D6C9B0;         /* filets (ex-lavande clair) */

  /* encre */
  --ink: #1E241F;          /* texte principal, noir tirant vert (ex-violet foncé) */
  --muted: #6B6A5E;        /* secondaire, légendes, folio (ex-violet doux) */

  /* accent — UNIQUE, jamais de 2e couleur sémantique */
  --purple: #D6482B;   --purple-d: #B83A21;    /* vermillon d'imprimerie */
  --accent: var(--purple);
  --focus: #F6DDD4;        /* aplat 12% : case active, surlignage (ex-lavande clair) */
  --ok: var(--ink);        /* validé = poids + aplat, jamais une teinte "succès" */

  /* alias legacy conservés pour les écrans pas encore repris (chest/rank/league/
     splash) — pointent tous sur l'encre ou l'accent unique, plus de rose/cyan/
     vert vifs. À supprimer quand ces écrans seront repris. */
  --cyan: var(--ink);      --cyan-d: var(--ink);
  --pink: var(--purple);   --pink-d: var(--purple-d);
  --green: var(--ink);     --green-d: var(--ink);   --green-sh: var(--ink);
  --gold: var(--purple);   --gold-d: var(--purple-d);  --gold-ink: var(--surface);
  --croise: var(--purple); --meles: var(--purple); --ana: var(--purple); --motus: var(--purple);

  --r-card: 4px;
  --r-chip: 4px;
  --tap: 48px;
  --sh-btn: none;           /* zéro ombre portée : le relief se fait par filet */
  --shadow: none;

  --display: "Fraunces", Georgia, serif;
  --text: "Public Sans", system-ui, sans-serif;
  --m: 20px;                /* marge unique, jamais négociée */
}

* {
  box-sizing: border-box; -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none; user-select: none;   /* jeu, pas une page à copier */
  -webkit-touch-callout: none;                     /* pas de menu au long-press */
}

html { font-size: 16px; }             /* corps ≥16px, tout en rem */

body {
  margin: 0;
  overflow-x: hidden;                 /* aucune barre horizontale : le contenu se cale sur la largeur écran */
  color: var(--ink);
  background: var(--bg);              /* papier plat — plus de halo dégradé */
  font-family: var(--text);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

button {
  font: inherit; font-weight: 600;
  min-height: var(--tap);
  min-width: var(--tap);
  border: none;
  border-radius: var(--r-chip);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  /* Supprime le délai de ~300 ms + le double-tap-zoom : le `click` part
   * immédiatement, donc la page n'a pas le temps de défiler entre le toucher
   * et le clic (cause n°1 des taps qui atterrissent sur la touche d'à-côté). */
  touch-action: manipulation;
}
button:focus-visible { outline: 3px solid var(--purple); outline-offset: 2px; }


@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/* --- Grille (core/grid-view.js) --------------------------------------------- */
.grid {
  --cell: min(11vw, 44px);
  display: grid;
  gap: 4px;
  justify-content: center;
  margin: 0 auto;
}
.cell {
  position: relative;
  width: var(--cell);
  height: var(--cell);
  min-width: 0; min-height: 0;          /* la cible tactile 48px est la case entière + gap */
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--surface);
  font-weight: 600;
  font-family: var(--text);
  font-size: calc(var(--cell) * 0.5);
  line-height: 1;
  text-transform: uppercase;
  display: flex; align-items: center; justify-content: center;
}
.cell-gap { border: none; background: transparent; visibility: hidden; }
/* Case noire d'une grille dense. Aplat d'encre, pas de filet : c'est la forme du bloc
 * qui porte la lecture, et un contour ferait un damier bruyant. `--ink` plutôt qu'un
 * noir pur pour rester dans l'encre du magazine (noir tirant vert du :root). */
.cell-black { border: none; background: var(--ink); }
.cell-num {
  position: absolute; top: 1px; left: 3px;
  font-size: calc(var(--cell) * 0.24); font-weight: 600; color: var(--muted);
}
/* Remplie mais PAS validée (lettre tapée, mot en cours ou faux après shake()) : seul le
   filet s'assombrit vers --muted, jamais un aplat ni --purple — ces deux-là restent le
   monopole du mot ACTIF (.hl/.cursor, juste après) et du résolu (.ok, plus bas). Sans
   cette règle, la case se confondait avec une case vide dès qu'on quittait le mot :
   même filet --line, seul le glyphe différait. Toujours « le poids fait le travail, pas
   une couleur » — priorité de cascade : filled < hl/cursor < ok, dans cet ordre du fichier. */
.cell.filled { border-color: var(--muted); }
.cell.hl { background: var(--focus); border-color: var(--purple); }
.cell.cursor { border-color: var(--purple); background: var(--focus); box-shadow: inset 0 0 0 1px var(--purple); }
/* Résolu : le poids fait le travail, pas une couleur « succès » — filet accent
   + aplat léger, jamais de vert. */
.cell.ok {
  background: var(--focus); color: var(--ink); border-color: var(--purple); font-weight: 700;
  animation: cell-bounce 0.35s ease both;
  animation-delay: calc(var(--i, 0) * 60ms);
}
.cell.pop { animation: cell-pop 0.16s ease; }
.cell.shake { animation: cell-shake 0.3s ease; }
/* Apparition de la grille : chaque case tombe et se pose, en vague diagonale
   (--ei = distance à l'angle haut-gauche). Bornée : ~24 pas max * 34ms < 1 s. */
.cell.cell-enter { animation: cell-drop 0.42s cubic-bezier(.2,.9,.3,1.2) both; animation-delay: calc(var(--ei, 0) * 34ms); }
/* Dial de révélation : lettre offerte au démarrage, « bloup » en cascade (--ri).
   Déclaré APRÈS .cell-enter (même spécificité) pour gagner sur une case neuve. */
.cell.reveal { animation: cell-reveal 0.32s cubic-bezier(.2,.9,.3,1.4) both; animation-delay: calc(var(--ri, 0) * 90ms); }
@keyframes cell-reveal { 0% { transform: scale(.5); } 60% { transform: scale(1.2); } 100% { transform: scale(1); } }

@keyframes cell-pop   { 0% { transform: scale(1); } 50% { transform: scale(1.18); } 100% { transform: scale(1); } }
@keyframes cell-bounce{ 0% { transform: scale(0.6); } 60% { transform: scale(1.15); } 100% { transform: scale(1); } }
@keyframes cell-shake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
@keyframes cell-drop  { 0% { opacity: 0; transform: translateY(-18px) scale(.4) rotate(-8deg); } 70% { opacity: 1; transform: translateY(2px) scale(1.05); } 100% { opacity: 1; transform: none; } }
/* Case « croquée » par le Glouton : petit flash avant l'éclat de particules. */
.cell.chomped { animation: cell-chomp 0.28s ease both; }
@keyframes cell-chomp { 0% { transform: scale(1); } 40% { transform: scale(1.25); filter: brightness(1.6); } 100% { transform: scale(1); } }

/* --- Clavier (core/keyboard.js) --------------------------------------------- */
/* Le clavier reste collé en bas : la grille défile au-dessus, mais les touches
 * ne bougent jamais -> plus de tap qui atterrit à côté à cause de l'inertie du
 * scroll. Le clavier se lit comme le bas de page d'un cahier : fond creusé,
 * touches à filet, aucune ombre 3D. */
.kb-dock { position: sticky; bottom: 0; z-index: 3; background: var(--paper-deep); border-top: 1px solid var(--line); padding-bottom: env(safe-area-inset-bottom, 0); }
.kb { display: flex; flex-direction: column; gap: 6px; padding: 10px 8px; max-width: 520px; margin: 0 auto; }
.kb-row { display: flex; gap: 5px; justify-content: center; }
.kb-key {
  flex: 1 1 0; height: var(--tap); min-width: 0;
  border-radius: 3px; border: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  font-family: var(--text); font-weight: 500; font-size: 1.05rem;
}
.kb-key:active { transform: translateY(1px); background: var(--focus); }
.kb-wide { flex: 1.5 1 0; background: var(--ink); color: var(--surface); border-color: var(--ink); }

/* --- Shells de jeu communs -------------------------------------------------- */
/* min-width: 0 — le shell de jeu est un ITEM FLEX (main est en flex). Sans ça, sa
   largeur mini est celle de son contenu : la grille poussait la page au-delà du
   viewport, le navigateur recalait le scroll horizontal sur la case qui prend le
   focus, et tout le layout « allait et venait » d'un tap à l'autre. */
/* width: 100% obligatoire. `main` est un flex COLONNE : les marges auto y annulent
   le `stretch`, la largeur du shell tombait donc sur son max-content — la grille
   poussait la page au-delà du viewport, le navigateur recalait le scroll horizontal
   sur la case qui prend le focus, et le layout « allait et venait » d'un tap à
   l'autre. Avec width:100% + min-width:0, le shell ne dépasse jamais l'écran. */
.game { width: 100%; max-width: 560px; margin: 0 auto; min-width: 0; }
/* La définition en cours = un chapô de magazine, pas un badge de jeu vidéo :
   filet double au-dessus/dessous, fond papier, encre. */
.clue-bar {
  position: sticky; top: 0; z-index: 2;
  margin: 2px var(--m) 0; border-radius: 0;
  border-top: 2px solid var(--ink); border-bottom: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink); font-family: var(--text); font-weight: 500;
  padding: 10px 4px; text-align: center;
  display: flex; align-items: center; gap: 10px; justify-content: center;
}
.game-crossword { --accent: var(--purple); }
.clue-bar .nav { background: var(--surface); border: 1px solid var(--line); color: var(--ink); min-width: 40px; min-height: 40px; font-size: 1.2rem; border-radius: 3px; }

/* Thème du mot en cours (2026-07-25). Affiché UNIQUEMENT quand il diffère du thème de la
   grille — un mot du thème dominant n'apprendrait rien au joueur, qui le lit déjà dans le
   titre, et l'afficher partout diluerait la difficulté des forces 3-4.
   Même patron que .game-living .folio (filet gauche 1 px, flex figé) mais en encre douce
   et en italique : c'est une mention de bas de page, pas une donnée de jeu. Règle séparée
   à dessein — mutualiser avec .folio ferait porter un risque de régression au Grand Cahier
   pour six lignes de CSS.
   ZÉRO pixel de chrome ajouté : il vit sur la ligne existante. */
.game-crossword .clue-bar { justify-content: flex-start; text-align: left; }
/* La définition tient sur DEUX lignes (2026-07-27) : en une seule, l'ellipse mangeait la
   moitié des indices sur mobile. min-height figé à 2 lignes pour que la grille ne saute
   pas d'un mot à l'autre. */
.game-crossword .clue {
  flex: 1 1 auto; min-width: 0;
  line-height: 1.35; min-height: 2.7em;
  white-space: normal; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.clue-theme {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  margin-left: 10px; padding-left: 10px;
  border-left: 1px solid var(--line);
  font-family: var(--text); font-style: italic; font-size: 0.8125rem;
  color: var(--muted); line-height: 1; white-space: nowrap;
}
.clue-theme:empty { display: none; }        /* pas de filet orphelin quand il n'y a rien à dire */
.msg { text-align: center; min-height: 1.4em; margin: 12px 0; font-weight: 700; color: var(--ink); }

/* ── Le Grand Cahier (grille vivante, 2026-07-25) ────────────────────────────
   Règle de fer : ZÉRO pixel de chrome ajouté. Le compteur et la barre de
   progression se logent dans la .clue-bar existante et dans son filet inférieur.
   La grille est déjà à ~26 px/case sur un téléphone : tout bandeau supplémentaire
   la ferait passer sous le seuil de lisibilité. */
.game-living { --accent: var(--purple); }
.game-living .clue-bar { justify-content: flex-start; text-align: left; }
.game-living .clue { flex: 1 1 auto; min-width: 0; }

/* Folio de bout de ligne : « 23/54 ». tabular-nums est obligatoire, sinon le
   chiffre danse à chaque mot trouvé et attire l'œil hors de la grille. */
.game-living .folio {
  flex: 0 0 auto; margin-left: 10px; padding-left: 10px;
  border-left: 1px solid var(--line);
  font-family: var(--text); font-weight: 600; font-size: 0.875rem;
  font-variant-numeric: tabular-nums; color: var(--ink);
  line-height: 1; white-space: nowrap;
}
.game-living .folio-total { color: var(--muted); font-weight: 400; }
/* Accusé de réception d'un mot trouvé : un bump de graisse, pas de nombre
   flottant (vocabulaire arcade, hors charte magazine). tabular-nums garantit
   qu'aucun déplacement de layout n'en découle. */
.game-living .folio.tick { animation: folio-tick 140ms ease-out; }
@keyframes folio-tick { 0% { font-weight: 600 } 45% { font-weight: 800; color: var(--purple) } 100% { font-weight: 600 } }

/* La barre de progression EST le filet inférieur de la clue-bar, qu'on charge
   d'encre. Une règle d'imprimeur qui avance sous le chapô — coût : 0 px. */
.game-living .clue-bar::after {
  content: ""; position: absolute; left: 0; bottom: -1px; height: 1px;
  width: calc(var(--prog, 0) * 100%); background: var(--ink);
  transition: width 320ms cubic-bezier(.33, 0, .67, 1);
}
/* Jalon de vague : la clue-bar est vide pendant la transition, elle porte donc
   l'intertitre. Centré ici seulement — c'est un titre, pas du texte courant. */
.game-living .clue-bar.wave-title .clue {
  font-family: var(--display); font-weight: 600; font-size: 1rem;
  letter-spacing: .18em; text-transform: uppercase; text-align: center;
}
/* Récolte : la grille s'efface d'un bloc, sans mouvement (scale/opacity seuls). */
.game-living .board.wave-out { opacity: 0; transition: opacity 260ms cubic-bezier(.33, 0, .67, 1); }
.game-living .board { transition: opacity 200ms linear; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* prefers-reduced-motion : la règle globale (plus haut) écrase toute animation à
   0.001ms. Appliquée telle quelle ici, la grille serait REMPLACÉE d'un coup —
   exactement la sensation « un bug a effacé mon travail », et pour les personnes
   qui en ont le plus besoin. On garde donc un fondu (l'opacité n'est pas
   vestibulaire) et on ne tue que le mouvement. */
@media (prefers-reduced-motion: reduce) {
  .game-living .board.wave-out,
  .game-living .board { transition: opacity 200ms linear !important; }
}

/* Sélecteur de force (mots croisés) */
.force-bar { display: flex; gap: 8px; justify-content: center; padding: 10px; flex-wrap: wrap; }
.force-chip { padding: 6px 12px; border-radius: 3px; border: 1px solid var(--line); background: var(--surface); font-weight: 600; letter-spacing: 2px; color: var(--muted); }
.force-chip.on { background: var(--purple); border-color: var(--purple); color: var(--surface); }
.board { padding: 8px 6px; }

/* Mots mêlés */
.game-wordsearch { --accent: var(--meles); padding: 12px; }
/* --cell dépend du nb de colonnes (posé par wordsearch.js) : sans ça une grille large
   (9+ colonnes) déborde de l'écran (13vw fixe * N colonnes > largeur dispo). */
.ws-grid { display: grid; gap: 4px; justify-content: center; }
.ws-grid .cell { border-color: var(--line); }
.ws-grid .cell.sel { background: var(--focus); border-color: var(--accent); }
.ws-grid .cell.ok { background: linear-gradient(180deg, var(--cyan), var(--cyan-d)); color: #fff; border-color: var(--cyan-d); }
.ws-words { list-style: none; display: flex; flex-wrap: wrap; gap: 8px 16px; justify-content: center; padding: 14px 0 0; font-weight: 700; }
.ws-words li.done { color: var(--muted); text-decoration: line-through; }
/* Liste masquée : jetons « · · · » tant que le mot n'est pas trouvé/indicé. */
.ws-words li.veiled { color: var(--muted); background: #EEE8FA; border-radius: 10px;
  padding: 2px 10px; letter-spacing: 2px; user-select: none; }
.ws-words li.hinted { color: var(--purple-d); font-weight: 800; }
.ws-count { text-align: center; font-weight: 800; font-size: .85rem; color: var(--muted); margin: 2px 0 8px; }

/* Anagramme */
.game-anagram { --accent: var(--ana); padding: 16px; text-align: center; }
.ana-clue { font-size: 1.15rem; font-weight: 700; }
.ana-scramble { display: flex; gap: 6px; justify-content: center; margin: 16px 0; flex-wrap: nowrap; }
.tile { width: min(44px, 11vw); height: min(52px, 13vw); flex: 0 1 auto; display: flex; align-items: center; justify-content: center; background: linear-gradient(180deg, var(--gold), var(--gold-d)); color: var(--gold-ink); border-radius: var(--r-chip); font-weight: 900; font-size: clamp(0.85rem, 4vw, 1.3rem); box-shadow: inset 0 2px 3px rgba(255,255,255,.5), 0 3px 0 #B45309; }
.ana-slots { display: flex; gap: 6px; justify-content: center; flex-wrap: nowrap; }
.slot { width: min(44px, 11vw); height: min(52px, 13vw); flex: 0 1 auto; display: flex; align-items: center; justify-content: center; border: 2px solid var(--line); border-radius: var(--r-chip); background: #fff; font-weight: 900; font-size: clamp(0.85rem, 4vw, 1.3rem); }
.slot.cursor { border-color: var(--purple); background: var(--focus); }
.slot.ok { background: linear-gradient(180deg, var(--green), var(--green-d)); color: #fff; border-color: var(--green-d); box-shadow: inset 0 2px 3px rgba(255,255,255,.35); }

/* Motus */
.game-motus { --accent: var(--motus); padding: 12px; text-align: center; }
.motus-clue { font-weight: 700; }
.motus-clue.veiled { color: var(--muted); font-weight: 700; font-style: italic; opacity: .85; }
/* Colonnes fluides : les cases rétrécissent pour tenir dans la largeur (mots longs
 * comme « casserole »), sans jamais dépasser 46px pour les mots courts. */
.motus-board { display: flex; flex-direction: column; gap: 5px; margin: 12px auto; width: 100%; max-width: calc(var(--n) * 46px + (var(--n) - 1) * 5px); }
.motus-row { display: grid; grid-template-columns: repeat(var(--n), 1fr); gap: 5px; }
.motus-cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; border: 2px solid var(--line); border-radius: 12px; background: #fff; font-weight: 900; font-size: 1.3rem; text-transform: uppercase; }
.motus-cell.cursor { border-color: var(--purple); background: var(--focus); }
.motus-cell.ok { background: linear-gradient(180deg, var(--green), var(--green-d)); color: #fff; border-color: var(--green-d); box-shadow: inset 0 2px 3px rgba(255,255,255,.35); }
.motus-cell.present { background: linear-gradient(180deg, var(--gold), var(--gold-d)); color: var(--gold-ink); border-color: var(--gold-d); }
.motus-cell.absent { background: #C4B8DE; color: #fff; border-color: #C4B8DE; }
.motus-cell.flip { animation: motus-flip .26s ease; }
@keyframes motus-flip { 0% { transform: rotateX(0); } 50% { transform: rotateX(90deg); } 100% { transform: rotateX(0); } }

.shake { animation: cell-shake 0.3s ease; }

/* --- Journal du jour -------------------------------------------------------- */
/* Le bandeau d'un numéro de magazine : wordmark + date, filet dessous, pas de
   cartouche coloré. Croquy y vit en petit, comme une vignette d'illustrateur —
   calmé sans être supprimé (DESIGN-SYSTEM 2026-07-22). */
.masthead { padding: 14px var(--m) 12px; margin-bottom: 4px; border-bottom: 1px solid var(--line); }
.hero {
  display: flex; align-items: center; gap: 12px;
  padding: 0; border-radius: 0;
  background: none; box-shadow: none;
  position: relative; overflow: visible;
}
.hero::after { content: none; }
.croquy {
  flex: none; font-size: 40px; line-height: 1; position: relative;
  filter: none;
  animation: none;
}
.croquy img { display: block; width: 44px; height: 44px; object-fit: contain; }
.croquy .croquy-dodo, .croquy .croquy-happy { display: none; }   /* spécificité > .croquy img (sinon les mascottes s'empilent) */
.hero-body { flex: 1; position: relative; min-width: 0; }
.masthead .date { display: block; color: var(--muted); font-weight: 600; font-size: .72rem;
  letter-spacing: .1em; text-transform: uppercase; }
.jj-title { font-family: var(--display); font-variation-settings: "opsz" 60, "SOFT" 20, "WONK" 1;
  font-size: 1.55rem; font-weight: 600; color: var(--ink); letter-spacing: -.01em; line-height: 1.05; margin: 2px 0 0; }
.hero-stats { display: flex; gap: 6px; margin-top: 8px; min-width: 0; }
/* Ligue = seul pill élastique : son nom peut être long (« Cruciverbiste III · 1180 »).
   Il rétrécit avec ellipsis pour que Crédits reste toujours visible (plus de troncature). */
#league-pill { flex: 0 1 auto; min-width: 0; overflow: hidden; }
#league-pill #league-rank { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jj-sub { padding: 10px 4px 0; color: var(--muted); font-weight: 500; font-size: .9rem; }

/* Pastilles : filet + texte, plus de rainbow par pastille. Une seule respire
   (série) — la répétition quotidienne est ce qui mérite l'appel visuel. */
.pill {
  display: inline-flex; align-items: center; gap: 4px; flex: none;
  border-radius: 3px; border: 1px solid var(--line); padding: 5px 10px; font-size: .82rem;
  font-family: var(--text); font-weight: 600; color: var(--ink); background: var(--surface);
  transition: transform .1s ease;
}
#streak-pill  { color: var(--purple); border-color: var(--purple); cursor: pointer; }
#credits-pill { cursor: pointer; }
#league-pill  { cursor: pointer; }

.cards-head { display: flex; align-items: center; justify-content: space-between; padding: 18px var(--m) 6px; max-width: 560px; margin: 0 auto; }
.ch-title { font-size: .72rem; font-weight: 600; color: var(--muted); letter-spacing: .12em; text-transform: uppercase; }

/* Grille dorée du week-end (partie spéciale) : carte premium sur le journal. */
/* Grille dorée du week-end : édition spéciale, pas un jeu vidéo. Un seul liseré
   ambre (comme un cachet de cire sur une couverture) — c'est l'exception assumée
   à la règle « un seul accent », au même titre que le badge de rang. Tout le
   reste (fond, texte) reste sur le système papier/encre. */
.special { max-width: 560px; margin: 8px auto 0; width: calc(100% - 2 * var(--m)); display: flex; align-items: center; gap: 12px;
  position: relative; overflow: hidden; padding: 13px 15px; border-radius: 0; text-align: left;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-left: 3px solid #C88A2E;
  box-shadow: none; animation: none; }
.special .sp-glow { display: none; }
.special .sp-ic { font-size: 26px; color: #C88A2E; }
.special .sp-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.special .sp-name { font-family: var(--display); font-variation-settings: "opsz" 24, "WONK" 1; font-weight: 600; font-size: 1rem; }
.special .sp-desc { font-weight: 500; font-size: .78rem; opacity: .85; color: var(--muted); }
.special .sp-go { font-weight: 600; color: #C88A2E; white-space: nowrap; }
#game-view.golden { background: var(--bg); }
#game-view.golden .gh-mid > span:first-child { color: #C88A2E; }

/* Voyage « Atlas des mots » : carte destination (journal) + écran Atlas. */
.theme-art { position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center;
  border-radius: 14px; color: #fff; flex: none; }
.theme-art b { font-size: 1.3rem; font-weight: 900; text-shadow: 0 1px 4px rgba(0,0,0,.25); }
.theme-art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.theme-art.unknown { background: #E4DDF4; color: var(--muted); font-weight: 900; font-size: 1.2rem; }
/* Bannière d'étape (accueil) — couche « coup d'oeil ». Illustration plein cadre +
   voile : la typo reste lisible quelle que soit la luminance de l'image générée
   (ou de la tuile de repli, dont la teinte est dérivée du nom). */
.journey { position: relative; max-width: 560px; margin: 14px auto 2px; width: calc(100% - 2 * var(--m));
  min-height: 104px; display: block; overflow: hidden;
  background: var(--paper-deep); border-radius: 0; border-top: 2px solid var(--ink);
  padding: 0; box-shadow: none;
  border-left: none; border-right: none; border-bottom: none;
  font-family: var(--text); color: #fff; text-align: left; transition: transform .08s ease; }
.journey:active { transform: translateY(1px); }
.journey .j-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.journey .j-veil { position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(30,36,31,.28), rgba(30,36,31,.72)); }
.journey .j-body { position: relative; display: flex; flex-direction: column; gap: 5px;
  padding: 14px 16px; text-shadow: 0 2px 10px rgba(30,36,31,.5); }
/* Hiérarchie INVERSÉE : le numéro d'étape domine, le nom de lieu est le sous-titre. */
.journey .j-kicker { font-family: var(--text); font-weight: 700; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase;
  display: flex; align-items: center; gap: 6px; }
.journey .j-name { font-family: var(--display); font-variation-settings: "opsz" 40, "WONK" 1;
  font-weight: 600; font-size: 1.3rem; opacity: 1; margin-top: 1px; }
/* Sur illustration, la piste doit rester lisible quel que soit le fond : voile
   sombre + liseré clair, comme la typo. */
.journey .q-track { margin: 3px 0 0; display: block; background: rgba(43,35,64,.45);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.45); }
.journey .j-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.journey .j-left { font-weight: 800; font-size: .78rem; opacity: .92; }
.journey .j-prog { font-weight: 900; font-size: 1rem; white-space: nowrap; }
.journey .j-prog i { font-style: normal; font-size: .75rem; opacity: .8; }
.atlas-scroll { max-width: 560px; margin: 0 auto; padding: 18px 16px 110px; }
.atlas-head h2 { font-family: var(--display); font-variation-settings: "opsz" 30, "WONK" 1;
  margin: 0 0 2px; font-size: 1.2rem; font-weight: 600; }
.atlas-sub { margin: 0 0 14px; font-weight: 500; font-size: .82rem; color: var(--muted); }
.atlas-list { display: flex; flex-direction: column; gap: 10px; }
.atlas-dest { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card); padding: 12px 14px; box-shadow: none; }
.atlas-dest .a-art { width: 46px; height: 46px; }
.atlas-dest .a-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.atlas-dest .a-name { font-family: var(--display); font-variation-settings: "opsz" 20, "WONK" 1; font-weight: 600; font-size: .98rem; }
.atlas-dest .a-state { font-weight: 500; font-size: .76rem; color: var(--muted); }
.atlas-dest .q-track { display: block; margin: 2px 0 0; }
.atlas-dest.next { opacity: .6; }
.atlas-dest.cur { border: 1px solid var(--purple); box-shadow: none; }
.atlas-dest .a-play { flex-basis: 100%; border: none; border-radius: 3px; padding: 12px;
  font-family: var(--text); font-weight: 600; font-size: .88rem; color: var(--surface);
  background: var(--purple); box-shadow: none; }
.atlas-dest .a-play:active { transform: translateY(1px); }
.reward-ic .theme-art { width: 84px; height: 84px; margin: 0 auto; }

/* Numéro d'étape + fil vertical de l'itinéraire : c'est le trait qui fait lire
   « route » au lieu de « catalogue de thèmes ». */
.atlas-dest { position: relative; }
.atlas-dest .a-step { width: 24px; height: 24px; flex: none; display: grid; place-items: center;
  border-radius: 50%; background: var(--purple); color: var(--surface); font-weight: 700; font-size: .76rem; }
.atlas-dest.next .a-step { background: var(--line); color: var(--muted); }
.atlas-list { position: relative; }
.atlas-list::before { content: ""; position: absolute; left: 27px; top: 12px; bottom: 12px; width: 1px;
  background: var(--line); opacity: 1; border-radius: 0; }

/* Héros de l'écran Atlas : la destination courante en pleine largeur. */
.atlas-hero { position: relative; height: 150px; border-radius: 0; overflow: hidden;
  margin-bottom: 12px; box-shadow: none; background: var(--paper-deep); }
.atlas-hero img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.atlas-hero .ah-veil { position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 30%, rgba(30,36,31,.65)); }
.atlas-hero .ah-body { position: absolute; left: 14px; right: 14px; bottom: 10px; color: #fff;
  display: flex; flex-direction: column; text-shadow: 0 1px 8px rgba(30,36,31,.6); }
.atlas-hero .ah-kicker { font-weight: 600; font-size: .7rem; letter-spacing: .12em; text-transform: uppercase; opacity: .9; }
.atlas-hero .ah-name { font-family: var(--display); font-variation-settings: "opsz" 40, "WONK" 1; font-weight: 600; font-size: 1.35rem; }

/* Toile de fond du jeu : l'illustration du thème en filigrane derrière le header. */
.game-header { position: relative; }
.game-header::before { content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: var(--game-art, none); background-size: cover; background-position: center 30%;
  opacity: .16; mask: linear-gradient(180deg, #000 60%, transparent); }
#game-view.golden .game-header::before { background-image: none; }

/* « Destination tamponnée » : le timbre en grand, qui claque comme un vrai tampon. */
.stamp-card { position: relative; width: min(92vw, 400px); background: #fff; border-radius: var(--r-card);
  padding: 22px 18px 18px; text-align: center; box-shadow: 0 24px 60px -18px rgba(43,35,64,.55); }
.stamp-title { margin: 0 0 14px; font-size: 1.25rem; }
.stamp-frame { position: relative; width: min(74vw, 300px); margin: 0 auto; aspect-ratio: 8 / 5;
  background: #fff; padding: 8px; transform: rotate(-3deg);
  outline: 3px dashed var(--muted); outline-offset: -7px;
  box-shadow: 0 14px 30px -12px rgba(43,35,64,.5);
  opacity: 0; }
.stamp-overlay.slammed .stamp-frame { animation: stamp-slam .5s cubic-bezier(.2,1.1,.3,1) forwards; }
@keyframes stamp-slam {
  0%   { opacity: 0; transform: scale(2.6) rotate(9deg); }
  55%  { opacity: 1; transform: scale(.94) rotate(-4deg); }
  75%  { transform: scale(1.05) rotate(-2.5deg); }
  100% { opacity: 1; transform: scale(1) rotate(-3deg); }
}
.stamp-overlay.slammed .stamp-card { animation: card-kick .5s cubic-bezier(.2,.9,.3,1) .18s; }
.stamp-frame .stamp-art { position: absolute; inset: 8px; width: auto; height: auto; border-radius: 4px; }
/* Oblitération : anneau de cachet postal qui apparaît après l'impact. */
/* Cachet « VALIDÉ ! » : vrai tampon rouge encré, centré sur le timbre, qui claque. */
.stamp-frame .postmark { position: absolute; left: 50%; top: 50%; z-index: 2;
  transform: translate(-50%,-50%) rotate(-13deg); transform-origin: center;
  padding: 4px 15px; border: 3.5px solid #DC2626; border-radius: 9px;
  color: #DC2626; font-weight: 900; font-size: 1.65rem; letter-spacing: 1.5px;
  text-transform: uppercase; white-space: nowrap; opacity: 0; line-height: 1.15;
  box-shadow: inset 0 0 0 2px rgba(220,38,38,.35); text-shadow: 0 1px 0 rgba(255,255,255,.45);
  pointer-events: none; }
.stamp-overlay.slammed .postmark { animation: postmark-hit .42s cubic-bezier(.2,1.5,.4,1) .42s both; }
@keyframes postmark-hit {
  0%   { opacity: 0; transform: translate(-50%,-50%) rotate(-13deg) scale(2.7); }
  70%  { opacity: .95; }
  100% { opacity: .9; transform: translate(-50%,-50%) rotate(-13deg) scale(1); }
}
/* Conservé de l'ex-splash (supprimé le 2026-07-22) : le tampon s'en sert encore
   pour faire monter le nom puis le sous-titre après l'impact. */
@keyframes ds-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: .92; transform: none; } }
.stamp-name { margin-top: 16px; font-weight: 900; font-size: 1.15rem;
  opacity: 0; }
.stamp-overlay.slammed .stamp-name { animation: ds-rise .4s ease-out .3s forwards; }
.stamp-overlay .reward-sub { opacity: 0; }
.stamp-overlay.slammed .reward-sub { animation: ds-rise .4s ease-out .45s forwards; }

/* Quête hebdo (journal) : barre de progression + paliers réclamables. */
.quest { max-width: 560px; margin: 6px auto 0; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card); padding: 14px 16px 22px; box-shadow: none; }
.q-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 18px; }
.q-title { font-weight: 600; font-size: .95rem; }
.q-prog { font-weight: 500; font-size: .78rem; color: var(--muted); }
/* Piste de progression partagée (quête/journée/ligue) : filet, pas de dégradé
   doré ni de reflet animé — la barre bouge, elle ne scintille pas. */
.q-track { position: relative; height: 8px; border-radius: 0; background: var(--paper-deep); margin: 0 18px;
  box-shadow: inset 0 0 0 1px var(--line); }
.q-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 0; overflow: hidden;
  background: var(--purple); transition: width .5s ease; }
.q-fill::after { content: none; }
.q-tier { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 32px; height: 32px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--line); display: flex; align-items: center; justify-content: center; font-size: 16px; }
.q-tier.ready { border-color: var(--purple); box-shadow: 0 0 0 3px var(--focus); animation: q-pulse 1.2s ease-in-out infinite; }
.q-tier.done { background: var(--ink); border-color: var(--ink); color: var(--surface); }
@keyframes q-pulse { 0%,100% { transform: translate(-50%,-50%) scale(1); } 50% { transform: translate(-50%,-50%) scale(1.12); } }

.cards { display: flex; flex-direction: column; gap: 10px; padding: 4px 14px 110px; max-width: 560px; margin: 0 auto; }
/* « Nouvelle grille » : CTA plein largeur en pied de liste, discret (pointillés). */
.more-grid { width: 100%; border: 1px dashed var(--purple); background: var(--focus);
  color: var(--purple-d); border-radius: var(--r-card); padding: 15px; margin-top: 2px;
  font-family: var(--text); font-weight: 600; font-size: .92rem;
  display: flex; align-items: center; justify-content: center; gap: 8px; transition: transform .08s ease; }
.more-grid:active { transform: translateY(1px); }
.more-grid .mg-cost { font-size: .78rem; font-weight: 600; opacity: .65; }
/* Une rangée de sommaire, pas une tuile de jeu vidéo : filet, fond surface,
   plus de dégradé par couleur de jeu — l'accent unique porte le badge seul. */
.card {
  display: flex; align-items: center; gap: 13px;
  border: 1px solid var(--line); text-align: left; width: 100%;
  border-radius: var(--r-card); padding: 14px 15px;
  background: var(--surface); color: var(--ink);
  transition: transform .08s ease;
}
.card:active { transform: translateY(1px); }
.card.c-crossword, .card.c-wordsearch, .card.c-anagram, .card.c-motus { background: var(--surface); box-shadow: none; }
/* Vignette de thème (theme-art) : plus grande que l'ancien pictogramme — c'est
   elle qui porte l'accroche visuelle de la carte, comme une miniature de
   sommaire de magazine. Fallback tuile CSS géré par theme-art. */
.card .card-ic {
  flex: none; width: 56px; height: 56px; border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  background: var(--focus); color: var(--purple);
}
.card .card-ic.theme-art b { font-size: 1rem; }
.card .card-badge .ic { vertical-align: -.15em; }
.card .card-body { flex: 1; min-width: 0; }
.card .card-name { font-family: var(--display); font-variation-settings: "opsz" 30, "WONK" 1;
  font-size: 1.1rem; font-weight: 600; color: var(--ink); }
.card .card-desc { font-size: .82rem; opacity: .85; font-weight: 500; margin-top: 1px; color: var(--muted); }
.card .card-badge { font-weight: 600; font-size: .8rem; padding: 4px 10px; border-radius: 3px;
  border: 1px solid var(--line); color: var(--muted); white-space: nowrap; }
/* Jeu terminé : filet accent, repos visuel sans couleur « succès ». */
.card.done { background: var(--bg); color: var(--muted); border: 1px solid var(--line); box-shadow: none; }
.card.done .card-ic { background: var(--paper-deep); color: var(--muted); }
.card.done .card-ic.theme-art img { filter: grayscale(.6) brightness(.95); }
.card.done .card-desc { color: var(--muted); }
.card.done .card-badge { background: var(--ink); color: var(--surface); border-color: var(--ink); }

/* --- Vue jeu (en-tête retour) ----------------------------------------------- */
.game-header {
  display: flex; align-items: center; gap: 8px;
  padding: 6px var(--m); font-weight: 600;
  border-bottom: 1px solid var(--line);
}
.game-header .back { min-width: 42px; width: 42px; height: 42px; border-radius: 3px; font-size: 1.4rem;
  background: var(--surface); border: 1px solid var(--line); box-shadow: none; }
.gh-mid { flex: 1; min-width: 0; }
#game-title { display: block; font-size: .95rem; font-weight: 600; line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Pips « parties restantes » : filet accent sur la courante, encre sur les faites. */
.pips { display: flex; gap: 5px; margin-top: 6px; }
.pip { width: 20px; height: 3px; border-radius: 0; background: var(--line); }
.pip.done { background: var(--ink); }
.pip.cur { background: var(--purple); }
.game-header .pill { min-height: 0; }
/* Indice compact « ? » rond, à droite. */
.game-header .hint-btn { position: relative; min-width: 42px; width: 42px; height: 42px; padding: 0; border-radius: 3px; font-size: 1.2rem; line-height: 1; }
/* Compteur d'indices gratuits restants (badge, façon Glouton). .paid = à 0 gratuit. */
.hint-btn .hb-count { position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px; padding: 0 3px; border-radius: 9px;
  background: var(--purple); color: var(--surface); font-size: .68rem; font-weight: 700; line-height: 17px; box-shadow: 0 0 0 2px var(--bg); }
.hint-btn .hb-count.paid { background: var(--surface); border: 1px solid var(--purple); box-shadow: none; }
.hint-btn .hb-count.paid .ic { width: 11px; height: 11px; vertical-align: -1px; color: var(--purple); }

/* --- Célébration numéro terminé --------------------------------------------- */
.celebration { text-align: center; padding: 40px 20px; max-width: 480px; margin: 0 auto; }
.celebration .big { font-size: 3.2rem; animation: pop-in .4s ease both; }
.celebration h2 { font-family: var(--display); font-variation-settings: "opsz" 60, "WONK" 1;
  font-size: 1.7rem; font-weight: 600; margin: 8px 0; color: var(--ink);
  background: none; -webkit-background-clip: initial; background-clip: initial; -webkit-text-fill-color: initial; }
.celebration .streak { display: inline-flex; align-items: center; gap: 6px; background: var(--surface);
  border: 1px solid var(--line); border-radius: 3px; padding: 8px 16px; font-weight: 600; box-shadow: none; margin: 10px 0; }
.celebration .next { color: var(--muted); font-weight: 500; margin: 14px 0; }
.celebration .cta {
  display: block; width: 100%; max-width: 300px; margin: 0 auto;
  background: var(--purple); color: var(--surface); font-family: var(--text);
  padding: 15px 26px; border-radius: 3px; font-weight: 600; font-size: 1rem;
  box-shadow: none;
}
.celebration .cta + .cta { margin-top: 12px; }
.celebration .cta:active { transform: translateY(1px); }
/* Bouton secondaire « Revoir le journal » : bordé, discret, pour ne pas rivaliser avec la CTA. */
.celebration .cta.ghost { background: var(--surface); color: var(--ink); border: 1px solid var(--line); box-shadow: none; }
.celebration .cta.ghost:active { transform: translateY(1px); }
@keyframes pop-in { 0% { transform: scale(0); } 70% { transform: scale(1.2); } 100% { transform: scale(1); } }

.hidden { display: none !important; }

/* --- Indice + wiggle -------------------------------------------------------- */
.hint-btn { background: var(--purple); color: var(--surface); font-weight: 600; padding: 10px 18px; border-radius: 3px; }
.hint-btn .ic { vertical-align: -.18em; }
.wiggle { animation: wiggle 0.5s ease; }
@keyframes wiggle { 0%,100% { transform: rotate(0); } 20% { transform: rotate(-6deg); } 40% { transform: rotate(6deg); } 60% { transform: rotate(-4deg); } 80% { transform: rotate(4deg); } }

/* --- Bottom sheet (crédit 0, indices) — « jamais de popup centrée » ---------- */
/* z-index au-dessus de la .tabbar (40) : sinon la dernière option d'un sheet passe
   DERRIÈRE la barre d'onglets fixe. Reste sous les overlays de récompense (60). */
.sheet-backdrop { position: fixed; inset: 0; background: rgba(30,36,31,.4); display: flex; align-items: flex-end; z-index: 50; }
.sheet {
  width: 100%; max-width: 560px; margin: 0 auto;
  background: var(--bg); border-radius: 0; border-top: 2px solid var(--ink);
  padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  box-shadow: none;
  animation: sheet-up 0.22s ease both;
}
.sheet h3 { font-family: var(--display); font-variation-settings: "opsz" 30, "WONK" 1;
  margin: 0 0 4px; font-size: 1.25rem; font-weight: 600; }
.sheet p { margin: 0 0 14px; color: var(--muted); font-weight: 500; }
.sheet .opt { display: block; width: 100%; text-align: left; padding: 15px; border-radius: 3px;
  border: 1px solid var(--line); margin-bottom: 10px; font-family: var(--text); font-weight: 500;
  background: var(--surface); box-shadow: none; }
.sheet .opt.primary { background: var(--purple); border-color: var(--purple); color: var(--surface); font-weight: 600; box-shadow: none; }
.sheet .opt.ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--muted); font-weight: 500; }
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
.sheet { position: relative; }
/* Croix de fermeture explicite (feuilles + moments festifs) — ne pas obliger à
 * taper le fond pour fermer. */
.sheet-x { position: absolute; top: 12px; right: 12px; width: 34px; height: 34px; border-radius: 50%;
  background: var(--paper-deep); border: 1px solid var(--line); color: var(--muted); font-size: 1rem; font-weight: 600; line-height: 1;
  display: flex; align-items: center; justify-content: center; }
.reward-card { position: relative; }

/* --- Icônes SVG (core/icons.js) — remplacent les emojis --------------------- */
.ic { vertical-align: -0.15em; flex: none; }
.pill .ic { color: currentColor; }
.pill.bump { animation: score-bump .4s ease; }
@keyframes score-bump { 0% { transform: scale(1); } 40% { transform: scale(1.28); } 100% { transform: scale(1); } }
.celebration .big .ic { color: var(--gold); }

/* --- Barre d'onglets (bas) — maquette 4a ------------------------------------ */
/* Folio de bas de page plutôt qu'une barre d'app flottante : filet dessus, fond
   papier plat, pas de flou ni d'ombre. */
.tabbar {
  position: fixed; left: 0; right: 0; z-index: 40;
  bottom: 0; padding-bottom: env(safe-area-inset-bottom, 0);
  max-width: 560px; margin: 0 auto;
  display: flex; padding: 6px 8px; gap: 4px;
  background: var(--bg);
  border-top: 1px solid var(--line); border-radius: 0;
  box-shadow: none;
}
.tab {
  flex: 1; min-height: 0; background: none; border: none; box-shadow: none;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 4px 0; font-family: var(--text);
}
.tab .ti {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; font-size: 20px;
  opacity: .55; filter: grayscale(.4); transition: all .16s ease;
}
.tab .ti .tab-art { width: 30px; height: 30px; object-fit: contain; border-radius: 3px; }
.tab span:last-child { font-size: 10px; font-weight: 600; letter-spacing: .04em; color: var(--muted); }
.tab.on .ti { opacity: 1; filter: none; transform: none; }
.tab.on .ti .tab-art { filter: none; }
.tab.on span:last-child { color: var(--purple); font-weight: 700; }

/* --- Écran Ligue (badge + progression) ---------------------------------------
   Carte plate, filet, typo éditoriale — SEUL le badge de rang garde sa couleur
   (dégradé --tier-a/--tier-b posé par JS par palier) : c'est traité comme une
   illustration de récompense, pas comme du chrome, donc exempté de la règle
   « un seul accent ». Tout ce qui l'entoure reste sur le système papier/encre. */
.league-scroll { max-width: 560px; margin: 0 auto; padding: 18px 16px 110px; }
.league-card {
  position: relative; overflow: hidden; text-align: center;
  border-radius: 0; padding: 30px 20px 24px; color: var(--ink);
  background: var(--surface); border-top: 2px solid var(--ink);
  box-shadow: none;
}
.league-glow { display: none; }
.league-badge {
  width: 88px; height: 88px; margin: 0 auto 14px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.2); box-shadow: none;
  animation: none;
}
.league-badge .ic { width: 48px; height: 48px; }
.league-badge { position: relative; overflow: hidden; }
.league-badge .league-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
.league-tier { font-family: var(--display); font-variation-settings: "opsz" 40, "WONK" 1;
  font-size: 1.5rem; font-weight: 600; letter-spacing: -.01em; }
.league-rating { font-family: var(--text); font-weight: 500; color: var(--muted); margin-top: 2px; }
.league-progress { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 14px 16px 18px; margin-top: 14px; box-shadow: none; }
.league-progress-head { display: flex; justify-content: space-between; font-weight: 500; font-size: .85rem; color: var(--ink); margin-bottom: 10px; }
.league-progress-head b { color: var(--purple); font-weight: 600; }
#league-to-next { color: var(--muted); font-weight: 600; }
.league-stats { display: flex; gap: 10px; margin-top: 14px; }
.league-stat { flex: 1; background: var(--surface); border: 1px solid var(--line); border-radius: 4px; padding: 14px; text-align: center; box-shadow: none; }
.league-stat .ls-val { display: block; font-family: var(--display); font-variation-settings: "opsz" 24;
  font-size: 1.3rem; font-weight: 600; color: var(--purple); }
.league-stat .ls-lbl { font-size: .75rem; font-weight: 500; color: var(--muted); }
.league-tip { text-align: center; color: var(--muted); font-weight: 500; font-size: .85rem; margin-top: 18px; line-height: 1.5; }

/* --- Écran Perso (profil + skins + réglages) -------------------------------- */
.perso-scroll { max-width: 560px; margin: 0 auto; padding: 12px 16px 110px; }
.perso-head { text-align: center; padding: 8px 0 2px; }
.perso-avatar { font-size: 72px; line-height: 1; filter: none; animation: none; }
.perso-name { font-family: var(--display); font-variation-settings: "opsz" 30, "WONK" 1;
  font-size: 1.3rem; font-weight: 600; margin-top: 6px; }
.perso-sub { color: var(--muted); font-weight: 500; font-size: .88rem; }
.perso-label { font-size: .72rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .12em; margin: 22px 4px 10px; }
/* En-tête « Tes avatars » avec le solde de points (monnaie des avatars) à droite. */
.perso-label.av-head { display: flex; align-items: center; justify-content: space-between; }
.av-score { display: inline-flex; align-items: center; gap: 4px; text-transform: none; letter-spacing: 0;
  border: 1px solid var(--line); color: var(--ink); background: var(--surface);
  padding: 4px 11px; border-radius: 3px; font-size: .8rem; box-shadow: none; }
.av-score svg { width: 13px; height: 13px; }
/* Onglets internes de l'écran Perso : Collection (le plaisir) / Réglages (l'outil). */
.perso-tabs { display: flex; gap: 0; margin: 14px 0 4px; background: none; border-bottom: 1px solid var(--line); padding: 0; }
.ptab { flex: 1; border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: 10px 9px; font-family: var(--text);
  font-weight: 500; font-size: .88rem; color: var(--muted); background: transparent; }
.ptab.on { background: transparent; color: var(--ink); font-weight: 600; border-bottom-color: var(--purple); box-shadow: none; }

/* Avatars : tuiles achetables en score, l'actif est cerclé. L'illustration reste
   son propre événement de couleur — le chrome autour (coût, sélection) reste plat. */
.avatars .av { border: none; font-family: var(--text); position: relative; }
.avatars .av.sel { outline: 2px solid var(--purple); outline-offset: 2px; }
/* Coût SOUS le nom, dans le flux (ni sur l'illustration ni sur le nom, plus de chevauchement).
   Sélecteur volontairement large : la boutique papiers/encres réutilise ce badge
   dans .collection-grid.cosm, hors de .avatars. */
.av-cost { display: inline-flex; align-items: center; gap: 3px; margin-top: 1px;
  font-size: .68rem; font-weight: 600; color: var(--purple); background: var(--focus);
  border-radius: 3px; padding: 1px 7px; }
.avatars .av.locked { opacity: 1; }
.avatars .av.locked .theme-art { filter: grayscale(.75) brightness(.9); }
.av-art, .avatars .theme-art { width: 100%; flex: 1; border-radius: 4px; min-height: 0; }
.perso-av-art { width: 88px; height: 88px; margin: 0 auto; border-radius: 50%; display: flex;
  box-shadow: none; border: 1px solid var(--line); }
.hero-avatar { position: absolute; right: -8px; bottom: -6px; width: 34px; height: 34px; }
.hero-avatar .theme-art, .hero-avatar .av-art { width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--bg); box-shadow: none; }
.hero-avatar .theme-art b { font-size: .8rem; }

/* Visionneuse de collection : la pièce en grand. */
.art-lightbox .lb-card { position: relative; width: min(88vw, 360px); background: var(--bg);
  border-radius: 0; border-top: 2px solid var(--ink); padding: 48px 16px 20px; text-align: center;   /* padding-top dégage le bouton ✕ */
  box-shadow: none; animation: lb-pop .3s cubic-bezier(.2,1.2,.4,1); }
.art-lightbox .lb-art .theme-art { width: min(70vw, 280px); height: min(70vw, 280px); margin: 0 auto; border-radius: 4px; }
.art-lightbox .lb-art .lb-stamp { width: min(74vw, 300px); height: auto; aspect-ratio: 8 / 5; margin: 0 auto;
  border-radius: 2px; outline: 2px dashed var(--muted); outline-offset: -6px; padding: 6px; background: var(--surface); }
.art-lightbox .lb-name { font-family: var(--display); font-variation-settings: "opsz" 30, "WONK" 1; margin-top: 12px; font-weight: 600; font-size: 1.15rem; }
@keyframes lb-pop { from { transform: scale(.7); opacity: 0; } }

/* Variante « carte à collectionner » (avatars) : la carte ARRIVE en tournant sur
   elle-même (rotateY) et en grandissant (zoom) — mise en avant façon carte Pokémon.
   La perspective sur le parent rend la rotation 3D ; .lb-art garde son foil holo. */
.art-lightbox.spin { perspective: 1000px; }
.art-lightbox.spin .lb-card { transform-style: preserve-3d;
  animation: lb-spin .62s cubic-bezier(.2, 1, .3, 1.15) both; }
@keyframes lb-spin {
  0%   { opacity: 0; transform: rotateY(-205deg) scale(.35); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: rotateY(0deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {           /* pas de tournoiement : simple pop doux */
  .art-lightbox.spin .lb-card { animation: lb-pop .3s ease both; }
}

/* Collection de collectibles (écran Perso). */
.collection { display: flex; flex-direction: column; gap: 8px; }
.collection-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.coll-label { font-size: .72rem; font-weight: 600; color: var(--muted); margin-top: 6px; text-transform: uppercase; letter-spacing: .1em; }
.coll .c-art { width: 100%; flex: 1; border-radius: 3px; min-height: 0; }
.coll .c-art b { font-size: 1.1rem; }
/* Timbre du passeport : cadre papier + liseré pointillé façon philatélie. */
/* ponytail: dentelure réelle en mask CSS si la DA le réclame plus tard */
.coll.stamp.on { padding: 5px; background: var(--surface); border: none;
  outline: 1px dashed var(--muted); outline-offset: -4px;
  box-shadow: none; transform: rotate(-2deg); }
.coll.stamp.on:nth-child(even) { transform: rotate(2deg); }
.coll { aspect-ratio: 1; border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  background: var(--surface); border: 1px solid var(--line); font-size: 30px; }
.coll.on { background: var(--focus); border: 1px solid var(--purple); box-shadow: none; }
/* Verrouillé : sachet scellé (diagonales fines) plutôt qu'un simple fondu à l'opacité —
   teaser du foil à venir, pas une case cassée. */
.coll.locked { color: var(--muted); font-size: 20px;
  background: repeating-linear-gradient(135deg, var(--paper-deep) 0 8px, var(--bg) 8px 16px);
  border-color: var(--line); }
.coll.locked .ic-lock { opacity: .7; }
.coll .cn { font-size: .6rem; font-weight: 600; color: var(--purple); }

/* Papiers & encres (2026-07-23) — la vignette EST le rendu : un carré du papier
   réel barré d'un filet de l'encre réelle. Aucun asset, donc rien à charger et
   rien qui puisse manquer. Filets 1 px, coins ≤ 4 px, aucune ombre (DESIGN-SYSTEM). */
/* --line (le token RÉEL), pas --rule (la nomenclature de DESIGN-SYSTEM.md) : une
   variable inconnue invalide tout le raccourci `border` → plus aucun filet. */
.cosm-sw { width: 100%; flex: 1; min-height: 0; border-radius: 3px; border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; }
.cosm-bar { width: 60%; height: 3px; border-radius: 2px; }
.coll.cosm-item.sel { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Bouton Solutions (header de jeu, ex-Glouton) — révèle la grille, premium rare. */
.glouton-btn { position: relative; min-width: 42px; width: 42px; height: 42px; padding: 0; border-radius: 3px; font-size: 1.3rem; line-height: 1;
  background: var(--ink); color: var(--surface); box-shadow: none; }
.glouton-btn .gb-count { position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px; padding: 0 3px; border-radius: 9px;
  background: var(--purple); color: var(--surface); font-size: .68rem; font-weight: 700; line-height: 17px; box-shadow: 0 0 0 2px var(--bg); }
.glouton-img { object-fit: contain; vertical-align: middle; border-radius: 8px; }
.settings-list { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-card); overflow: hidden; box-shadow: none; }
.set-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; padding: 14px 16px; background: none; border: none; border-bottom: 1px solid var(--line); border-radius: 0; min-height: 0; font-family: var(--text); font-weight: 500; font-size: .95rem; color: var(--ink); box-shadow: none; }
.settings-list .set-row:last-child { border-bottom: none; }
.set-ic { font-size: 20px; flex: none; color: var(--muted); }
.set-txt { flex: 1; }
.set-chev { color: var(--muted); font-size: 20px; }
/* Toggle façon interrupteur (appearance:none → pastille qui glisse). */
.settings-list input[type="checkbox"] {
  -webkit-appearance: none; appearance: none; flex: none;
  width: 44px; height: 26px; border-radius: 13px; background: var(--line);
  position: relative; cursor: pointer; transition: background .15s;
}
.settings-list input[type="checkbox"]::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 20px; height: 20px; border-radius: 50%; background: var(--surface);
  box-shadow: none; border: 1px solid var(--line); transition: transform .15s;
}
.settings-list input[type="checkbox"]:checked { background: var(--purple); }
.settings-list input[type="checkbox"]:checked::after { transform: translateX(18px); border-color: var(--purple); }

/* --- Overlay de victoire d'une grille (games/*.js) -------------------------- */
/* fixed (pas absolute) : les overlays montés sur <body> se centrent dans la
   FENÊTRE, pas au sommet du document — sinon, page scrollée, ils s'ouvrent
   hors écran (bug lightbox de collection). z-index au-dessus tabbar + contenu. */
.win-overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(30,36,31,.4); z-index: 80; }
#game-view { position: relative; }
.win-card { background: var(--bg); border-radius: 0; border-top: 2px solid var(--ink);
  padding: 24px 32px; text-align: center; box-shadow: none; animation: pop-in .45s ease both; }
.win-card.won { animation: win-pop .5s cubic-bezier(.18,1.3,.4,1) both; }
.win-card.won .ic { animation: trophy-jig .6s ease .15s both; }
@keyframes win-pop { 0% { transform: scale(.85); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
@keyframes trophy-jig { 0%,100% { transform: rotate(0) scale(1); } 25% { transform: rotate(-6deg) scale(1.08); } 60% { transform: rotate(4deg) scale(1.08); } }
.win-card .ic { color: var(--purple); }
/* Mascotte « gg ! » : présente, calmée — plus de flottement continu ni d'ombre
   portée (DESIGN-SYSTEM 2026-07-22 : « calmer sans supprimer »). */
.gg-mascot { position: relative; height: 88px; margin: -6px 0 2px; }
.gg-mascot .gg-img { width: 88px; height: 88px; object-fit: contain;
  filter: none; animation: mascot-pop .5s cubic-bezier(.18,1.3,.4,1) both; }
.gg-badge { position: absolute; top: -6px; right: calc(50% - 62px); z-index: 2;
  background: var(--purple); color: var(--surface); font-weight: 600;
  font-size: .78rem; padding: 3px 10px; border-radius: 3px; transform: none;
  box-shadow: none; animation: chip-pop .4s cubic-bezier(.18,1.5,.4,1) .35s both; }
@keyframes mascot-pop { 0% { transform: translateY(28px) scale(.4) rotate(-14deg); opacity: 0; }
  60% { transform: translateY(-4px) scale(1.12) rotate(6deg); opacity: 1; } 100% { transform: none; opacity: 1; } }
.win-card h3 { font-family: var(--display); font-variation-settings: "opsz" 40, "WONK" 1;
  margin: 8px 0 0; font-size: 1.35rem; font-weight: 600; }
.win-card .win-back { margin-top: 16px; background: var(--purple); color: var(--surface); font-family: var(--text);
  font-weight: 600; padding: 13px 22px; border-radius: 3px; box-shadow: none; }
.win-overlay.hidden { display: none; }

/* Bloc d'étape de la carte de victoire — LE correctif de lisibilité de la
   progression : la jauge part de l'ancienne valeur et GLISSE vers la nouvelle.
   Une barre statique n'enseigne rien ; une barre qui bouge enseigne la causalité. */
.win-step { margin: 12px 0 2px; padding: 10px 12px; border-radius: 0;
  background: var(--focus); text-align: left; }
/* Une seule ligne, coupée proprement : les noms de destination sont longs et
   variables, et un retour à la ligne ferait sauter la hauteur de la carte. */
.win-step .ws-kicker { display: flex; align-items: center; gap: 5px;
  font-weight: 700; font-size: .76rem; letter-spacing: .06em; text-transform: uppercase; color: var(--purple);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.win-step .ws-kicker svg { flex: none; }
.win-step .q-track { display: block; margin: 7px 0 5px; background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line); }
/* Transition posée seulement au moment du glissement (sinon la valeur initiale
   s'animerait aussi depuis 0). 300 ms : plafond de confort de mouvement. */
.win-step .q-fill { width: 0; }
.win-step .q-fill.sliding { transition: width .3s ease-out; }
.win-step .ws-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.win-step .ws-gain { font-weight: 800; font-size: .78rem; color: var(--muted); }
/* Le +2 se distingue du +1 comme un coup critique se distingue d'un coup normal :
   c'est ainsi que le joueur découvre le bonus thématique sans lire de règle. */
.win-step .ws-gain.themed { color: var(--purple); font-weight: 900; font-size: .86rem;
  animation: gain-pop .4s cubic-bezier(.2,1.6,.4,1) .45s both; }
.win-step .ws-prog { font-weight: 900; font-size: .95rem; white-space: nowrap; }
.win-step .ws-prog i { font-style: normal; font-size: .75rem; color: var(--muted); }
.win-note { margin: 8px 0 0; font-weight: 800; font-size: .85rem; color: var(--purple); }
.win-flash { font-weight: 800; color: var(--gold-d); }
@keyframes gain-pop { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } }

/* Étape suivante dévoilée sur la MÊME carte que le tampon (ex-splash fondu ici). */
.stamp-next { max-height: 0; opacity: 0; overflow: hidden; margin-top: 0;
  display: flex; flex-direction: column; gap: 2px;
  transition: max-height .3s ease-out, opacity .3s ease-out, margin-top .3s ease-out; }
.stamp-overlay.nexted .stamp-next { max-height: 90px; opacity: 1; margin-top: 14px; }
.stamp-next .sn-kicker { display: flex; align-items: center; justify-content: center; gap: 5px;
  font-weight: 900; font-size: .74rem; letter-spacing: .6px; text-transform: uppercase; color: var(--muted); }
.stamp-next .sn-name { font-weight: 900; font-size: 1.05rem; color: var(--purple); }

@media (prefers-reduced-motion: reduce) {
  .win-step .q-fill.sliding, .stamp-next { transition: none; }
  .win-step .ws-gain.themed { animation: none; opacity: 1; transform: none; }
  .stamp-overlay.nexted .stamp-next { max-height: none; }
}

/* --- Moment festif (coffre, quête, cadeaux) : pictogramme qui pop + confettis - */
.reward-overlay { position: fixed; z-index: 60; background: rgba(30,36,31,.5); }
.reward-card { background: var(--bg); border-radius: 0; border-top: 2px solid var(--ink); padding: 28px 30px; text-align: center;
  max-width: min(90vw, 360px); box-shadow: none; animation: win-pop .5s cubic-bezier(.18,1.3,.4,1) both; }
.reward-ic { font-size: 56px; line-height: 1; color: var(--purple); animation: trophy-jig .6s ease .15s both; }
.reward-card h3 { font-family: var(--display); font-variation-settings: "opsz" 40, "WONK" 1;
  margin: 10px 0 2px; font-size: 1.35rem; font-weight: 600; }
.reward-card .reward-sub { margin: 0; color: var(--muted); font-weight: 500; }
.reward-card .win-back, .stamp-card .win-back { margin-top: 18px; background: var(--purple);
  color: var(--surface); font-family: var(--text); font-weight: 600; padding: 13px 26px; border-radius: 3px;
  box-shadow: none; }


/* --- Ouverture de coffre : tremblement -> explosion -> lot + aura de rareté -- */
.chest-open { --rarity: #7f8cff; }
.chest-open.r-common    { --rarity: #7f8cff; }
.chest-open.r-rare      { --rarity: var(--gold-d); }
.chest-open.r-epic      { --rarity: #a855f7; }
.chest-open.r-legendary { --rarity: #22c55e; }

.chest-stage { position: relative; width: 130px; height: 130px; margin: 2px auto 0; }
.chest-box { position: relative; font-size: 92px; line-height: 130px; text-align: center; will-change: transform;
  animation: chest-shake .5s ease-in-out 2; }
/* Coffre illustré (img/ui/coffre.webp) par-dessus l'emoji ; absent → emoji visible. */
.chest-box .chest-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
/* Le coffre se compresse (deux à-coups) puis explose. */
.chest-open.popped .chest-box { animation: chest-explode .55s cubic-bezier(.3,.1,.4,1) forwards; }

/* Le lot ne doit jamais capter le clic : sinon (opacity:0, au-dessus) il masque le
   coffre à taper du coffre premium → tap sans effet. */
.loot { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; }
/* Pop élastique (overshoot marqué) puis flottement doux — deux temps, deux animations. */
.chest-open.popped .loot { animation: loot-rise .6s cubic-bezier(.2,1.7,.35,1) .1s both; }
.loot-ic { position: relative; z-index: 1; font-size: 64px; line-height: 1;
  filter: drop-shadow(0 4px 12px var(--rarity)); }
.chest-open.popped .loot-ic { animation: halo-pulse 1.5s ease-in-out .7s infinite, loot-bob 2.6s ease-in-out .9s infinite alternate; }
.loot-ic svg { width: 64px; height: 64px; }
/* Lot illustré (collectible/timbre = theme-art sans taille propre) : sinon minuscule. */
.loot-ic .theme-art { width: 108px; height: 108px; border-radius: 18px; box-shadow: 0 6px 16px -6px var(--rarity); }
.loot-ic .theme-art b { font-size: 2rem; }
.loot-ic img { width: 108px; height: 108px; object-fit: contain; border-radius: 18px; }
.reward-ic .theme-art { width: 108px !important; height: 108px !important; }
@keyframes loot-bob { from { transform: translateY(0); } to { transform: translateY(-6px); } }

/* Impact d'ouverture : flash radial + double onde de choc + coup d'épaule de la carte. */
.chest-stage .flash { position: absolute; inset: -46px; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, #fff 20%, transparent 68%); opacity: 0; }
.chest-open.popped .flash { animation: flash-hit .38s ease-out; }
@keyframes flash-hit { 0% { opacity: 0; } 12% { opacity: 1; } 100% { opacity: 0; } }
.chest-stage .shockwave { position: absolute; left: 50%; top: 50%; width: 26px; height: 26px;
  margin: -13px; border-radius: 50%; border: 3px solid var(--rarity); opacity: 0; pointer-events: none; }
.chest-open.popped .shockwave { animation: shock .65s cubic-bezier(.1,.8,.3,1) forwards; }
.chest-open.popped .shockwave.s2 { border-width: 2px; animation-delay: .1s; }
@keyframes shock { 0% { opacity: .95; transform: scale(.35); } 100% { opacity: 0; transform: scale(10); } }
.chest-open.popped .reward-card { animation: card-kick .5s cubic-bezier(.2,.9,.3,1); }
@keyframes card-kick { 0% { transform: none; } 18% { transform: scale(1.05) translateY(-5px); }
  55% { transform: scale(.985); } 100% { transform: none; } }

/* Rayons divins : deux couches coniques contrarotatives, fondu radial doux. */
.godrays { position: absolute; left: 50%; top: 50%; width: 250px; height: 250px; margin: -125px;
  border-radius: 50%; pointer-events: none; opacity: 0; mix-blend-mode: screen;
  background: repeating-conic-gradient(from 0deg, var(--rarity) 0 9deg, transparent 9deg 28deg);
  -webkit-mask: radial-gradient(circle, #000 0 34%, transparent 70%);
          mask: radial-gradient(circle, #000 0 34%, transparent 70%); }
.chest-open.popped .godrays { opacity: .5; animation: rays-cw 14s linear .2s infinite; }
.chest-open.popped .godrays.g2 { opacity: .28; animation: rays-ccw 22s linear .2s infinite; }
@keyframes rays-cw { from { transform: rotate(0); } to { transform: rotate(360deg); } }
@keyframes rays-ccw { from { transform: scale(1.28) rotate(0); } to { transform: scale(1.28) rotate(-360deg); } }

/* Étincelles : petites étoiles qui scintillent en décalé autour du lot. */
.loot .spark { position: absolute; width: 11px; height: 11px; background: #fff; opacity: 0; pointer-events: none;
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  filter: drop-shadow(0 0 4px var(--rarity)); }
.loot .spark.k1 { left: 18%; top: 22%; } .loot .spark.k2 { right: 14%; top: 32%; }
.loot .spark.k3 { left: 24%; bottom: 18%; } .loot .spark.k4 { right: 22%; bottom: 26%; }
.chest-open.popped .spark { animation: twinkle 1.6s ease-in-out infinite; }
.chest-open.popped .spark.k2 { animation-delay: .4s; } .chest-open.popped .spark.k3 { animation-delay: .8s; }
.chest-open.popped .spark.k4 { animation-delay: 1.2s; }
@keyframes twinkle { 0%, 100% { opacity: 0; transform: scale(.4) rotate(0); }
  45% { opacity: 1; transform: scale(1.15) rotate(40deg); } }

.rarity-chip { display: inline-block; margin: 12px 0 0; padding: 3px 12px; border-radius: 999px;
  font-size: .78rem; font-weight: 800; letter-spacing: .03em; text-transform: uppercase;
  color: #fff; background: var(--rarity); box-shadow: 0 3px 10px -2px var(--rarity);
  opacity: 0; transform: scale(.6); }
.chest-open.popped .rarity-chip { animation: chip-pop .4s cubic-bezier(.18,1.5,.4,1) .3s both; }

@keyframes chest-shake {
  0%,100% { transform: rotate(0) translateY(0); }
  20% { transform: rotate(-8deg); } 40% { transform: rotate(7deg); }
  60% { transform: rotate(-6deg) translateY(-2px); } 80% { transform: rotate(5deg); }
}
@keyframes chest-burst {
  0% { transform: scale(1); } 40% { transform: scale(1.25); opacity: 1; }
  100% { transform: scale(.35); opacity: 0; }
}
@keyframes chest-explode {
  0%   { transform: scale(1, 1); }
  25%  { transform: scale(1.18, .78); }   /* écrasement */
  45%  { transform: scale(.82, 1.22); }   /* rebond étiré */
  68%  { transform: scale(1.5); opacity: 1; }
  100% { transform: scale(.2); opacity: 0; }
}
@keyframes halo-pulse {
  0%,100% { filter: drop-shadow(0 0 8px var(--rarity)); }
  50%     { filter: drop-shadow(0 0 24px var(--rarity)); }
}
@keyframes loot-rise {
  0% { transform: scale(.2) translateY(16px); opacity: 0; }
  60% { transform: scale(1.18) translateY(-4px); opacity: 1; }
  100% { transform: scale(1) translateY(0); opacity: 1; }
}
@keyframes rays-spin { to { transform: rotate(360deg); } }
@keyframes chip-pop { 0% { opacity: 0; transform: scale(.6); } 100% { opacity: 1; transform: scale(1); } }

/* --- Images UI (coffre, trophée) : dimension portée en inline, ici juste le rendu. --- */
.ui-art { object-fit: contain; vertical-align: middle; }
.q-art { width: 24px; height: 24px; object-fit: contain; }        /* pastille de piste de quête */
.sheet-art { text-align: center; margin: 0 0 10px; }
.sheet-art img { width: 72px; height: 72px; object-fit: contain; }
/* Trophée de l'écran « Numéro terminé » : médaillon rond assumé (sinon le fond
   carré du webp tranche sur le papier). */
.big-art { width: 132px; height: 132px; object-fit: cover; border-radius: 50%;
  box-shadow: 0 12px 28px -10px rgba(80,50,120,.45), inset 0 0 0 4px rgba(255,255,255,.75); }

/* --- Coffre PREMIUM (fin de quête) : coffre à TAPER, invite dorée pulsée. --- */
.chest-open.premium { --rarity: #a855f7; }
.chest-open.premium .chest-box { cursor: pointer; border: none; background: none; padding: 0;
  animation: chest-invite 1.6s ease-in-out infinite;
  filter: drop-shadow(0 8px 22px rgba(168,85,247,.55)); }
.chest-open.premium.popped .chest-box { animation: chest-explode .55s cubic-bezier(.3,.1,.4,1) forwards; }
@keyframes chest-invite { 0%,100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-5px) scale(1.04); } }

/* --- Ouverture de coffre NUE (option A) : AUCUNE boîte popin. Le coffre flotte sur le
   fond assombri, tremble, explose ; puis la carte de l'item ARRIVE seule (rotation sur
   elle-même + zoom), le foil holo posé sur .lb-art. Bouton « Fermer » flottant en bas. */
.chest-reveal { flex-direction: column;                         /* fond plus sombre = spotlight sur la carte */
  background: radial-gradient(circle at 50% 44%, rgba(30,20,55,.5), rgba(18,12,36,.72)); }
.chest-reveal .cr-stage { position: relative; width: min(72vw, 260px); height: min(72vw, 260px);
  display: flex; align-items: center; justify-content: center; }
/* Coffre : taille FIXE centrée pour que le webp (.chest-art, inset:0) recouvre l'emoji
   de secours — sinon les deux se superposent (« 2 coffres »). */
.chest-reveal .chest-box { position: absolute; top: 50%; left: 50%; width: 150px; height: 150px;
  margin: -75px 0 0 -75px; font-size: 118px; line-height: 150px; text-align: center; }
.chest-reveal .chest-box .chest-emoji { display: none; }                 /* emoji = secours si webp KO */
.chest-reveal .chest-box.no-art .chest-emoji { display: block; }
.chest-reveal .cr-card { position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px; opacity: 0; pointer-events: none;
  perspective: 1000px; transform-style: preserve-3d; }
.chest-reveal.popped .cr-card { opacity: 1; pointer-events: auto;
  animation: cr-card-in .62s cubic-bezier(.2, 1, .3, 1.15) .12s both; }
@keyframes cr-card-in {
  0%   { opacity: 0; transform: rotateY(-205deg) scale(.35); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: rotateY(0deg) scale(1); }
}
.chest-reveal .cr-card .lb-art .theme-art { width: min(70vw, 258px); height: min(70vw, 258px);
  border-radius: 24px; box-shadow: 0 0 64px -6px var(--rarity), 0 22px 55px -16px rgba(0,0,0,.6); }
.chest-reveal .cr-card .lb-art .theme-art b { font-size: 3rem; }
/* Tuile-carte des tirages non-collectibles (points/glouton/crédits) : même gabarit que la
   carte collectible (theme-art), pour tout harmoniser dans le coffre. Le holo se pose dessus. */
.chest-reveal .cr-card .lb-art .cr-tile { display: flex; align-items: center; justify-content: center;
  width: min(70vw, 258px); height: min(70vw, 258px); border-radius: 24px;
  background: linear-gradient(135deg, #FFF7E9, #E7DBFF);
  box-shadow: 0 0 64px -6px var(--rarity), 0 22px 55px -16px rgba(0,0,0,.6); }
.chest-reveal .cr-card .lb-art .cr-tile img { width: 82%; height: 82%; object-fit: contain; }
.chest-reveal .cr-card .lb-art .cr-tile svg { width: 58%; height: 58%; }
.chest-reveal .cr-name { font-weight: 900; font-size: 1.25rem; color: #fff; text-shadow: 0 2px 12px rgba(20,10,40,.6); }
.chest-reveal .cr-sub { margin: 0; font-weight: 800; color: #fff; opacity: .92; text-shadow: 0 2px 10px rgba(20,10,40,.5); }
.chest-reveal .cr-close { position: fixed; left: 50%; bottom: max(30px, env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%); border: none; color: #fff; font-weight: 800; padding: 14px 30px; border-radius: 16px;
  background: linear-gradient(180deg, var(--green), var(--green-d));
  box-shadow: inset 0 2px 3px rgba(255,255,255,.4), 0 4px 0 var(--green-sh);
  opacity: 0; pointer-events: none; }
.chest-reveal.popped .cr-close { opacity: 1; pointer-events: auto; transition: opacity .35s .45s; }
@media (prefers-reduced-motion: reduce) {           /* pas de tournoiement : simple pop doux */
  .chest-reveal.popped .cr-card { animation: lb-pop .3s ease both; }
}

/* --- Effet holographique (carte « foil ») — variables écrites par core/holo.js :
   --hx/--hy = position du reflet (%), --rx/--ry = inclinaison, --ha = intensité (0/1).
   MOBILE : la nappe arc-en-ciel (::before) balaie EN CONTINU (animation CSS) donc
   l'effet est visible sans survol ; le doigt/gyro ne fait qu'ajouter le reflet (::after)
   et l'inclinaison. À NE PAS poser sur un élément qui a déjà ::before/::after (ex.
   .game-header) — collision de pseudo-élément.
   .lb-art = tilt complet ; .loot-ic (animé) = foil seul (une anim transform gagne). */
.holo { transform: perspective(600px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));
  transition: transform .2s ease; transform-style: preserve-3d; position: relative; isolation: isolate; }
.holo::before, .holo::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none; transition: opacity .3s ease; z-index: 3; }
.holo::before {                                   /* nappe arc-en-ciel : balayage auto continu */
  background: repeating-linear-gradient(115deg,
    #ff5a5a 0%, #ffd75a 12%, #5aff8f 24%, #5ad7ff 36%, #b45aff 48%, #ff5ad7 60%, #ff5a5a 72%);
  background-size: 300% 300%; mix-blend-mode: color-dodge; filter: brightness(.85) contrast(1.15);
  opacity: calc(.22 + var(--ha,0) * .4);          /* ambiant, renforcé à l'interaction */
  animation: holo-sweep 5s linear infinite; }
@keyframes holo-sweep { 0% { background-position: 0% 50%; } 100% { background-position: 300% 50%; } }
.holo::after {                                     /* reflet ponctuel qui suit le doigt */
  background: radial-gradient(circle at var(--hx,50%) var(--hy,50%),
    rgba(255,255,255,.85), rgba(255,255,255,0) 45%);
  mix-blend-mode: overlay; opacity: var(--ha,0); }

/* Centre la grille verticalement dans l'espace dispo (au lieu de la coller en haut
   et laisser un vide sous elle) ; "safe" évite tout clip du haut si un contenu
   dépasse la hauteur dispo (grille XL, longue banderole d'indice). */
#stage { position: relative; display: flex; flex-direction: column; justify-content: safe center; }
/* Révélation « Solutions » (2026-07-23, remplace l'aspiration du glouton) :
   un cachet qui claque, tient 2 s (le temps de le voir vraiment), puis s'efface
   — même famille que le tampon d'étape de l'Atlas, mais sans confettis : c'est
   un geste qu'on choisit, pas une récompense qu'on reçoit. Un seul accent,
   comme le reste du système. */
.solutions-stamp { position: absolute; inset: 0; z-index: 15; pointer-events: none;
  display: flex; align-items: center; justify-content: center; }
.solutions-stamp .ss-mark { display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 20px; border: 2px solid var(--purple); border-radius: 3px;
  color: var(--purple); background: var(--bg); font-family: var(--text);
  font-weight: 700; font-size: .95rem; letter-spacing: .08em; text-transform: uppercase;
  transform: scale(1.8) rotate(-6deg); opacity: 0; transition: transform .28s cubic-bezier(.2,1.4,.4,1), opacity .2s ease; }
.solutions-stamp.in .ss-mark { transform: scale(1) rotate(-3deg); opacity: 1; }
.solutions-stamp.out .ss-mark { opacity: 0; transition: opacity .2s ease; }
@media (prefers-reduced-motion: reduce) {
  .solutions-stamp .ss-mark, .solutions-stamp.in .ss-mark { transition: opacity .15s ease; transform: rotate(-3deg); }
}

/* Pastille « coffre à ouvrir » sur l'onglet Coffre. */
.tab.has-badge { position: relative; }
.tab.has-badge::after { content: ""; position: absolute; top: 4px; right: calc(50% - 18px);
  width: 9px; height: 9px; border-radius: 50%; background: var(--pink); box-shadow: 0 0 0 2px #fff; }

/* --- Confettis (fin de jeu / numéro terminé) -------------------------------- */
.confetti { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 20; }
.confetti i {
  position: absolute; top: -12px; left: var(--x);
  width: 10px; height: 14px; border-radius: 2px;
  animation: confetti-fall var(--dur, 1.4s) ease-in var(--delay, 0s) forwards;
}
@keyframes confetti-fall {
  0% { transform: translate(0, -10px) rotate(0); opacity: 1; }
  100% { transform: translate(var(--dx, 0), 105vh) rotate(var(--rot, 360deg)); opacity: .9; }
}
#celebration { position: relative; overflow: hidden; }

/* --- Paysage sur téléphone : on invite à revenir en portrait ----------------
 * Le seuil est la HAUTEUR (résolution) : < 500px = téléphone couché → illisible. */
.rotate-hint { display: none; }
@media (orientation: landscape) and (max-height: 500px) {
  .rotate-hint {
    display: flex; position: fixed; inset: 0; z-index: 100;
    flex-direction: column; align-items: center; justify-content: center; gap: 16px;
    background: linear-gradient(160deg, var(--purple), var(--pink));
    color: #fff; text-align: center; padding: 20px;
  }
  .rotate-hint .ic { width: 46px; height: 46px; animation: rotate-nudge 1.8s ease-in-out infinite; }
  .rotate-hint p { font-size: 1.15rem; font-weight: 800; line-height: 1.4; }
}
@keyframes rotate-nudge { 0%,15% { transform: rotate(-90deg); } 55%,100% { transform: rotate(0deg); } }

/* --- Écrans bas (téléphone en paysage) : tout se compacte -------------------- */
@media (max-height: 480px) {
  :root { --chrome-h: 235px; --cell-min: 11px; }
  .kb { gap: 3px; padding: 4px; }
  .kb-key { min-height: 0; height: 34px; font-size: .9rem; }
  .game-header { padding: 4px 10px; }
  .game-header .back { min-height: 36px; height: 36px; }
  .clue-bar { padding: 8px 12px; font-size: .9rem; margin-top: 2px; }
  .clue-bar .nav { min-width: 32px; min-height: 32px; }
  .board { padding: 6px 4px; }
  .game-header .hint-btn { height: 36px; min-height: 36px; padding: 0 12px; font-size: .9rem; }
  .game-header .glouton-btn { width: 36px; height: 36px; min-height: 36px; font-size: 1.1rem; }
}

/* --- Identifiant anonyme (réglages) : ligne d'info secondaire ---------------- */
.set-sub { display: block; font-size: .72rem; font-weight: 600; color: var(--muted);
  word-break: break-all; margin-top: 2px; }

/* --- Action destructive : suppression RGPD (réglages + sheet) ---------------- */
.set-row.set-danger { color: #B3372B; }
.set-row.set-danger .set-chev { color: #B3372B; }
.sheet .opt.danger { background: #B3372B; border-color: #B3372B; color: var(--surface);
  box-shadow: none; }

/* --- Document in-app (privacy.html) : overlay plein écran ------------------- */
.doc-overlay { position: fixed; inset: 0; z-index: 30; background: var(--bg);
  display: flex; flex-direction: column; }
.doc-bar { flex: none; display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  background: var(--surface); border-bottom: 1px solid var(--line); }
.doc-back { background: none; border: none; font-weight: 600; font-size: 1rem; color: var(--purple);
  padding: 6px 8px; min-height: 40px; }
.doc-title { font-family: var(--text); font-weight: 600; color: var(--ink); }
.doc-frame { flex: 1; width: 100%; border: none; }

/* --- Finitions « juice » (Lot D) ------------------------------------------- */

/* 8h — splash de démarrage : la première frame doit déjà sentir le papier, pas
   l'app casual — c'est la toute première impression du produit. */
#splash { position: fixed; inset: 0; z-index: 200; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 14px;
  background: var(--bg); transition: opacity .4s ease; }
#splash img { width: 120px; height: 120px; object-fit: contain;
  filter: none;
  animation: splash-pop .5s cubic-bezier(.18,1.2,.4,1) both; }
#splash .splash-name { font-family: var(--display); font-variation-settings: "opsz" 60, "WONK" 1;
  color: var(--ink); font-weight: 600; font-size: 1.6rem; letter-spacing: -.01em;
  text-shadow: none; }
#splash.gone { opacity: 0; pointer-events: none; }
@keyframes splash-pop { 0% { transform: scale(.7); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }

/* 8g — pull-to-refresh ludique : Croquy descend et tire la langue. */
#ptr { position: fixed; top: -64px; left: 50%; transform: translateX(-50%); z-index: 45;
  font-size: 40px; line-height: 1; pointer-events: none; transition: top .2s ease, opacity .2s ease; opacity: 0; }
#ptr.pull { opacity: 1; }
#ptr.spin { animation: ptr-spin .6s ease; }
@keyframes ptr-spin { 50% { transform: translateX(-50%) rotate(180deg) scale(1.2); } 100% { transform: translateX(-50%) rotate(360deg); } }
/* Changement d'expression (🐊→😝) : petit rebond au lieu d'un remplacement sec du texte. */
#ptr.flip { animation: ptr-flip .28s cubic-bezier(.34,1.56,.64,1); }
@keyframes ptr-flip { 0% { transform: translateX(-50%) scale(.6) rotate(-8deg); } 100% { transform: translateX(-50%) scale(1) rotate(0); } }

/* 8e — transition d'entrée de vue (sensation « app native »). */
.view-in { animation: view-in .26s ease both; }
@keyframes view-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* 8d — retour tactile visuel : les CTA s'enfoncent au toucher. */
.tab, .win-back, .set-row, .kb-key, .nav, .glouton-btn, .hint-btn, .card { transition: transform .08s ease; }
.tab:active, .win-back:active, .set-row:active, .nav:active,
.glouton-btn:active, .hint-btn:active, .card:active { transform: translateY(1px) scale(.97); }

/* 8f — mascotte réactive (masthead). Tap = sursaut ; inactif = petit somme (mascotte-dodo,
   qui porte déjà son « ZzzzZZz » dessiné, remplace le logo — pas d'emoji superposé). */
.croquy { cursor: pointer; }
.croquy.react img { animation: mascot-react .5s ease; }
@keyframes mascot-react { 0% { transform: scale(1) rotate(0); } 30% { transform: scale(1.2) rotate(-10deg); }
  55% { transform: scale(.92) rotate(8deg); } 100% { transform: scale(1) rotate(0); } }
.croquy.sleepy .croquy-logo { display: none; }
.croquy.sleepy .croquy-dodo { display: block; width: 76px; height: 76px; margin: -8px -8px -8px -8px;
  animation: dodo-breathe 2.6s ease-in-out infinite; }
@keyframes dodo-breathe { 0%,100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-3px) scale(1.03); } }
/* Content (combo, numéro terminé) : réutilise l'asset « GG » déjà dessiné (pouce levé,
   clin d'œil) plutôt qu'une nouvelle génération — même style que idle/dodo, cohérence
   garantie. Pas de nouvel asset : gg_transp.png existait déjà, juste jamais branché ici. */
.croquy.happy .croquy-logo { display: none; }
.croquy.happy .croquy-happy { display: block; animation: mascot-pop .5s cubic-bezier(.2,1.4,.4,1); }
/* Inquiet (presque !) : pas d'asset dédié, un tilt/wince sur l'image idle suffit — la
   mascotte réagit sans qu'on ait besoin de produire un 4e visuel pour un état bref. */
.croquy.worried .croquy-logo { animation: mascot-worried .5s ease; }
@keyframes mascot-worried { 0%,100% { transform: rotate(0) scaleY(1); } 30% { transform: rotate(-6deg) scaleY(.94); } 60% { transform: rotate(3deg) scaleY(.97); } }

/* 8a — série « en feu » : exception assumée (comme la ligue) — la flamme est un
   signal de contenu, pas du chrome. Plate, sans halo. */
#streak-pill.hot { color: var(--surface); background: #C88A2E; border-color: #C88A2E; }
#streak-pill.blaze { color: var(--surface); background: #B3502B; border-color: #B3502B;
  box-shadow: none; }
#streak-pill.hot .ic, #streak-pill.blaze .ic { animation: flame-flicker .5s ease-in-out infinite; transform-origin: bottom center; }
@keyframes flame-flicker { 0%,100% { transform: scale(1) rotate(-3deg); } 50% { transform: scale(1.18) rotate(3deg); } }

/* 8j — micro-toast d'encouragement (« presque ! ») + combos (8c). */
.toastlet { position: fixed; left: 50%; top: 34%; transform: translateX(-50%);
  background: var(--ink); color: var(--surface); font-family: var(--text); font-weight: 600; padding: 10px 18px; border-radius: 3px;
  z-index: 70; pointer-events: none; box-shadow: none;
  animation: toastlet .95s ease both; }
.toastlet.combo { top: 26%; background: var(--purple); font-size: 1.1rem; }
@keyframes toastlet { 0% { opacity: 0; transform: translate(-50%, 8px) scale(.8); }
  20% { opacity: 1; transform: translate(-50%, 0) scale(1.08); }
  80% { opacity: 1; transform: translate(-50%, -4px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -18px) scale(.96); } }

/* MOT-MYSTÈRE des grilles denses. Une grille dense est remplie en français courant : elle
 * n'a plus de thème, et c'est ce gabarit qui le lui rend. Il occupe la place qu'occupait
 * une liste de définitions, écartée au playtest (taper une case sélectionne déjà son mot). */
.myst { display: flex; align-items: center; gap: 10px; padding: 8px 20px 4px; }
.myst-lbl { font: 600 11px/1 var(--sans, system-ui); text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); flex: none; }
.myst-boxes { display: flex; gap: 4px; }
.myst-box { width: 22px; height: 26px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--surface); display: flex; align-items: center; justify-content: center;
  font: 600 15px/1 var(--text, system-ui); color: var(--ink); text-transform: uppercase; }
.myst-box.on { background: var(--focus); border-color: var(--purple); }
/* Case de la grille qui nourrit le gabarit. Playtest 2026-08-04 : elle était teintée en
 * `--focus`, donc PIXEL-IDENTIQUE au mot en cours de saisie (`.hl`) et au curseur — « des
 * fois on sait pas trop où on est ». `--focus` est désormais réservé à l'ÉTAT ACTIF, et
 * l'appartenance au mot-mystère se dit par un filet, pas par un fond.
 * Pas de `background` ici, même pas `--surface` : `.hl` a la même spécificité (0,2,0) et
 * arrive AVANT dans le fichier, donc une déclaration explicite ici la gagnerait et la case
 * mystère cesserait de réagir à la sélection.
 * Filet et non pastille : `--cell` va de 44px à `--cell-min: 11px` en paysage, où un point
 * proportionnel ferait 1,2px (invisible, rendu subpixel). Un filet s'étire sur la largeur
 * et tient à toutes les tailles — c'est aussi ce que prévoit DESIGN-SYSTEM.md. */
.cell.cell-mystery { box-shadow: inset 0 -2px 0 var(--purple); }
/* `.cell.cursor` pose déjà un `inset` : sans cette règle, l'une des deux ombres efface l'autre. */
.cell.cell-mystery.cursor { box-shadow: inset 0 0 0 1px var(--purple), inset 0 -2px 0 var(--purple); }
