:root {
  /* Refonte graphique (2026-08-27, voir design_handoff_booster_refonte) :
     thème clair unique, accent unique, plus de dégradé jaune/rouge. Anciens
     noms de token (--bg/--panel/--text/--muted/--nav-border/--row-tint)
     conservés pour ne pas casser tout ce qui les référence déjà ailleurs
     dans ce fichier — seules leurs valeurs changent. */
  --bg: #f7f8fa;
  --bg-glow: #f7f8fa;
  --panel: #ffffff;
  --panel-2: #f7f8fa;
  --text: #101828;
  --text-secondary: #475467;
  --text-tertiary: #667085;
  --text-weak: #98a2b3;
  --muted: #667085;
  --nav-border: rgba(16, 24, 40, 0.09);
  --border-fine: rgba(16, 24, 40, 0.07);
  --border-strong: rgba(16, 24, 40, 0.12);
  --row-tint: rgba(16, 24, 40, 0.04);
  --track: rgba(16, 24, 40, 0.08);
  --veil: rgba(16, 24, 40, 0.32);

  --accent: oklch(0.6164 0.1081 161.32);
  --accent-text: oklch(0.5164 0.1081 161.32);
  --accent-hover: oklch(0.4164 0.1081 161.32);
  --accent-shadow: oklch(0.6164 0.1081 161.32 / 0.45);

  --font-display: "Space Grotesk", "Segoe UI", Arial, sans-serif;
  --font-body: "Manrope", "Segoe UI", Arial, sans-serif;

  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 14px;
  --radius-xl: 16px;
  --radius-pill: 999px;

  --shadow-card: 0 0 0 1px var(--border-fine), 0 6px 14px rgba(16, 24, 40, 0.12);
  --shadow-primary-btn: 0 12px 28px oklch(0.6164 0.1081 161.32 / 0.4);
  --shadow-central-btn: 0 12px 28px oklch(0.6164 0.1081 161.32 / 0.45);
  --shadow-dark-btn: 0 8px 20px rgba(16, 24, 40, 0.22);
  --shadow-nav: 0 10px 30px rgba(16, 24, 40, 0.12);
  --shadow-sheet: 0 -12px 40px rgba(16, 24, 40, 0.18);

  --common: #9aa0b4;
  --uncommon: #4caf6a;
  --rare: #4a8ff0;
  --double-rare: #9a5cf0;
  --ultra-rare: #ff9d2f;
  --illustration-rare: #ff6fae;
  --special-illustration-rare: #ff3b5c;
  --hyper-rare: #f5c518;

  /* 6 raretés ajoutées le 2026-08-14 (voir server/pack.js RARITY_ORDER pour
     l'ordre/le raisonnement) — restent fixes quel que soit le thème, comme
     les 8 au-dessus. */
  --ace-spec-rare: #2fd0c7;
  --shiny-rare: #5ad1ff;
  --shiny-ultra-rare: #22c7b5;
  --mega-attack-rare: #ff7a1a;
  --mega-hyper-rare: #ff2fd6;
  --black-white-rare: #eef0f5;
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1.25rem 1rem 6.5rem;
}

h1, h2, h3,
.coins-badge,
button {
  font-family: var(--font-display);
}

header {
  position: relative;
  margin-bottom: 1.15rem;
  width: 100%;
  max-width: 1000px;
}

/* Ligne logo + bannière (2026-09-10, la bannière ne prend plus sa propre
   bande sous le logo — voir #profile-banner). #header-menu/#header-actions
   restent en position absolute sur header (inchangés), donc le padding
   latéral leur laisse juste la place (~ largeur bouton 2.2rem + un peu
   d'air) sans se soucier de leur ordre dans le DOM. */
#header-top-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0 2.7rem;
}

header h1 {
  margin: 0;
  line-height: 0;
  flex: 0 0 auto;
}

#logo {
  height: clamp(2.2rem, 8vw, 3.2rem);
  width: auto;
}

/* Cluster des boutons utilitaires en haut à droite du header : changelog en
   accès direct (2026-08-11), + un bouton ☰ qui déroule comment jouer/à
   propos/admin/déconnexion ("?" reparti dans le dropdown le même jour,
   après un aller-retour). Changelog et ☰ restent visibles même déconnecté ;
   admin/déconnexion sont montrés/masqués par showApp()/showLogin() dans
   script.js (à l'intérieur du dropdown). */
#header-actions {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Menu ☰ déplacé en haut à gauche (2026-08-25, demande joueur), séparé du
   cluster chat/thème resté à droite — même #menu-wrap/#menu-dropdown
   qu'avant, juste un conteneur différent pour le positionnement. */
#header-menu {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: center;
}

#header-actions button {
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 999px;
  width: 2.2rem;
  height: 2.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: none;
  color: var(--text);
  flex-shrink: 0;
  cursor: pointer;
}

/* Pastille de notification (2026-08-13) : nouveaux messages de chat non lus
   (voir checkChatUnread()/wireChatModal côté script.js) — masquée par
   défaut, activée en ajoutant .has-unread sur #chat-btn. Bordure de la
   couleur du bouton pour rester lisible sur l'icône même en thème clair. */
#chat-btn {
  position: relative;
}

#chat-btn.has-unread::after {
  content: "";
  position: absolute;
  top: 1px;
  right: 1px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #e0393e;
  border: 1.5px solid var(--panel-2);
}

/* Pastille de notification (2026-08-30, à l'origine pour les défis
   permanents ; conservée pour la Passe de combat 2026-09-13) : défi
   journalier ou palier de passe complété dont la récompense n'a pas encore
   été récupérée (voir updateBattlePassBadge()/checkBattlePassClaimable(),
   script.js). Déplacé du header vers la barre du bas au rebalance BÊTA v2
   (2026-09-06) — bordure/position alignées sur #nav-market.has-unread
   ci-dessous (même barre, var(--panel)) plutôt que sur #chat-btn.has-unread
   (header, var(--panel-2)). */
#battlepass-btn {
  position: relative;
}

#battlepass-btn.has-reward::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #e0393e;
  border: 1.5px solid var(--panel);
}

/* Pastille de notification (2026-09-04, demande produit Dylan : "des notifs
   pour dire aux joueurs qu'on veut échanger avec eux") : offre d'échange
   direct reçue en attente (voir checkTradeUnread()/updateTradeBadge(),
   script.js — GET /api/trade/offers) — même style que les pastilles
   ci-dessus, posée sur #nav-market (barre du bas) plutôt que sur un bouton
   de header, donc bordure de la couleur de la barre (var(--panel)) au lieu
   de var(--panel-2). */
#nav-market {
  position: relative;
}

#nav-market.has-unread::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #e0393e;
  border: 1.5px solid var(--panel);
}

#menu-wrap {
  position: relative;
}

/* #menu-btn stylé à part (2026-08-25) : n'est plus dans #header-actions
   depuis le déplacement du menu à gauche, il perd donc le style de bouton
   rond hérité de la règle `#header-actions button` — reproduit ici à
   l'identique. */
#menu-btn {
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 999px;
  width: 2.2rem;
  height: 2.2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: none;
  color: var(--text);
  flex-shrink: 0;
  cursor: pointer;
}

#menu-dropdown {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 180px;
  background: color-mix(in srgb, var(--panel-2) 65%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--nav-border);
  border-radius: 14px;
  padding: 0.5rem;
}

/* Boutons du dropdown : icône rondes → ligne icône + texte (2026-08-25,
   demande joueur), plus lisible qu'une colonne d'icônes muettes. */
#menu-dropdown button {
  width: 100%;
  background: transparent;
  border: none;
  border-radius: 8px;
  padding: 0.45rem 0.6rem;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.6rem;
  box-shadow: none;
  color: var(--text);
  font-size: 0.8rem;
  white-space: nowrap;
  cursor: pointer;
}

#menu-dropdown button svg {
  flex-shrink: 0;
}

#menu-dropdown button:hover,
#menu-dropdown button:focus-visible {
  background: var(--panel-2);
}

/* Bannière de profil — refonte HUD (2026-09-09, demande produit : "plus
   minimaliste, regroupé, à la Call of Duty, tout en gardant le côté épuré
   de la DA"), brièvement raccourcie et intégrée à la ligne du logo puis
   remise sous le logo, sur sa propre ligne, le jour même (2026-09-10, retour
   "j'aime pas le bandeau à côté du logo") — version courte conservée.
   Groupes de stats serrés (.banner-group) séparés par des traits fins
   (.banner-sep). Bande discrète pleine largeur centrée sous le logo, sans
   fond lourd ni liseré (2026-09-10, retour "retire la barre de séparation").
   overflow-x reste un filet de sécurité (scroll interne plutôt que retour à
   la ligne) si le contenu déborde (mesure navigateur 2026-09-20 : tout le
   contenu, taux/h inclus, tient sur une ligne dès 320px de large).
   Bouton entier cliquable vers Profil (voir #profile-banner click,
   public/script.js). */
#profile-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-width: 0;
  width: 100%;
  margin: 0.5rem 0 0;
  padding: 0.35rem 0.7rem 0.55rem;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  font: inherit;
  font-size: 0.74rem;
  line-height: 1;
  color: var(--text-secondary);
  cursor: pointer;
  overflow-x: auto;
  scrollbar-width: none;
  white-space: nowrap;
}

#profile-banner::-webkit-scrollbar {
  display: none;
}

.banner-sep {
  flex: 0 0 auto;
  align-self: center;
  width: 1px;
  height: 60%;
  background: var(--nav-border);
}

.banner-group {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  line-height: 1;
}

.banner-level-text {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.banner-group-medals {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

.banner-group-economy {
  gap: 0.55rem;
}

/* Cluster PKC (restyle 2026-09-02, déplacé dans #profile-banner le
   2026-09-09, dépouillé du fond en pill le 2026-09-09 pour le HUD) : solde +
   production, monochrome, sans emoji (le ● emoji rendait mal). */
.banner-eco-item {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  line-height: 1;
}
.pkc-coin {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  border: 1.5px solid var(--accent-text);
  background: radial-gradient(circle at 35% 30%, #ffffff, oklch(0.87 0.09 161.32));
  box-shadow: inset 0 0 0 1px oklch(0.94 0.05 161.32 / 0.9);
}
.coins-badge {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
.pkc-unit {
  color: var(--text-tertiary);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}
.pkc-rate-value {
  color: var(--accent-text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
#banner-rate .pkc-unit {
  color: var(--accent-text);
  opacity: 0.7;
}

.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;
}

#view-admin {
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.view-admin-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.view-admin-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--text);
}

#admin-unlock-form {
  width: 100%;
  background: var(--panel);
  border-radius: 12px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

#admin-unlock-form label {
  font-size: 0.9rem;
  color: var(--muted);
}

#admin-unlock-form input {
  font: inherit;
  padding: 0.65rem 0.9rem;
  border-radius: 8px;
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
}

#admin-unlock-error {
  color: var(--special-illustration-rare);
  font-size: 0.85rem;
  margin: 0;
}

#admin-content {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

#admin-content .shop-hint {
  margin-bottom: 0.15rem;
}

#admin-danger {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--nav-border);
}

#admin-danger-actions {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Actions admin : icônes rondes + petit label, sur une ligne (même
   vocabulaire visuel que la nav), mais en gardant les couleurs "chaudes"
   des boutons d'origine (danger rouge, dégradé du bouton principal pour
   l'action positive) au lieu du monochrome utilisé pour la nav/le header. */
#admin-actions {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.admin-action {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--muted);
  font-size: 0.65rem;
  font-weight: 600;
  max-width: 5.5rem;
  text-align: center;
}

.admin-action svg {
  flex-shrink: 0;
  width: 2.4rem;
  height: 2.4rem;
  padding: 0.55rem;
  border-radius: 10px;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  box-sizing: border-box;
  color: var(--text);
}

.admin-action.danger svg {
  background: #c0392b;
  border-color: transparent;
  color: #fff;
}

.admin-action.hot svg {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
}

.nav-btn {
  font: inherit;
  font-weight: 600;
  padding: 0.5rem 1.1rem;
  border-radius: 999px;
  border: 1px solid var(--nav-border);
  cursor: pointer;
  background: transparent;
  color: var(--muted);
  white-space: nowrap;
}

.nav-btn.active {
  background: var(--panel-2);
  color: var(--text);
  border-color: transparent;
}

/* Nav principale — barre flottante + bouton central (2026-08-27,
   design_handoff_booster_refonte, icônes seules depuis le rebalance combats
   BÊTA v2 2026-09-06 — retour joueur "trop large avec les libellés" une
   fois Combat + Défis ajoutés à la barre). 6 nav-btn (Minage/Classement/
   Combat/Défis/Marché/Profil) + bouton central, répartis 3+3, fond blanc
   flottant, ombre. */
#view-nav {
  position: fixed;
  left: 14px;
  right: 14px;
  bottom: 18px;
  z-index: 11;
  max-width: 340px;
  width: max-content;
  margin: 0 auto;
  height: 62px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.28rem;
  background: var(--panel);
  border: 1px solid var(--border-fine);
  border-radius: 22px;
  box-shadow: var(--shadow-nav);
}

#view-nav .nav-btn {
  flex: 0 0 auto;
  width: 2.2rem;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 0.1rem;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-weak);
  box-shadow: none;
  transition: color 0.15s ease;
}

#view-nav .nav-btn:active {
  transform: scale(0.9);
}

#view-nav .nav-btn svg {
  flex-shrink: 0;
  width: 1.3rem;
  height: 1.3rem;
  color: inherit;
}

#view-nav .nav-btn.active {
  color: var(--text);
}

/* Bouton central d'ouverture de booster — seul déclencheur sur mobile
   depuis la refonte (demande client "bouton en bas"), fonctionne depuis
   n'importe quel écran (voir wireOpenButton()/openCurrentPack(),
   public/script.js). */
#nav-open-btn {
  flex: 0 0 auto;
  width: 66px;
  height: 66px;
  margin-top: -34px;
  border-radius: 50%;
  border: 5px solid var(--bg);
  background: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-central-btn);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: transform 0.15s ease;
}

#nav-open-btn:active {
  transform: scale(0.93);
}

@media (hover: hover) {
  #nav-open-btn:hover {
    transform: translateY(-3px);
  }
}

main {
  width: 100%;
  max-width: 1000px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}

.hidden {
  display: none !important;
}

/* ---------- Booster ---------- */

#view-pack {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
}

#pack-stage {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 1rem;
}

#pack-wrapper {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 800px;
}

/* Flèches de navigation rapide entre séries de part et d'autre du booster
   (2026-09-13, demande Benjamin). Même esprit visuel que .icon-row-btn
   mais un peu plus grandes (cible de clic principale de l'écran). */
.pack-nav-btn {
  flex: 0 0 auto;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border-fine);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  transition: transform 0.15s ease;
}

.pack-nav-btn:active {
  transform: scale(0.92);
}

@media (max-width: 480px) {
  .pack-nav-btn {
    width: 2rem;
    height: 2rem;
  }
}

#pack-wrapper.fading-out {
  transition: opacity 0.3s ease;
  opacity: 0;
}

/* Halo statique derrière le pack (design_handoff). */
#pack-halo {
  position: absolute;
  width: 250px;
  height: 250px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 70%);
  pointer-events: none;
}

#pack-tier-line {
  text-align: center;
  margin: -0.5rem 0 0;
}

#pack-tier-cap {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-weak);
}

#pack {
  width: clamp(200px, 55vw, 236px);
  height: auto;
  aspect-ratio: 466 / 700;
  object-fit: contain;
  filter: drop-shadow(0 22px 34px rgba(16, 24, 40, 0.28));
  animation: floatPack 6s ease-in-out infinite;
  transition: transform 0.4s ease, opacity 0.4s ease;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
  pointer-events: none;
}

@keyframes floatPack {
  0%, 100% { transform: translateY(0) rotate(-0.4deg); }
  50% { transform: translateY(-10px) rotate(0.4deg); }
}

@media (prefers-reduced-motion: reduce) {
  #pack {
    animation: none;
  }
}

/* Bloc de complétion de série — repos (#pack-completion) et bilan
   (#bilan-completion) partagent ces mêmes classes, voir
   renderCompletionBlock()/showBilan() dans script.js. */
.completion-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.completion-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
}

.completion-count {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.progress-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--track);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
}

#pack-completion {
  width: 100%;
  max-width: 340px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Animation d'ouverture (2026-08-10) : le pack fait un petit tremblement
   d'anticipation, puis se "déchire" en deux rabats qui pivotent en 3D
   pendant qu'un flash radial éclate, avant que la pile de cartes apparaisse
   en pop-in — remplace l'ancien fade simple (#pack.opening). */
#pack.anticipating {
  animation: bstAnticipate 0.15s ease;
}

.pack-flap {
  position: absolute;
  inset: 0;
  display: none;
  pointer-events: none;
}

.pack-flap img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

#pack-flap-top {
  clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 90% 54%, 80% 48%, 70% 55%, 60% 47%, 50% 55%, 40% 47%, 30% 55%, 20% 48%, 10% 54%, 0% 50%);
  transform-origin: top center;
}

#pack-flap-bottom {
  clip-path: polygon(0% 50%, 10% 54%, 20% 48%, 30% 55%, 40% 47%, 50% 55%, 60% 47%, 70% 55%, 80% 48%, 90% 54%, 100% 50%, 100% 100%, 0% 100%);
  transform-origin: bottom center;
}

.pack-flap.tearing#pack-flap-top {
  display: block;
  animation: bstTearTop 0.6s ease-in forwards;
}

.pack-flap.tearing#pack-flap-bottom {
  display: block;
  animation: bstTearBottom 0.6s ease-in forwards;
}

#pack-flash {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 140%;
  height: 140%;
  margin-left: -70%;
  margin-top: -70%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.98), var(--accent-shadow) 42%, transparent 72%);
  pointer-events: none;
  opacity: 0;
}

#pack-flash.flashing {
  animation: bstFlash 0.5s ease-out forwards;
}

#stack.popping-in {
  animation: bstStackIn 0.45s cubic-bezier(0.2, 0.9, 0.25, 1.4);
}

@keyframes bstAnticipate {
  0% { transform: scale(1) rotate(0); }
  40% { transform: scale(0.94) rotate(-1deg); }
  70% { transform: scale(1.05) rotate(1deg); }
  100% { transform: scale(1) rotate(0); }
}

@keyframes bstTearTop {
  from { transform: rotateX(0) translateY(0); opacity: 1; }
  to { transform: rotateX(-118deg) translateY(-46px); opacity: 0; }
}

@keyframes bstTearBottom {
  from { transform: rotateX(0) translateY(0); opacity: 1; }
  to { transform: rotateX(118deg) translateY(40px); opacity: 0; }
}

@keyframes bstFlash {
  0% { opacity: 0; transform: scale(0.2); }
  35% { opacity: 1; transform: scale(1.4); }
  100% { opacity: 0; transform: scale(2.6); }
}

@keyframes bstStackIn {
  0% { opacity: 0; transform: scale(0.82) translateY(28px); }
  60% { opacity: 1; transform: scale(1.04) translateY(-4px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* Éclat de rareté (2026-08-10) : burst + étincelles affichés brièvement sur
   #stack quand une carte Ultra Rare ou plus est révélée, voir
   playRarityBurst() dans script.js. */
.rarity-burst {
  position: absolute;
  inset: -20%;
  border-radius: 50%;
  pointer-events: none;
  animation: bstBurst 0.7s ease-out forwards;
}

@keyframes bstBurst {
  0% { opacity: 0.95; transform: scale(0.3); }
  100% { opacity: 0; transform: scale(2.4); }
}

.rarity-sparkle {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  pointer-events: none;
  animation: bstSparkle 0.7s ease-out forwards;
}

@keyframes bstSparkle {
  0% { opacity: 1; transform: translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0); }
}

/* Bouton "Ouvrir" : posé au centre du pack (repositionné le 2026-08-27,
   demande produit). Visible sur desktop ET mobile depuis le 2026-08-29 :
   #nav-open-btn (bouton du bas) ne fait plus que naviguer vers cet écran
   (voir le commentaire au-dessus d'openCurrentPack(), public/script.js) —
   #open-btn est donc redevenu le seul déclencheur, y compris sur mobile
   (l'ancien `@media (max-width: 699px) { display: none }` qui le masquait
   sur mobile datait de l'époque où #nav-open-btn ouvrait directement, et
   rendait toute ouverture de booster impossible sur mobile une fois ce
   raccourci retiré — supprimé). La "dopamine" vient de la séquence
   d'ouverture elle-même, plus d'un bouton qui clignote en permanence
   (ancien @keyframes btn-glow, retiré). */
#open-btn {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  padding: 0 22px;
  height: 42px;
  border-radius: var(--radius-xl);
  white-space: nowrap;
}

@media (hover: hover) {
  #open-btn:hover {
    transform: translate(-50%, calc(-50% - 2px));
  }
}

#open-btn:disabled {
  background: var(--panel-2);
  color: var(--text-weak);
  box-shadow: none;
  opacity: 1;
}

/* Série terminée (2026-09-14) : pack grisé + badge, voir updatePackDisplay()
   (public/script.js). #pack:disabled n'existe pas (c'est une <img>), donc le
   grisage passe par .set-done sur #pack-wrapper plutôt que sur #pack seul. */
#pack-wrapper.set-done #pack {
  filter: grayscale(0.85) drop-shadow(0 22px 34px rgba(16, 24, 40, 0.28));
  opacity: 0.55;
}

#pack-done-badge {
  position: absolute;
  top: 8%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-weak);
  background: var(--panel);
  border: 1px solid var(--border-fine);
  border-radius: var(--radius-pill);
  padding: 0.2rem 0.7rem;
  box-shadow: var(--shadow-card);
}

#pack-all-done-message {
  text-align: center;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-tertiary);
  max-width: 320px;
  margin: 0.25rem auto 0;
}

/* ---------- Stack (one card at a time) ---------- */

#stack-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
}

/* Frise "CARTE n / N" (2026-08-27, design_handoff) — voir
   renderStackFrieze()/updateProgress(), public/script.js. */
#stack-frieze {
  display: flex;
  gap: 5px;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 320px;
}

.frieze-dot {
  width: 22px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--track);
  transition: background 0.3s ease;
}

.frieze-dot.seen {
  background: var(--text);
}

.frieze-dot.current {
  background: var(--accent);
}

#stack-progress {
  color: var(--text-weak);
  margin: 0;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
}

#stack {
  position: relative;
  width: clamp(200px, 65vw, 260px);
  aspect-ratio: 63 / 88;
  margin-top: 8px;
}

/* Halo derrière la carte active quand sa rareté est >= Ultra Rare (voir
   BURST_RARITIES/playRarityBurst(), public/script.js) — couleur posée en
   inline style au moment du burst. */
#stack-halo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 330px;
  height: 330px;
  transform: translate(-50%, -50%) scale(0.96);
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

#stack-halo.visible {
  opacity: 0.8;
  animation: haloPulse 1.5s ease-in-out infinite alternate;
}

@keyframes haloPulse {
  from { opacity: 0.35; transform: translate(-50%, -50%) scale(0.96); }
  to { opacity: 0.8; transform: translate(-50%, -50%) scale(1.06); }
}

/* Nom/rareté/prix sous la carte (2026-08-27) — hauteur fixe pour ne jamais
   sauter en layout d'une carte à l'autre, voir updateStackCardInfo(). Marge
   au-dessus + gap interne agrandis (2026-09-07, demande produit : le titre
   était trop collé à la carte pendant l'ouverture) — scoped à ce bloc, ne
   change pas le rythme des autres écrans. */
#stack-card-info {
  min-height: 56px;
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
}

#stack-card-name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
}

#stack-card-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--text-tertiary);
}

.rarity-pill {
  padding: 0.2rem 0.5rem;
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--panel-2);
  color: var(--text-tertiary);
}

.rarity-pill.rarity-common { background: rgba(154, 160, 180, 0.14); color: var(--common); }
.rarity-pill.rarity-uncommon { background: rgba(76, 175, 106, 0.14); color: var(--uncommon); }
.rarity-pill.rarity-rare { background: rgba(74, 143, 240, 0.14); color: var(--rare); }
.rarity-pill.rarity-double-rare { background: rgba(154, 92, 240, 0.14); color: var(--double-rare); }
.rarity-pill.rarity-ultra-rare { background: rgba(255, 157, 47, 0.16); color: var(--ultra-rare); }
.rarity-pill.rarity-illustration-rare { background: rgba(255, 111, 174, 0.16); color: var(--illustration-rare); }
.rarity-pill.rarity-special-illustration-rare { background: rgba(255, 59, 92, 0.16); color: var(--special-illustration-rare); }
.rarity-pill.rarity-hyper-rare { background: rgba(245, 197, 24, 0.18); color: color-mix(in srgb, var(--hyper-rare) 70%, var(--text) 30%); }
.rarity-pill.rarity-ace-spec-rare,
.rarity-pill.rarity-shiny-rare,
.rarity-pill.rarity-shiny-ultra-rare,
.rarity-pill.rarity-mega_attack_rare,
.rarity-pill.rarity-mega-hyper-rare,
.rarity-pill.rarity-black-white-rare {
  background: var(--panel-2);
  color: var(--text);
}

#stack-hint {
  margin: 0;
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--text-weak);
  text-align: center;
  min-height: 1em;
}

#stack-reveal-all {
  display: block;
  margin: 0.35rem auto 0;
  padding: 0.2rem 0.4rem;
  background: none;
  border: none;
  font-size: 0.68rem;
  font-weight: 500;
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-align: center;
  cursor: pointer;
}

#stack-reveal-all:hover,
#stack-reveal-all:focus-visible {
  color: var(--accent-hover);
}

#stack-reveal-all:active {
  opacity: 0.7;
}

#stack-reveal-all:disabled {
  opacity: 0.5;
  cursor: default;
}

.stack-card {
  position: absolute;
  inset: 0;
  perspective: 900px;
  cursor: pointer;
  transition: transform 0.2s ease, opacity 0.2s ease;
}

.stack-card .card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.stack-card.flipped .card-inner {
  transform: rotateY(180deg);
}

.stack-card.consumed {
  transform: translateY(-120%) rotate(-8deg) scale(0.9);
  opacity: 0;
  pointer-events: none;
}

.stack-card:not(.active) {
  pointer-events: none;
}

.stack-card.flipped {
  animation: cardIn 0.45s cubic-bezier(0.2, 0.9, 0.25, 1.4);
}

@keyframes cardIn {
  0% { opacity: 0; transform: scale(0.86) translateY(26px); }
  60% { opacity: 1; transform: scale(1.03) translateY(-4px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}

.card-face {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  backface-visibility: hidden;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--text);
}

.card-back {
  background: linear-gradient(140deg, #101828, #2b3550);
}

.card-back::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(75deg, transparent 40%, rgba(255, 255, 255, 0.22) 50%, transparent 60%);
  background-size: 250% 100%;
  animation: shine 2.2s ease-in-out infinite;
}

@keyframes shine {
  0% { background-position: -160% 0; }
  100% { background-position: 260% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .card-back::after {
    animation: none;
  }
}

.card-front {
  transform: rotateY(180deg);
  background: var(--panel);
}

.card-front img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #fff;
}

/* Le liseré de rareté (autrefois accompagné d'un glow pulsant sur chaque
   palier) est réduit à une couleur fixe le 2026-08-27 — l'énergie visuelle
   d'une carte rare vient maintenant du halo derrière elle (#stack-halo,
   rareté >= Ultra Rare) plutôt que d'un scintillement permanent sur son
   propre cadre. */
.card-front.rarity-common { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--common); }
.card-front.rarity-uncommon { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--uncommon); }
.card-front.rarity-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--rare); }
.card-front.rarity-double-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--double-rare); }
.card-front.rarity-ace-spec-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--ace-spec-rare); }
.card-front.rarity-ultra-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--ultra-rare); }
.card-front.rarity-shiny-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--shiny-rare); }
.card-front.rarity-shiny-ultra-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--shiny-ultra-rare); }
.card-front.rarity-illustration-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--illustration-rare); }
.card-front.rarity-special-illustration-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--special-illustration-rare); }
/* Classe en underscore (pas en tiret comme les autres) : slugify() ne
   transforme que les espaces, et la rareté brute renvoyée par l'API est
   "MEGA_ATTACK_RARE" (voir server/pack.js RARITY_ORDER). */
.card-front.rarity-mega_attack_rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--mega-attack-rare); }
.card-front.rarity-hyper-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--hyper-rare); }
.card-front.rarity-mega-hyper-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--mega-hyper-rare); }
.card-front.rarity-black-white-rare { box-shadow: 0 18px 40px rgba(16, 24, 40, 0.22), 0 0 0 3px var(--black-white-rare); }

/* Badge "Nouvelle" (2026-08-20 ; repositionné haut-gauche/accent le
   2026-08-27 puis remis en haut-droite/rouge le même jour sur retour
   direct — plus visible/contrasté que l'accent vert) : carte jamais
   possédée avant ce booster, voir buildStackCard(), public/script.js. */
.new-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 0.6rem;
  font-weight: 700;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--special-illustration-rare);
  color: #fff;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  box-shadow: 0 6px 16px rgba(255, 59, 92, 0.5);
  animation: badgePop 0.4s cubic-bezier(0.2, 0.9, 0.25, 1.5);
}

@keyframes badgePop {
  0% { opacity: 0; transform: scale(0.4) rotate(-8deg); }
  70% { opacity: 1; transform: scale(1.12) rotate(2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

/* ---------- Bilan (feuille de fin de booster, 2026-08-27) ----------
   Remplace le simple texte "booster terminé" + bouton "ouvrir un autre" —
   voir showBilan(), public/script.js. */
#bilan-sheet {
  position: fixed;
  inset: 0;
  z-index: 9;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

#bilan-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#bilan-content {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 460px;
  background: var(--panel);
  border-radius: 26px 26px 0 0;
  padding: 22px 20px calc(128px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: var(--shadow-sheet);
  animation: riseFade 0.35s cubic-bezier(0.2, 0.9, 0.25, 1.1);
}

@keyframes riseFade {
  0% { opacity: 0; transform: translateY(10px); }
  100% { opacity: 1; transform: translateY(0); }
}

#bilan-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.2rem;
}

#bilan-subtitle {
  margin: -12px 0 0;
  font-size: 0.75rem;
  color: var(--text-tertiary);
}

#bilan-stats {
  display: flex;
  gap: 10px;
}

.bilan-stat {
  flex: 1;
  padding: 12px;
  border-radius: var(--radius-lg);
  background: var(--panel-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bilan-stat-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.2rem;
}

#bilan-stat-new {
  color: var(--accent-text);
}

.bilan-stat-label {
  font-size: 0.56rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-weak);
}

#bilan-completion {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Grille récap des cartes du booster (2026-09-10, demande produit : "grille
   qui tient sur mobile, cartes en petites, toutes affichées") — mêmes
   couleurs de bordure par rareté que .collection-card (voir plus bas),
   juste en format vignette et sans badges/interaction. */
#bilan-cards {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#bilan-cards-title {
  margin: 0;
}

.bilan-cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
  gap: 6px;
  max-height: 220px;
  overflow-y: auto;
}

.collection-card.bilan-card-thumb {
  border-width: 2px;
  border-radius: 6px;
  cursor: pointer;
  transition: transform 0.12s ease;
}

.collection-card.bilan-card-thumb:active {
  transform: scale(0.96);
}

.bilan-cards-hint {
  margin: 0;
  font-size: 0.68rem;
  color: var(--muted);
  text-align: center;
}

#bilan-buttons {
  display: flex;
  gap: 10px;
}

#bilan-buttons button {
  flex: 1;
  height: 50px;
  border-radius: var(--radius-lg);
  padding: 0;
}

/* ---------- Buttons ----------
   Refonte 2026-08-27 : plus de dégradé jaune/rouge — bouton par défaut en
   plein sombre (même traitement que le bouton primaire de la feuille de
   bilan du handoff), l'accent vert unique est réservé aux 1-2 CTA les plus
   importants de l'app (bouton central de nav, bouton desktop d'ouverture —
   voir plus bas), pour que l'accent garde du poids. Micro-feedback partout
   (demande produit : "boutons plus dynamiques") — :active sur tous les
   boutons y compris tactile, :hover réservé aux pointeurs à survol réel
   pour ne pas laisser un état de hover collé au doigt sur mobile. */

button {
  font: inherit;
  font-family: var(--font-display);
  font-weight: 600;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-pill);
  border: none;
  cursor: pointer;
  background: var(--text);
  color: #fff;
  box-shadow: var(--shadow-dark-btn);
  transition: transform 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease;
}

button:active {
  transform: scale(0.97);
}

@media (hover: hover) {
  button:hover {
    transform: translateY(-2px);
  }
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

.btn-secondary {
  background: var(--panel);
  color: var(--text);
  box-shadow: none;
  border: 1px solid var(--border-strong);
  font-size: 0.85rem;
  padding: 0.6rem 1.1rem;
}

/* Réservé aux CTA les plus importants de l'app (bouton central de nav,
   bouton d'ouverture desktop) — accent unique, jamais utilisé pour un
   fond de bloc/bouton secondaire ailleurs. */
.btn-accent {
  background: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-primary-btn);
}

@media (hover: hover) {
  .btn-accent:hover {
    transform: translateY(-2px);
    background: var(--accent-hover);
  }
}

/* "Vente rapide doublons" en vert (2026-09-14, demande Benjamin) : même
   accent que #nav-open-btn, mais pas .btn-accent (réservée aux CTA
   principaux ci-dessus) — override ciblé sur cet id, pas une classe
   générique réutilisable ailleurs. */
#sell-duplicates-btn {
  background: var(--accent);
  color: #fff;
  border: none;
}

@media (hover: hover) {
  #sell-duplicates-btn:hover {
    background: var(--accent-hover);
  }
}

/* ---------- Collection (fusionnée dans Profil le 2026-08-27, voir
   #view-profile pour son propre display:flex/gap qui joue maintenant ce
   rôle) ---------- */

.market-bulk-row {
  display: flex;
  justify-content: center;
  margin-bottom: 0.75rem;
}

/* Boutons icône ronds de la ligne d'actions Collection (tri, prestige,
   vaulter tout) — remplace .prestige-row-btn (2026-08-25, retour joueur :
   "réduit l'icône passer prestige à la même taille que les autres" + les 3
   tenant maintenant sur une seule ligne). Même gabarit que #menu-btn du
   header pour rester cohérent avec l'autre bouton icône rond de l'app. */
.icon-row-btn {
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-size: 1rem;
  box-shadow: none;
  flex-shrink: 0;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--nav-border);
}

/* ---------- Profil (2026-08-13, fusion Village/Vault) : pseudo + avatar ---------- */

#view-profile {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.8rem;
}

.profile-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 0.25rem 0 0.75rem;
}

/* En-tête profil réorganisé (2026-09-15, demande produit) : avatar à gauche,
   nom + niveau/XP empilés à droite — scopé pour ne pas affecter la version
   en lecture seule de .profile-header (#profile-visit-modal-content, qui
   garde son propre override colonne centrée). */
.profile-header-self {
  justify-content: flex-start;
  gap: 0.85rem;
  max-width: 460px;
  margin: 0 auto;
}

.profile-header-info {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1;
  min-width: 0;
}

/* Niveau de joueur "XP" (2026-08-27, demande produit) — même structure que
   #pack-completion (.completion-row + .progress-track/.progress-fill,
   classes déjà génériques). */
#profile-level {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Ligne niveau resserrée (2026-09-15, en-tête profil réorganisé) : polices
   et barre réduites, .completion-row/.progress-track restent génériques
   (partagés avec #pack-completion) donc scopés à #profile-level pour ne pas
   les affecter ailleurs. */
#profile-level .completion-name {
  font-size: 0.75rem;
}

#profile-level .completion-count {
  font-size: 0.68rem;
}

#profile-level .progress-track {
  height: 5px;
}

/* 4 tuiles cliquables Maison / Voiture / Coffre / Parchemins (2026-09-15,
   remplace la rangée déco à 2 colonnes + le cadeau quotidien textuel) : une
   ligne de 4 carrés compacts, clic -> popup #profile-tile-modal listant ce
   que le joueur possède — voir renderProfileCosmetics()/
   openProfileTileModal(), public/script.js. */
.profile-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.3rem;
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
}

.profile-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  aspect-ratio: 1 / 1;
  background: var(--panel);
  border: 1px solid var(--border-fine);
  border-radius: var(--radius-lg);
  padding: 0.3rem 0.15rem;
  color: var(--text-weak);
  box-shadow: none;
  min-width: 0;
}

.profile-tile-icon {
  width: 20px;
  height: 20px;
  color: inherit;
}

.profile-tile-icon svg {
  width: 100%;
  height: 100%;
}

.profile-tile-label {
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-weak);
  min-width: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
}

.profile-tile-status {
  font-size: 0.62rem;
  color: var(--text-secondary);
  text-align: center;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Parchemins de série (2026-09-10, passe 1/4 "diversifier l'envie de
   jouer") — trophée doré par série possédée à 100%, voir renderParchemins(),
   public/script.js. Grille pleine (4 colonnes) réservée à la visite de
   profil en lecture seule (#profile-visit-parchemins) depuis 2026-09-15 : le
   profil perso n'affiche plus qu'une tuile résumée + popup, voir
   .profile-tiles ci-dessus. */
#profile-visit-parchemins {
  width: 100%;
  max-width: 460px;
  margin: 0 auto 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Grille en 4 colonnes fixes (2026-09-13, retour Benjamin : cases façon
   cosmétiques, jamais de retour à la ligne pour les 4 premiers parchemins) —
   repeat(4, 1fr) plutôt que auto-fill/minmax pour garantir exactement 4
   colonnes même sur petit écran (360px) ; au-delà de 4 parchemins, les
   suivants passent normalement à la ligne suivante. */
.parchemins-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.4rem;
  width: 100%;
}

/* Même habillage "case" que .profile-tile (panneau + liseré + coins
   arrondis, contenu centré), doré conservé (color: var(--hyper-rare)). */
.parchemin-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  background: var(--panel);
  border: 1px solid var(--border-fine);
  border-radius: var(--radius-lg);
  padding: 0.4rem 0.25rem;
  color: var(--hyper-rare);
}

.parchemin-icon {
  width: 24px;
  height: 24px;
}

.parchemin-name {
  font-size: 0.58rem;
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.15;
}

.profile-avatar {
  position: relative;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--nav-border);
  padding: 0;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  flex-shrink: 0;
}

.profile-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Version en lecture seule (modale de visite) : pas de bouton d'upload, pas
   de curseur ni de badge crayon sur le conteneur — seule la photo elle-même
   (#profile-visit-avatar-img, curseur zoom ci-dessous) est cliquable, pas le
   pictogramme silhouette affiché quand le joueur n'a pas d'avatar. */
.profile-avatar-readonly {
  cursor: default;
}

#profile-visit-avatar-img {
  cursor: zoom-in;
}

button.profile-avatar {
  cursor: pointer;
}

.profile-avatar-edit {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 6px;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.65rem;
  color: var(--text);
}

.profile-name {
  font-size: 1.15rem;
  font-weight: 700;
}

#collection-empty {
  color: var(--muted);
}

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.85rem;
  width: 100%;
}

.collection-card {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  border: 3px solid var(--common);
  aspect-ratio: 63 / 88;
  cursor: pointer;
  transition: transform 0.15s ease;
}

.collection-card:hover {
  transform: translateY(-2px);
}

.collection-card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #fff;
}

.collection-card .count-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.collection-card .price-badge {
  position: absolute;
  bottom: 4px;
  left: 4px;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.collection-card.missing {
  cursor: default;
  border-color: var(--nav-border);
}

.collection-card.missing img {
  filter: grayscale(1);
  opacity: 0.35;
}

.collection-card.missing:hover {
  transform: none;
}

/* Showroom (2026-08-14) : bouton +/🏆 sur une carte du Vault (renderVaultView,
   script.js) pour l'épingler/retirer du Showroom du classement. Coin
   bas-droit, seul emplacement libre (count/vault en haut, prix en bas-gauche). */
.collection-card .showroom-toggle-badge {
  position: absolute;
  bottom: 4px;
  right: 4px;
  min-width: 1.4rem;
  height: 1.4rem;
  padding: 0 0.3rem;
  border: none;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1.4rem;
  cursor: pointer;
}

.collection-card .showroom-toggle-badge:hover {
  background: rgba(0, 0, 0, 0.9);
}

.collection-card .showroom-toggle-badge.pinned {
  background: var(--hyper-rare);
  color: #000;
}

/* Cartes gradées (2026-09-10, chantier "diversifier l'envie de jouer" 3/4) :
   cadre doré/holo pour une carte gradée (voir POST /api/cards/grade,
   server/index.js) — réutilise --hyper-rare (déjà la couleur "prestige" du
   badge Vitrine ci-dessus) plutôt qu'une nouvelle teinte dédiée. Le halo
   animé (::after) est coupé sous prefers-reduced-motion, le cadre doré et le
   badge restent (aucune info n'est portée uniquement par l'animation). */
.collection-card.graded {
  border-color: var(--hyper-rare);
  box-shadow: 0 0 0 2px var(--hyper-rare), 0 0 18px rgba(245, 197, 24, 0.45);
}

.collection-card.graded::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 20%, rgba(255, 255, 255, 0.55) 40%, transparent 60%);
  background-size: 220% 220%;
  animation: gradedHoloSheen 3.2s ease-in-out infinite;
  mix-blend-mode: overlay;
}

@keyframes gradedHoloSheen {
  0% { background-position: 120% 0; }
  100% { background-position: -20% 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .collection-card.graded::after {
    animation: none;
    background: none;
  }
}

/* Bouton/badge 🏅 Grader (2026-09-10) : coin haut-gauche, seul emplacement
   libre (compteur en haut-droite, prix en bas-gauche, Vitrine en bas-droite
   — voir les badges ci-dessus). <button> quand interactif (Collection du
   joueur courant, voir buildCollectionCardEl()), <span> en lecture seule
   ailleurs (visite de profil) : seul le <button> a un curseur cliquable. */
.collection-card .grade-badge {
  position: absolute;
  top: 4px;
  left: 4px;
  min-width: 1.4rem;
  height: 1.4rem;
  padding: 0 0.3rem;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1.4rem;
  white-space: nowrap;
}

.collection-card button.grade-badge {
  border: none;
  cursor: pointer;
}

.collection-card button.grade-badge:hover {
  background: rgba(0, 0, 0, 0.9);
}

.collection-card .grade-badge.graded {
  background: var(--hyper-rare);
  color: #131826;
}

.type-groups {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  width: 100%;
}

.collection-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  margin-bottom: 0.2rem;
}
.collection-head .type-group-title {
  margin-bottom: 0;
}

/* Cartes gradées (2026-09-10) : compteur "X dispo" à côté du titre "Ma
   collection". */
#grading-available-badge {
  color: var(--hyper-rare);
  font-weight: 700;
}

.type-group-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.6rem;
  font-size: 1rem;
}

.type-group-count {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
}

.collection-card.rarity-common { border-color: var(--common); }
.collection-card.rarity-uncommon { border-color: var(--uncommon); }
.collection-card.rarity-rare { border-color: var(--rare); }
.collection-card.rarity-double-rare { border-color: var(--double-rare); }
.collection-card.rarity-ultra-rare { border-color: var(--ultra-rare); }
.collection-card.rarity-illustration-rare { border-color: var(--illustration-rare); }
.collection-card.rarity-special-illustration-rare { border-color: var(--special-illustration-rare); }
.collection-card.rarity-hyper-rare { border-color: var(--hyper-rare); }
.collection-card.rarity-ace-spec-rare { border-color: var(--ace-spec-rare); }
.collection-card.rarity-shiny-rare { border-color: var(--shiny-rare); }
.collection-card.rarity-shiny-ultra-rare { border-color: var(--shiny-ultra-rare); }
.collection-card.rarity-mega_attack_rare { border-color: var(--mega-attack-rare); }
.collection-card.rarity-mega-hyper-rare { border-color: var(--mega-hyper-rare); }
.collection-card.rarity-black-white-rare { border-color: var(--black-white-rare); }

/* Sélection d'une carte pour une proposition d'échange (#market-trade) —
   même tuile que .collection-card, juste un anneau d'accent en plus quand
   choisie, voir buildTradeCardEl()/renderTradeCardGrids() (public/script.js). */
.collection-card.trade-selected {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- Succès / Défis (2026-08-29, onglet Profil ; liste compacte
   2026-09-14, demande Benjamin : "au lieu de cases, fais sous forme de
   liste, assez compacte" — une ligne par défi/palier au lieu d'une tuile,
   séparateurs fins plutôt qu'un fond de carte arrondi). ---------- */

.achievements-grid {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.achievement-badge {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.1rem;
  padding: 0.35rem 0.4rem;
  border-bottom: 1px solid var(--border-fine);
  border-left: 3px solid transparent;
}

.achievements-grid .achievement-badge:last-child {
  border-bottom: none;
}

.achievement-badge.locked {
  opacity: 0.5;
  filter: grayscale(0.6);
}

.achievement-badge.unlocked {
  border-left-color: var(--accent);
}

.achievement-emoji {
  font-size: 1.1rem;
  flex: 0 0 auto;
  width: 1.3rem;
  text-align: center;
}

.achievement-name {
  font-size: 0.8rem;
  font-weight: 600;
  flex: 0 1 auto;
  max-width: 9rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.achievement-description {
  font-size: 0.7rem;
  color: var(--muted);
  flex: 0 1 auto;
  max-width: 45%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  margin-left: auto;
}

.achievement-progress {
  flex: 1 1 auto;
  width: auto;
  min-width: 48px;
  height: 5px;
  margin: 0;
}

.achievement-progress-text {
  font-size: 0.65rem;
  color: var(--muted);
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Récompense PKC + récupération au clic (2026-08-30, demande produit) :
   .claimable ajoute un liseré rouge + curseur pointer pour signaler qu'une
   action est possible sur cette ligne précise (distinct de .unlocked, qui
   reste juste "complété" — un défi unlocked mais déjà claimed n'a plus ce
   liseré, voir openAchievementsModal(), script.js). */
.achievement-badge.claimable {
  cursor: pointer;
  border-left-color: #e0393e;
}

.achievement-badge.claimable:active {
  background: var(--panel-2);
}

.achievement-reward {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--accent);
  flex: 0 1 auto;
  max-width: 45%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  margin-left: auto;
}

.achievement-reward-claimed {
  color: var(--muted);
  font-weight: 500;
}

/* ---------- Défis du jour en cases (2026-09-14, demande Benjamin : la passe
   "liste compacte" ci-dessus ne devait viser QUE les 30 paliers de la Passe
   de combat — les 3 défis du jour retrouvent leur apparence en tuiles
   d'avant. Scopé sur #daily-challenges-grid uniquement : mêmes classes
   .achievement-badge/emoji/name/progress/reward que les paliers (JS
   partagé), donc la différence carrés/liste passe entièrement par ce
   scoping CSS plutôt que par un renommage de classes. ---------- */

#daily-challenges-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.4rem;
}

#daily-challenges-grid .achievement-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.25rem;
  min-height: 0;
  min-width: 0;
  background: var(--panel);
  border-radius: 12px;
  padding: 0.5rem 0.35rem;
  border-bottom: none;
}

#daily-challenges-grid .achievement-badge.unlocked {
  border-left-color: transparent;
  box-shadow: 0 0 0 1px var(--accent) inset;
}

#daily-challenges-grid .achievement-badge.claimable {
  border-left-color: transparent;
  box-shadow: 0 0 0 2px #e0393e inset;
}

#daily-challenges-grid .achievement-badge.claimable:active {
  background: var(--panel-2);
  transform: scale(0.97);
}

#daily-challenges-grid .achievement-emoji {
  font-size: 1.3rem;
  width: auto;
}

#daily-challenges-grid .achievement-name {
  max-width: 100%;
  white-space: normal;
  text-align: center;
  font-size: 0.72rem;
  line-height: 1.15;
}

#daily-challenges-grid .achievement-progress {
  width: 100%;
  flex: 0 0 auto;
}

#daily-challenges-grid .achievement-progress-text {
  text-align: center;
  font-size: 0.62rem;
}

#daily-challenges-grid .achievement-reward {
  max-width: 100%;
  white-space: normal;
  text-align: center;
  margin-left: 0;
  font-size: 0.6rem;
  line-height: 1.2;
}

/* ---------- Échange direct (2026-08-29, onglet Marché → Échanger) ---------- */

.trade-label {
  font-size: 0.9rem;
  color: var(--muted);
  align-self: flex-start;
}

#trade-player-select {
  width: 100%;
  max-width: 320px;
  font: inherit;
  padding: 0.65rem 0.9rem;
  border-radius: 8px;
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
}

#trade-propose-block {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.trade-columns {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

@media (max-width: 480px) {
  .trade-columns {
    grid-template-columns: 1fr;
  }
}

.trade-column h4 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  text-align: center;
}

/* Sélection d'échange en LISTE classique (2026-09-01, retour Benjamin :
   la grille d'images serrées était illisible — « cartes à la suite
   empilées »). Surcharge scopée de .cards-grid/.collection-card. */
.trade-grid {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-height: 80px;
  max-height: 260px;
  overflow-y: auto;
}

.trade-grid .collection-card {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  aspect-ratio: auto;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  background: var(--panel-2);
  overflow: hidden;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.trade-grid .collection-card:hover {
  transform: none;
  border-color: var(--text-tertiary);
}

.trade-grid .collection-card img {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  object-fit: contain;
  border-radius: 6px;
  background: #fff;
}

.trade-grid .collection-card .trade-row-name {
  flex: 1;
  min-width: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.trade-grid .collection-card .count-badge,
.trade-grid .collection-card .price-badge {
  position: static;
  flex: 0 0 auto;
  background: rgba(16, 24, 40, 0.72);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
}

/* Badge "Manquante" / "Déjà possédée" (2026-09-05, demandes Elina) : sur la
   liste "Sa carte à recevoir", indique si je possède déjà cette carte de
   l'autre joueur — pour repérer vite quoi demander en échange. */
.trade-grid .collection-card .trade-owned-badge {
  position: static;
  flex: 0 0 auto;
  font-size: 0.7rem;
  font-weight: 700;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  white-space: nowrap;
}

.trade-grid .collection-card .trade-owned-badge.missing {
  background: color-mix(in srgb, var(--accent) 25%, transparent);
  color: var(--text);
}

.trade-grid .collection-card .trade-owned-badge.owned {
  background: transparent;
  color: var(--muted);
}

.trade-grid .collection-card.trade-selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--panel-2));
}

/* Fix échange (2026-09-01) : liste plus haute sur mobile pour que la
   sélection reste visible — le scrollbar y est invisible, une zone trop
   courte donnait l'impression que la carte avait disparu. */
@media (max-width: 480px) {
  .trade-grid {
    max-height: 340px;
  }
}

.trade-pkc-row {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.trade-pkc-row input {
  width: 5rem;
  font: inherit;
  padding: 0.4rem;
  border-radius: 6px;
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
  text-align: center;
  margin-left: 0.4rem;
}

.trade-offer-list {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.trade-offer-row img {
  width: 40px;
  height: 56px;
  object-fit: contain;
  background: #fff;
  border-radius: 6px;
  flex-shrink: 0;
}

.trade-offer-actions {
  display: flex;
  gap: 0.4rem;
  flex-shrink: 0;
}

@media (max-width: 480px) {
  .cards-grid {
    grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
  }

  /* Nav mobile (2026-08-27, resserré au rebalance combats BÊTA v2
     2026-09-06 pour les 6 nav-btn + bouton central — Combat et Défis ajoutés
     à la barre) : groupe compact centré, width: max-content + margin: auto
     (règle de base ci-dessus). Icônes gardées à 1.3rem/20.8px (jamais sous
     ~20px, demande produit) ; on resserre le padding/gap des boutons plutôt
     que l'icône pour que tout tienne sur mobile étroit (~360px CSS). */
  #view-nav {
    gap: 0.22rem;
    padding: 0 8px;
    flex-wrap: nowrap;
  }

  #view-nav .nav-btn {
    width: 2.05rem;
    padding: 0.4rem 0.05rem;
  }

  #view-nav .nav-btn svg {
    width: 1.3rem;
    height: 1.3rem;
  }

  /* Sous 480px, on resserre le padding latéral du logo et la bannière
     (gap/padding/font-size réduits) pour que tout tienne sur une ligne.
     Le taux de production horaire reste affiché sur mobile depuis le
     2026-09-20 : mesuré en navigateur (320/360/390px, valeurs longues
     incluses), le contenu complet de la bannière tient sur une ligne dès
     320px — le masquage n'était pas nécessaire. Le scroll interne
     d'#profile-banner reste un filet de sécurité si un pseudo/niveau long
     dépasse quand même. */
  #header-top-row {
    padding: 0 2.5rem;
    gap: 0.4rem;
  }

  #profile-banner {
    gap: 0.35rem;
    padding: 0.24rem 0.5rem 0.42rem;
    font-size: 0.68rem;
  }

  .banner-group {
    gap: 0.22rem;
  }

  .banner-group-economy {
    gap: 0.35rem;
  }
}

/* ---------- Connexion ---------- */

#view-login {
  width: 100%;
  display: flex;
  justify-content: center;
  padding-top: 2rem;
}

#login-form {
  background: var(--panel);
  border-radius: 12px;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: 100%;
  max-width: 320px;
}

#login-form label {
  font-size: 0.9rem;
  color: var(--muted);
}

#login-form input {
  font: inherit;
  padding: 0.65rem 0.9rem;
  border-radius: 8px;
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
}

#login-error {
  color: var(--special-illustration-rare);
  font-size: 0.85rem;
  margin: 0;
}

#tcgbb-info-btn {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 0.8rem;
  text-decoration: underline;
  padding: 0.5rem;
  margin-top: 0.5rem;
  box-shadow: none;
}

#tcgbb-info-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#tcgbb-info-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#tcgbb-info-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: var(--radius-lg);
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 420px);
  max-height: 90vh;
  overflow-y: auto;
  color: var(--text-secondary);
  font-size: 0.85rem;
  text-align: left;
}

#tcgbb-info-modal-title {
  margin: 0 0 0.75rem;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--text);
  text-align: center;
}

#tcgbb-info-modal-body p {
  margin: 0 0 0.6rem;
  line-height: 1.5;
}

#tcgbb-info-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-sm);
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Classement ---------- */

#view-leaderboard {
  width: 100%;
}

/* Mini avatar (2026-08-13) : classement + showroom, à côté du pseudo — */
/* placeholder silhouette non dupliqué ici, l'image n'est rendue que si */
/* row.avatar existe (voir renderLeaderboardShowroom). */
.leaderboard-avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  cursor: zoom-in;
}

.leaderboard-showroom-title {
  margin: 2rem 0 1rem;
  text-align: center;
}

.showroom-player {
  background: var(--panel);
  border-radius: 12px;
  padding: 1rem;
  margin-bottom: 1rem;
}

.showroom-player.me {
  outline: 2px solid var(--hyper-rare);
  outline-offset: -2px;
}

/* Classement fusionné à la vitrine (2026-09-14) : nom cliquable + rang en
   petit sur la même ligne, jamais un pavé séparé — repasse en 2 lignes
   seulement si l'espace manque vraiment (pseudo long + petit écran). */
.showroom-player-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.2rem 0.6rem;
  margin: 0 0 0.75rem;
}

.showroom-player-name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  background: none;
  border: none;
  box-shadow: none;
  font: inherit;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

.showroom-player-name:hover {
  color: var(--rare);
}

.showroom-player-rank {
  font-size: 0.72rem;
  color: var(--text-weak);
  font-variant-numeric: tabular-nums;
  white-space: normal;
}

.showroom-empty {
  margin: 0;
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
}

.showroom-cards {
  grid-template-columns: repeat(3, minmax(100px, 160px));
}

/* ---------- Modale "à propos" (contenu ex-footer + ex-"comment jouer",
   fusionnés le 2026-08-27 — voir README design_handoff) ---------- */

#about-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#about-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#about-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: var(--radius-lg);
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 520px);
  max-height: 90vh;
  overflow-y: auto;
  color: var(--text-secondary);
  font-size: 0.8rem;
  text-align: left;
}

#about-modal-help-title {
  margin: 0 0 1rem;
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--text);
  text-align: center;
}

#about-modal-help-body p {
  margin: 0 0 0.75rem;
  line-height: 1.5;
}

#about-modal-credit {
  text-align: center;
  padding-top: 1rem;
  border-top: 1px solid var(--border-fine);
}

#about-modal-content p {
  margin: 0 0 0.5rem;
}

#about-modal-content .legal {
  font-size: 0.7rem;
  opacity: 0.7;
  line-height: 1.5;
  text-align: center;
}

#about-modal-content a {
  color: var(--text-secondary);
  text-decoration: underline;
}

#about-modal-content .version {
  opacity: 0.6;
  font-size: 0.7rem;
  margin-bottom: 0;
  text-align: center;
}

#about-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-sm);
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Modale "changelog" ---------- */

#changelog-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#changelog-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#changelog-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 520px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
  font-size: 0.8rem;
  text-align: left;
}

#changelog-modal-title {
  margin: 0 0 1rem;
  font-size: 1rem;
  text-align: center;
}

#changelog-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

.changelog-day + .changelog-day {
  margin-top: 1.1rem;
}

.changelog-day-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--nav-border);
  padding-bottom: 0.3rem;
}

.changelog-entry {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
  line-height: 1.45;
}

.changelog-entry:last-child {
  margin-bottom: 0;
}

.changelog-tag {
  flex-shrink: 0;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  padding: 0.12rem 0.4rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.changelog-tag.ajout { color: var(--uncommon); }
.changelog-tag.modification { color: var(--rare); }
.changelog-tag.suppression { color: var(--special-illustration-rare); }
.changelog-tag.evenement { color: var(--double-rare); }

.changelog-entry-text {
  color: var(--muted);
}

/* ---------- Modale "activité récente" (2026-08-25) ---------- */

#activity-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#activity-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#activity-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 520px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
  font-size: 0.8rem;
  text-align: left;
}

#activity-modal-title {
  margin: 0 0 1rem;
  font-size: 1rem;
  text-align: center;
}

#activity-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

#activity-modal-empty {
  color: var(--muted);
  text-align: center;
  margin: 0;
}

/* ---------- Page "Passe de combat" (2026-08-29, déplacée du Profil vers un
   bouton de header ; convertie de modale en page pleine 2026-09-06, demande
   produit "plus de popup" ; remplace les défis permanents par les défis
   journaliers + la Passe de combat le 2026-09-13, demande directe Benjamin —
   même pattern que #view-admin, juste un peu plus large pour laisser
   respirer la grille de badges). ---------- */

#view-battlepass {
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  color: var(--text);
  font-size: 0.8rem;
  text-align: left;
}

#daily-challenges-section,
#battlepass-section {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

/* ---------- Modale "acheter un booster" (2026-08-27, bouton sur l'écran
   d'accueil — remplace l'onglet Boosters retiré du Marché) ---------- */

#buy-booster-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#buy-booster-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#buy-booster-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 420px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
}

#buy-booster-modal-title {
  margin: 0 0 1rem;
  font-size: 1rem;
  text-align: center;
}

#buy-booster-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* Ouvrir/Acheter côte à côte (2026-08-27, demande produit : "Changer de
   booster" à côté d'"Acheter un booster") — #view-pack est en colonne par
   défaut, ce conteneur redevient une ligne pour ces 2 boutons secondaires
   uniquement. */
#pack-secondary-actions {
  width: 100%;
  display: flex;
  justify-content: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* ---------- Popup partagée des 4 tuiles Profil (2026-09-15) : même
   structure que #change-set-modal ci-dessous, contenu injecté au clic sur
   une tuile — voir openProfileTileModal(), public/script.js. ---------- */

#profile-tile-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#profile-tile-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#profile-tile-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 420px);
  max-height: 80vh;
  overflow-y: auto;
  color: var(--text);
}

#profile-tile-modal-title {
  margin: 0 0 0.6rem;
  font-size: 1rem;
  text-align: center;
}

#profile-tile-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

#profile-tile-modal-body {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

#profile-tile-modal-body .shop-hint {
  text-align: center;
  margin: 0;
}

.profile-tile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--border-fine);
  border-radius: 10px;
  padding: 0.5rem 0.7rem;
}

.profile-tile-row-name {
  font-weight: 600;
  font-size: 0.85rem;
}

.profile-tile-row-bonus {
  font-size: 0.75rem;
  color: var(--text-secondary);
  flex-shrink: 0;
}

/* ---------- Modale "changer de booster" (2026-08-27, bouton sur l'écran
   d'accueil) — même structure que #buy-booster-modal ci-dessus. ---------- */

#change-set-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#change-set-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#change-set-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 460px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
}

#change-set-modal-title {
  margin: 0 0 0.4rem;
  font-size: 1rem;
  text-align: center;
}

#change-set-modal-content .shop-hint {
  text-align: center;
  margin: 0 0 1rem;
}

#change-set-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Modale "cartes du set" (2026-09-04, demande produit Dylan :
   aperçu avant ouverture) — même structure que #change-set-modal ci-dessus,
   grille de cartes comme #profile-visit-modal-content .cards-grid. ---------- */

#set-cards-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#set-cards-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#set-cards-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 480px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
}

#set-cards-modal-content .cards-grid {
  max-width: 100%;
}

#set-cards-modal-title {
  margin: 0 0 0.4rem;
  font-size: 1rem;
  text-align: center;
}

#set-cards-modal-content .shop-hint {
  text-align: center;
  margin: 0 0 1rem;
}

#set-cards-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Modale "détail d'une offre d'échange" (2026-09-04, demande
   produit Benjamin) — même structure que #set-cards-modal, 2 colonnes pour
   comparer visuellement la carte donnée et la carte demandée. ---------- */

#trade-view-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#trade-view-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#trade-view-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  max-width: min(92vw, 480px);
  max-height: 85vh;
  overflow-y: auto;
  color: var(--text);
}

#trade-view-modal-title {
  margin: 0 0 0.8rem;
  font-size: 1rem;
  text-align: center;
}

.trade-view-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.trade-view-col .type-group-title {
  text-align: center;
  font-size: 0.85rem;
}

.trade-view-col .cards-grid {
  grid-template-columns: 1fr;
}

.trade-view-col .shop-hint {
  text-align: center;
  margin: 0.5rem 0 0;
}

.trade-view-rarity {
  text-align: center;
  font-weight: 600;
  font-size: 0.85rem;
  margin: 0.5rem 0 0;
}

#trade-view-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

#change-set-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 0.6rem;
}

.set-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  background: var(--panel-2);
  border: 1px solid var(--border-fine);
  border-radius: 12px;
  padding: 0.75rem 0.5rem;
  box-shadow: none;
}

.set-option:active {
  transform: scale(0.96);
}

.set-option img {
  height: 22px;
  max-width: 100%;
  object-fit: contain;
}

.set-option-name {
  font-size: 0.7rem;
  font-weight: 600;
  text-align: center;
  color: var(--text);
}

.set-option.locked {
  opacity: 0.4;
  filter: grayscale(0.8);
  cursor: default;
}

.set-option.locked:active {
  transform: none;
}

.set-option.done {
  opacity: 0.55;
  filter: grayscale(0.7);
  cursor: default;
}

.set-option.done:active {
  transform: none;
}

.set-option-badge {
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  background: var(--track);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.5rem;
}

.activity-entry {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  line-height: 1.45;
}

.activity-entry:last-child {
  margin-bottom: 0;
}

.activity-entry-icon {
  flex-shrink: 0;
}

.activity-entry-text {
  flex: 1;
}

.activity-entry-time {
  flex-shrink: 0;
  font-size: 0.68rem;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------- Popup d'annonce (2026-08-06) ---------- */

#announcement-modal {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#announcement-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
}

#announcement-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border: 1px solid var(--hyper-rare);
  border-radius: 14px;
  padding: 1.5rem;
  max-width: min(90vw, 480px);
  max-height: 85vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

#announcement-modal-title {
  margin: 0;
  text-align: center;
}

#announcement-modal-body {
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text);
}

#announcement-modal-body p {
  margin: 0 0 0.6rem;
}

#announcement-modal-body ul {
  margin: 0 0 0.6rem;
  padding-left: 1.2rem;
}

#announcement-close {
  background: var(--accent);
  color: #fff;
}

/* ---------- Ouverture automatique de série (2026-09-10) ---------- */
/* Même style que la popup d'annonce ci-dessus (voir #announcement-modal),
   sous un id dédié car le contenu est dynamique (slug/nom) et la clé
   localStorage est par slug plutôt que versionnée globalement. */

#series-unlock-modal {
  position: fixed;
  inset: 0;
  z-index: 310;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#series-unlock-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
}

#series-unlock-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border: 1px solid var(--hyper-rare);
  border-radius: 14px;
  padding: 1.5rem;
  max-width: min(90vw, 420px);
  max-height: 85vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

#series-unlock-modal-title {
  margin: 0;
  text-align: center;
}

#series-unlock-modal-body {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text);
  text-align: center;
}

#series-unlock-close {
  background: var(--accent);
  color: #fff;
}

/* ---------- Vote de la prochaine série (2026-09-03) ---------- */

#vote-modal {
  position: fixed;
  inset: 0;
  z-index: 320;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#vote-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
}

#vote-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border: 1px solid var(--hyper-rare);
  border-radius: 14px;
  padding: 1.5rem;
  max-width: min(90vw, 420px);
  max-height: 85vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

#vote-modal-header {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

#vote-modal-title {
  margin: 0;
  text-align: center;
  font-size: 1.05rem;
}

#vote-modal-hint,
#vote-modal-details {
  margin: 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--muted);
}

#vote-modal-candidates {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.vote-candidate-btn {
  width: 100%;
  background: #fff;
  color: #131826;
  border: none;
  border-radius: 10px;
  padding: 0.7rem 1rem;
  font: inherit;
  font-weight: 600;
  box-shadow: none;
}

.vote-candidate-btn:active {
  transform: scale(0.98);
}

#vote-modal-status {
  margin: 0;
  text-align: center;
  font-weight: 600;
  color: var(--text);
}

/* ---------- Espace admin : panneau de vote (2026-09-03) ---------- */

#admin-vote-panel {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  text-align: center;
}

#admin-vote-panel-title {
  margin: 0;
  font-weight: 600;
}

#admin-vote-panel-candidates {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  color: var(--text);
}

#admin-vote-panel-voters {
  margin: 0;
  color: var(--muted);
  font-size: 0.75rem;
}

/* ---------- Boutique (fusionnée dans Marché le 2026-08-27) ---------- */

#view-woodcut,
#view-market {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
}

/* Sous-panneaux d'onglet de #view-market — pas de gap propre par défaut :
   leurs blocs internes (ex. Booster et Bâtiments & voitures) se
   retrouveraient collés l'un à l'autre sans ça. */
#market-sell,
#market-cosmetics {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
}

.shop-block {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

.shop-block h2 {
  margin: 0;
  font-size: 1.1rem;
}

.shop-hint {
  color: var(--muted);
  font-size: 0.8rem;
  text-align: center;
  margin: 0;
  max-width: 480px;
}

.shop-sell-list {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.shop-sell-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--panel);
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
}

.shop-sell-row img {
  width: 40px;
  height: 56px;
  object-fit: contain;
  background: #fff;
  border-radius: 6px;
  flex-shrink: 0;
}

.shop-sell-info {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.shop-sell-name {
  font-size: 0.85rem;
}

.shop-sell-count {
  color: var(--muted);
}

.shop-sell-price {
  color: var(--muted);
  font-size: 0.75rem;
}

.shop-sell-qty {
  width: 3.5rem;
  font: inherit;
  padding: 0.4rem;
  border-radius: 6px;
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
  text-align: center;
}

/* ---------- Diff de collection (2026-09-13) : compteurs sobres + liste
   scrollable (hauteur plafonnée pour ne pas allonger indéfiniment la
   visite de profil sur une grosse différence de collection). .wishlist-block
   (ligne compacte réutilisée depuis le bloc Objectifs, retiré le 2026-09-15 —
   la classe reste car #profile-visit-diff-list s'en sert toujours, voir
   buildDiffRow(), public/script.js). ---------- */

.wishlist-block {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
}

#profile-visit-diff {
  width: 100%;
}

#profile-visit-diff-his,
#profile-visit-diff-mine {
  text-align: left;
  margin: 0 0 0.25rem;
}

.diff-list {
  max-height: 140px;
  overflow-y: auto;
}

.diff-list .shop-sell-row {
  flex-shrink: 0;
}

/* ---------- Toast discret, flottant au-dessus de la nav (2026-09-13) :
   .floating-toast masqué par défaut, jamais empilé, utilisé par
   #daily-gift-toast ("cadeau du jour", showDailyGiftToast()),
   public/script.js. ---------- */

.floating-toast {
  position: fixed;
  left: 50%;
  bottom: 96px;
  transform: translate(-50%, 8px);
  max-width: min(90vw, 360px);
  padding: 0.6rem 1rem;
  background: var(--panel);
  border: 1px solid var(--border-fine);
  border-radius: 999px;
  box-shadow: var(--shadow-card);
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 400;
}

.floating-toast.visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

@media (prefers-reduced-motion: reduce) {
  .floating-toast {
    transition: opacity 0.01ms;
    transform: translate(-50%, 0);
  }
}

@media (max-width: 380px) {
  .floating-toast {
    max-width: 92vw;
    white-space: normal;
  }
}

/* ---------- Marché : onglet "Acheter" (2026-09-10, ex-"Cartes à l'unité"
   de la modale Boutique) ---------- */

.shop-cards-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.shop-cards-toolbar label {
  font-size: 0.9rem;
  color: var(--muted);
}

.shop-cards-list {
  max-height: 320px;
  overflow-y: auto;
}

.shop-cards-list .shop-sell-btn {
  flex-shrink: 0;
}

/* ---------- Pokédex des espèces (2026-09-10) ---------- */

#view-pokedex {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
}

#pokedex-summary {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

#pokedex-summary-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
}

#pokedex-info-btn {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.7rem;
}

#pokedex-summary-count {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text);
}

#pokedex-controls {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

#pokedex-search {
  font: inherit;
  width: 100%;
  padding: 0.55rem 0.9rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
}

#pokedex-filters {
  display: flex;
  justify-content: center;
  gap: 0.4rem;
}

#pokedex-filters .nav-btn {
  padding: 0.4rem 0.85rem;
  font-size: 0.85rem;
}

#pokedex-list {
  width: 100%;
  max-width: 560px;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.pokedex-row {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  background: var(--panel);
  border-radius: 10px;
  padding: 0.45rem 0.7rem;
}

.pokedex-row.missing {
  opacity: 0.55;
}

.pokedex-row img {
  width: 40px;
  height: 56px;
  object-fit: contain;
  background: #fff;
  border-radius: 6px;
  flex-shrink: 0;
  filter: grayscale(var(--pokedex-row-grayscale, 0));
}

.pokedex-row.missing img {
  --pokedex-row-grayscale: 1;
}

.pokedex-row-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.pokedex-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.pokedex-row-name {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pokedex-row-count {
  font-size: 0.75rem;
  color: var(--muted);
  flex-shrink: 0;
}

.shop-catalog {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0.75rem;
  width: 100%;
}

.shop-item {
  background: var(--panel);
  border-radius: 12px;
  padding: 0.9rem 0.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  text-align: center;
}

.shop-item-emoji {
  font-size: 2rem;
}

.shop-item-name {
  font-size: 0.85rem;
  font-weight: 600;
}

.shop-item-category {
  color: var(--muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.shop-item-income {
  color: var(--uncommon);
  font-size: 0.7rem;
  font-weight: 600;
}

.shop-item-buy {
  font-size: 0.8rem;
  padding: 0.5rem 1rem;
  margin-top: 0.25rem;
}

/* Cosmétiques de prestige (2026-09-13, demande directe Benjamin : "des
   cosmetiques très durs à avoir") : jamais achetables, affichés grisés dans
   le même catalogue visuel que les cosmétiques normaux (.shop-item), avec
   juste un bouton "i" compact pour dérouler la progression exacte de
   l'exploit plutôt qu'un pavé de texte toujours visible — même pattern que
   #pokedex-info-btn. */
.shop-item.locked {
  opacity: 0.55;
  filter: grayscale(0.5);
}

.shop-item-info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 1px solid var(--nav-border);
  background: transparent;
  color: var(--text);
  font-size: 0.6rem;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  margin-left: 0.25rem;
  vertical-align: middle;
}

.shop-item-condition {
  font-size: 0.7rem;
  color: var(--muted);
}

.shop-item-effect {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--accent);
}

/* 4 onglets sur une seule ligne même à 360px (2026-09-13, retour Benjamin) :
   ciblé sur ce conteneur uniquement (pas .nav-btn global, qui sert aussi à la
   barre de nav) — padding/police/gap réduits, jamais de retour à la ligne. */
.shop-tabs {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  gap: 0.3rem;
}

.shop-tabs .nav-btn {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.4rem 0.35rem;
  font-size: 0.74rem;
  white-space: nowrap;
  text-align: center;
}

/* "Cosmétiques" (2026-09-14, débordement signalé) : le plus long des 4
   libellés, il lui faut plus de place que ses voisins pour tenir dans sa
   boîte à 360px — flex-grow renforcé plutôt qu'un padding uniforme qui
   aurait fait déborder la barre. */
#market-tab-cosmetics {
  flex-grow: 1.5;
  padding-left: 0.2rem;
  padding-right: 0.2rem;
}

/* Dropdown de tri (2026-08-25, remplace les 3 boutons #collection-sort-tabs
   "Rareté/Prix - Par type - Par série") : même pattern visuel que
   #menu-wrap/#menu-dropdown du header (bouton icône rond + panneau flottant
   flouté), pour que le tri tienne sur la même ligne que Passer prestige/
   Vaulter tout au lieu de sa propre rangée pleine largeur. */
#sort-wrap {
  position: relative;
}

#sort-dropdown {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 160px;
  background: color-mix(in srgb, var(--panel-2) 65%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--nav-border);
  border-radius: 14px;
  padding: 0.5rem;
}

#sort-dropdown button {
  width: 100%;
  background: transparent;
  border: none;
  border-radius: 8px;
  padding: 0.45rem 0.6rem;
  display: block;
  text-align: left;
  box-shadow: none;
  color: var(--text);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

#sort-dropdown button:hover,
#sort-dropdown button:focus-visible {
  background: var(--panel-2);
}

#sort-dropdown button.active {
  background: var(--panel-2);
  font-weight: 700;
}

/* ---------- Ferme de minage PokeCoin (2026-08-26, bûcheronnage à
   l'origine ; reskin crypto le 2026-08-27 — mine directement le PKC,
   plus de ressource intermédiaire donc plus de chaîne à afficher, juste
   le bouton de minage + un seul palier d'automatisation) ---------- */

.info-btn {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 2.9rem;
  height: 2.9rem;
  border-radius: 50%;
  border: 1.5px solid #fff;
  background: #fff;
  color: #131826;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  box-shadow:
    0 0 0 1.5px oklch(0.6164 0.1081 161.32 / 0.5),
    0 10px 24px rgba(16, 24, 40, 0.2);
}

.info-btn:hover {
  background: #fff;
  color: #131826;
  border-color: #fff;
}


/* Restyle "salle des machines" (2026-09-06, demande produit : "restylise la
   page de minage, surprends-moi, reste dans la DA") — toute la vue devient
   une suite de panneaux console (en-tête, baie du rig, fenêtre terminal,
   pupitre d'extraction) qui posent l'identité "crypto" du bouton de
   minage/Matrix (2026-08-27) sur l'ensemble de la page plutôt que sur ce
   seul bouton. Repassé en DA claire le même jour (retour Benjamin : "pas fan
   du fond noir, fait que ça s'incruste avec le fond blanc de l'appli") —
   panneaux clairs (var(--panel)/var(--panel-2)), liserés fins existants
   (var(--nav-border)), ombre douce existante (var(--shadow-card)), la teinte
   accent (--accent) sert d'identité "console" plutôt que le noir. Les
   illustrations SVG du rig (#gpu-illustration, renderMiningRigIllustration(),
   public/script.js) ne sont pas touchées, seul leur écrin change. */
#view-woodcut {
  gap: 0.85rem;
}

/* Baie du rig (2026-08-27, demande produit : flèches pour parcourir les
   niveaux d'illustration plutôt qu'une carte d'amélioration séparée
   en-dessous) — fusionné dans la carte console le 2026-09-19 (demande
   produit : plus de bouton à ouvrir, le rig s'affiche directement entre la
   bande terminal et le bouton Miner) : carte console en 2 zones : vitrine du
   carrousel (#mining-rig-carousel) puis plaque signalétique
   (#mining-rig-plate : palier/infos/bouton de déblocage). Le niveau
   parcouru (miningRigPreviewLevel, public/script.js) est indépendant du
   niveau possédé (mining.rigLevel) : les flèches naviguent librement de 0 à
   MINING_RIG_DEF.maxLevel, un niveau au-delà de celui possédé s'affiche
   grisé (.locked ci-dessous). Plus de surcouche (pas de bordure/ombre
   propres, `#mining-matrix-window` porte déjà le cadre de la carte) : simple
   séparateur avec la bande terminal au-dessus pour rester lisible. Flèches
   remises en flux (2026-09-19, correctif visuel : la flèche droite en
   position absolue recouvrait l'artwork du niveau 5) — rangée flex
   centrée et bornée à 320px (flèche/illustration/flèche) au lieu de
   flèches en overlay par-dessus l'illustration. */
#mining-rig-panel {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--panel);
  border-bottom: 1px solid var(--nav-border);
}

#mining-rig-carousel {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
  padding: 1.1rem 0.5rem 0.6rem;
}

.mining-rig-arrow {
  transform: none;
  flex-shrink: 0;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background: var(--panel);
  border: 1px solid var(--nav-border);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: 0 6px 14px rgba(16, 24, 40, 0.12);
}

@media (hover: hover) {
  .mining-rig-arrow:hover:not(:disabled) {
    background: var(--panel-2);
    transform: scale(1.08);
  }
}

.mining-rig-arrow:active:not(:disabled) {
  transform: scale(0.92);
}

.mining-rig-arrow:disabled {
  opacity: 0.3;
  cursor: default;
}

/* Illustration 3D du rig (2026-08-27, demande produit) — carte graphique
   stylisée, inline SVG (pas d'asset externe). */
#gpu-illustration {
  width: 100%;
  max-width: 200px;
  margin: 0 auto;
}

#gpu-illustration svg {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.45));
  transition: filter 0.15s ease, opacity 0.15s ease;
}

/* Niveau parcouru pas encore possédé : rendu grisé pour signaler qu'il
   reste à débloquer (voir renderMiningRigCarousel(), public/script.js). */
#gpu-illustration.locked svg {
  filter: grayscale(0.9) brightness(0.55);
  opacity: 0.6;
}

/* Plaque signalétique — pied de carte, barre de couture avec la vitrine. */
#mining-rig-plate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.7rem 1rem 0.9rem;
  border-top: 1px solid var(--nav-border);
  background: var(--panel-2);
}

#mining-rig-stage-name {
  margin: 0;
  font-family: "Space Mono", var(--font-display), monospace;
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text);
  text-align: center;
}

#mining-rig-info {
  margin: 0;
  font-size: 0.74rem;
  color: var(--muted);
  text-align: center;
}

/* Bouton de déblocage — barre d'action pleine largeur en pied de plaque
   quand un palier suivant est disponible (classe .hidden togglée en JS). */
#mining-rig-unlock-btn {
  width: 100%;
  margin-top: 0.15rem;
}

/* Fenêtre terminal (2026-08-27, demande produit : "des 0 et 1 façon matrix
   lors du minage" ; chrome de fenêtre ajouté le 2026-09-06, repassé clair le
   même jour) — chrome HTML statique (#mining-matrix-window/-titlebar/
   .mining-matrix-dot) autour du contenu piloté par JS, ne touche ni à
   #mining-matrix.active (opacité, togglée par startMiningProblem()) ni aux
   .matrix-column injectées par renderMatrixColumns(). Pastilles de titre
   reprenant les 3 teintes RGB déjà utilisées par le rig (#gpuRgb,
   public/script.js) plutôt que le classique rouge/jaune/vert, gardées en
   accents colorés même sur fond clair. */
#mining-matrix-window {
  width: 100%;
  max-width: 460px;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--nav-border);
  box-shadow: var(--shadow-card);
}

/* Corps de la console = bande terminal (#mining-terminal) + zone claire
   d'action (#woodcut-manual) empilées (2026-09-06, demande produit : "la
   zone d'action toujours visible, sur fond blanc, la matrix seulement en
   haut") — remplace la fusion matrix/pupitre du même jour plus tôt : la
   pluie verte redevient une simple bande d'écran plutôt qu'un calque
   plein-cadre derrière tout le corps. */
#mining-matrix-body {
  display: flex;
  flex-direction: column;
}

#mining-matrix-titlebar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.6rem;
  background: var(--panel-2);
  border-bottom: 1px solid var(--nav-border);
}

.mining-matrix-dot {
  width: 0.5rem;
  height: 0.5rem;
  flex-shrink: 0;
  border-radius: 50%;
  opacity: 0.85;
}

.mining-matrix-dot-1 { background: oklch(0.62 0.17 155); }
.mining-matrix-dot-2 { background: oklch(0.6 0.17 250); }
.mining-matrix-dot-3 { background: oklch(0.62 0.17 330); }

#mining-matrix-title {
  margin-left: 0.15rem;
  font-family: "Space Mono", var(--font-display), monospace;
  font-size: 0.65rem;
  letter-spacing: 0.02em;
  color: var(--text-weak);
}

/* Bande écran (2026-09-06, demande produit) : seule zone sombre de la
   console, confine la pluie matrix (#mining-matrix, absolute inset:0) à une
   hauteur fixe plutôt qu'à tout le corps. Relative pour porter la ligne
   problème + le gain par-dessus (z-index 1) en rangée space-between. */
#mining-terminal {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
  min-height: 3.5rem;
  padding: 0.6rem 0.8rem;
  background: #0d1220;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  overflow: hidden;
}

/* Pluie de fond — reste visible en continu (bande dédiée, plus besoin de
   l'éteindre au repos comme quand elle couvrait tout le corps) ; .active
   (togglée par startMiningProblem()/cancelMiningProblem(), public/script.js)
   ne fait plus que renforcer légèrement l'opacité pendant un calcul. */
#mining-matrix {
  position: absolute;
  inset: 0;
  overflow: hidden;
  display: flex;
  justify-content: space-evenly;
  opacity: 0.85;
  transition: opacity 0.3s ease;
  pointer-events: none;
}

#mining-matrix.active {
  opacity: 1;
}

.matrix-column {
  font-family: "Space Mono", var(--font-display), monospace;
  font-size: 0.8rem;
  line-height: 1.3;
  color: oklch(0.6164 0.1081 161.32);
  text-shadow: 0 0 6px oklch(0.6164 0.1081 161.32 / 0.8);
  white-space: pre;
  animation: matrixFall linear infinite;
}

@keyframes matrixFall {
  0% { transform: translateY(-100%); }
  100% { transform: translateY(100%); }
}

/* Ligne titre du terminal (2026-09-07, remplace #woodcut-problem-line du
   calcul mental) — statique, la tension vient maintenant du choix de mise
   ci-dessous, pas d'un énoncé qui change. */
#mining-terminal-line {
  position: relative;
  z-index: 1;
  margin: 0;
  font-family: "Space Mono", var(--font-display), monospace;
  font-size: 0.95rem;
  font-weight: 600;
  color: oklch(0.72 0.15 161);
  text-shadow: 0 0 8px var(--accent-shadow);
}

/* Zone claire d'action — minage au clic (#mining-click) + défi (2026-09-06,
   demande produit : plus de bouton "Miner du PKC" à déclencher, le bloc
   reste affiché en continu) : fond du panneau, coins arrondis en bas pour
   prolonger #mining-terminal au-dessus. */
#woodcut-manual {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  padding: 1.1rem 1rem;
  background: var(--panel);
}

/* Minage au clic (2026-09-19, demande directe Benjamin — remplace la
   machine à sous à 3 chiffres du 2026-09-07) : carte claire posée
   directement sur le fond clair du panneau. Bouton CTA par défaut de l'app
   (fond sombre, voir règle `button` globale) — juste redimensionné en gros
   bouton bien tapable. Voir miningClick()/renderMiningClick(),
   public/script.js. */
#mining-click {
  width: 100%;
  max-width: 380px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
}

#mining-click-btn {
  width: 100%;
  min-height: 3rem;
  font-size: 1.05rem;
}

#mining-click-feedback {
  margin: 0;
  min-height: 1.1em;
  font-family: "Space Mono", var(--font-display), monospace;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--muted);
}

#mining-click-feedback.ok { color: var(--accent-text); }
#mining-click-feedback.ko { color: var(--muted); }

#mining-click-count {
  margin: 0;
  font-family: "Space Mono", var(--font-display), monospace;
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: var(--muted);
}

#mining-click-legend {
  margin: 0;
  font-size: 0.65rem;
  color: var(--text-weak);
  text-align: center;
}

/* ---------- Marché & Cosmétiques (2026-08-26) ---------- */

.cosmetic-badge {
  display: inline-block;
  background: var(--panel);
  border: 1px solid var(--nav-border);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
  font-size: 0.75rem;
  margin: 0 0.3rem 0.3rem 0;
}

#profile-visit-cosmetics:empty {
  display: none;
}

/* ---------- Modale visite de profil (depuis un pseudo cliqué au classement/showroom) ---------- */

#profile-visit-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#profile-visit-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#profile-visit-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  max-width: min(90vw, 480px);
  max-height: 90vh;
  overflow-y: auto;
}

#profile-visit-modal-content .cards-grid {
  max-width: 100%;
}

#profile-visit-modal-content h2 {
  margin: 0;
  text-align: center;
  font-size: 1.1rem;
}

#profile-visit-modal-content .profile-header {
  flex-direction: column;
  gap: 0.4rem;
}

#profile-visit-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Modale chat global (2026-08-13, bouton 💬 dans le header) ---------- */

#chat-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#chat-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#chat-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.5rem 1.25rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: min(90vw, 420px);
  height: min(75vh, 560px);
}

#chat-modal-content h2 {
  margin: 0;
  text-align: center;
  font-size: 1.1rem;
  flex-shrink: 0;
}

#chat-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

#chat-messages {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding-right: 0.25rem;
}

.chat-message {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.chat-message-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  flex-shrink: 0;
}

.chat-message-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: zoom-in;
}

.chat-message-body {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.chat-message-meta {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}

.chat-message-name {
  font-weight: 600;
  font-size: 0.8rem;
}

.chat-message-time {
  color: var(--muted);
  font-size: 0.65rem;
}

.chat-message-text {
  margin: 0;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

#chat-form {
  display: flex;
  gap: 0.5rem;
  flex-shrink: 0;
}

#chat-input {
  flex: 1;
  min-width: 0;
}

/* ---------- Modale carte agrandie ---------- */

#card-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#card-modal-backdrop {
  position: absolute;
  inset: 0;
  background: var(--veil);
}

#card-modal-content {
  position: relative;
  z-index: 1;
  background: var(--panel);
  border-radius: 14px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  max-width: min(90vw, 380px);
  max-height: 90vh;
  overflow-y: auto;
}

#card-modal-img {
  width: 100%;
  border-radius: 10px;
  background: #fff;
}

#card-modal-name {
  font-weight: 700;
  text-align: center;
  margin: 0;
}

#card-modal-meta {
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
  margin: 0;
}

#card-modal-close {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}

/* ---------- Modale "photo de profil en grand" (2026-08-13) ---------- */
/* Avatars en lecture seule uniquement (classement, showroom, modale de */
/* visite, chat) — voir wireAvatarZoomTrigger()/openAvatarZoom() côté script.js. */

#avatar-zoom-modal {
  position: fixed;
  inset: 0;
  z-index: 210;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

#avatar-zoom-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
}

#avatar-zoom-modal-content {
  position: relative;
  z-index: 1;
}

#avatar-zoom-img {
  display: block;
  width: min(90vw, 420px);
  height: min(90vw, 420px);
  max-height: 80vh;
  object-fit: cover;
  border-radius: 16px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

#avatar-zoom-modal-close {
  position: absolute;
  top: -0.6rem;
  right: -0.6rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: 8px;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  padding: 0;
  box-shadow: none;
  color: var(--text);
}


/* ---------- Combat PvE "façon DS" (2026-09-06, demande produit, BETA) ----------
   #view-battle : 2 plateaux (adversaire en haut, équipe joueur en bas),
   barres de PV, texte qui défile façon dialogue DS, menu d'actions. Pas de
   canvas/assets externes — artworks réels des cartes (server/battle.js
   cardId -> cardsById), animations en CSS pur (shake/flash/slide), pas de
   son (demande produit explicite). */

#battle-btn {
  position: relative;
}

#battle-btn::after {
  content: "β";
  position: absolute;
  bottom: -3px;
  right: -3px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1.5px solid var(--panel);
  font-size: 9px;
  font-weight: 700;
  line-height: 13px;
  text-align: center;
  color: var(--text-tertiary);
}

#view-battle,
#view-battle-arena {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
}

/* ---------- Menu Combat (2026-09-06, demande produit "un menu qui indique
   les combats gagnés/perdus et les zones où je veux essayer") : stats +
   liste de zones, écran d'accueil de #nav-battle avant l'arène. ---------- */

#battle-stats {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.battle-stat {
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.4rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.battle-stat-value {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.battle-stat-label {
  font-size: 0.68rem;
  color: var(--text-tertiary);
}

/* Équipe jouable (2026-09-06, msg Dylan #335 "rendre éligible les Pokémon
   connus qu'on possède" ; passage à 3 emplacements le 2026-09-15) : 3
   emplacements cliquables + picker déroulant des 20 combattants, entre les
   stats et les zones de #view-battle. */
#battle-team-block {
  width: 100%;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-md, 16px);
  padding: 0.7rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#battle-team-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

#battle-team-title {
  font-family: var(--font-display);
  font-size: 0.9rem;
  color: var(--text);
}

#battle-team-info-btn {
  width: 1.8rem;
  height: 1.8rem;
  font-size: 0.75rem;
}

#battle-team-hint {
  margin: 0;
  font-size: 0.72rem;
  color: var(--text-tertiary);
}

#battle-team-slots {
  display: grid;
  /* minmax(0, 1fr) plutôt que 1fr sec (leçon du 2026-09-15, tuiles du profil) :
     3 emplacements ne doivent pas déborder à 360px, un enfant (image/nom)
     plus large que sa colonne ne doit jamais forcer la grille à s'étendre. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.battle-team-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
}

.battle-team-slot img {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

.battle-team-slot-name {
  font-size: 0.75rem;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Picker « Tes cartes » (2026-09-21) : le conteneur n'est plus la grille
   elle-même — il porte les contrôles (recherche/filtres/tri), la grille des
   résultats (#battle-team-picker-results, rendu progressif par pages) et le
   bouton « Voir plus ». Défilement porté par le conteneur. */
#battle-team-picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow-y: auto;
  padding-top: 4px;
  border-top: 1px solid var(--nav-border);
}

#battle-team-picker-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

#battle-team-picker-controls input[type="search"] {
  flex: 1 1 150px;
  min-width: 0;
}

#battle-team-picker-controls select {
  flex: 0 1 auto;
  max-width: 47%;
  font-size: 0.72rem;
}

#battle-team-picker-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 6px;
}

#battle-team-picker-more {
  align-self: center;
  font-size: 0.72rem;
}

.battle-team-picker-empty {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.75rem;
  color: var(--text-tertiary);
  text-align: center;
}

.battle-team-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0.4rem;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  position: relative;
}

.battle-team-option img {
  width: 40px;
  height: 40px;
  object-fit: contain;
}

.battle-team-option-name {
  font-size: 0.68rem;
  text-align: center;
}

.battle-team-option-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.battle-team-option-meta .rarity-pill,
.battle-team-option .rarity-pill {
  font-size: 0.56rem;
  padding: 0 0.3rem;
}

.battle-team-option-stats {
  font-size: 0.6rem;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}

.battle-team-option-fallback {
  font-size: 0.56rem;
  padding: 0 0.3rem;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text-tertiary);
}

/* Marque « gradée » (2026-09-21) : .grade-badge n'était stylé que dans
   .collection-card — même esprit ici, en pastille compacte dans le coin. */
.battle-team-option .grade-badge,
.battle-team-slot .grade-badge {
  position: absolute;
  top: 2px;
  left: 3px;
  font-size: 0.7rem;
  line-height: 1;
}

.battle-team-slot {
  position: relative;
}

#battle-player-rarity {
  font-size: 0.62rem;
}

#battle-player-graded {
  font-size: 0.62rem;
  color: var(--text-tertiary);
}

.battle-team-option.locked {
  opacity: 0.45;
}

.battle-team-option-lock {
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 0.7rem;
}

/* Vagues infinies (2026-09-10) : chip d'entrée entre l'équipe et les zones,
   même famille visuelle que .battle-zone mais en CTA sombre (mode à part,
   pas une zone parmi d'autres) — voir #battle-endless-chip, index.html. */
#battle-endless-chip {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0.7rem 0.85rem;
  background: var(--text);
  border: none;
  border-radius: var(--radius-md, 16px);
  color: var(--panel);
  text-align: left;
}

#battle-endless-chip-title {
  font-family: var(--font-display);
  font-size: 0.9rem;
}

.battle-endless-record {
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.7);
}

#battle-zones {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.battle-zone {
  width: 100%;
  text-align: left;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-md, 16px);
  padding: 0.7rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: var(--text);
}

.battle-zone.locked {
  opacity: 0.5;
}

.battle-zone-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.battle-zone-name {
  font-family: var(--font-display);
  font-size: 0.9rem;
}

.battle-zone-level {
  font-size: 0.7rem;
  color: var(--text-tertiary);
}

.battle-zone-preview {
  display: flex;
  gap: 6px;
}

.battle-zone-preview img {
  width: 40px;
  height: 40px;
  object-fit: contain;
  background: var(--panel);
  border-radius: 8px;
}

.battle-zone-lock {
  font-size: 0.72rem;
  color: var(--text-tertiary);
}

#battle-arena {
  width: 100%;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-md, 16px);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.battle-side {
  display: flex;
  align-items: center;
  gap: 10px;
}

#battle-opponent-side {
  justify-content: flex-start;
}

#battle-player-side {
  justify-content: flex-start;
  flex-direction: row-reverse;
  text-align: right;
}

.battle-sprite {
  width: 76px;
  height: 76px;
  object-fit: contain;
  filter: drop-shadow(0 6px 10px rgba(16, 24, 40, 0.18));
  flex-shrink: 0;
  animation: battleSpriteIn 0.35s cubic-bezier(0.2, 0.9, 0.25, 1.1);
}

@keyframes battleSpriteIn {
  0% { opacity: 0; transform: translateY(6px) scale(0.9); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

.battle-hpbar {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

/* Nombre flottant "-X PV" (2026-09-06, demande produit "animations de
   dégâts") : posé par spawnDamageFloat() (public/script.js) au-dessus de la
   barre de PV touchée, monte et s'estompe, puis se retire lui-même
   (animationend). Centré horizontalement pour marcher pareil des deux
   côtés du plateau (joueur à droite, adversaire à gauche). */
.battle-dmg-float {
  position: absolute;
  left: 50%;
  top: -2px;
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
  color: #e0393e;
  pointer-events: none;
  white-space: nowrap;
  animation: battleDmgFloat 0.9s ease-out forwards;
}

.battle-dmg-float.crit {
  font-size: 1.15rem;
  color: #ff9d2f;
}

.battle-dmg-float.miss {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-tertiary);
}

@keyframes battleDmgFloat {
  0% { opacity: 0; transform: translateX(-50%) translateY(4px); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%) translateY(-28px); }
}

.battle-hpbar-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}

#battle-type-info-btn {
  width: 1.5rem;
  height: 1.5rem;
  font-size: 0.68rem;
}

#battle-player-side .battle-hpbar-head {
  justify-content: flex-end;
}

.battle-hpfill {
  background: var(--accent);
  transition: width 0.5s ease-out, background 0.3s ease-out;
}

.battle-hpfill.hp-mid {
  background: #ff9d2f;
}

.battle-hpfill.hp-low {
  background: #e0393e;
}

/* PV chiffrés sous la barre (2026-09-06, rebalance BÊTA v2) — complète la
   barre de progression, ne la remplace pas (demande produit : lire le
   combat "d'un coup d'œil"). */
.battle-hp-text {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

#battle-player-side .battle-hp-text {
  text-align: right;
}

#battle-team-dots {
  display: flex;
  flex-wrap: wrap; /* 3 combattants (2026-09-15) : passe à la ligne plutôt que déborder à 360px */
  justify-content: flex-end;
  gap: 4px 8px;
}

.battle-team-chip {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

.battle-team-chip.fainted {
  opacity: 0.55;
}

.battle-team-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}

.battle-team-dot.fainted {
  background: var(--border-strong);
}

/* Impact reçu : léger tremblement + flash rouge, appliqué/retiré par JS
   (public/script.js) sur le sprite touché à chaque événement de dégâts. */
@keyframes battleShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}

.battle-sprite.battle-hit {
  animation: battleShake 0.4s ease-in-out;
}

.battle-sprite.battle-faint {
  animation: battleFaint 0.5s ease-in forwards;
}

@keyframes battleFaint {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(16px); }
}

#battle-textbox {
  width: 100%;
  min-height: 4.4rem;
  background: var(--panel);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  box-shadow: var(--shadow-card, none);
}

#battle-text {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
}

#battle-menu {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#battle-menu-main {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

#battle-menu-main button {
  padding: 0.65rem 0.5rem;
  font-size: 0.85rem;
}

#battle-menu-main button:disabled {
  opacity: 0.45;
}

.battle-submenu {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.battle-submenu button {
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.8rem;
  font-size: 0.85rem;
  background: var(--panel-2);
  border: 1px solid var(--nav-border);
  color: var(--text);
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.battle-submenu button:disabled {
  opacity: 0.4;
}

.battle-submenu .move-type {
  font-size: 0.7rem;
  color: var(--text-tertiary);
}

/* Aperçu des dégâts (2026-09-06, msg Dylan #337) : #battle-menu-moves passe
   les boutons en 2 lignes (nom/type puis plage de dégâts), le panneau
   Équipe (#battle-menu-switch) garde 1 seule ligne. */
#battle-menu-moves button {
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
}

.move-head {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.move-dmg {
  font-size: 0.7rem;
  color: var(--text-tertiary);
}

#battle-end {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}

#battle-end-text {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1rem;
}

/* Vagues infinies (2026-09-10) : ligne récap "Vague N atteinte · Record : X"
   sous #battle-end-text, voir showBattleEnd() script.js. */
#battle-end-endless {
  margin: 0;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-tertiary);
}

/* ---------- Push4Packs : barre Pompes (2026-11-14) ----------
   Mécanique centrale : barre visible en haut de l'écran principal (sous la
   nav), compacte et utilisable au pouce. Toutes les données viennent du
   résumé serveur (GET /api/me / GET /api/pushups) — voir
   renderPushupsHud(), public/script.js. */
#pushups-hud {
  width: 100%;
  max-width: 1000px;
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem 0.75rem;
  background: var(--panel);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.pushups-hud-top,
.pushups-progress-row,
.pushups-hud-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.pushups-hud-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
}

.pushups-streak {
  font-size: 0.8rem;
  color: var(--text-secondary);
  white-space: nowrap;
}

.pushups-progress-row {
  font-size: 0.82rem;
}

#pushups-today-label {
  font-weight: 700;
  color: var(--text);
}

#pushups-next-pack {
  color: var(--text-secondary);
}

#pushups-progress-fill {
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
}

.pushups-hud-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.pushup-quick-btn,
.pushup-custom-form button,
.pushup-undo-btn,
.pushup-open-btn {
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  border-radius: var(--radius-pill);
  border: 1px solid var(--nav-border);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
  padding: 0.38rem 0.7rem;
}

.pushup-quick-btn:hover,
.pushup-custom-form button:hover,
.pushup-undo-btn:hover {
  border-color: var(--accent);
  color: var(--accent-text);
}

.pushup-custom-form {
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.pushup-custom-form input {
  width: 5.2rem;
  font: inherit;
  font-size: 0.8rem;
  padding: 0.38rem 0.5rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--panel);
  color: var(--text);
}

.pushup-undo-btn {
  border-color: transparent;
  background: transparent;
  color: var(--text-tertiary);
  font-weight: 600;
  font-size: 0.75rem;
}

.pushups-feedback {
  margin: 0;
  min-height: 0.95rem;
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.pushups-feedback.error {
  color: #b42318;
}

.pushups-feedback.gain {
  color: var(--accent-text);
  font-weight: 700;
}

.pushup-open-btn {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
  box-shadow: var(--shadow-primary-btn);
}

.pushup-open-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}

.pushups-history {
  display: flex;
  align-items: stretch;
  justify-content: flex-end;
  gap: 0.25rem;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  padding-bottom: 1px;
}

.pushup-day {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 2.05rem;
  padding: 0.2rem 0.22rem;
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  color: var(--text-tertiary);
  line-height: 1.15;
}

.pushup-day-label {
  font-size: 0.58rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.pushup-day-count {
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text-secondary);
}

.pushup-day.today {
  outline: 1px solid var(--accent);
}

.pushup-day.goal {
  background: color-mix(in oklab, var(--accent) 14%, var(--panel));
}

.pushup-day.goal .pushup-day-count {
  color: var(--accent-text);
}

/* ---------- Classement Pompes (onglet 2026-11-14) ---------- */
#view-leaderboard #leaderboard-tabs {
  margin-bottom: 0.9rem;
}

.pushups-leaderboard {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  width: 100%;
  max-width: 760px;
}

.pushup-rank-row {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.8rem;
  background: var(--panel);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
}

.pushup-rank-row.me {
  outline: 2px solid var(--accent);
}

.pushup-rank-pos {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--text-tertiary);
  text-align: center;
}

.pushup-rank-name {
  font-weight: 700;
  border: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-display);
  text-align: left;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pushup-rank-stats {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.05rem;
  font-size: 0.75rem;
  color: var(--text-secondary);
  white-space: nowrap;
}

.pushup-rank-total {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
}

.pushups-leaderboard-empty {
  color: var(--text-tertiary);
  font-size: 0.85rem;
}

/* ---------- Admin : annulation de pompes (2026-11-14) ---------- */
#admin-pushups-panel {
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border-fine);
}

.admin-pushups-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  margin-bottom: 0.4rem;
}

.admin-pushups-row .admin-pushups-meta {
  font-size: 0.78rem;
  color: var(--text-secondary);
}

.admin-pushups-row button {
  font: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  padding: 0.35rem 0.65rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--nav-border);
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
}

.admin-pushups-row button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

@media (max-width: 480px) {
  #pushups-hud {
    padding: 0.6rem 0.7rem 0.65rem;
  }

  .pushups-hud-bottom {
    align-items: flex-start;
  }

  .pushups-history {
    justify-content: flex-start;
    width: 100%;
  }

  .pushup-rank-row {
    grid-template-columns: 2.1rem minmax(0, 1fr) auto;
    gap: 0.4rem;
    padding: 0.5rem 0.6rem;
  }
}
