/* Polices AUTO-HÉBERGÉES (web/public/fonts/, récupérées une fois par `npm run fetch-fonts`) : aucune requête vers
   Google Fonts, donc aucune adresse IP de visiteur transmise à un tiers (RGPD) et pas de chaîne @import bloquante.
   Fichier absent = le navigateur passe simplement à la police système suivante (font-display: swap). */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/inter-latin-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/inter-latin-500.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('/fonts/inter-latin-600.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url('/fonts/inter-latin-700.woff2') format('woff2'); }
@font-face { font-family: 'Bungee'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/bungee-latin-400.woff2') format('woff2'); }

:root {
  /* Le site n'a qu'un thème sombre (pas de bascule clair/sombre) : sans cette ligne, le
     navigateur dessine certains éléments natifs (icône du sélecteur datetime-local, cases à
     cocher, barres de défilement sous Chrome) avec son rendu "clair" par défaut, souvent
     illisible sur ce fond sombre — ex: l'icône de calendrier des champs de date était noire sur
     fond quasi-noir. color-scheme: dark indique au navigateur d'utiliser directement ses
     variantes sombres natives pour tout ça, sans bricolage CSS par élément. */
  color-scheme: dark;
  --bg: #08070C;
  --surface: #131019;
  --surface-2: #1B1626;
  --line: #2C2438;
  --accent: #7C3AED;
  --accent-bright: #9D5CFF;
  --accent-soft: #C9B8F5;
  --text: #F3F1F8;
  --text-dim: #A79FBD;
  /* Échelle d'arrondis et d'ombres communes : cartes, boutons et champs n'utilisent plus chacun leur valeur. */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --shadow-card: 0 10px 24px -14px rgba(0, 0, 0, 0.6);
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* Thème clair (bouton 🌙/☀️ de la barre de navigation, voir theme.js) : seules les variables changent ; les couleurs « de statut » (vert, jaune,
   rouge…) ont leur variante lisible sur fond clair dans le bloc suivant. Sombre par défaut. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #F6F4FB;
  --surface: #FFFFFF;
  --surface-2: #EEE9F8;
  --line: #D5CCE6;
  --accent: #6D28D9;
  --accent-bright: #7C3AED;
  --accent-soft: #5B21B6;
  --text: #1B1530;
  --text-dim: #5A5170;
}
:root[data-theme="light"] body {
  background-image:
    radial-gradient(circle at 15% 0%, rgba(124, 58, 237, 0.10), transparent 45%),
    radial-gradient(circle at 100% 20%, rgba(124, 58, 237, 0.06), transparent 40%);
}
/* Contraste renforcé à la demande du système (prefers-contrast: more), dans les deux thèmes. */
@media (prefers-contrast: more) {
  :root { --text-dim: #D2CBE3; --line: #6B5E86; }
  :root[data-theme="light"] { --text-dim: #3A3150; --line: #7A6E94; }
}
.theme-toggle { background: transparent; border: 1px solid var(--line); color: var(--text); border-radius: 999px; width: 2.2rem; height: 2.2rem; cursor: pointer; font-size: 1rem; line-height: 1; }
.theme-toggle:hover { border-color: var(--accent-bright); }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(124, 58, 237, 0.16), transparent 45%),
    radial-gradient(circle at 100% 20%, rgba(124, 58, 237, 0.10), transparent 40%);
  background-attachment: fixed;
  color: var(--text);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  line-height: 1.55;
}

h1, h2, h3 { margin: 0 0 0.4em 0; line-height: 1.08; }
h1 { font-family: 'Bungee', 'Inter', sans-serif; font-weight: 400; letter-spacing: 0.5px; }
h2, h3 { font-family: 'Inter', sans-serif; font-weight: 700; }

a { color: inherit; text-decoration: none; }
/* Lien au milieu d'une phrase : souligné, sinon rien ne le distingue du texte autour sans la couleur (WCAG 1.4.1). Les liens-boutons et les cartes
   portent une classe : ils ne sont pas concernés. */
p a:not([class]), .stat-sub a:not([class]) { text-decoration: underline; text-underline-offset: 2px; }
p { color: var(--text-dim); }

/* Emoji personnalisé Discord (shared/emoji.js:emojiHtml, <:nom:id>) rendu en <img> — sans cette
   règle, l'image apparaît à sa taille native (souvent 128×128px, bien plus grosse qu'un emoji
   unicode) et casse la mise en page partout où elle s'affiche. Alignée comme du texte, à la
   taille de la police environnante ; les endroits qui l'affichent en plus grand (ex. l'icône
   d'une carte d'équipement, voir .equip-card-icon) surchargent width/height explicitement. */
.custom-emoji { width: 1em; height: 1em; vertical-align: -0.15em; object-fit: contain; }

/* --- Navbar --- */
/* Bannière du mode maintenance (propriétaire) */
.maintenance-banner { background: #b45309; color: #fff; text-align: center; padding: 0.5rem 1rem; font-size: 0.9rem; }
.maintenance-banner a { color: #fff; text-decoration: underline; }

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.1rem 3.5rem;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: rgba(8, 7, 12, 0.85);
  backdrop-filter: blur(10px);
  z-index: 10;
}

.brand { display: flex; align-items: center; gap: 0.6rem; font-weight: 700; font-size: 1.05rem; }
.brand img { width: 38px; height: 38px; border-radius: 50%; }

.nav-links { display: flex; gap: 1.6rem; align-items: center; }
.nav-links a { color: var(--text-dim); transition: color 0.15s ease; font-size: 0.95rem; }
.nav-links a:hover { color: var(--text); }
/* Le bouton de connexion est un lien de la barre : sans cette ligne il héritait du gris des liens (illisible sur le violet, surtout en thème clair). */
.nav-links a.btn-discord, .nav-links a.btn-discord:hover { color: #fff; }
/* Déconnexion : un BOUTON dans un formulaire POST (et non un lien), habillé comme les autres liens de la barre. */
.nav-logout { margin: 0; display: flex; }
.nav-logout-button { background: none; border: 0; padding: 0; margin: 0; font: inherit; font-size: 0.95rem; color: var(--text-dim); cursor: pointer; transition: color 0.15s ease; }
.nav-logout-button:hover, .nav-logout-button:focus-visible { color: var(--text); }

.btn-discord, .btn-primary {
  background: linear-gradient(135deg, var(--accent-bright), var(--accent));
  color: #fff;
  padding: 0.7rem 1.5rem;
  border-radius: var(--radius-md);
  font-weight: 600;
  display: inline-block;
  box-shadow: 0 8px 24px -8px rgba(124, 58, 237, 0.6);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.btn-discord:hover, .btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 28px -6px rgba(124, 58, 237, 0.75);
}

.btn-secondary {
  border: 1px solid var(--line);
  padding: 0.7rem 1.5rem;
  border-radius: var(--radius-md);
  font-weight: 600;
  color: var(--text);
  display: inline-block;
  /* Explicite, même si un <a> n'en a pas besoin (fond transparent par défaut) : sans ça, un
     <button class="btn-secondary"> hérite du fond clair par défaut du navigateur, invisible
     avec le texte quasi-blanc du thème (--text) — c'était le cas des boutons "Chercher"
     (leaderboard), "Filtrer" (journal de modération), "Réclamée"/"Complet" (profil). */
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.95rem;
  transition: border-color 0.15s ease;
}
.btn-secondary:hover { border-color: var(--accent-bright); }
.btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }

/* Bouton "Modifier mon profil" de l'en-tête (/profile) : un <label for="tab4"> qui coche
   directement le radio de l'onglet Profil & Confidentialité au clic, sans JavaScript. */
.profile-edit-link { margin-top: 1rem; }

.btn-danger {
  background: #E74C3C;
  color: #fff;
  border: none;
  padding: 0.7rem 1.5rem;
  border-radius: var(--radius-md);
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.95rem;
  display: inline-block;
  transition: background 0.15s ease;
}
.btn-danger:hover { background: #cf4436; }

/* --- Hero --- */
.hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 3rem;
  max-width: var(--wide-max);
  margin: 0 auto;
  padding: 5rem 3.5rem 4rem;
}

.hero-copy { max-width: 34rem; }

.hero-eyebrow {
  display: inline-block;
  color: var(--accent-soft);
  font-weight: 600;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

.hero h1 { font-size: 3.1rem; margin-bottom: 0.7rem; }
.hero-copy p { font-size: 1.08rem; margin-bottom: 2rem; max-width: 30rem; }

.hero-actions { display: flex; gap: 1rem; flex-wrap: wrap; }

.hero-logo {
  flex-shrink: 0;
  width: 320px;
}
.hero-logo picture { display: block; }
.hero-logo img {
  width: 100%;
  border-radius: 50%;
  filter: drop-shadow(0 20px 60px rgba(124, 58, 237, 0.45));
}

/* --- Stats strip --- */
.stats {
  display: flex;
  justify-content: center;
  gap: 4rem;
  padding: 2.5rem 3.5rem;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.stat { text-align: center; }
.stat .number { font-family: 'Bungee', sans-serif; font-size: 2rem; color: var(--accent-bright); display: block; }
.stat .label { color: var(--text-dim); font-size: 0.85rem; }

/* --- Features --- */
.features {
  max-width: var(--wide-max);
  margin: 0 auto;
  padding: 5rem 3.5rem;
}
.features-head { max-width: 34rem; margin-bottom: 3rem; }
.features-head h2 { font-size: 2rem; }

.features-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}

.feature-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 2rem;
  transition: border-color 0.15s ease, transform 0.15s ease;
  /* La moitié des cartes sont maintenant des <a> (voir landing.ejs) : sans ces deux lignes,
     elles hériteraient du bleu souligné par défaut du navigateur pour un lien, cassant
     l'homogénéité visuelle avec les cartes restées de simples <div> (ex: "Salons vocaux à la
     demande", qui n'a pas de page dédiée vers laquelle pointer). */
  display: block;
  color: inherit;
  text-decoration: none;
}
/* Affordance de survol réservée aux cartes RÉELLEMENT cliquables : l'appliquer aussi aux <div>
   sans lien promettrait visuellement une interaction qui n'existe pas (bug corrigé — avant
   cette mise à jour, TOUTES les cartes avaient ce survol alors qu'aucune n'était cliquable). */
a.feature-card:hover, .feature-card:focus-visible { border-color: var(--accent); transform: translateY(-3px); }
.feature-card .icon { font-size: 1.6rem; margin-bottom: 1rem; display: block; }
.feature-card h3 { font-size: 1.05rem; margin-bottom: 0.5rem; }
.feature-card p { font-size: 0.92rem; margin: 0; }

/* --- CTA band --- */
.cta-band {
  max-width: var(--wide-max);
  margin: 0 auto 5rem;
  padding: 3.5rem;
  background: linear-gradient(135deg, rgba(124, 58, 237, 0.18), rgba(124, 58, 237, 0.04));
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.cta-band h2 { font-size: 1.6rem; margin-bottom: 0.3rem; }
.cta-band p { margin: 0; }

footer {
  border-top: 1px solid var(--line);
  padding: 2rem 3.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: var(--text-dim);
  font-size: 0.85rem;
  flex-wrap: wrap;
  gap: 1rem;
}
footer .brand img { width: 26px; height: 26px; }
footer a { text-decoration: underline; text-underline-offset: 2px; }
footer a:hover { color: var(--text); }

/* --- Dashboard --- */
.dashboard { padding: 3rem 3.5rem; max-width: var(--admin-max); margin: 0 auto; }
.dashboard > h1 { font-size: 1.8rem; margin-bottom: 2rem; }

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.75rem 2rem;
  margin-bottom: 1.75rem;
}
.panel h2 { font-size: 1.05rem; margin-bottom: 1.1rem; }

table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
th { text-align: left; color: var(--text-dim); font-weight: 500; padding: 0.55rem 0.5rem; border-bottom: 1px solid var(--line); }
td { padding: 0.65rem 0.5rem; border-bottom: 1px solid var(--line); }
tr:last-child td { border-bottom: none; }

.error-page { max-width: 480px; margin: 6rem auto; text-align: center; padding: 0 2rem; }
.error-page img { width: 72px; margin-bottom: 1.5rem; border-radius: 50%; }
.error-page p { margin-bottom: 1.5rem; }

@media (max-width: 900px) {
  .navbar, .hero, .features, .dashboard, footer { padding-left: 1.5rem; padding-right: 1.5rem; }
  .hero { flex-direction: column-reverse; text-align: center; padding-top: 3rem; }
  .hero-copy p { margin-left: auto; margin-right: auto; }
  .hero-actions { justify-content: center; }
  .hero-logo { width: 220px; }
  .features-grid { grid-template-columns: 1fr; }
  .cta-band { flex-direction: column; text-align: center; }
  .stats { gap: 2rem; }
}

/* --- Avatars dans les tableaux du dashboard --- */
.user-cell { display: flex; align-items: center; gap: 0.6rem; }
.user-cell img { width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0; }

/* --- Pages génériques (règles, équipe) --- */
.page-content { max-width: var(--page-max); margin: 0 auto; padding: 4rem 3.5rem 5rem; }
.page-head { text-align: center; max-width: 42rem; margin: 0 auto 3rem; }
.page-head h1 { font-size: 2.4rem; margin-bottom: 0.6rem; }

/* --- Règles --- */
.rules-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1rem; counter-reset: rule; }
.rule-item {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1.5rem 1.75rem;
  position: relative;
  padding-left: 4.2rem;
  counter-increment: rule;
}
.rule-item::before {
  content: counter(rule);
  position: absolute;
  left: 1.5rem;
  top: 1.5rem;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent-bright), var(--accent));
  color: #fff;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.95rem;
}
.rule-item h3 { font-size: 1.05rem; margin-bottom: 0.35rem; }
.rule-item p { margin: 0; font-size: 0.92rem; }

/* --- Équipe --- */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1.5rem;
}
.team-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.75rem 1.25rem;
  text-align: center;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.team-card:hover { border-color: var(--accent); transform: translateY(-3px); }
.team-card img { width: 72px; height: 72px; border-radius: 50%; margin-bottom: 0.9rem; border: 2px solid var(--line); }
.team-card h3 { font-size: 0.98rem; margin-bottom: 0.5rem; }
.role-badge {
  display: inline-block;
  background: rgba(124, 58, 237, 0.18);
  color: var(--accent-soft);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.25rem 0.7rem;
  border-radius: 999px;
}

@media (max-width: 900px) {
  .page-content { padding-left: 1.5rem; padding-right: 1.5rem; }
}

/* --- Bannières de statut (boutique) --- */
.banner {
  max-width: var(--page-max);
  margin: 0 auto 2rem;
  padding: 0.9rem 1.25rem;
  border-radius: var(--radius-md);
  font-size: 0.92rem;
  text-align: center;
}
.banner-success { background: rgba(46, 204, 113, 0.15); border: 1px solid rgba(46, 204, 113, 0.4); color: #7EE2A8; }
.banner-error { background: rgba(231, 76, 60, 0.15); border: 1px solid rgba(231, 76, 60, 0.4); color: #F1948A; }
/* Rappel discret (ni succès ni erreur) — ex. « pense à équiper l'arme/l'armure que tu viens
   d'acheter », voir web/views/shop.ejs. Teinte dérivée de --accent plutôt que success/error, pour
   ne pas laisser croire à une confirmation ou un problème. */
.banner-info { background: rgba(124, 58, 237, 0.15); border: 1px solid rgba(124, 58, 237, 0.4); color: #C4B5FD; }

/* --- Boutique --- */
/* Filtre de catégorie cliquable sur le côté (armes/armures/potions/autres — voir
   shared/shop-categories.js et web/views/shop.ejs) : même principe CSS-only radio+label que
   .tabs-input/.tabs-nav plus bas (aucun JavaScript requis), mais en barre latérale plutôt qu'en
   onglets horizontaux, et avec un panneau par catégorie affiché OU masqué plutôt qu'un simple
   défilement vers un titre. Les identifiants de catégorie ci-dessous (weapon/armor/potion/
   standard) DOIVENT rester synchronisés avec shared/shop-categories.js:SHOP_CATEGORIES. */
.shop-cat-filter { display: flex; align-items: flex-start; gap: 2rem; }
.shop-cat-nav {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 0 0 190px;
  position: sticky;
  top: 6rem; /* sous la navbar sticky, même marge que .shop-card:scroll-margin-top plus bas */
}
.shop-cat-nav label {
  padding: 0.65rem 0.9rem;
  border-radius: var(--radius-md);
  cursor: pointer;
  color: var(--text-dim);
  font-weight: 600;
  font-size: 0.9rem;
}
.shop-cat-nav label:hover { background: var(--surface-2); color: var(--text); }
.shop-cat-content { flex: 1; min-width: 0; }
.shop-cat-panel { display: none; }

/* « Toutes » (toujours cochée par défaut au chargement — voir le commentaire dans shop.ejs sur
   l'ancre #item-X de la liste de souhaits) affiche tous les panneaux d'un coup. */
#shopcat-all:checked ~ .shop-cat-content .shop-cat-panel { display: block; }
#shopcat-weapon:checked ~ .shop-cat-content #shop-cat-panel-weapon,
#shopcat-armor:checked ~ .shop-cat-content #shop-cat-panel-armor,
#shopcat-potion:checked ~ .shop-cat-content #shop-cat-panel-potion,
#shopcat-companion:checked ~ .shop-cat-content #shop-cat-panel-companion,
#shopcat-standard:checked ~ .shop-cat-content #shop-cat-panel-standard { display: block; }

#shopcat-all:checked ~ .shop-cat-nav label[for="shopcat-all"],
#shopcat-weapon:checked ~ .shop-cat-nav label[for="shopcat-weapon"],
#shopcat-armor:checked ~ .shop-cat-nav label[for="shopcat-armor"],
#shopcat-potion:checked ~ .shop-cat-nav label[for="shopcat-potion"],
#shopcat-companion:checked ~ .shop-cat-nav label[for="shopcat-companion"],
#shopcat-standard:checked ~ .shop-cat-nav label[for="shopcat-standard"] {
  background: var(--accent);
  color: #fff;
}

@media (max-width: 780px) {
  .shop-cat-filter { flex-direction: column; gap: 1rem; }
  .shop-cat-nav { flex-direction: row; flex-wrap: wrap; position: static; flex: none; width: 100%; }
}

/* Titre de catégorie (armes/armures/potions/autres — voir shared/shop-categories.js) au-dessus de
   chaque grille : même principe que .equip-slot-title sur /profile (équipement de combat). */
.shop-category-title { margin: 1.25rem 0 0.75rem; font-size: 1rem; }
.shop-category-title:first-of-type { margin-top: 0; }
.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}
.shop-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 2rem 1.5rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  z-index: 1;
}
/* Même correctif que .game-card (voir plus bas) : sans remonter la carte survolée au-dessus de
   ses voisines, l'infobulle qui déborde de la carte passerait derrière la carte suivante de la
   grille. */
.shop-card:hover, .shop-card:focus-within { z-index: 50; }
.shop-card .icon { font-size: 2.2rem; }
.shop-card h3 { font-size: 1.05rem; margin: 0; }
.shop-card p { font-size: 0.88rem; margin: 0 0 0.5rem; }
.shop-card .price { font-weight: 700; color: var(--accent-soft); margin-bottom: 0.75rem; }
.shop-card form { width: 100%; }
.shop-card button, .shop-card .btn-secondary { width: 100%; border: none; cursor: pointer; font-family: inherit; font-size: 0.95rem; }
.shop-card button:disabled { opacity: 0.4; cursor: not-allowed; }
/* Vignettes d'administration (monstres, articles, recettes) : même langage visuel que les cartes de la boutique — un emoji et un nom — ; un clic
   déplie la vignette sur toute la largeur et montre son formulaire. <details>/<summary> : accessible au clavier, aucun JavaScript requis. */
.admin-tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; align-items: start; }
.admin-tile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); }
.admin-tile[open] { grid-column: 1 / -1; }
.admin-tile > summary { list-style: none; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; padding: 1.1rem 0.75rem; text-align: center; border-radius: var(--radius-lg); }
.admin-tile > summary::-webkit-details-marker { display: none; }
.admin-tile > summary:hover { background: var(--surface-2); }
.admin-tile > summary:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }
.admin-tile .icon { font-size: 2.2rem; line-height: 1.1; }
.admin-tile__name { font-weight: 600; font-size: 0.95rem; word-break: break-word; }
.admin-tile__meta { font-size: 0.78rem; opacity: 0.75; }
.admin-tile--inactive { opacity: 0.6; }
.admin-tile[open] > summary { flex-direction: row; justify-content: flex-start; gap: 0.75rem; text-align: left; border-bottom: 1px solid var(--line); border-radius: 16px 16px 0 0; }
.admin-tile[open] .icon { font-size: 1.8rem; }
.admin-tile__body { padding: 1rem 1.25rem; }
.admin-tile-filter { display: block; width: 100%; max-width: 360px; margin: 0.5rem 0 1rem; }
.admin-tile[hidden] { display: none; }

.shop-card { scroll-margin-top: 6rem; } /* évite que l'ancre #item-X n'atterrisse sous la navbar sticky */

/* Étoile de liste de souhaits, en haut à droite de la carte — un simple bouton-icône, pas un
   bouton pleine largeur comme les autres actions de la carte (voir .shop-card form/button
   ci-dessus, dont ce bouton doit s'affranchir explicitement). */
.wishlist-toggle { position: absolute; top: 0.75rem; right: 0.75rem; width: auto !important; z-index: 2; }
.wishlist-star {
  width: 2rem !important;
  height: 2rem;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 50%;
}
.wishlist-star:hover { color: var(--accent-bright); background: rgba(124, 58, 237, 0.15); }
.wishlist-star.active { color: #F4C430; }

/* Zone de toucher d'au moins 44 px sur téléphone (constaté par tests/visual/mobile.spec.js au premier vrai lancement : les
   `button.btn-primary` des formulaires mesuraient 38 px et l'étoile de liste de souhaits 32 px). Sur mobile UNIQUEMENT : le bureau
   reste identique au pixel près.
   - `button.btn-primary` : hauteur minimale (un <button> centre son texte ; les liens .btn-primary mesurent déjà plus de 44 px).
   - Étoile : la zone passe de 32 à 44 px MAIS son décalage diminue de 6 px (0.375rem) sur chaque côté concerné : le centre de l'étoile
     ne bouge pas, seule la zone cliquable grandit. Doit rester APRÈS `.wishlist-star` (même spécificité, `!important` des deux côtés). */
@media (max-width: 780px) {
  button.btn-primary { min-height: 44px; }
  .wishlist-toggle { top: 0.375rem; right: 0.375rem; }
  .wishlist-star { width: 2.75rem !important; height: 2.75rem; }
}
.wishlist-summary-list { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.wishlist-summary-chip {
  display: inline-block;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.4rem 0.9rem;
  font-size: 0.85rem;
  color: var(--text);
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.wishlist-summary-chip:hover { border-color: var(--accent-bright); }

/* Aperçu des avantages au survol — même principe CSS pur que .game-tooltip (voir plus bas) :
   contenu déjà dans le HTML, révélé par :hover/:focus-within, aucun JavaScript nécessaire. Un
   <span tabindex="0"> sert de déclencheur focusable au clavier (la carte n'est pas elle-même
   un lien ni un bouton, contrairement à .game-card). */
.perks-trigger {
  display: inline-block;
  font-size: 0.78rem;
  color: var(--accent-soft);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: default;
}
.perks-tooltip {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.5rem);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1rem;
  z-index: 20;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-6px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  text-align: left;
}
.shop-card:hover .perks-tooltip, .shop-card:focus-within .perks-tooltip {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.perks-tooltip-title { font-size: 0.78rem; color: var(--text-dim); margin: 0 0 0.6rem; }
.perks-tooltip-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.85rem; }
.perks-tooltip-list li { padding-left: 1.1rem; position: relative; }
.perks-tooltip-list li::before { content: '✓'; position: absolute; left: 0; color: var(--accent-soft); }

/* --- Profil --- */
.profile-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  margin-bottom: 1.75rem;
}
/* Profil public : 3 blocs. Modificateur DÉFINI AVANT la règle @media (max-width: 700px) plus bas, à
   spécificité égale : c'est elle qui l'emporte alors sur mobile (2 colonnes, puis 1 sous 480 px).
   Ne PAS remettre cette valeur en style="" dans la vue : un style en ligne l'emporterait sur toutes
   les règles mobiles (c'était le cas, et les stats débordaient de l'écran sur un téléphone de 320 px). */
.profile-stats-3 { grid-template-columns: repeat(3, 1fr); }
/* min-width: 0 : une colonne de grille « 1fr » ne rétrécit pas sous la largeur de son contenu sans cela. */
.stat-block { text-align: center; display: flex; flex-direction: column; align-items: center; gap: 0.4rem; min-width: 0; max-width: 100%; }
.stat-label { color: var(--text-dim); font-size: 0.85rem; }
.stat-value { font-family: 'Bungee', sans-serif; font-size: 1.8rem; overflow-wrap: anywhere; }
.stat-sub { color: var(--text-dim); font-size: 0.8rem; }

.xp-bar { width: 100%; height: 8px; background: var(--surface-2); border-radius: 999px; overflow: hidden; margin: 0.3rem 0; }
.xp-bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-bright)); border-radius: 999px; }

.badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}
.badge-item {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  text-align: center;
}
.badge-item.owned { background: rgba(124, 58, 237, 0.1); border-color: var(--accent); }
/* Badge NON obtenu. Avant : `opacity: 0.55` sur toute la carte — elle divisait aussi le contraste du texte, et la description tombait à ~3:1
   (audit axe-core de tests/visual/a11y.spec.js : « color-contrast », 6 éléments sur le profil public, 11 sur « mon profil »), sous les 4.5:1
   d'un texte normal. Maintenant : le texte garde des couleurs EXPLICITES (≥ 4.5:1 même sur --surface-2), seul l'emoji est atténué. */
.badge-item.locked .badge-emoji { opacity: 0.55; }
.badge-item.locked .badge-name { color: var(--text-dim); }
.badge-item.locked .badge-desc { color: #8E86A3; }
:root[data-theme="light"] .badge-item.locked .badge-desc { color: #6B6280; }
.badge-emoji { font-size: 1.6rem; }
.badge-name { font-weight: 600; font-size: 0.92rem; }
.badge-desc { font-size: 0.78rem; color: var(--text-dim); }

@media (max-width: 700px) {
  .profile-stats { grid-template-columns: 1fr 1fr; }
}

/* --- Champs de formulaire (nommés « mc- » pour des raisons historiques) --- */
.form-stack { display: flex; flex-direction: column; gap: 1.1rem; }
.form-field { display: flex; flex-direction: column; gap: 0.4rem; font-size: 0.9rem; color: var(--text-dim); }
.form-field input, .form-field select, select.form-field, .form-field textarea {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
}
.form-field textarea { resize: vertical; min-height: 4.5rem; line-height: 1.4; }
/* Le fond/texte du <select> fermé ne suffit pas : la liste déroulante elle-même (les
   <option>) est une surface de rendu à part que les navigateurs ne stylent pas forcément
   d'après le <select> parent — sans cette règle, elle reste blanche par défaut même quand la
   boîte fermée est bien sombre. `select.form-field` couvre aussi les cas où la classe est posée
   directement sur le <select> plutôt que sur un <label> englobant (ex: filtre du journal de
   modération) — sans ça, ni la boîte fermée ni la liste n'étaient stylées du tout. */
.form-field option, select.form-field option {
  background: var(--surface-2);
  color: var(--text);
}
/* --- Picker visuel d'objets : web/public/visual-select.js transforme un <select class=
   "visual-select-source"> en cette liste déroulante custom, seule façon d'afficher une VRAIE icône
   (un <select>/<option> natif ne peut pas contenir d'image). Le <select> d'origine reste dans le
   DOM (masqué visuellement par .js-enhanced ci-dessous, jamais avec display:none : il reste la
   SEULE source soumise au formulaire) — sans JS, il n'est jamais masqué et le formulaire fonctionne
   normalement, juste sans icône visuelle (texte simple, voir emojiForOption côté serveur). */
.visual-select { position: relative; }
.visual-select-source.js-enhanced {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.visual-select__toggle {
  display: flex; align-items: center; gap: 0.5rem; width: 100%;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem; color: var(--text); font-family: inherit; font-size: 0.95rem;
  cursor: pointer; text-align: left;
}
.visual-select__toggle:hover, .visual-select__toggle:focus-visible { border-color: var(--accent-bright); }
.visual-select__toggle-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; gap: 0.4rem; }
.visual-select__toggle-caret { opacity: 0.7; }
.visual-select__panel {
  position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; right: 0;
  max-height: 260px; overflow-y: auto; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 0.3rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.visual-select__option {
  display: flex; align-items: center; gap: 0.5rem; width: 100%; background: none; border: none;
  border-radius: var(--radius-sm); padding: 0.5rem 0.6rem; color: var(--text); font-family: inherit;
  font-size: 0.92rem; text-align: left; cursor: pointer;
}
.visual-select__option:hover, .visual-select__option:focus-visible { background: var(--surface); }
.visual-select__option[aria-selected="true"] { background: var(--accent); color: #fff; }

.form-checkbox { display: flex; align-items: center; gap: 0.6rem; font-size: 0.92rem; }
.form-stack button { align-self: flex-start; border: none; cursor: pointer; font-family: inherit; }

.form-actions { display: flex; gap: 0.75rem; margin-left: auto; flex-wrap: wrap; }
.form-actions button { border: none; cursor: pointer; font-family: inherit; }
.form-actions button:disabled, .form-stack button:disabled { opacity: 0.4; cursor: not-allowed; }

/* --- Responsive : menu hamburger --- */
/* Corrigé : display:none retirait ce checkbox de l'ordre de tabulation,
   rendant le menu mobile totalement inaccessible au clavier (impossible de l'ouvrir sans souris/
   tactile). Repris de la même technique déjà utilisée par .sr-only/.showcase-option input :
   masqué visuellement mais toujours focalisable et activable (Espace) au clavier. Le label associé
   (.nav-toggle-label) reçoit un anneau de focus visible juste en dessous. */
.nav-toggle-checkbox {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.nav-toggle-checkbox:focus-visible ~ .nav-toggle-label {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
.nav-toggle-label {
  display: none;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--text);
  line-height: 1;
  padding: 0.3rem;
}

.nav-search { display: flex; }
.nav-search input {
  width: 11rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.45rem 0.8rem;
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  transition: border-color 0.15s ease, width 0.2s ease;
}
.nav-search input:focus { border-color: var(--accent-bright); width: 14rem; outline: none; }

.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll table { min-width: 480px; }
/* Le conteneur défilant est focalisable au clavier (tabindex="0" dans les vues) : il lui faut un anneau de focus visible. */
.table-scroll:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

@media (max-width: 780px) {
  .navbar { flex-wrap: wrap; padding: 1rem 1.25rem; }
  .nav-toggle-label { display: block; }

  .nav-links {
    display: none;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    order: 3;
    margin-top: 1rem;
    border-top: 1px solid var(--line);
    padding-top: 0.75rem;
  }
  .nav-links a { padding: 0.75rem 0.25rem; border-bottom: 1px solid var(--line); }
  .nav-links a:last-child { border-bottom: none; }
  .nav-links .nav-logout { display: block; }
  .nav-links .nav-logout-button { display: block; width: 100%; min-height: 44px; padding: 0.75rem 0.25rem; text-align: left; }
  .nav-links .btn-discord { text-align: center; margin-top: 0.5rem; }
  .nav-search { order: -1; margin-bottom: 0.5rem; }
  .nav-search input, .nav-search input:focus { width: 100%; }

  /* Bug corrigé : .navbar est collante (position: sticky, plus haut) et, sur mobile, tous
     les sous-menus sont dépliés — le menu ouvert devenait PLUS HAUT QUE L'ÉCRAN. Un élément collant ne
     défile pas : le doigt faisait défiler la page derrière et le bas du menu restait inaccessible. Le
     menu ouvert est donc limité à la hauteur visible (moins la ligne du logo, ~6.5rem) et défile
     DANS LUI-MÊME ; overscroll-behavior: contain empêche qu'arrivé en bout de menu, le geste ne
     reparte vers la page. 100vh d'abord (navigateurs anciens), puis 100dvh (hauteur réellement
     visible quand la barre d'adresse du navigateur mobile est affichée) qui le remplace s'il est compris. */
  .nav-toggle-checkbox:checked ~ .nav-links {
    display: flex;
    max-height: calc(100vh - 6.5rem);
    max-height: calc(100dvh - 6.5rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  .hero h1 { font-size: 2.2rem; }
  .hero-eyebrow { font-size: 0.8rem; }
  .stats { gap: 1.5rem; padding: 2rem 1.25rem; }
  .stat .number { font-size: 1.5rem; }

  .panel { padding: 1.25rem 1.1rem; }
  .shop-grid, .team-grid, .badge-grid { grid-template-columns: 1fr 1fr; }
  .cta-band { padding: 2rem 1.5rem; text-align: center; flex-direction: column; }
  footer { flex-direction: column; text-align: center; gap: 0.75rem; }
}

@media (max-width: 480px) {
  .shop-grid, .team-grid, .badge-grid { grid-template-columns: 1fr; }
  .hero h1 { font-size: 1.9rem; }
  .page-head h1 { font-size: 1.7rem; }
  .form-actions { width: 100%; }
  .form-actions button { flex: 1; }
}

/* --- Quêtes (profil) --- */
.quest-list { display: flex; flex-direction: column; gap: 1.25rem; }
.quest-item { padding-bottom: 0.25rem; }
.quest-item.completed .quest-status { color: #7EE2A8; }
.quest-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; gap: 0.3rem; }
.quest-label { font-weight: 600; font-size: 0.95rem; }
.quest-status { color: var(--text-dim); font-size: 0.88rem; }
.quest-item .stat-sub { display: block; margin-top: 0.4rem; }

/* --- Récompenses (profil) --- */
.reward-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.reward-item:last-of-type { border-bottom: none; }
.reward-item button:disabled { opacity: 0.5; cursor: not-allowed; }

/* --- Statuts d'achat --- */
.status-badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.78rem; font-weight: 600; }
.status-active { background: rgba(46, 204, 113, 0.15); color: #7EE2A8; }
.status-expired { background: rgba(127, 140, 141, 0.2); color: var(--text-dim); }

/* --- Statuts de suggestion : jusqu'ici .status-badge seul, donc tous
   les statuts (en attente, à l'étude, retenue, refusée, déjà faite) rendaient avec le même badge
   neutre — seul l'emoji du texte les distinguait. Réutilise les couleurs déjà en place ailleurs
   sur le site pour rester cohérent (vert = positif/.status-active, rouge = .btn-danger). */
.status-open { background: rgba(127, 140, 141, 0.2); color: var(--text-dim); }
.status-considering { background: rgba(241, 196, 15, 0.15); color: #F4D874; }
.status-accepted { background: rgba(46, 204, 113, 0.15); color: #7EE2A8; }
.status-rejected { background: rgba(231, 76, 60, 0.15); color: #F1948A; }
.status-implemented { background: rgba(124, 58, 237, 0.18); color: var(--accent-soft); }

@media (max-width: 480px) {
  .quest-header { flex-direction: column; align-items: flex-start; }
}

/* --- Page Soutiens --- */
.support-panel { text-align: center; max-width: 640px; margin: 0 auto 1.5rem; padding: 2.5rem 2rem; }
.support-icon { font-size: 2.2rem; margin-bottom: 0.75rem; }
.support-panel h2 { font-size: 1.3rem; margin-bottom: 0.75rem; }
.support-panel p { margin-bottom: 1rem; }
.support-panel .btn-primary, .support-panel .btn-secondary { display: inline-block; margin-top: 0.5rem; }

/* --- Modération rapide (dashboard) --- */
.mod-search-form { display: flex; gap: 0.75rem; margin-bottom: 1.25rem; }
.mod-search-form input { flex: 1; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 0.6rem 0.9rem; color: var(--text); font-family: inherit; }
/* Zone de toucher d'au moins 44 px (recommandation Apple) : le champ mesurait 37 px de haut. Les boutons et
   listes du même formulaire sont alignés sur la même hauteur pour qu'ils restent de niveau sur une ligne. */
.mod-search-form input, .mod-search-form select, .mod-search-form button { min-height: 44px; }

.mod-member-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--line);
}
.mod-member-row:last-child { border-bottom: none; }

.mod-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.mod-action-form { display: flex; gap: 0.4rem; align-items: center; }
.mod-action-form input {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
}
.mod-action-form input[name="reason"] { width: 140px; }
.mod-input-narrow { width: 55px !important; }

.btn-mod, .btn-mod-danger {
  border: 1px solid var(--line);
  padding: 0.45rem 0.9rem;
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  background: transparent;
  color: var(--text);
  white-space: nowrap;
}
.btn-mod:hover { border-color: var(--accent-bright); }
.btn-mod-danger { border-color: #E74C3C; color: #F1948A; }
.btn-mod-danger:hover { background: rgba(231, 76, 60, 0.12); }

@media (max-width: 780px) {
  .mod-member-row { flex-direction: column; align-items: flex-start; }
  .mod-actions { width: 100%; }
  .mod-action-form { flex: 1; flex-wrap: wrap; }
  .mod-action-form input[name="reason"] { width: 100%; }
  .mod-search-form { flex-direction: column; }
}

/* --- Pagination --- */
.pagination { display: flex; align-items: center; justify-content: center; gap: 1.5rem; margin-top: 2rem; }
.pagination a.btn-secondary { padding: 0.5rem 1.1rem; }

/* Sélecteur de période du classement général (Tout / Cette semaine / Ce mois-ci) */
.period-selector { display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.period-selector a {
  padding: 0.45rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.period-selector a:hover { border-color: var(--accent-bright); color: var(--text); }
.period-selector a.active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* --- Boîte à suggestions --- */
.suggestion-card { display: flex; gap: 1.25rem; margin-bottom: 1rem; align-items: flex-start; }
.suggestion-vote { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; flex-shrink: 0; }
.vote-btn {
  width: 2.2rem;
  height: 2.2rem;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}
.vote-btn:hover { border-color: var(--accent-bright); color: var(--text); }
.vote-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.vote-btn.active-down { background: #E74C3C; border-color: #E74C3C; color: #fff; }
.vote-score { font-weight: 700; font-size: 1.05rem; }
.suggestion-body { flex: 1; min-width: 0; }

/* --- Sondages --- */
.poll-card { margin-bottom: 1.25rem; }
.poll-options { display: flex; flex-direction: column; gap: 0.6rem; }
.poll-option-form { display: block; }
.poll-option {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  border: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: var(--radius-md);
  padding: 0.7rem 1rem;
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  text-align: left;
  overflow: hidden;
}
button.poll-option:hover { border-color: var(--accent-bright); }
button.poll-option.active { border-color: var(--accent); }
.poll-options-readonly .poll-option { cursor: default; }
.poll-option-label { flex: 0 0 auto; min-width: 30%; font-weight: 600; z-index: 1; }
.poll-option-bar-track {
  flex: 1;
  height: 0.5rem;
  background: var(--surface);
  border-radius: 999px;
  overflow: hidden;
  z-index: 1;
}
.poll-option-bar-fill { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-bright)); border-radius: 999px; transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
.poll-option-percent { flex: 0 0 auto; font-size: 0.8rem; color: var(--text-dim); z-index: 1; white-space: nowrap; }

/* En dessous de 480px, label (min-width: 30% ci-dessus) + barre + pourcentage sur une seule
   ligne devient trop serré pour un intitulé de réponse un peu long — aucun des trois éléments
   n'avait de traitement mobile dédié jusqu'ici, contrairement au reste des ajouts récents
   (voir .trade-columns, .quest-header ci-dessus). Le label passe sur sa propre ligne
   (min-width: 100% force le retour dans le flex), barre + pourcentage se partagent la ligne
   suivante. */
@media (max-width: 480px) {
  .poll-option { flex-wrap: wrap; row-gap: 0.4rem; }
  .poll-option-label { min-width: 100%; }
}

/* --- Échange direct entre membres --- */
.trade-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
@media (max-width: 780px) { .trade-columns { grid-template-columns: 1fr; } }
.trade-item-list { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.75rem; max-height: 16rem; overflow-y: auto; }
.trade-item-check {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: 0.88rem;
  transition: border-color 0.15s ease;
}
.trade-item-check:hover { border-color: var(--accent-bright); }
.trade-item-check input { width: auto; }
.trade-row { padding: 0.9rem 0; border-bottom: 1px solid var(--line); }
.trade-row:last-child { border-bottom: none; }
.trade-row p { margin: 0 0 0.4rem; }

/* --- Profil public --- */
.public-profile-avatar { width: 88px; height: 88px; border-radius: 50%; margin: 0 auto 1rem; display: block; border: 3px solid var(--line); }

/* --- Liens sur les cellules membres (classement, journal) --- */
a.user-cell { text-decoration: none; }
a.user-cell:hover .badge-name, a.user-cell:hover { color: var(--accent-soft); }

/* --- Liens rapides du dashboard --- */
.dashboard-links { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.5rem; }

/* --- Admin boutique --- */
.shop-admin-item { border-bottom: 1px solid var(--line); padding-bottom: 1.25rem; margin-bottom: 1rem; }
.shop-admin-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.shop-admin-row .form-field { flex: 1; min-width: 140px; }

/* Regroupement visuel des formulaires de création admin — utilisé par dashboard-shop.ejs et dashboard-monsters.ejs.
   Un <fieldset> reste un formulaire NORMAL (jamais désactivé), seulement un cadre visuel + un titre. */
.form-fieldset { border: 1px solid var(--line); border-radius: var(--radius-md); padding: 1rem 1.1rem 1.1rem; margin: 0; display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.form-fieldset legend { padding: 0 0.5rem; font-weight: 700; }
/* Note affichée seulement quand le JS de masquage par type est actif (voir shop-admin-type.js) —
   sans JS, tous les champs restent visibles et cette précision n'aurait pas de sens. */
.shop-type-js-note { display: none; }
.js-shop-type-active .shop-type-js-note { display: inline; }
/* Bloc « Suggérer prix et bonus » de /dashboard/shop-admin : révélé par shop-item-suggest.js (sans JavaScript, il resterait inutile). */
.suggest-item-stats { display: none; }
.suggest-item-stats.is-active { display: block; margin-bottom: 1rem; }

/* --- Événements --- */
.event-list { display: flex; flex-direction: column; gap: 1rem; max-width: 700px; margin: 0 auto; }
.event-card { display: flex; align-items: center; gap: 1.5rem; }
.event-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(124, 58, 237, 0.15);
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
  min-width: 80px;
  flex-shrink: 0;
}
.event-day { font-weight: 700; font-size: 0.95rem; text-transform: capitalize; }
.event-time { font-size: 0.8rem; color: var(--text-dim); }
.event-info h3 { font-size: 1.05rem; margin-bottom: 0.3rem; }
.event-info p { margin: 0; font-size: 0.9rem; }

/* --- Menus déroulants de la navbar --- */
.nav-group { position: relative; }
.nav-group-label { color: var(--text-dim); cursor: default; user-select: none; padding: 0.4rem 0; display: inline-block; }
.nav-group:hover .nav-group-label, .nav-group:focus-within .nav-group-label { color: var(--text); }

/* Bug corrigé : .nav-dropdown était en `display: none` par défaut, donc ses liens n'étaient
   jamais dans l'ordre de tabulation (un élément display:none est ignoré par le clavier) —
   :focus-within sur .nav-group n'aurait jamais pu se déclencher, puisqu'il n'y avait tout
   simplement rien à l'intérieur du groupe sur lequel atterrir au clavier. Résultat concret :
   Infos, Aide, Événements, Soutiens & bons plans, Jeux, Classement, Boutique et Hôtel des
   ventes étaient TOTALEMENT inatteignables au clavier sur desktop, uniquement à la souris.
   Le passage à opacity/pointer-events (déjà le principe utilisé par .game-tooltip et
   .perks-tooltip ailleurs sur ce même site) garde les liens présents et focusables en
   permanence, juste masqués visuellement — donc Tab peut les atteindre, et :focus-within
   fonctionne enfin. tabindex="0" sur .nav-group-label (voir navbar.ejs) permet en plus
   d'ouvrir le menu dès qu'on tabule jusqu'au label lui-même, avant même ses liens. */
.nav-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 0.5rem;
  min-width: 180px;
  display: flex;
  flex-direction: column;
  z-index: 20;
  box-shadow: 0 12px 30px -8px rgba(0,0,0,0.5);
  opacity: 0;
  pointer-events: none;
  transform: translateY(-6px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.nav-group:hover .nav-dropdown, .nav-group:focus-within .nav-dropdown {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.nav-dropdown a { padding: 0.5rem 0.75rem !important; border-radius: var(--radius-sm); }
.nav-dropdown a:hover, .nav-dropdown a:focus-visible { background: var(--surface-2); }

@media (max-width: 780px) {
  .nav-group-label { display: block; padding: 0.75rem 0.25rem; font-weight: 600; border-bottom: 1px solid var(--line); color: var(--text); }
  .nav-dropdown {
    opacity: 1 !important;
    pointer-events: auto !important;
    transform: none !important;
    position: static;
    border: none;
    padding: 0;
    box-shadow: none;
    background: transparent;
  }
  .nav-dropdown a { border-bottom: 1px solid var(--line); border-radius: 0; padding: 0.65rem 0.5rem !important; }
}

/* --- Hub admin --- */
.admin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.25rem;
  margin-bottom: 2rem;
}
.admin-card { text-align: center; text-decoration: none; transition: transform 0.15s ease, border-color 0.15s ease; }
.admin-card:hover { transform: translateY(-3px); border-color: var(--accent); }
.admin-card-icon { font-size: 2rem; display: block; margin-bottom: 0.6rem; }
.admin-card h3 { font-size: 1.05rem; margin-bottom: 0.4rem; color: var(--text); }
.admin-card p { font-size: 0.85rem; margin: 0; }

.activity-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.activity-row:last-child { border-bottom: none; }
.activity-emoji { font-size: 1.1rem; }
.activity-time { margin-left: auto; }

/* Brève mise en évidence des lignes ajoutées en direct via le flux SSE du hub admin
   (web/views/admin-hub.ejs) — purement décoratif, s'estompe après 3s. */
.activity-row-new { animation: activity-row-highlight 3s ease-out; }
@keyframes activity-row-highlight {
  from { background: var(--surface-2); }
  to { background: transparent; }
}

.error-message { font-family: monospace; font-size: 0.8rem; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Onglets CSS-only (radio hack, pas de JS) --- */
/* Corrigé : display:none retirait chaque radio de l'ordre de
   tabulation, rendant TOUS les onglets du site (profil, boutique, dashboard, aventure, infos)
   impossibles à changer au clavier seul. Même technique que .sr-only : masqué
   visuellement, toujours focalisable et activable (flèches/Espace, comportement natif des
   groupes radio). Le label associé reçoit un anneau de focus visible ci-dessous. */
.tabs-input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.tabs-nav { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; border-bottom: 1px solid var(--line); }
.tabs-nav label {
  padding: 0.7rem 1.1rem;
  cursor: pointer;
  color: var(--text-dim);
  font-weight: 600;
  font-size: 0.92rem;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tabs-nav label:hover { color: var(--text); }
/* Barre faite de LIENS (/aventure : une page par section) : même apparence que les onglets en boutons radio ci-dessus. */
.tabs-nav .tabs-nav__item {
  padding: 0.7rem 1.1rem; color: var(--text-dim); font-weight: 600; font-size: 0.92rem; text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs-nav .tabs-nav__item:hover { color: var(--text); }
.tabs-nav .tabs-nav__item[aria-current="page"] { color: var(--text); border-bottom-color: var(--accent); }
.tabs-nav .tabs-nav__item:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; border-radius: var(--radius-xs); }
#tab1:focus-visible ~ .tabs-nav label[for="tab1"], #tab2:focus-visible ~ .tabs-nav label[for="tab2"],
#tab3:focus-visible ~ .tabs-nav label[for="tab3"], #tab4:focus-visible ~ .tabs-nav label[for="tab4"],
#tab5:focus-visible ~ .tabs-nav label[for="tab5"], #tab6:focus-visible ~ .tabs-nav label[for="tab6"] {
  outline: 2px solid var(--accent-soft);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
.tab-panel { display: none; }
.tab-panel.tab-panel--current { display: block; } /* section rendue seule par le serveur (/aventure/<section>) */
/* Marge de défilement sous la navbar sticky (même principe que .shop-card plus haut) — nécessaire
   depuis que web/public/tabs-deep-link.js fait défiler explicitement jusqu'au panneau ciblé par
   #tabX après une redirection serveur, pour que son début n'apparaisse pas caché derrière la
   navbar. */
.tab-panel { scroll-margin-top: 6rem; }

/* Règles génériques réutilisées sur toutes les pages à onglets (dashboard, profil) */
#tab1:checked ~ #panel1, #tab2:checked ~ #panel2, #tab3:checked ~ #panel3,
#tab4:checked ~ #panel4, #tab5:checked ~ #panel5, #tab6:checked ~ #panel6 { display: block; }

#tab1:checked ~ .tabs-nav label[for="tab1"], #tab2:checked ~ .tabs-nav label[for="tab2"],
#tab3:checked ~ .tabs-nav label[for="tab3"], #tab4:checked ~ .tabs-nav label[for="tab4"],
#tab5:checked ~ .tabs-nav label[for="tab5"], #tab6:checked ~ .tabs-nav label[for="tab6"] { color: var(--text); border-bottom-color: var(--accent); }

/* ───────────── /admin/economie : choix de période et courbe de la masse monétaire ───────────── */
.period-nav { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0 0 1rem; }
.period-nav a { padding: 0.35rem 0.8rem; border: 1px solid var(--line); border-radius: 999px; color: var(--text-dim); text-decoration: none; font-weight: 600; font-size: 0.9rem; }
.period-nav a:hover { color: var(--text); }
.period-nav a[aria-current="page"] { color: var(--text); border-color: var(--accent); background: var(--surface-2); }
.supply-chart { margin: 1rem 0 0; color: var(--accent-bright); }
.supply-chart svg { display: block; width: 100%; height: 120px; background: var(--surface-2); border-radius: var(--radius-sm); }

/* --- Sections de la page événements --- */
.event-section-title { text-align: center; font-size: 1.2rem; margin: 2.5rem 0 1.25rem; }
.event-list-past .event-card { opacity: 0.6; }
.event-list-past .event-date { background: rgba(127, 140, 141, 0.15); }

/* --- Collection d'objets --- */
.collection-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
}
.collection-item {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1.25rem 1rem;
  text-align: center;
  background: rgba(124, 58, 237, 0.06);
}
.collection-item .badge-emoji { font-size: 1.8rem; display: block; margin-bottom: 0.4rem; }
.collection-item .badge-name { display: block; font-weight: 600; font-size: 0.9rem; margin-bottom: 0.25rem; }
.collection-item .badge-desc { display: block; font-size: 0.78rem; color: var(--text-dim); }

/* --- Sélection d'équipement de combat (/profile, onglet Craft & Inventaire) : une carte
   cliquable par arme/armure possédée, plutôt qu'un menu déroulant. Chaque carte est un bouton de
   formulaire à elle seule (voir web/views/profile.ejs) : fonctionne nativement au clavier, sans
   JavaScript, avec un état "équipé" visible en permanence — même principe visuel que
   .badge-item.owned ci-dessus, décliné en bouton. */
.equip-slot-title { margin: 1.25rem 0 0.75rem; font-size: 1rem; }
.equip-slot-title:first-of-type { margin-top: 0; }
.equip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.equip-card-form { margin: 0; }

/* Échange d'un point de compétence (amélioration) : deux menus + un bouton, sur une
   ligne en grand écran, empilés sur mobile. */
.skill-swap-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin: 0;
}
.skill-swap-form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
}
.skill-swap-form select {
  padding: 0.4rem 0.5rem;
  border-radius: 0.5rem;
}
.equip-card {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1rem 0.75rem;
  background: rgba(124, 58, 237, 0.06);
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.equip-card:hover { border-color: var(--accent-bright); transform: translateY(-2px); }
.equip-card:focus-visible { outline: 2px solid var(--accent-bright); outline-offset: 2px; }
.equip-card.equipped { border-color: var(--accent); background: rgba(124, 58, 237, 0.18); cursor: default; }
.equip-card-icon { font-size: 1.8rem; line-height: 1; }
.equip-card-icon img.custom-emoji { width: 1.8rem; height: 1.8rem; vertical-align: middle; }
.equip-card-none .equip-card-icon { opacity: 0.6; }
.equip-card-name { font-weight: 600; font-size: 0.88rem; }
.equip-card-stat { font-size: 0.78rem; color: var(--accent-soft); }
/* Aperçu "avant / après" sur les cartes de compétences (amélioration). */
.equip-card-preview { font-size: 0.72rem; color: var(--text-dim, #9aa); font-style: italic; }
.equip-card-badge { font-size: 0.72rem; font-weight: 700; color: var(--accent-bright); }

/* --- Bio du profil public --- */
.profile-bio { max-width: 32rem; margin: 0.75rem auto 0; font-size: 0.95rem; }

/* --- Page Aide --- */
.help-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem;
}
.help-item {
  background: var(--surface-2);
  border-radius: var(--radius-md);
  padding: 1rem 1.1rem;
  font-size: 0.9rem;
  line-height: 1.5;
}
a.help-item {
  display: block;
  text-decoration: none;
  color: var(--text);
  transition: background 0.15s ease, transform 0.15s ease;
  cursor: pointer;
}
a.help-item:hover { background: rgba(124, 58, 237, 0.15); transform: translateY(-2px); }
.help-item code { background: rgba(124, 58, 237, 0.15); padding: 0.1rem 0.4rem; border-radius: var(--radius-xs); font-size: 0.85em; }
.help-list { margin: 0; padding-left: 1.25rem; font-size: 0.92rem; line-height: 1.8; }
.help-list code { background: rgba(124, 58, 237, 0.15); padding: 0.1rem 0.4rem; border-radius: var(--radius-xs); font-size: 0.9em; }

/* --- Page Jeux (activité de jeu des membres, via Presence Discord) --- */
.games-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 1.5rem;
}
.game-card {
  position: relative;
  display: block;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.75rem 1.5rem;
  color: var(--text);
  transition: border-color 0.15s ease, transform 0.15s ease;
  z-index: 1;
}
/* Bug corrigé : sans z-index relevé ici, l'infobulle (qui déborde hors de sa carte, voir
   .game-tooltip plus bas) passait derrière les cartes suivantes de la grille — z-index sur
   .game-tooltip seul ne suffit pas, il ne joue que par rapport aux AUTRES éléments À
   L'INTÉRIEUR de cette même carte, pas par rapport aux cartes voisines. Il faut remonter la
   carte entière (celle qui est survolée) au-dessus de ses voisines. */
.game-card:hover, .game-card:focus-within { border-color: var(--accent); transform: translateY(-3px); z-index: 50; }
.game-card-head { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.5rem; }
.game-icon { font-size: 1.4rem; }
.game-card h3 { font-size: 1.05rem; margin: 0; }
.game-card .stat-sub { margin: 0; }

/* Aperçu au survol : contenu déjà présent dans le HTML, simplement masqué par défaut (opacité
   0 + pointer-events none) et révélé en CSS pur — pas de JavaScript nécessaire, cohérent avec
   les onglets déjà présents ailleurs sur le site (voir .tabs-input). :focus-within permet la
   même révélation au clavier (Tab), pas seulement à la souris. */
.game-tooltip {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.5rem);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: 1rem;
  z-index: 20;
  opacity: 0;
  pointer-events: none;
  transform: translateY(-6px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  text-align: left;
}
.game-card:hover .game-tooltip, .game-card:focus-within .game-tooltip {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
.game-tooltip-title { font-size: 0.78rem; color: var(--text-dim); margin: 0 0 0.6rem; }
.game-tooltip-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.45rem; }
.game-tooltip-list .user-cell { justify-content: space-between; font-size: 0.85rem; }
.game-tooltip-more { font-size: 0.78rem; color: var(--text-dim); margin: 0.6rem 0 0; }

.game-players-section { margin-bottom: 2rem; }
.game-players-section h2 { font-size: 1.1rem; margin-bottom: 1rem; }
.game-players-list { display: flex; flex-direction: column; gap: 0.75rem; }
.game-players-list .user-cell img { width: 32px; height: 32px; }
.game-players-list .user-cell { justify-content: space-between; padding: 0.5rem 0.25rem; }

/* Le survol n'existe pas au tactile : sur petit écran, l'aperçu ne s'affiche jamais (il
   flasherait juste avant la navigation au clic) — un tap va directement à /jeux/:slug, qui
   affiche la liste complète de toute façon. */
@media (max-width: 480px) {
  .game-tooltip { display: none; }
  /* Contrairement à .game-tooltip ci-dessus, il n'existe pas de page dédiée où retrouver les
     avantages d'un article de boutique après coup : plutôt que de masquer l'info sur mobile
     (où le survol n'existe pas), on l'affiche directement dans le flux de la carte, sans
     déclencheur ni positionnement absolu. */
  .perks-trigger { display: none; }
  .perks-tooltip {
    position: static;
    opacity: 1;
    pointer-events: auto;
    transform: none;
    margin-top: 0.5rem;
    width: 100%;
  }
}

/* ==========================================================================
   Animations & micro-interactions — appliquées site-wide plutôt que
   page par page, pour rester cohérentes partout sans avoir à les répéter
   dans chaque vue. Toujours de simples transitions/keyframes CSS (jamais de
   JS) : rien ici ne doit pouvoir casser une page si un navigateur les
   ignore, juste rendre l'ensemble un peu plus vivant.
   ========================================================================== */

/* Préférence système "réduire les animations" (mal des transports, troubles
   vestibulaires...) : coupe tout ce qui suit à la source, prioritaire sur le
   reste de cette section. Toujours la première règle du groupe. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Exception : .banner utilise animation-duration pour minuter combien
     de temps un message de succès/erreur reste lisible avant de s'effacer (voir banner-lifecycle
     plus bas), pas pour un effet de mouvement — la règle générale ci-dessus le réduisait à
     0.001ms, donc le message sautait quasi instantanément à son état final (invisible) avant
     même d'avoir pu être lu par quelqu'un ayant activé "réduire les animations". On lui redonne
     sa durée complète ; le léger travelling vertical (translateY(-8px)) reste minime et ne
     déclenche pas les soucis vestibulaires que ce réglage vise à éviter. */
  .banner { animation-duration: 6.5s !important; }
}

html { scroll-behavior: smooth; }

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Contenu principal : léger fondu/glissement à chaque chargement de page —
   .page-content est le conteneur générique utilisé par la quasi-totalité
   des vues (voir web/views/partials), donc cette seule règle couvre déjà
   l'ensemble du site sans rien changer aux vues elles-mêmes. */
.page-content, .dashboard, .hero { animation: fade-in-up 0.45s ease-out; }

/* L'entrée des bannières est désormais intégrée à banner-lifecycle plus bas dans ce fichier
   (voir la section "toast" qui gère aussi leur disparition automatique). */

/* Bouton "Acheter à l'hôtel des ventes" d'une carte boutique :
   quand le même article s'y vend moins cher, ce bouton doit sauter aux yeux à côté du bouton
   "Acheter" (boutique, plein tarif) juste au-dessus — même vert que .banner-success/.status-active
   (une bonne affaire, pas une alerte), neutralisée par @media (prefers-reduced-motion: reduce)
   tout en haut de cette section comme toutes les autres animations du site. Retirée côté vue
   (voir shop.ejs) quand le solde ne suffit pas : rien à mettre en avant sur un bouton désactivé. */
@keyframes cheaper-listing-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(46, 204, 113, 0.55); }
  50% { box-shadow: 0 0 0 7px rgba(46, 204, 113, 0); }
}
.btn-cheaper-pulse {
  /* Vert assez foncé pour un texte blanc lisible (contraste ≥ 4,5:1, WCAG AA) : l'ancien #27AE60 n'atteignait que 2,9:1. */
  background: #1E8449;
  border-color: #1E8449;
  color: #fff;
  animation: cheaper-listing-pulse 1.6s ease-in-out infinite;
}
.btn-cheaper-pulse:hover { background: #196F3D; border-color: #196F3D; }

/* Changement d'onglet (boutique/marché, dashboard communauté...) : fondu
   doux plutôt qu'un basculement sec — .tab-panel passe de display:none à
   display:block au clic (voir le "radio hack" plus haut dans ce fichier),
   ce qui relance l'animation à chaque bascule. */
.tab-panel { animation: fade-in-up 0.3s ease-out; }

/* Onglets de /aventure —
   scopé à .page-aventure (voir web/views/aventure.ejs) pour ne PAS toucher les autres pages qui
   réutilisent les mêmes ids génériques #tab1..#tab6 (boutique, dashboard, profil...). Neutralisé comme
   tout le reste de ce fichier par @media (prefers-reduced-motion: reduce) tout en haut. */
.page-aventure .tabs-nav {
  position: relative;
  overflow: hidden;
}
/* Filet lumineux qui glisse doucement sous toute la barre d'onglets ("tout autour") — purement
   décoratif, sous les libellés (z-index implicite : les labels du site n'ont pas de fond opaque ici). */
.page-aventure .tabs-nav::after {
  content: '';
  /* 400 % de la barre, motif de 200 % : on le décale d'une tuile (= 200 % de la barre) par cycle, sans repeindre (transform). */
  position: absolute;
  left: 0; width: 400%; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent-soft), var(--accent), var(--accent-soft), transparent);
  background-size: 50% 100%;
  will-change: transform;
  animation: aventure-shimmer 2.5s linear infinite;
  pointer-events: none;
}
.page-aventure .tabs-nav .tabs-nav__item {
  position: relative;
  transition: color 0.2s ease, transform 0.2s ease;
}
.page-aventure .tabs-nav .tabs-nav__item:hover { transform: translateY(-2px); }
/* Soulignement animé de l'onglet actif : se dessine de gauche à droite au lieu d'apparaître d'un coup
   (remplace visuellement le border-bottom statique des règles génériques ci-dessus, laissées telles
   quelles pour les autres pages). */
.page-aventure .tabs-nav .tabs-nav__item::before {
  content: '';
  position: absolute;
  left: 0; bottom: -2px;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}
.page-aventure .tabs-nav .tabs-nav__item[aria-current="page"]::before,
.page-aventure .tabs-nav .tabs-nav__item:hover::before { transform: scaleX(1); }
/* Navigation à deux niveaux : groupes (grosses pastilles) au-dessus, sections du groupe affiché en dessous. Les liens des autres groupes restent dans la
   page mais masqués (aventure-nav.js y lit les anciennes ancres) : [hidden] doit l'emporter sur les display: inline-flex de la version mobile. */
.page-aventure .tabs-nav[hidden], .page-aventure .tabs-nav [hidden] { display: none !important; }
.page-aventure .groups-nav { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 0.5rem; margin-bottom: 0.75rem; }
.page-aventure .groups-nav__item {
  display: flex; align-items: center; justify-content: center; gap: 0.35rem; min-height: 44px; padding: 0.5rem 0.75rem;
  border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface, rgba(255, 255, 255, 0.03));
  color: var(--text-dim); font-weight: 600; text-decoration: none; white-space: nowrap;
  transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.page-aventure .groups-nav__item:hover { color: var(--text); transform: translateY(-2px); border-color: var(--accent-soft); }
.page-aventure .groups-nav__item[aria-current="true"] { color: var(--text); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); box-shadow: 0 0 0 1px var(--accent); }
.page-aventure .groups-nav__item:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }
@media (max-width: 480px) {
  .page-aventure .groups-nav { grid-template-columns: repeat(3, 1fr); gap: 0.4rem; }
  .page-aventure .groups-nav__item { padding: 0.45rem 0.4rem; font-size: 0.85rem; white-space: normal; text-align: center; flex-direction: column; gap: 0.1rem; }
}
/* Entrée du contenu d'un onglet : légèrement plus marquée que le fondu générique (fade-in-up
   ci-dessus, qui continue de s'appliquer aussi) — un léger zoom en plus, pour une page qu'on consulte
   très souvent (pour la rendre plus vivante). */
.page-aventure .tab-panel { animation: fade-in-up 0.3s ease-out, aventure-panel-in 0.35s ease-out; }

@keyframes aventure-shimmer {
  to { transform: translate3d(-50%, 0, 0); }
}
@keyframes aventure-panel-in {
  from { transform: scale(0.98); }
  to { transform: scale(1); }
}

/* Page /aventure « plus vivante ». TOUT est scopé à .page-aventure (mêmes raisons que ci-dessus : les classes .panel, .badge-item,
   .equip-card, .hp-bar-fill sont réutilisées par d'autres pages) et neutralisé par
   @media (prefers-reduced-motion: reduce) tout en haut de ce fichier.

   1) Fond animé : lueurs colorées qui dérivent + petites particules qui montent, derrière le contenu
      (z-index -1 dans le contexte d'empilement propre à la page — `isolation: isolate` — donc jamais
      par-dessus un bouton ni la navbar, et pointer-events: none). */
.page-aventure { position: relative; isolation: isolate; }
/* PERFORMANCE : ces deux couches plein écran bougent en permanence. Animer `background-position` oblige le navigateur à REPEINDRE tout l'écran à chaque
   image (du CPU, en continu) ; animer `transform` est pris en charge par la carte graphique, sans repeindre (une fois la couche créée, quasi aucun CPU).
   Même rendu, même vitesse : on déplace un élément plus grand que l'écran au lieu de déplacer le motif dans un élément de la taille de l'écran. */
.page-aventure::before {
  content: '';
  /* 140 % de l'écran (comme l'ancien background-size) ancré en haut à gauche : la lueur dérive de -28,57 % de sa propre taille (= 40 % de l'écran). */
  position: fixed; top: 0; left: 0; width: 140vw; height: 140vh; z-index: -1; pointer-events: none;
  background:
    radial-gradient(circle at 15% 20%, rgba(124, 58, 237, 0.20), transparent 45%),
    radial-gradient(circle at 85% 70%, rgba(46, 204, 113, 0.13), transparent 45%),
    radial-gradient(circle at 50% 100%, rgba(245, 176, 65, 0.11), transparent 40%);
  will-change: transform;
  animation: aventure-glow 18s ease-in-out infinite alternate;
}
.page-aventure::after {
  content: '';
  /* Plus haut que l'écran de 880 px : la couche monte de 880 px puis reboucle sans à-coup (880 = 4 × 220 = 3 × 293,33 = 2 × 440, hauteurs des trois tuiles). */
  position: fixed; top: 0; left: 0; right: 0; bottom: -880px; z-index: -1; pointer-events: none; opacity: 0.5;
  background-image:
    radial-gradient(circle, rgba(196, 181, 253, 0.85) 0 1.5px, transparent 2.5px),
    radial-gradient(circle, rgba(125, 211, 252, 0.65) 0 1px, transparent 2px),
    radial-gradient(circle, rgba(253, 224, 71, 0.55) 0 1.5px, transparent 2.5px);
  background-size: 180px 220px, 260px 293.333px, 340px 440px;
  background-position: 0 0, 60px 90px, 120px 40px;
  will-change: transform;
  animation: aventure-particles 40s linear infinite;
}
@keyframes aventure-glow {
  0% { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-28.571%, -28.571%, 0); }
}
@keyframes aventure-particles {
  to { transform: translate3d(0, -880px, 0); }
}
/* Les particules tournent en continu : on les coupe sur petit écran (économie de batterie/GPU), les
   lueurs restent. */
@media (max-width: 720px) {
  .page-aventure::after { display: none; }
}

/* 2) Cartes : entrée en cascade (bestiaire, grilles d'équipement/compétences, panneaux) puis survol animé.
      fill-mode `backwards` (et non `both`) : l'état final n'est PAS figé par l'animation, donc les
      transform des états :hover existants (lift des cartes) continuent de s'appliquer normalement. */
@keyframes aventure-card-in {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
  to { opacity: 1; transform: none; }
}
.page-aventure .badge-grid > .badge-item,
/* Amélioration d'équipement (migration 40) : la carte d'arme/armure et son bloc « Améliorer » forment une seule cellule de la grille. */
.page-aventure .equip-card-wrap { display: flex; flex-direction: column; gap: 0.35rem; }
.page-aventure .equip-upgrade-info { margin: 0; text-align: center; }
.page-aventure .equip-upgrade-btn { width: 100%; font-size: 0.85em; }

.page-aventure .equip-grid > *,
.page-aventure .tab-panel > section.panel { animation: aventure-card-in 0.45s ease-out backwards; }
.page-aventure .badge-grid > .badge-item:nth-child(1) { animation-delay: 0.04s; }
.page-aventure .badge-grid > .badge-item:nth-child(2) { animation-delay: 0.08s; }
.page-aventure .badge-grid > .badge-item:nth-child(3) { animation-delay: 0.12s; }
.page-aventure .badge-grid > .badge-item:nth-child(4) { animation-delay: 0.16s; }
.page-aventure .badge-grid > .badge-item:nth-child(5) { animation-delay: 0.2s; }
.page-aventure .badge-grid > .badge-item:nth-child(6) { animation-delay: 0.24s; }
.page-aventure .badge-grid > .badge-item:nth-child(7) { animation-delay: 0.28s; }
.page-aventure .badge-grid > .badge-item:nth-child(8) { animation-delay: 0.32s; }
.page-aventure .badge-grid > .badge-item:nth-child(9) { animation-delay: 0.36s; }
.page-aventure .badge-grid > .badge-item:nth-child(10) { animation-delay: 0.4s; }
.page-aventure .badge-grid > .badge-item:nth-child(11) { animation-delay: 0.44s; }
.page-aventure .badge-grid > .badge-item:nth-child(12) { animation-delay: 0.48s; }
.page-aventure .equip-grid > *:nth-child(1) { animation-delay: 0.05s; }
.page-aventure .equip-grid > *:nth-child(2) { animation-delay: 0.1s; }
.page-aventure .equip-grid > *:nth-child(3) { animation-delay: 0.15s; }
.page-aventure .equip-grid > *:nth-child(4) { animation-delay: 0.2s; }
.page-aventure .equip-grid > *:nth-child(5) { animation-delay: 0.25s; }
.page-aventure .equip-grid > *:nth-child(6) { animation-delay: 0.3s; }
.page-aventure .equip-grid > *:nth-child(7) { animation-delay: 0.35s; }
.page-aventure .equip-grid > *:nth-child(8) { animation-delay: 0.4s; }
.page-aventure .tab-panel > section.panel:nth-of-type(1) { animation-delay: 0.0s; }
.page-aventure .tab-panel > section.panel:nth-of-type(2) { animation-delay: 0.08s; }
.page-aventure .tab-panel > section.panel:nth-of-type(3) { animation-delay: 0.16s; }
.page-aventure .tab-panel > section.panel:nth-of-type(4) { animation-delay: 0.24s; }
.page-aventure .tab-panel > section.panel:nth-of-type(5) { animation-delay: 0.32s; }
.page-aventure .tab-panel > section.panel:nth-of-type(6) { animation-delay: 0.4s; }

@keyframes aventure-wiggle {
  0%, 100% { transform: rotate(0); }
  25% { transform: rotate(-9deg) scale(1.12); }
  75% { transform: rotate(9deg) scale(1.12); }
}
.page-aventure .equip-card:not(:disabled):hover { box-shadow: 0 6px 20px rgba(124, 58, 237, 0.35); }
.page-aventure .equip-card:not(:disabled):hover .equip-card-icon,
.page-aventure .badge-item.owned:hover .badge-emoji { display: inline-block; animation: aventure-wiggle 0.5s ease; }
.page-aventure .badge-item.owned:hover { box-shadow: 0 6px 20px rgba(245, 176, 65, 0.28); }

/* 3) Barres de PV / d'énergie : reflet lumineux qui la traverse en boucle et pulsation quand les PV sont
      bas (--low). PAS de remplissage « depuis zéro » à l'affichage (retiré) : le panneau de
      combat est remplacé en entier après chaque coup, la barre repartait donc de vide à chaque attaque ;
      la barre glisse maintenant de son ANCIENNE largeur vers la nouvelle (voir combat-live.js). Le reflet est un ::after clippé par
      overflow:hidden sur la barre pleine ; le passage de couleur (ok/mid/low) reste piloté par les
      classes existantes. */
@keyframes aventure-bar-shine {
  0%, 15% { transform: translateX(0); }
  100% { transform: translateX(270%); }
}
@keyframes aventure-bar-pulse {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.35); }
}
.page-aventure .hp-bar-fill {
  position: relative; overflow: hidden;
}
.page-aventure .hp-bar-fill::after {
  content: '';
  position: absolute; top: 0; bottom: 0; left: -60%; width: 60%; pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.38), transparent);
  animation: aventure-bar-shine 2.8s ease-in-out infinite;
}
.page-aventure .hp-bar-fill--low {
  animation: aventure-bar-pulse 1.2s ease-in-out infinite;
}

/* Cartes cliquables : lift + ombre douce au survol, uniformisé sur tous les
   types de cartes du site (certaines l'avaient déjà individuellement plus
   haut dans ce fichier — ceci comble les autres : boutique, collection,
   badges débloqués). */
.shop-card, .collection-item, .badge-item.owned {
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.shop-card:hover, .collection-item:hover, .badge-item.owned:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 28px -10px rgba(0, 0, 0, 0.5);
}

/* Boutons et liens-boutons : retour tactile immédiat au clic, en plus des
   transitions de survol déjà en place plus haut — s'applique à tous les
   styles de bouton du site (primaire, secondaire, modération, danger). */
button, .btn-primary, .btn-secondary, .btn-discord, .btn-danger, .btn-mod, .btn-mod-danger {
  transition: transform 0.1s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
button:active, .btn-primary:active, .btn-secondary:active, .btn-discord:active,
.btn-danger:active, .btn-mod:active, .btn-mod-danger:active {
  transform: scale(0.97);
}

/* Barres de progression (quêtes, défis collectifs) : remplissage animé
   plutôt qu'un saut instantané à la largeur finale. */
.xp-bar-fill { transition: width 0.9s cubic-bezier(0.22, 1, 0.36, 1); }

/* Lien de nav : petit soulignement animé au survol, sous le texte plutôt
   qu'un simple changement de couleur (déjà en place, conservé). */
.nav-links a { position: relative; }
.nav-links a::after {
  content: '';
  position: absolute;
  left: 0; right: 100%;
  bottom: -4px;
  height: 2px;
  background: var(--accent-bright);
  transition: right 0.2s ease;
}
.nav-links a:hover::after { right: 0; }

/* Modale de confirmation (voir site-ui.js:confirmAction) — remplace window.confirm() natif sur
   les actions sensibles (suppression, ban, achat...). */
.confirm-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(8, 7, 12, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  animation: fade-in-up 0.15s ease-out;
  padding: 1.5rem;
}
.confirm-modal {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 1.75rem;
  max-width: 420px;
  width: 100%;
  box-shadow: 0 20px 60px -12px rgba(0, 0, 0, 0.6);
}
.confirm-modal p { margin: 0 0 1.5rem; line-height: 1.5; }
.confirm-modal-actions { display: flex; justify-content: flex-end; gap: 0.75rem; }

/* Avatar : léger zoom au survol dans les tableaux (mod log, classement, recherche membre...) —
   purement décoratif, sans changer la mise en page environnante (transform ne déplace pas les
   éléments voisins). */
.user-cell img { transition: transform 0.15s ease; }
.user-cell:hover img { transform: scale(1.35); }

/* Bannières de succès/erreur : disparaissent d'elles-mêmes après quelques secondes plutôt que
   de rester affichées jusqu'au prochain chargement de page (effet "toast"). La séquence
   0%→8%→85%→100% garde le message pleinement lisible un moment avant de l'estomper — le total
   (~6.5s) laisse largement le temps de lire un message court sans avoir à agir dessus. */
@keyframes banner-lifecycle {
  0%   { opacity: 0; transform: translateY(-8px); }
  8%   { opacity: 1; transform: translateY(0); }
  85%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-8px); max-height: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; border-width: 0; overflow: hidden; }
}
.banner {
  max-height: 220px;
  overflow: hidden;
  animation: banner-lifecycle 6.5s ease-out forwards;
}

/* --- Vitrine du profil : bannière (préréglages), pronoms, badges épinglés ---
   Les couleurs sont des dégradés FIXES choisis dans une liste blanche côté serveur
   (shared/profile-showcase.js) : aucune valeur saisie par un membre n'arrive jamais dans le CSS. */
.profile-banner {
  width: 100%;
  height: 110px;
  border-radius: var(--radius-md);
  margin: 0 0 -44px; /* l'avatar chevauche le bas de la bannière */
  border: 1px solid var(--line);
}
.profile-banner-aurore     { background: linear-gradient(120deg, #1f6f5c, #5b3fd1 55%, #c94fa8); }
.profile-banner-ocean      { background: linear-gradient(120deg, #0b3c6e, #1479b8 55%, #35c2d6); }
.profile-banner-foret      { background: linear-gradient(120deg, #123524, #2f7d4a 55%, #a3c65a); }
.profile-banner-braise     { background: linear-gradient(120deg, #5a1414, #c2410c 55%, #f5a524); }
.profile-banner-crepuscule { background: linear-gradient(120deg, #2b1b54, #a03a83 55%, #f08a5d); }
.profile-banner-nuit       { background: radial-gradient(circle at 20% 30%, #3b2f7a, transparent 45%), radial-gradient(circle at 80% 60%, #1d3f72, transparent 40%), #0c0a1a; }
.profile-banner-sakura     { background: linear-gradient(120deg, #5c2a4a, #c45c8e 55%, #f6c1d8); }
.profile-banner-neon       { background: linear-gradient(120deg, #0a1a3a, #12b5cb 55%, #e0409f); }
.profile-banner-desert     { background: linear-gradient(120deg, #4a2e14, #c58b3c 55%, #f1d08a); }
.profile-banner-glace      { background: linear-gradient(120deg, #16314d, #4f8fc0 55%, #cfeaf7); }
.profile-banner-lave       { background: linear-gradient(120deg, #14090a, #9b1c1c 55%, #ff7a1a); }
.profile-banner-galaxie    { background: radial-gradient(circle at 25% 35%, #5a2d91, transparent 50%), radial-gradient(circle at 75% 65%, #1a1038, transparent 45%), #0b0b1e; }
.profile-banner + .public-profile-avatar { position: relative; }
/* Cadres d'avatar (shared/profile-frames.js) : un double anneau en box-shadow [couleur intérieure, couleur extérieure] — les mêmes couleurs que la carte
   en image (un test vérifie la concordance). Les cadres animés pulsent ; « réduire les animations » les fige (règle globale prefers-reduced-motion). */
.avatar-frame-bronze { box-shadow: 0 0 0 4px #CD7F32, 0 0 0 8px #7A4A1B; }
.avatar-frame-argent { box-shadow: 0 0 0 4px #D9DEE3, 0 0 0 8px #8A949E; }
.avatar-frame-or { box-shadow: 0 0 0 4px #F5C542, 0 0 0 8px #B8860B, 0 0 16px 4px rgba(245, 197, 66, 0.35); }
.avatar-frame-vocal { box-shadow: 0 0 0 4px #38BDF8, 0 0 0 8px #1D4ED8; animation: frame-pulse-vocal 2.4s ease-in-out infinite; }
.avatar-frame-flamme { box-shadow: 0 0 0 4px #FB923C, 0 0 0 8px #DC2626; animation: frame-pulse-flamme 1.8s ease-in-out infinite; }
.avatar-frame-dragon { box-shadow: 0 0 0 4px #84CC16, 0 0 0 8px #365314; }
.avatar-frame-cristal { box-shadow: 0 0 0 4px #67E8F9, 0 0 0 8px #7C3AED, 0 0 14px 3px rgba(103, 232, 249, 0.3); }
.avatar-frame-champion { box-shadow: 0 0 0 4px #FDE047, 0 0 0 8px #F43F5E; animation: frame-pulse-champion 2s ease-in-out infinite; }
.avatar-frame-metier10 { box-shadow: 0 0 0 4px #34D399, 0 0 0 8px #065F46; }
.avatar-frame-metier20 { box-shadow: 0 0 0 4px #F59E0B, 0 0 0 8px #92400E; animation: frame-pulse-metier20 2.4s ease-in-out infinite; }
@keyframes frame-pulse-metier20 { 0%, 100% { box-shadow: 0 0 0 4px #F59E0B, 0 0 0 8px #92400E, 0 0 0 8px rgba(245, 158, 11, 0); } 50% { box-shadow: 0 0 0 4px #F59E0B, 0 0 0 8px #92400E, 0 0 14px 10px rgba(245, 158, 11, 0.4); } }
.avatar-frame-prestige5 { box-shadow: 0 0 0 4px #A78BFA, 0 0 0 8px #4C1D95, 0 0 14px 3px rgba(167, 139, 250, 0.3); }
.avatar-frame-prestige10 { box-shadow: 0 0 0 4px #F472B6, 0 0 0 8px #9D174D; animation: frame-pulse-prestige10 2.2s ease-in-out infinite; }
.avatar-frame-prestige15 { box-shadow: 0 0 0 4px #FACC15, 0 0 0 8px #7E22CE; animation: frame-pulse-prestige15 1.8s ease-in-out infinite; }
@keyframes frame-pulse-prestige10 { 0%, 100% { box-shadow: 0 0 0 4px #F472B6, 0 0 0 8px #9D174D, 0 0 0 8px rgba(244, 114, 182, 0); } 50% { box-shadow: 0 0 0 4px #F472B6, 0 0 0 8px #9D174D, 0 0 14px 10px rgba(244, 114, 182, 0.35); } }
@keyframes frame-pulse-prestige15 { 0%, 100% { box-shadow: 0 0 0 4px #FACC15, 0 0 0 8px #7E22CE, 0 0 0 8px rgba(250, 204, 21, 0); } 50% { box-shadow: 0 0 0 4px #FACC15, 0 0 0 8px #7E22CE, 0 0 16px 12px rgba(250, 204, 21, 0.4); } }
@keyframes frame-pulse-vocal { 0%, 100% { box-shadow: 0 0 0 4px #38BDF8, 0 0 0 8px #1D4ED8, 0 0 0 8px rgba(56, 189, 248, 0); } 50% { box-shadow: 0 0 0 4px #38BDF8, 0 0 0 8px #1D4ED8, 0 0 14px 10px rgba(56, 189, 248, 0.45); } }
@keyframes frame-pulse-flamme { 0%, 100% { box-shadow: 0 0 0 4px #FB923C, 0 0 0 8px #DC2626, 0 0 0 8px rgba(251, 146, 60, 0); } 50% { box-shadow: 0 0 0 4px #FB923C, 0 0 0 8px #DC2626, 0 0 14px 10px rgba(251, 146, 60, 0.5); } }
@keyframes frame-pulse-champion { 0%, 100% { box-shadow: 0 0 0 4px #FDE047, 0 0 0 8px #F43F5E, 0 0 0 8px rgba(253, 224, 71, 0); } 50% { box-shadow: 0 0 0 4px #FDE047, 0 0 0 8px #F43F5E, 0 0 16px 10px rgba(253, 224, 71, 0.45); } }
.frame-swatch { width: 44px; height: 44px; margin: 6px auto; border-radius: 50%; }
.showcase-option.is-locked { opacity: 0.55; cursor: not-allowed; }
.deal-badge { display: inline-block; margin: 0.25rem 0; padding: 0.15rem 0.6rem; border-radius: 999px; background: rgba(245, 197, 66, 0.18); border: 1px solid #F5C542; color: #F5C542; font-size: 0.8rem; font-weight: 600; }
.price-old { color: var(--text-dim); font-weight: 400; margin-right: 0.35rem; }
.nav-bell { position: relative; display: inline-flex; align-items: center; gap: 0.25rem; }
.nav-bell-count { min-width: 1.15rem; padding: 0 0.3rem; border-radius: 999px; background: #DC2626; color: #fff; font-size: 0.7rem; font-weight: 700; line-height: 1.15rem; text-align: center; }
.notification-list { list-style: none; margin: 0 0 1rem; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.notification-item { padding: 0.6rem 0.8rem; border-radius: var(--radius-md); border: 1px solid var(--line); }
.notification-unread { border-color: var(--accent-bright); background: rgba(124, 58, 237, 0.12); }
.notification-new { display: inline-block; margin-right: 0.4rem; padding: 0.05rem 0.5rem; border-radius: 999px; background: var(--accent-bright); color: #fff; font-size: 0.7rem; font-weight: 700; }
.notification-link { margin-left: 0.5rem; font-weight: 600; }
.rsvp-form { display: inline-block; margin: 0 0 0 0.5rem; }
.daily-bonus-banner { margin: 0 0 1rem; padding: 0.6rem 0.9rem; border-radius: var(--radius-md); border: 1px solid var(--accent); background: rgba(124, 58, 237, 0.12); font-size: 0.9rem; }
.activity-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.activity-icon { display: inline-block; min-width: 1.6rem; text-align: center; }
.profile-title { margin: -0.5rem 0 0.25rem; font-weight: 600; font-size: 1.05rem; letter-spacing: 0.02em; }
.profile-pronouns { margin: -0.5rem 0 0.5rem; font-size: 0.9rem; color: var(--text-dim); }

.pinned-badges { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; padding: 0; margin: 1rem 0 0; }
.pinned-badge { padding: 0.3rem 0.75rem; border: 1px solid var(--accent); border-radius: 999px; background: rgba(124, 58, 237, 0.12); font-size: 0.85rem; }

.showcase-fieldset { border: 0; padding: 0; margin: 0 0 1rem; min-width: 0; }
.showcase-fieldset legend { padding: 0; margin-bottom: 0.5rem; }
.showcase-banners { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 0.6rem; }
.showcase-option { position: relative; display: flex; flex-direction: column; gap: 0.35rem; cursor: pointer; }
.showcase-option input { position: absolute; opacity: 0; pointer-events: none; }
/* background-color (et non « background ») : la règle .profile-banner-<id> pose un DÉGRADÉ (background-image) ; le raccourci « background » le remettait à zéro et les pastilles de bannière s'affichaient vides. */
.showcase-swatch { display: block; height: 44px; border-radius: var(--radius-sm); border: 2px solid var(--line); background-color: var(--surface-2); }
.showcase-option input:checked + .showcase-swatch { border-color: var(--accent-bright); }
.showcase-option input:focus-visible + .showcase-swatch { outline: 2px solid var(--accent-soft); outline-offset: 2px; }
.showcase-option-label { font-size: 0.8rem; color: var(--text-dim); text-align: center; }
/* --- Relevé de pièces (/profile/releve) : le signe + / − accompagne toujours la couleur (jamais la couleur seule) --- */
.statement-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.25rem; margin-top: 0.75rem; }
.statement-summary h3 { font-size: 1rem; margin: 0 0 0.4rem; }
.statement-list { margin: 0; padding-left: 1.1rem; }
.statement-gain { color: #58D68D; }
.statement-loss { color: #F1948A; }
.card-style-sombre { background: linear-gradient(120deg, #08070C, #1A1023); }
.card-style-clair { background: linear-gradient(120deg, #FBFAFE, #E7E1F4); }
.card-style-minimal { background: #14121C; }
.profile-card-preview { display: block; width: 100%; max-width: 560px; height: auto; border-radius: var(--radius-md); border: 1px solid var(--line); margin: 0.75rem 0 1rem; }
.accent-swatches { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: -0.25rem 0 1rem; }
.accent-swatch { width: 44px; height: 44px; border-radius: 50%; border: 2px solid var(--line); cursor: pointer; padding: 0; }
.accent-swatch:hover, .accent-swatch:focus-visible { border-color: var(--accent-bright); outline: 2px solid var(--accent-soft); outline-offset: 2px; }
.pin-picker { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pin-option { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.4rem 0.7rem; border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer; min-height: 44px; }
.pin-option input { width: auto; }
@media (max-width: 480px) {
  .profile-banner { height: 84px; margin-bottom: -36px; }
}

/* --- Très petits écrans (≤480 px) : blocs de statistiques du profil sur une seule colonne ---
   Placé en fin de fichier pour l'emporter sur `.profile-stats` (4 colonnes) et `.profile-stats-3`.
   À 320 px, deux colonnes de ~140 px ne contiennent pas une valeur en Bungee 1,8 rem (« 🎙️ 12h34 »). */
@media (max-width: 480px) {
  .profile-stats, .profile-stats-3 { grid-template-columns: 1fr; }
  .stat-value { font-size: 1.5rem; }
}

/* --- Page « État du système » (/admin/etat) --- */
.status-list { list-style: none; padding: 0; margin: 0; }
.status-list li { padding: 0.55rem 0; border-bottom: 1px solid var(--line); }
.status-list li:last-child { border-bottom: none; }

/* Encadré « Aujourd'hui pour toi » de /profile (voir shared/today-panel.js) : à faire en premier, déjà fait en dernier et atténué. */
.today-list { list-style: none; margin: 0; padding: 0; }
.today-item { padding: 0.55rem 0; border-bottom: 1px solid var(--line); line-height: 1.45; }
.today-item:last-child { border-bottom: none; }
.today-todo { font-weight: 600; }
.today-info { color: var(--text-dim); }
.today-done { color: var(--text-dim); opacity: 0.8; }
.today-item a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }

/* Sélecteur d'émojis, bouton « Dupliquer » et achat en quantité : utilisés hors /aventure. */
/* Sélecteur d'émojis du serveur (voir web/public/emoji-picker.js) : bouton sous le champ emoji
   des formulaires admin + panneau flottant avec recherche et grille. Ajouté seulement si le script trouve
   des émojis à proposer (amélioration progressive). */
.emoji-picker { position: relative; margin-top: 0.35rem; }
.emoji-picker__toggle { font-size: 0.85em; padding: 0.3rem 0.7rem; }
.emoji-picker__panel {
  position: absolute; z-index: 30; left: 0; top: calc(100% + 0.3rem);
  width: min(360px, 90vw); max-height: 300px; overflow-y: auto;
  background: var(--surface, #1e1e2e); border: 1px solid var(--accent, #7c3aed); border-radius: var(--radius-md);
  padding: 0.6rem; box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.emoji-picker__panel[hidden] { display: none; }
.emoji-picker__search { width: 100%; margin-bottom: 0.5rem; }
.emoji-picker__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 0.25rem; }
.emoji-picker__item {
  background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm); padding: 0.2rem; cursor: pointer; line-height: 0;
}
.emoji-picker__item[hidden] { display: none; }
.emoji-picker__item:hover, .emoji-picker__item:focus-visible { border-color: var(--accent-bright, #a78bfa); background: rgba(124, 58, 237, 0.2); outline: none; }
.emoji-picker__item img { width: 32px; height: 32px; object-fit: contain; }

/* Bouton « Dupliquer » et bandeau de pré-remplissage des formulaires admin (voir
   web/public/form-duplicate.js). */
.duplicate-button { margin-left: 0.5rem; }
.duplicate-note { margin: 0 0 1rem; }

/* Achat en quantité : champ « Quantité » et total sur les cartes d'articles stockables de /shop. */
.shop-buy-form { display: grid; gap: 0.35rem; }
.shop-quantity { display: flex; align-items: center; gap: 0.5rem; }
.shop-quantity input[type="number"] { width: 5rem; }
.shop-quantity-total { margin: 0; }


/* ===== Thème clair : surfaces et couleurs « de statut » (voir le bloc de variables en haut du fichier et theme.js) =====
   Les teintes pastel pensées pour un fond sombre (vert #7EE2A8, rouge #F1948A, jaune…) sont remplacées par leur version foncée, du même ton,
   et les voiles blancs translucides (invisibles sur du clair) par un voile violet foncé. */
:root[data-theme="light"] .navbar { background: rgba(255, 255, 255, 0.88); }
:root[data-theme="light"] .page-aventure .tabs-nav { background: rgba(246, 244, 251, 0.96); }
:root[data-theme="light"] .page-aventure .bestiary-card__back { background: #EFE9FA; }
:root[data-theme="light"] .nav-dropdown { box-shadow: 0 12px 30px -8px rgba(40, 25, 70, 0.25); }
:root[data-theme="light"] .emoji-picker__panel { box-shadow: 0 8px 28px rgba(40, 25, 70, 0.25); }
:root[data-theme="light"] .banner-success { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .banner-error { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .banner-info { color: hsl(252, 95%, 28%); }
:root[data-theme="light"] .wishlist-star.active { color: hsl(45, 90%, 28%); }
:root[data-theme="light"] .quest-item.completed .quest-status { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .status-active { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .status-considering { color: hsl(47, 85%, 28%); }
:root[data-theme="light"] .status-accepted { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .status-rejected { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .btn-mod-danger { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .deal-badge { color: hsl(44, 90%, 28%); }
:root[data-theme="light"] .statement-gain { color: hsl(145, 61%, 28%); }
:root[data-theme="light"] .statement-loss { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .dmg-float--enemy { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .dmg-float--player { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .dmg-float--heal { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .dmg-float--crit { color: hsl(44, 92%, 28%); }
:root[data-theme="light"] .dmg-float--ultimate { color: hsl(268, 85%, 28%); }
:root[data-theme="light"] .dmg-float--dodge { color: hsl(204, 71%, 28%); }
:root[data-theme="light"] .grinder-warning { color: hsl(44, 92%, 28%); }
:root[data-theme="light"] .combat-poison { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .combat-burn { color: hsl(25, 100%, 28%); }
:root[data-theme="light"] .combat-fire-resist { color: hsl(204, 100%, 28%); }
:root[data-theme="light"] .combat-difficulty-badge--tres_faible { color: hsl(168, 55%, 26%); }
:root[data-theme="light"] .combat-difficulty-badge--faible { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .combat-difficulty-badge--moyen { color: hsl(204, 71%, 28%); }
:root[data-theme="light"] .combat-difficulty-badge--fort { color: hsl(6, 79%, 28%); }
:root[data-theme="light"] .combat-difficulty-badge--tres_fort { color: hsl(280, 55%, 32%); }
:root[data-theme="light"] .combat-status-tag--ice { color: hsl(204, 71%, 28%); }
:root[data-theme="light"] .combat-ice { color: hsl(204, 71%, 28%); }
:root[data-theme="light"] .combat-status-tag--poison { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .combat-status-tag--burn { color: hsl(37, 91%, 28%); }
:root[data-theme="light"] .combat-status-tag--stun { color: hsl(282, 55%, 28%); }
:root[data-theme="light"] .evolve-overlay__title { color: hsl(44, 92%, 28%); }
:root[data-theme="light"] .evolve-overlay__sub { color: hsl(282, 55%, 28%); }
:root[data-theme="light"] .skill-meter { background: var(--line, rgba(40, 25, 70, 0.12)); }
:root[data-theme="light"] .hero-avatar--none { background: var(--surface-2, rgba(40, 25, 70, 0.06)); }
:root[data-theme="light"] .hero-stat { background: var(--surface-2, rgba(40, 25, 70, 0.05)); }
:root[data-theme="light"] .equip-diff--up { color: hsl(145, 61%, 28%); }
:root[data-theme="light"] .balance-box { background: var(--surface-2, rgba(40, 25, 70, 0.05)); border: 1px solid var(--line, rgba(40, 25, 70, 0.12)); }
:root[data-theme="light"] .achievement-meter { background: var(--line, rgba(40, 25, 70, 0.12)); }
:root[data-theme="light"] .combat-dungeon-badge { color: hsl(282, 55%, 28%); }
:root[data-theme="light"] .dungeon-panel { border: 1px solid var(--line, rgba(40, 25, 70, 0.14)); background: var(--surface-2, rgba(40, 25, 70, 0.04)); }
:root[data-theme="light"] .dungeon-floor { background: var(--surface, rgba(40, 25, 70, 0.06)); border: 1px solid var(--line, rgba(40, 25, 70, 0.14)); }
:root[data-theme="light"] .dungeon-floor--done { color: hsl(145, 63%, 28%); }
:root[data-theme="light"] .boss-row { background: var(--surface-2, rgba(40, 25, 70, 0.05)); }
:root[data-theme="light"] .boss-reward { color: hsl(48, 89%, 28%); }
:root[data-theme="light"] .page-aventure .duel-fighter { background: rgba(40, 25, 70, 0.04); border: 1px solid rgba(40, 25, 70, 0.08); }
:root[data-theme="light"] .page-aventure .duel-log li { background: rgba(40, 25, 70, 0.03); }
:root[data-theme="light"] .hero-bar { background: var(--surface, rgba(40, 25, 70, 0.03)); }
:root[data-theme="light"] .progress-bar { background: var(--surface-2, rgba(40, 25, 70, 0.08)); }
:root[data-theme="light"] .page-aventure .reward-card { background: var(--surface-2, rgba(40, 25, 70, 0.05)); }
:root[data-theme="light"] .page-aventure .reward-card--rare .reward-card__label { color: hsl(48, 89%, 28%); }
:root[data-theme="light"] .page-aventure .equip-cmp__track { background: rgba(40, 25, 70, 0.07); }
:root[data-theme="light"] .page-aventure .bestiary-stats__resist--immune strong { color: hsl(130, 65%, 28%); }
:root[data-theme="light"] .page-aventure .combat-actions-row .btn-mod { background: rgba(40, 25, 70, 0.05); }
:root[data-theme="light"] .page-aventure .combat-actions-row .btn-mod-danger { background: rgba(40, 25, 70, 0.05); }
:root[data-theme="light"] .page-aventure .combat-actions-row .btn-primary { background: rgba(40, 25, 70, 0.05); }

/* ───────────── Largeur et échelle FLUIDES selon l'écran ─────────────
   Avant : un conteneur figé à 900 px (1 080 px en administration, 1 180 px sur l'accueil), donc de grands vides de chaque côté sur un grand écran. Maintenant la largeur suit l'écran
   (--page-max : de 900 px jusqu'à 80 % de la fenêtre, plafonnée à 2 200 px) et, sur les très grands écrans, tout grossit un peu (la taille de base de la page passe de 16 à 20 px)
   pour que les modules occupent la place ; à l'inverse, sur un petit téléphone, la taille de base descend et les marges se resserrent pour laisser de la place au contenu.
   Les grilles (cartes, succès, vignettes…) se remplissent d'elles-mêmes : plus de colonnes quand il y a de la place. */
:root {
  --page-max: clamp(900px, 80vw, 2200px);
  --wide-max: clamp(1180px, 84vw, 2300px);
  --admin-max: clamp(1080px, 82vw, 2200px);
}
@media (min-width: 1600px) { html { font-size: 106.25%; } }
@media (min-width: 1920px) { html { font-size: 112.5%; } }
@media (min-width: 2560px) { html { font-size: 125%; } }
@media (min-width: 3200px) { html { font-size: 150%; } }
@media (max-width: 380px) {
  html { font-size: 93.75%; }
  .page-content { padding-left: 1rem; padding-right: 1rem; }
  .dashboard { padding-left: 1rem; padding-right: 1rem; }
}

/* Téléphone : champs, listes et sélecteur visuel à 44 px de haut (zone de toucher recommandée) ; 16 px de police évitent aussi le zoom automatique de Safari au focus. */
@media (max-width: 640px) {
  main input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
  main select,
  .visual-select__toggle { min-height: 44px; font-size: 1rem; }
}

/* Suggestions de la recherche de la barre de navigation (web/public/nav-search.js). */
.nav-search { position: relative; }
.nav-search__list {
  position: absolute; z-index: 40; top: calc(100% + 0.35rem); left: 0; min-width: 18rem; max-width: min(24rem, 92vw);
  margin: 0; padding: 0.3rem; list-style: none; text-align: left;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md); box-shadow: 0 12px 30px -8px rgba(0, 0, 0, 0.5);
}
.nav-search__list[hidden] { display: none; }
.nav-search__list a { display: flex; justify-content: space-between; gap: 1rem; align-items: center; padding: 0.5rem 0.65rem; border-radius: var(--radius-sm); color: var(--text); text-decoration: none; }
.nav-search__list [aria-selected="true"] a, .nav-search__list a:hover { background: var(--surface-2); }
.nav-search__hint { color: var(--text-dim); font-size: 0.8rem; white-space: nowrap; }
.search-result { display: flex; justify-content: space-between; padding: 0.5rem 0; }
@media (max-width: 640px) { .nav-search__list { position: static; max-width: none; margin-top: 0.4rem; } }

/* Un bouton `hidden` le reste malgré les règles « display » des classes de boutons. */
button[hidden] { display: none; }

/* Accueil : « En ce moment sur le serveur » (événement, podium, jeux en cours). */
.live-strip { max-width: 60rem; margin: 0 auto 2rem; padding: 0 1rem; }
.live-strip__title { font-size: 1.15rem; margin: 0 0 0.75rem; text-align: center; }
.live-strip__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.9rem; }
.live-card {
  display: flex; flex-direction: column; gap: 0.35rem; padding: 1rem 1.1rem; text-decoration: none; color: var(--text);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md);
  transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
a.live-card:hover, .live-card:focus-within { transform: translateY(-2px); border-color: var(--accent-soft); box-shadow: 0 10px 24px -14px rgba(124, 58, 237, 0.6); }
.live-card__label { font-size: 0.8rem; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase; color: var(--text-dim); }
.live-card__main { font-size: 1.1rem; }
.live-card__sub { color: var(--text-dim); font-size: 0.9rem; }
.live-card__more { margin-top: auto; padding-top: 0.4rem; font-size: 0.88rem; color: var(--accent-soft); text-decoration: none; }
.live-podium, .live-games { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.live-podium .user-cell { display: flex; align-items: center; gap: 0.5rem; }
.live-podium img { border-radius: 50%; }
.live-podium__name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-games a { color: var(--text); font-weight: 600; text-decoration: none; }
@media (prefers-reduced-motion: reduce) { .live-card { transition: none; } }

/* Une carte seule sur la dernière ligne (5e, 9e… de 4 colonnes) est centrée au lieu de rester collée à gauche. Bureau seulement : en 2 ou 1 colonne, la grille se remplit normalement. */
@media (min-width: 701px) {
  .profile-stats:not(.profile-stats-3) > .stat-block:last-child:nth-child(4n + 1) { grid-column: 2 / span 2; justify-self: center; width: calc(50% - 0.625rem); }
}

/* Barre de navigation basse (téléphone, membres connectés) : voir views/partials/bottom-nav.ejs. */
.bottom-nav { display: none; }
@media (max-width: 780px) {
  .bottom-nav {
    display: grid; grid-template-columns: repeat(5, 1fr); position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
    padding: 0.25rem 0.25rem calc(0.25rem + env(safe-area-inset-bottom));
    background: var(--surface); border-top: 1px solid var(--line); box-shadow: 0 -8px 24px -16px rgba(0, 0, 0, 0.6);
  }
  .bottom-nav__item {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.1rem; min-height: 48px; padding: 0.25rem 0.1rem;
    color: var(--text-dim); text-decoration: none; font-size: 0.7rem; font-weight: 600; border-radius: var(--radius-sm);
  }
  .bottom-nav__item span:first-child { font-size: 1.25rem; line-height: 1; }
  .bottom-nav__item[aria-current="page"] { color: var(--text); background: color-mix(in srgb, var(--accent) 18%, transparent); }
  .bottom-nav__item:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: -2px; }
  /* Le contenu ne doit jamais finir sous la barre. */
  body:has(.bottom-nav) { padding-bottom: calc(60px + env(safe-area-inset-bottom)); }
  /* Pendant un combat, la rangée d'actions est déjà collée en bas : la barre s'efface. */
  body:has(.combat-actions-row) .bottom-nav { display: none; }
  body:has(.combat-actions-row):has(.bottom-nav) { padding-bottom: 0; }
}

/* Transitions entre pages (navigateurs qui gèrent les View Transitions entre documents) : fondu court ; la barre de navigation reste en place au lieu de clignoter. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.16s; animation-timing-function: var(--ease); }
.navbar { view-transition-name: site-navbar; }
.bottom-nav { view-transition-name: site-bottom-nav; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

/* Retour tactile : les boutons s'enfoncent légèrement au clic. */
.btn-primary, .btn-secondary, .btn-mod, button.nav-logout-button { transition: transform 0.12s var(--ease), box-shadow 0.18s var(--ease), background 0.18s var(--ease), border-color 0.18s var(--ease); }
.btn-primary:active, .btn-secondary:active, .btn-mod:active { transform: scale(0.97); }

/* Squelette de chargement : zone grise qui « respire » pendant qu'un contenu arrive (suggestions de recherche, etc.). */
.skeleton, .nav-search input.is-loading {
  background-image: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--accent) 22%, transparent) 50%, transparent 100%);
  background-size: 200% 100%; animation: skeleton-shimmer 1.1s linear infinite;
}
.skeleton { border-radius: var(--radius-sm); min-height: 1em; background-color: var(--surface-2); }
@keyframes skeleton-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .skeleton, .nav-search input.is-loading { animation: none; } }

/* Gain de pièces / d'XP : le nombre « rebondit » quand une valeur vient d'augmenter (classe posée par site-ui.js). */
.value-bump { animation: value-bump 0.5s var(--ease); }
@keyframes value-bump { 0% { transform: scale(1); } 35% { transform: scale(1.25); color: var(--accent-bright); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .value-bump { animation: none; } }

/* Le bouton flottant ⚙️ de l'Aventure se pose AU-DESSUS de la barre de navigation basse au lieu de la recouvrir. */
@media (max-width: 780px) {
  body:has(.bottom-nav) .aventure-settings { bottom: calc(64px + env(safe-area-inset-bottom, 0px)); }
}
