/* =========================================================
   VoxelSprites - Community gallery
   Reuses tokens from app.css; namespaced .vs-c-*
   ========================================================= */

body.vs-community {
  background: var(--vs-bg);
  color: var(--vs-text);
  min-height: 100vh;
}

/* ---------- Top nav ----------
   Cleane, mono, full-bleed border-bottom. Pas de blur, pas de
   pill colorée sur active : on signale l'état actif via un
   simple souligné fin à l'accent, comme un onglet imprimé. */
.vs-c-nav {
  display: flex;
  align-items: center;
  gap: 28px;
  padding: 14px 28px;
  border-bottom: 1px solid var(--vs-border);
  background: var(--vs-surface-1);
  position: sticky;
  top: 0;
  z-index: 30;
  width: 100%;
  box-sizing: border-box;
}
.vs-c-nav__brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--vs-text);
  font-weight: 700;
  letter-spacing: -0.015em;
  font-size: 15px;
}
/* Brand mark - petit cube voxel pixelisé multicolore, remplace
   le losange gradient purple/cyan. */
.vs-c-nav__cube {
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  width: 18px;
  height: 18px;
  gap: 1px;
  flex: 0 0 auto;
  background: transparent;
  transform: none;
  border-radius: 0;
}
.vs-c-nav__cube > i { display: block; }
.vs-c-nav__cube > i:nth-child(1) { background: var(--vs-text); }
.vs-c-nav__cube > i:nth-child(2) { background: var(--vs-red); }
.vs-c-nav__cube > i:nth-child(3) { background: var(--vs-blue); }
.vs-c-nav__cube > i:nth-child(4) { background: var(--vs-yellow); }
.vs-c-nav__title { font-size: 15px; white-space: nowrap; }
.vs-c-nav__links {
  display: flex;
  gap: 4px;
  flex: 1;
  justify-content: flex-start;
  margin-left: 4px;
}
.vs-c-nav__link {
  position: relative;
  color: var(--vs-text-muted);
  text-decoration: none;
  font-size: var(--vs-fs-lg);
  font-weight: 500;
  padding: 8px 12px;
  border-radius: 0;
  white-space: nowrap;
  transition: color 120ms var(--vs-ease);
}
.vs-c-nav__link:hover { color: var(--vs-text); background: transparent; }
.vs-c-nav__link.is-active { color: var(--vs-text); background: transparent; }
.vs-c-nav__link.is-active::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 2px;
  height: 2px;
  background: var(--vs-accent);
}
.vs-c-nav__account { display: flex; gap: 8px; align-items: center; margin-left: auto; }
/* Account button = noir plein (primary) - un seul niveau de
   contraste, pas de couleur d'accent en aplat. */
.vs-c-nav__btn {
  padding: 8px 14px;
  border-radius: var(--vs-r-md);
  font-size: var(--vs-fs-md);
  font-weight: 600;
  background: var(--vs-text);
  color: #fff;
  text-decoration: none;
  border: 1px solid var(--vs-text);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
  white-space: nowrap;
}
.vs-c-nav__btn:hover { background: #1f1f1f; }
.vs-c-nav__btn--ghost {
  background: transparent;
  color: var(--vs-text);
  border: 1px solid var(--vs-border-strong);
}
.vs-c-nav__btn--ghost:hover { background: var(--vs-surface-3); border-color: var(--vs-text); }

/* ---------- Pill crédits dans la nav globale (toutes les pages) ----------
 * Rendue par _main_nav.php quand l'user est connecté, hydrée en live par
 * VoxelEditorUI._syncNavCredits() après chaque génération. Variantes par
 * niveau : is-ok / is-warn (<20) / is-danger (<5) / is-anon (anonyme avec
 * quota). Sur mobile, suit le drawer empilé.
 * --------------------------------------------------------- */
.vs-c-nav__credits {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: var(--vs-r-md);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  color: var(--vs-text);
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease), color 120ms var(--vs-ease);
  white-space: nowrap;
}
.vs-c-nav__credits:hover {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-strong);
}
.vs-c-nav__credits svg { width: 12px; height: 12px; }
.vs-c-nav__credits-suffix {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-dim);
}
.vs-c-nav__credits.is-ok     { color: var(--vs-accent-hi, #6244ff); border-color: rgba(98,68,255,0.30); background: rgba(98,68,255,0.08); }
.vs-c-nav__credits.is-ok .vs-c-nav__credits-suffix { color: rgba(98,68,255,0.75); }
.vs-c-nav__credits.is-warn   { color: var(--vs-warn,   #d97706); border-color: rgba(217,119,6,0.30); background: rgba(217,119,6,0.10); }
.vs-c-nav__credits.is-warn .vs-c-nav__credits-suffix { color: rgba(217,119,6,0.85); }
.vs-c-nav__credits.is-danger { color: var(--vs-danger, #dc2626); border-color: rgba(220,38,38,0.30); background: rgba(220,38,38,0.10); }
.vs-c-nav__credits.is-danger .vs-c-nav__credits-suffix { color: rgba(220,38,38,0.85); }
/* Anon : un poil moins coloré (l'user n'a pas de compte) - on incite plutôt
   à signup via les boutons à côté que via la pill. */
.vs-c-nav__credits.is-anon {
  color: var(--vs-text-muted);
  border-color: var(--vs-border);
  background: var(--vs-surface-1);
}

/* ---------- Mobile burger (≤720px) ----------
   Sur desktop, .vs-c-nav__drawer { display: contents } fait disparaître
   son box pour que ses enfants (.vs-c-nav__links + .vs-c-nav__account)
   se comportent comme s'ils étaient directement dans le flex parent.
   Sur mobile, le toggle apparaît, le drawer devient un dropdown absolu
   sous le header. */
.vs-c-nav__drawer { display: contents; }
.vs-c-nav__toggle {
  display: none;  /* visible seulement sur mobile */
  position: relative;
  width: 38px;
  height: 38px;
  margin-left: auto;
  background: transparent;
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  cursor: pointer;
  padding: 0;
  flex: 0 0 auto;
  transition: border-color 120ms var(--vs-ease), background 120ms var(--vs-ease);
}
.vs-c-nav__toggle:hover { border-color: var(--vs-text); background: var(--vs-surface-2); }
.vs-c-nav__toggle:focus-visible { outline: 2px solid var(--vs-accent); outline-offset: 2px; }
/* Burger pure-CSS : 3 traits via box + ::before + ::after.
   Position absolute pour pouvoir transformer les 2 outer en croix
   quand aria-expanded="true". */
.vs-c-nav__burger,
.vs-c-nav__burger::before,
.vs-c-nav__burger::after {
  display: block;
  position: absolute;
  width: 18px;
  height: 2px;
  background: var(--vs-text);
  border-radius: 1px;
  transition: transform 200ms var(--vs-ease), top 200ms var(--vs-ease), background 200ms var(--vs-ease);
}
.vs-c-nav__burger { top: 50%; left: 50%; transform: translate(-50%, -50%); }
.vs-c-nav__burger::before,
.vs-c-nav__burger::after { content: ''; left: 0; }
.vs-c-nav__burger::before { top: -6px; }
.vs-c-nav__burger::after  { top:  6px; }
/* État ouvert : trait du milieu disparaît, les 2 autres pivotent en X */
.vs-c-nav__toggle[aria-expanded="true"] .vs-c-nav__burger { background: transparent; }
.vs-c-nav__toggle[aria-expanded="true"] .vs-c-nav__burger::before { top: 0; transform: rotate(45deg); }
.vs-c-nav__toggle[aria-expanded="true"] .vs-c-nav__burger::after  { top: 0; transform: rotate(-45deg); }

/* ---------- Tablette compacte (881-1100px) ----------
   Avant de basculer en burger, on densifie : police, paddings et gaps
   réduits, on cache les éléments décoratifs (suffixe "cr", code lang)
   pour libérer de la place. Tout reste inline. */
@media (max-width: 1100px) and (min-width: 881px) {
  .vs-c-nav { gap: 16px; padding: 12px 20px; }
  .vs-c-nav__brand { font-size: 14px; }
  .vs-c-nav__title { font-size: 14px; }
  .vs-c-nav__links { gap: 0; margin-left: 0; }
  .vs-c-nav__link { padding: 6px 8px; font-size: var(--vs-fs-md); }
  .vs-c-nav__link.is-active::after { left: 8px; right: 8px; }
  .vs-c-nav__account { gap: 6px; }
  .vs-c-nav__btn { padding: 6px 10px; font-size: var(--vs-fs-sm); }
  .vs-c-nav__credits { padding: 5px 8px; font-size: 12px; gap: 4px; }
  .vs-c-nav__credits-suffix { display: none; }
  .vs-c-nav__lang { padding: 6px 8px; gap: 6px; }
  .vs-c-nav__lang-code { display: none; }
}

/* ---------- Tablette étroite (721-880px) ----------
   Dernier palier avant le burger : on continue de réduire (font-size,
   paddings, gaps). Le titre du brand est masqué pour libérer la place
   nécessaire ; il réapparaît sous 720px (à côté du burger). */
@media (max-width: 880px) and (min-width: 721px) {
  .vs-c-nav { gap: 10px; padding: 10px 14px; }
  .vs-c-nav__brand { font-size: 13px; gap: 8px; }
  .vs-c-nav__title { display: none; }
  .vs-c-nav__links { gap: 0; margin-left: 0; }
  .vs-c-nav__link { padding: 5px 6px; font-size: var(--vs-fs-sm); }
  .vs-c-nav__link.is-active::after { left: 6px; right: 6px; }
  .vs-c-nav__account { gap: 6px; }
  .vs-c-nav__btn { padding: 5px 8px; font-size: var(--vs-fs-sm); }
  .vs-c-nav__credits { padding: 4px 7px; font-size: 11px; gap: 4px; }
  .vs-c-nav__credits-suffix { display: none; }
  .vs-c-nav__lang { padding: 5px 7px; gap: 4px; }
  .vs-c-nav__lang-code { display: none; }
}

@media (max-width: 720px) {
  .vs-c-nav { gap: 12px; padding: 12px 16px; }
  .vs-c-nav__toggle { display: inline-grid; place-items: center; }
  /* Drawer = panneau flottant sous le header. position absolute pour
     ne pas pousser le contenu (compatible avec /edit fullscreen). */
  .vs-c-nav__drawer {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    background: var(--vs-surface-1);
    border-bottom: 1px solid var(--vs-border);
    padding: 12px 16px 16px;
    gap: 6px;
    box-shadow: 0 12px 32px -16px rgba(10,10,10,0.18);
  }
  .vs-c-nav__drawer.is-open { display: flex; }
  .vs-c-nav__links {
    flex-direction: column;
    flex: 0 0 auto;
    gap: 0;
    margin: 0;
    width: 100%;
  }
  .vs-c-nav__link {
    padding: 10px 8px;
    font-size: var(--vs-fs-xl);
    border-bottom: 1px solid var(--vs-border);
  }
  .vs-c-nav__link:last-child { border-bottom: 0; }
  /* L'underline `::after` sur is-active devient un bord gauche accent -
     plus lisible dans une liste verticale. */
  .vs-c-nav__link.is-active::after {
    left: 0;
    right: auto;
    top: 8px;
    bottom: 8px;
    width: 2px;
    height: auto;
  }
  .vs-c-nav__link.is-active { padding-left: 12px; }
  .vs-c-nav__account {
    flex-direction: column;
    align-items: stretch;
    margin-left: 0;
    width: 100%;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid var(--vs-border);
  }
  .vs-c-nav__btn { text-align: center; }
}

/* ---------- Main shell ---------- */
.vs-c-main {
  max-width: 1280px;
  margin: 0 auto;
  padding: 36px 28px 80px;
}
/* `.vs-c-loading` est défini globalement dans app.css avec ses 5 variantes
   (--cubes / --dots / --bar / --pulse / --shimmer) - voir la section
   "Loaders inline" en bas d'app.css. */
.vs-c-empty {
  padding: 60px 20px;
  text-align: center;
  color: var(--vs-text-muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.vs-c-empty h2 { color: var(--vs-text); margin: 0 0 4px; }
.vs-c-empty p  { margin: 0; }
.vs-c-empty .vs-c-btn { margin-top: 12px; }

/* ---------- État d'erreur riche (404 sprite sheet, échec chargement)
 * Calqué sur le pattern /404.php (vs-err) mais autonome - community.css
 * est chargé sur /sprites/<slug> sans landing.css. Trois éléments :
 * un gros chiffre fade en background, un eyebrow + titre + lead + CTAs,
 * et un <details> optionnel pour le message technique.
 * --------------------------------------------------------- */
.vs-c-error-state {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 64px 24px 80px;
  max-width: 640px;
  margin: 0 auto;
  min-height: calc(100vh - 220px);
}
.vs-c-error-state__visual {
  font-family: 'JetBrains Mono', ui-monospace, 'Menlo', monospace;
  font-size: clamp(96px, 20vw, 200px);
  font-weight: 700;
  line-height: 0.85;
  letter-spacing: -0.05em;
  color: var(--vs-text, #0a0a0c);
  opacity: 0.08;
  margin: 0 0 8px;
  user-select: none;
  pointer-events: none;
}
.vs-c-error-state__eyebrow {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--vs-accent-hi, #6244ff);
  margin: 0 0 14px;
}
.vs-c-error-state__title {
  font-size: clamp(28px, 4vw, 40px);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--vs-text);
  margin: 0 0 16px;
  line-height: 1.1;
}
.vs-c-error-state__lead {
  font-size: 15px;
  color: var(--vs-text-muted);
  max-width: 520px;
  margin: 0 0 28px;
  line-height: 1.55;
}
.vs-c-error-state__lead code {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 13px;
  background: var(--vs-surface-2);
  padding: 2px 7px;
  border-radius: 4px;
  color: var(--vs-text);
  border: 1px solid var(--vs-border);
  word-break: break-all;
}
.vs-c-error-state__ctas {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
  margin-bottom: 28px;
}
.vs-c-error-state__detail {
  font-size: 12px;
  color: var(--vs-text-dim);
  max-width: 480px;
  width: 100%;
  text-align: left;
  margin-top: 4px;
}
.vs-c-error-state__detail summary {
  cursor: pointer;
  user-select: none;
  text-align: center;
  color: var(--vs-text-muted);
  margin-bottom: 8px;
}
.vs-c-error-state__detail summary:hover { color: var(--vs-text); }
.vs-c-error-state__detail pre {
  display: block;
  background: var(--vs-surface-2);
  padding: 10px 12px;
  border-radius: 6px;
  border: 1px solid var(--vs-border);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  margin: 0;
  word-break: break-word;
  white-space: pre-wrap;
  color: var(--vs-text-muted);
}

/* ---------- Hero ---------- */
.vs-c-hero {
  text-align: center;
  margin-bottom: 36px;
  padding: 36px 16px 28px;
}
.vs-c-hero__title {
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 8px;
  background: linear-gradient(120deg, var(--vs-text) 30%, var(--vs-accent-hi));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.vs-c-hero__sub {
  color: var(--vs-text-muted);
  font-size: var(--vs-fs-lg);
  max-width: 560px;
  margin: 0 auto;
}

/* ---------- Filters ---------- */
.vs-c-filters {
  margin-bottom: 28px;
}
.vs-c-filters__row {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.vs-c-search {
  flex: 1;
  min-width: 220px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 10px 14px;
  color: var(--vs-text);
  font-size: var(--vs-fs-md);
  outline: none;
  transition: border-color 120ms var(--vs-ease), box-shadow 120ms var(--vs-ease);
}
.vs-c-search:focus { border-color: var(--vs-border-focus); box-shadow: var(--vs-glow); }
.vs-c-sort { display: flex; gap: 4px; background: var(--vs-surface-2); border-radius: var(--vs-r-md); padding: 4px; }
.vs-c-sort__btn {
  padding: 6px 14px;
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  color: var(--vs-text-muted);
  border-radius: 6px;
  transition: all 120ms var(--vs-ease);
}
.vs-c-sort__btn:hover { color: var(--vs-text); }
.vs-c-sort__btn.is-active { background: var(--vs-surface-3); color: var(--vs-text); }
.vs-c-perpage {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--vs-surface-2);
  border-radius: var(--vs-r-md);
  padding: 0 6px 0 12px;
  cursor: pointer;
}
.vs-c-perpage__label {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  font-weight: 500;
  white-space: nowrap;
}
.vs-c-perpage__select {
  background: transparent;
  border: 0;
  font: inherit;
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  color: var(--vs-text);
  cursor: pointer;
  padding: 8px 4px;
  outline: none;
  appearance: auto;
}
.vs-c-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.vs-c-tag {
  padding: 5px 12px;
  font-size: var(--vs-fs-sm);
  background: var(--vs-surface-2);
  color: var(--vs-text-muted);
  border-radius: var(--vs-r-pill);
  border: 1px solid var(--vs-border);
  transition: all 120ms var(--vs-ease);
  cursor: pointer;
  text-decoration: none;
}
.vs-c-tag:hover { background: var(--vs-surface-3); color: var(--vs-text); }
.vs-c-tag.is-active { background: var(--vs-accent); color: white; border-color: transparent; }
.vs-c-tag.is-link { display: inline-block; }

/* ---------- Grid ---------- */
.vs-c-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(244px, 1fr));
  gap: 18px;
}
/* État vide injecté dans le grid (cf. SpriteGallery.renderGrid quand
   items.length === 0). Sans `grid-column: 1 / -1`, le bloc tombe dans la
   1re colonne (largeur ~244-360px) et son `margin: 0 auto` interne ne peut
   plus centrer. On le force à occuper toute la largeur de la grille pour
   que le centrage horizontal du contenu marche. */
.vs-c-grid > .vs-c-error-state {
  grid-column: 1 / -1;
}
.vs-c-card {
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  display: flex;
  flex-direction: column;
  transition: border-color 160ms var(--vs-ease);
}
.vs-c-card:hover {
  border-color: var(--vs-accent);
}
/* Cards rendues cliquables côté JS (MyModels - public seulement). Le grid
   public CommunityGallery utilise un <a class="vs-c-card"> donc cursor
   pointer hérité du UA, mais ici la card est un <article> avec role="link".
   On donne un signal visuel cohérent avec le hover existant. */
.vs-c-card.is-clickable {
  cursor: pointer;
}
.vs-c-card.is-clickable:focus-visible {
  outline: 2px solid var(--vs-accent);
  outline-offset: 2px;
  border-color: var(--vs-accent);
}
.vs-c-card.is-clickable:hover .vs-c-card__title {
  color: var(--vs-accent-hi, var(--vs-accent));
}
.vs-c-card__thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--vs-surface-2);
}
.vs-c-card__thumb.is-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--vs-text-dim);
  font-size: 48px;
  background: linear-gradient(135deg, var(--vs-surface-2), var(--vs-surface-1));
}
.vs-c-card__body { padding: 14px; flex: 1; display: flex; flex-direction: column; gap: 8px; }
.vs-c-card__title { font-size: var(--vs-fs-lg); font-weight: 600; margin: 0; line-height: 1.3; }
.vs-c-card__desc { color: var(--vs-text-muted); font-size: var(--vs-fs-sm); margin: 0; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.vs-c-card__meta { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }
.vs-c-card__author { font-weight: 500; color: var(--vs-text-muted); }
.vs-c-card__author.is-official {
  background: linear-gradient(120deg, var(--vs-accent), var(--vs-accent-hi));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 700;
}
.vs-c-card__stat { color: var(--vs-text-dim); }
.vs-c-card__anims { display: flex; flex-wrap: wrap; gap: 4px; margin-top: auto; padding-top: 4px; }
.vs-c-card__anim {
  font-size: var(--vs-fs-xs);
  padding: 2px 9px;
  background: var(--vs-surface-2);
  color: var(--vs-text);
  border-radius: var(--vs-r-pill);
  border: 1px solid var(--vs-border);
  font-weight: 500;
  letter-spacing: 0.01em;
  transition: background 160ms var(--vs-ease), border-color 160ms var(--vs-ease);
}
.vs-c-card:hover .vs-c-card__anim {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-strong);
}
/* Mesh badge - variante "statique" (texte plus marqué, fond accent low + dot
   accent) pour bien le distinguer des anims listées juste à côté. */
.vs-c-card__anim--mesh {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--vs-accent-low);
  color: var(--vs-accent-hi);
  border-color: var(--vs-accent-line);
  font-weight: 600;
}
.vs-c-card__anim--mesh::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--vs-accent);
  flex: 0 0 auto;
}
.vs-c-card:hover .vs-c-card__anim--mesh {
  background: var(--vs-accent-low);
  border-color: var(--vs-accent);
}
/* Cluster spécifique aux cards admin de modération : on aligne les badges
   sous les boutons d'action, en bas de la colonne actions. */
.vs-admin-mod__badges {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed var(--vs-border);
  justify-content: flex-end;
}

/* ---------- Pagination ----------
   Layout 3 blocs : info (range + per-page) | nav (chevrons + numéros + ellipsis)
   | goto (input "Aller à"). En desktop tout sur une ligne ; en mobile (cf.
   media query plus bas) on wrap proprement.

   2 hosts maintenant (--top et --bottom). Visibilité contrôlée par le bp 1024px :
     - >1024px : top caché, bottom = chevrons classiques (UX desktop d'origine)
     - ≤1024px : top = chevrons "au cas où" (jump à la page X), bottom = sentinel
       lazy + état "loading"/"tout chargé" rendu par _paintLazyFooter
   Cf. CommunityGallery.js / SpriteGallery.js. */
.vs-c-pagination {
  display: flex;
  gap: 18px;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 32px;
  color: var(--vs-text-muted);
}
.vs-c-pagination--top {
  display: none;
  margin-top: 0;
  margin-bottom: 18px;
}
.vs-c-pagination--top:empty { display: none; }
@media (max-width: 1024px) {
  .vs-c-pagination--top { display: flex; }
  /* Le bottom devient zone "lazy footer" : padding réduit pour ne pas
     créer un grand vide quand vide ou en cours de loading. */
  .vs-c-pagination--bottom { margin-top: 18px; min-height: 24px; }
  .vs-c-pagination--bottom:empty { margin-top: 0; min-height: 0; }
}

/* Sentinel = cible IntersectionObserver pour déclencher le lazy-load.
   Pas visible (pas de bg, pas de border), mais doit avoir une hauteur > 0
   sinon IntersectionObserver ne fire jamais sur certains navigateurs.
   1px suffit ; le rootMargin de 600px (cf. JS) le pré-déclenche bien
   avant qu'il ne soit réellement à l'écran. Inactif sur desktop via JS
   (le _setupSentinel check `this._isLazy()` à chaque tick). */
.vs-c-sentinel {
  display: block;
  width: 100%;
  height: 1px;
  pointer-events: none;
  /* Marge visuelle nulle - le sentinel vit collé entre la grille et la
     pagination bottom. */
  margin: 0;
}

/* --- Bloc info : range + per-page selector --- */
.vs-c-paginate__info {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: var(--vs-fs-sm);
}
.vs-c-paginate__range {
  color: var(--vs-text-dim);
  white-space: nowrap;
}
.vs-c-paginate__range strong { color: var(--vs-text); font-weight: 600; }
.vs-c-paginate__perpage {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--vs-text-dim);
  white-space: nowrap;
}
.vs-c-paginate__perpage-sel {
  padding: 4px 8px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm, 6px);
  color: var(--vs-text);
  font-size: var(--vs-fs-sm);
  font-family: inherit;
  cursor: pointer;
}
.vs-c-paginate__perpage-sel:hover { background: var(--vs-surface-3); }
.vs-c-paginate__perpage-sel:focus-visible {
  outline: 2px solid var(--vs-primary, #2a8c4a);
  outline-offset: 1px;
}

/* --- Bloc nav : chevrons + numéros + ellipsis --- */
.vs-c-paginate__nav {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.vs-c-paginate__btn,
.vs-c-paginate__num {
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  color: var(--vs-text);
  font-size: var(--vs-fs-sm);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-c-paginate__btn:hover:not(:disabled),
.vs-c-paginate__num:hover:not(.is-active) {
  background: var(--vs-surface-3);
}
.vs-c-paginate__btn:disabled,
.vs-c-paginate__btn.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.vs-c-paginate__num.is-active {
  background: var(--vs-primary, #2a8c4a);
  border-color: var(--vs-primary, #2a8c4a);
  color: #fff;
  font-weight: 600;
  cursor: default;
}
.vs-c-paginate__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  color: var(--vs-text-dim);
  user-select: none;
}

/* --- Bloc goto : "Aller à __ / 42" --- */
.vs-c-paginate__goto {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-dim);
}
.vs-c-paginate__goto label { cursor: default; }
.vs-c-paginate__goto input {
  width: 64px;
  padding: 6px 8px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm, 6px);
  color: var(--vs-text);
  font-size: var(--vs-fs-sm);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  text-align: center;
  /* Cache les spinners natifs Chrome/FF - moches et inutiles ici, le min/max
     fait déjà le clamping et l'user peut taper un nombre directement. */
  -moz-appearance: textfield;
  appearance: textfield;
}
.vs-c-paginate__goto input::-webkit-outer-spin-button,
.vs-c-paginate__goto input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.vs-c-paginate__goto input:focus-visible {
  outline: 2px solid var(--vs-primary, #2a8c4a);
  outline-offset: 1px;
  border-color: var(--vs-primary, #2a8c4a);
}
.vs-c-paginate__goto-max { white-space: nowrap; }

/* Compat ancienne API : la classe `.vs-c-paginate__pos` n'existe plus dans
   le nouveau renderPagination, mais peut être encore utilisée ailleurs. */
.vs-c-paginate__pos   { font-size: var(--vs-fs-sm); }
.vs-c-paginate__total { font-size: var(--vs-fs-sm); color: var(--vs-text-dim); }

/* --- Search bar avec icône + bouton clear --- */
.vs-c-search-wrap {
  position: relative;
  flex: 1 1 280px;
  max-width: 480px;
  display: inline-flex;
  align-items: center;
}
.vs-c-search__icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--vs-text-dim);
  pointer-events: none;
  display: flex;
}
.vs-c-search--withicon { padding-left: 32px !important; padding-right: 32px !important; }
.vs-c-search__clear {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: var(--vs-surface-3);
  color: var(--vs-text);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.vs-c-search__clear:hover { background: var(--vs-border); }

/* --- Dropdown "Outils" pour grouper les actions batch --- */
.vs-c-tools { position: relative; display: inline-block; }
.vs-c-tools__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 280px;
  background: var(--vs-surface, #fff);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  padding: 6px;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.vs-c-tools__menu[hidden] { display: none; }
.vs-c-tools__item {
  background: transparent;
  border: 0;
  text-align: left;
  padding: 8px 10px;
  border-radius: var(--vs-r-sm, 6px);
  color: var(--vs-text);
  font-size: var(--vs-fs-sm);
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.vs-c-tools__item:hover { background: var(--vs-surface-2); }
.vs-c-tools__item:focus-visible {
  outline: 2px solid var(--vs-primary, #2a8c4a);
  outline-offset: -2px;
}
.vs-c-tools__sep {
  height: 1px;
  background: var(--vs-border);
  margin: 4px 0;
}

/* ---------- Detail page ---------- */
.vs-c-back {
  display: inline-block;
  color: var(--vs-text-muted);
  text-decoration: none;
  font-size: var(--vs-fs-sm);
  margin-bottom: 18px;
  transition: color 120ms var(--vs-ease);
}
.vs-c-back:hover { color: var(--vs-text); }
.vs-c-detail {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 32px;
}
@media (max-width: 880px) {
  .vs-c-detail { grid-template-columns: 1fr; }
}
.vs-c-detail__player {
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.vs-c-canvas {
  aspect-ratio: 1 / 1;
  background: var(--vs-surface-2);
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  position: relative;
}
.vs-c-canvas canvas { display: block; width: 100% !important; height: 100% !important; }
.vs-c-anim-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 12px 14px;
  background: var(--vs-surface-2);
  border-top: 1px solid var(--vs-border);
}
.vs-c-anim-pill {
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  padding: 5px 12px;
  background: var(--vs-surface-3);
  color: var(--vs-text-muted);
  border-radius: var(--vs-r-pill);
  border: 1px solid var(--vs-border);
  cursor: pointer;
  transition: background 160ms var(--vs-ease), color 160ms var(--vs-ease),
              border-color 160ms var(--vs-ease), box-shadow 160ms var(--vs-ease),
              transform 160ms var(--vs-ease);
}
.vs-c-anim-pill:hover {
  color: var(--vs-text);
  background: var(--vs-surface-hi);
  border-color: var(--vs-border-strong);
  transform: translateY(-1px);
}
.vs-c-anim-pill.is-active {
  background: var(--vs-accent);
  color: #fff;
  border-color: transparent;
  font-weight: 600;
  box-shadow: 0 2px 8px var(--vs-accent-line);
  transform: translateY(-1px);
}
/* Mesh : petit point accent devant le label, devient blanc quand actif. */
.vs-c-anim-pill[data-anim="__mesh__"] {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.vs-c-anim-pill[data-anim="__mesh__"]::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--vs-accent);
  flex: 0 0 auto;
  transition: background 160ms var(--vs-ease);
}
.vs-c-anim-pill[data-anim="__mesh__"].is-active::before {
  background: #fff;
}

/* ---------- Player tools (rotate / wireframe) ---------- */
.vs-c-player-tools {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: var(--vs-surface-1);
  border-top: 1px solid var(--vs-border);
  flex-wrap: wrap;
}
.vs-c-player-tools__hint {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  letter-spacing: 0.02em;
}
.vs-c-player-tools__btns { display: flex; gap: 6px; }
.vs-c-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  color: var(--vs-text-muted);
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
}
.vs-c-toggle svg { width: 14px; height: 14px; }
.vs-c-toggle:hover { color: var(--vs-text); background: var(--vs-surface-3); }
.vs-c-toggle.is-active {
  background: var(--vs-accent-low);
  color: var(--vs-accent-hi);
  border-color: var(--vs-accent-line);
}

/* ---------- Mesh stats (voxels / vertices / triangles) ---------- */
.vs-c-mesh-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 0;
  padding: 12px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-c-mesh-stats > div { display: flex; flex-direction: column; gap: 2px; }
.vs-c-mesh-stats dt {
  font-size: 10px;
  font-weight: 600;
  color: var(--vs-text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.vs-c-mesh-stats dd {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 700;
  color: var(--vs-text);
  font-variant-numeric: tabular-nums;
}

/* ---------- Palette PNG ---------- */
.vs-c-palette {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-c-palette__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.vs-c-palette__head h4 { margin: 0; font-size: var(--vs-fs-md); font-weight: 600; }
.vs-c-palette__count { color: var(--vs-text-dim); font-weight: 500; }
.vs-c-palette__dl {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  color: var(--vs-text-muted);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  text-decoration: none;
  transition: color 120ms var(--vs-ease), background 120ms var(--vs-ease);
}
.vs-c-palette__dl svg { width: 12px; height: 12px; }
.vs-c-palette__dl:hover { color: var(--vs-text); background: var(--vs-surface-3); }
.vs-c-palette__imgwrap {
  display: block;
  background:
    repeating-conic-gradient(rgba(15,23,42,0.04) 0 25%, transparent 0 50%) 0 / 16px 16px,
    var(--vs-surface-2);
  border-radius: var(--vs-r-sm);
  border: 1px solid var(--vs-border);
  padding: 8px;
  text-align: center;
}
.vs-c-palette__imgwrap img {
  display: inline-block;
  max-width: 100%;
  height: auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  border-radius: 2px;
}
.vs-c-palette__hint {
  margin: 0;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  line-height: 1.5;
}

/* ---------- Sprite sheet (mise en avant côté detail) ----------
   Visible AU PREMIER COUP D'ŒIL au lieu d'être planquée dans un dropdown.
   Cf. CommunityDetail._renderSpriteSheetSection. */
.vs-c-spritesheet {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-c-spritesheet__head h4 {
  margin: 0;
  font-size: var(--vs-fs-md);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.vs-c-spritesheet__head svg { width: 16px; height: 16px; flex-shrink: 0; }
.vs-c-spritesheet__meta {
  color: var(--vs-text-dim);
  font-weight: 500;
  font-size: var(--vs-fs-sm);
}
.vs-c-spritesheet__preview {
  position: relative;
  display: block;
  width: 100%;
  padding: 8px;
  background:
    repeating-conic-gradient(rgba(15,23,42,0.04) 0 25%, transparent 0 50%) 0 / 16px 16px,
    var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 120ms var(--vs-ease), transform 120ms var(--vs-ease);
}
.vs-c-spritesheet__preview:hover {
  border-color: var(--vs-border-focus);
}
.vs-c-spritesheet__preview img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 220px;
  object-fit: contain;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  border-radius: 2px;
}
.vs-c-spritesheet__preview-overlay {
  position: absolute;
  bottom: 8px; right: 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: rgba(15, 23, 42, 0.78);
  color: #fff;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  border-radius: var(--vs-r-sm);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 120ms var(--vs-ease), transform 120ms var(--vs-ease);
  pointer-events: none;
  /* z-index > anim-overlay pour que le badge "Aperçu rapide" reste visible
     au hover par-dessus les 4 mini-canvas qu'on superpose. */
  z-index: 2;
}
.vs-c-spritesheet__preview-overlay svg { width: 12px; height: 12px; }
.vs-c-spritesheet__preview:hover .vs-c-spritesheet__preview-overlay,
.vs-c-spritesheet__preview:focus-visible .vs-c-spritesheet__preview-overlay {
  opacity: 1;
  transform: translateY(0);
}

/* Mini-player animé en surimpression de la preview sprite-sheet : 4 cellules
   2×2 qui jouent (anim × vue), rotation 6s. Cf. CommunityDetail._mountSpritesheetMiniPlayer.
   pointer-events: none → le clic remonte au <button> parent (ouvre le big dialog). */
.vs-c-spritesheet__preview.is-mini-active img { visibility: hidden; }
.vs-c-spritesheet__anim-overlay {
  position: absolute;
  inset: 8px;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px;
  pointer-events: none;
}
.vs-c-spritesheet__anim-overlay .vs-sheet__anim-cell {
  padding: 6px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  animation: vs-c-spritesheet__cell-in 280ms var(--vs-ease) both;
}
.vs-c-spritesheet__anim-overlay .vs-sheet__anim-canvas {
  /* AnimatedPreview pose width/height en px inline ; on les override pour
     que le canvas remplisse la cellule. La résolution native (size=cellSize)
     est conservée → upscale pixel-perfect via image-rendering: pixelated. */
  width: auto !important;
  height: auto !important;
  max-width: 100%;
  max-height: 100%;
  flex: 1 1 auto;
  min-height: 0;
}
.vs-c-spritesheet__anim-overlay .vs-sheet__anim-label {
  font-size: 10px;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  flex: 0 0 auto;
}
@keyframes vs-c-spritesheet__cell-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1); }
}
.vs-c-spritesheet__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.vs-c-spritesheet__action {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  color: var(--vs-text-muted);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  text-decoration: none;
  cursor: pointer;
  transition: color 120ms var(--vs-ease), background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-c-spritesheet__action svg { width: 14px; height: 14px; }
.vs-c-spritesheet__action:hover {
  color: var(--vs-text);
  background: var(--vs-surface-3);
}
.vs-c-spritesheet__action--primary {
  color: var(--vs-accent-hi);
  background: var(--vs-accent-low, var(--vs-surface-2));
  border-color: var(--vs-accent);
}
.vs-c-spritesheet__action--primary:hover {
  color: #fff;
  background: var(--vs-accent);
  border-color: var(--vs-accent-hi);
}
.vs-c-spritesheet__hint {
  margin: 0;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  line-height: 1.5;
}
/* État "pas de cache" : carte plus discrète, juste un CTA pour générer. */
.vs-c-spritesheet--empty {
  background: var(--vs-surface-1);
  border-style: dashed;
}
.vs-c-spritesheet__empty-msg {
  margin: 0;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  line-height: 1.55;
}

.vs-c-detail__side { display: flex; flex-direction: column; gap: 18px; }
.vs-c-detail__head { display: flex; flex-direction: column; gap: 4px; }
.vs-c-detail__title { font-size: 26px; font-weight: 700; letter-spacing: -0.02em; margin: 0; line-height: 1.2; }
.vs-c-detail__author { color: var(--vs-text-muted); font-size: var(--vs-fs-md); }
.vs-c-detail__author.is-official {
  background: linear-gradient(120deg, var(--vs-accent), var(--vs-accent-hi));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 700;
}
.vs-c-detail__desc { color: var(--vs-text); font-size: var(--vs-fs-lg); margin: 0; line-height: 1.55; }
.vs-c-detail__stats { display: flex; gap: 18px; color: var(--vs-text-muted); font-size: var(--vs-fs-sm); }
.vs-c-detail__tags { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---------- Action buttons ---------- */
.vs-c-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.vs-c-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  color: var(--vs-text);
  font-size: var(--vs-fs-md);
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  transition: all 120ms var(--vs-ease);
}
.vs-c-btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.vs-c-btn:hover { background: var(--vs-surface-3); border-color: var(--vs-border-focus); }
.vs-c-btn--primary {
  background: var(--vs-accent);
  border-color: var(--vs-accent-hi);
  color: white;
}
.vs-c-btn--primary:hover { background: var(--vs-accent-hi); }
.vs-c-btn--ghost {
  background: transparent;
  color: var(--vs-text-muted);
  border-color: var(--vs-border);
}
.vs-c-btn--ghost:hover { color: var(--vs-text); border-color: var(--vs-border-strong); }
.vs-c-btn--small {
  padding: 5px 10px;
  font-size: var(--vs-fs-xs);
  letter-spacing: 0.01em;
}

/* ---------- Dropdown (actions detail page) ----------
   Bouton "Modifier avec l'IA" / "Télécharger" avec menu déroulant en dessous.
   Ouvert via JS (.is-open + menu.hidden=false). Click outside / Escape ferment.
   Le menu est positionné absolu au-dessus du flow ; .vs-c-actions n'est pas
   en overflow:hidden donc rien à compenser. */
.vs-c-dropdown { position: relative; }
.vs-c-dropdown__toggle {
  /* Le toggle est un .vs-c-btn standard mais on aligne le chevron à droite. */
  width: 100%;
  justify-content: flex-start;
}
.vs-c-dropdown__toggle > svg:last-child {
  margin-left: auto;
  transition: transform 160ms var(--vs-ease);
}
.vs-c-dropdown.is-open .vs-c-dropdown__toggle > svg:last-child {
  transform: rotate(180deg);
}
.vs-c-dropdown__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.32);
  overflow: hidden;
  z-index: 20;
}
.vs-c-dropdown__menu[hidden] { display: none; }
.vs-c-dropdown__item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  background: transparent;
  border: 0;
  color: var(--vs-text);
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  text-decoration: none;
  transition: background 100ms var(--vs-ease);
}
.vs-c-dropdown__item:hover:not(:disabled) { background: var(--vs-surface-2); }
.vs-c-dropdown__item:disabled { opacity: 0.45; cursor: not-allowed; }
.vs-c-dropdown__item + .vs-c-dropdown__item { border-top: 1px solid var(--vs-border); }
.vs-c-dropdown__item > svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-top: 2px;
  color: var(--vs-text-muted);
}
.vs-c-dropdown__item-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.vs-c-dropdown__item-title {
  font-size: var(--vs-fs-md);
  font-weight: 600;
  color: var(--vs-text);
}
.vs-c-dropdown__item-hint {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  line-height: 1.35;
}

/* ---------- Code modal ---------- */
.vs-c-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.vs-c-modal[hidden] { display: none; }
.vs-c-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(4px);
  cursor: pointer;
}
.vs-c-modal__panel {
  position: relative;
  width: min(880px, 100%);
  max-height: 80vh;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.vs-c-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
}
.vs-c-modal__head h3 { margin: 0; font-size: var(--vs-fs-lg); }
.vs-c-modal__close {
  font-size: 18px;
  color: var(--vs-text-muted);
  padding: 4px 8px;
  border-radius: var(--vs-r-sm);
}
.vs-c-modal__close:hover { color: var(--vs-text); background: var(--vs-surface-2); }
.vs-c-code {
  flex: 1;
  overflow: auto;
  padding: 16px 20px;
  margin: 0;
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12px;
  line-height: 1.5;
  color: var(--vs-text);
  background: var(--vs-surface-2);
}
.vs-c-modal__foot {
  padding: 12px 20px;
  border-top: 1px solid var(--vs-border);
  display: flex;
  justify-content: flex-end;
}
.vs-c-modal__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 12px 14px;
  background: var(--vs-surface-2);
  border-top: 1px solid var(--vs-border);
  justify-content: center;
}
.vs-c-modal__pills:empty { display: none; }

/* ---------- Status badges (My Models / Admin Moderation) ---------- */
.vs-c-status {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  border-radius: var(--vs-r-pill);
  letter-spacing: 0.02em;
  border: 1px solid transparent;
}
.vs-c-status.is-private  { background: var(--vs-surface-3); color: var(--vs-text-muted); }
.vs-c-status.is-pending  { background: var(--vs-warn-low); color: var(--vs-warn); }
.vs-c-status.is-public   { background: var(--vs-success-low); color: var(--vs-success); }
.vs-c-status.is-rejected { background: var(--vs-danger-low); color: var(--vs-danger); }
.vs-c-status-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 14px; }

.vs-c-card__head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.vs-c-card__head-row .vs-c-card__title { flex: 1; min-width: 0; }
.vs-c-card__actions { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.vs-c-card__actions .vs-c-btn { padding: 8px 12px; font-size: var(--vs-fs-sm); justify-content: center; }
.vs-c-card__hint { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); text-align: center; padding: 6px; }
.vs-c-card__reject {
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--vs-danger-low);
  color: var(--vs-danger);
  border-radius: var(--vs-r-sm);
  font-size: var(--vs-fs-xs);
  line-height: 1.4;
}
.vs-c-card__reject strong { display: block; margin-bottom: 2px; }

/* ---------- Admin moderation table layout ---------- */
.vs-admin-mod {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.vs-admin-mod__row {
  display: grid;
  grid-template-columns: 28px 100px 1fr auto;
  gap: 14px;
  align-items: center;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 12px 14px;
  transition: opacity 180ms ease, transform 180ms ease, border-color 180ms ease, background 120ms;
}
.vs-admin-mod__row.is-selected {
  border-color: var(--vs-accent, #4f8cff);
  background: color-mix(in srgb, var(--vs-accent, #4f8cff) 6%, var(--vs-surface-1));
}
.vs-admin-mod__row.is-removing {
  opacity: 0;
  transform: translateX(-8px);
  pointer-events: none;
}
.vs-admin-mod__select {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
}
.vs-admin-mod__select input {
  width: 18px;
  height: 18px;
  margin: 0;
  cursor: pointer;
  accent-color: var(--vs-accent, #4f8cff);
}
.vs-admin-mod__thumb {
  width: 100px;
  height: 100px;
  border-radius: var(--vs-r-sm);
  background: var(--vs-surface-2);
  object-fit: cover;
}
.vs-admin-mod__info { min-width: 0; }
.vs-admin-mod__title { font-weight: 600; font-size: var(--vs-fs-lg); margin: 0 0 4px; }
.vs-admin-mod__bench {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  margin-right: 4px;
  font-size: 18px;
  line-height: 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--vs-text-dim);
  cursor: pointer;
  vertical-align: -3px;
  transition: color 120ms, background 120ms, border-color 120ms;
}
.vs-admin-mod__bench:hover {
  background: var(--vs-surface-2);
  border-color: var(--vs-border);
}
.vs-admin-mod__bench.is-on {
  color: #f5a623;
}
.vs-admin-mod__bench.is-on:hover {
  color: #d28b16;
}
.vs-admin-mod__bench.is-disabled {
  opacity: 0.35;
  cursor: not-allowed;
  pointer-events: none;
}
.vs-admin-mod__meta {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.vs-admin-mod__desc {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.vs-admin-mod__actions { display: flex; flex-direction: column; gap: 6px; min-width: 240px; }
/* Largeur uniforme : sans width:100%, chaque bouton prenait la largeur de son
   texte ("Aperçu sprite sheet" vs "Refuser") d'où un effet escalier disgracieux. */
.vs-admin-mod__actions .vs-c-btn { padding: 7px 12px; font-size: var(--vs-fs-sm); width: 100%; justify-content: center; }
.vs-admin-mod__check {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--vs-fs-xs); color: var(--vs-text-muted);
  cursor: pointer;
  padding: 6px 10px;
  border: 1px dashed var(--vs-border);
  border-radius: var(--vs-r-sm);
  background: var(--vs-surface-2);
}
.vs-admin-mod__check input { margin: 0; cursor: pointer; }
/* Séparateur visuel : isole l'action destructive Supprimer du reste. */
.vs-admin-mod__sep { height: 1px; background: var(--vs-border); margin: 4px 0 2px; }
.vs-admin-mod__actions [data-act="delete"] { color: var(--vs-danger); }
.vs-admin-mod__actions [data-act="delete"]:hover { background: var(--vs-danger-low); color: var(--vs-danger); }
@media (max-width: 720px) {
  .vs-admin-mod__row { grid-template-columns: 28px 80px 1fr; }
  .vs-admin-mod__actions { grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; min-width: auto; }
  .vs-admin-mod__actions .vs-c-btn { width: auto; }
  .vs-admin-mod__sep { display: none; }
  .vs-admin-mod__check { width: 100%; }
}

/* Bulk action bar : barre flottante en bas, visible quand au moins 1 row coché. */
.vs-admin-mod__bulk {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 10px 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18), 0 2px 6px rgba(0, 0, 0, 0.08);
  animation: vs-admin-mod-bulk-in 160ms ease-out;
}
.vs-admin-mod__bulk[hidden] { display: none; }
.vs-admin-mod__bulk-count {
  font-weight: 600;
  font-size: var(--vs-fs-sm);
  padding-right: 4px;
  border-right: 1px solid var(--vs-border);
  margin-right: 4px;
}
@keyframes vs-admin-mod-bulk-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (max-width: 720px) {
  .vs-admin-mod__bulk {
    left: 12px;
    right: 12px;
    transform: none;
    flex-wrap: wrap;
    justify-content: center;
  }
}

/* ---------- Admin - vue ÉDITION (split form / preview 3D) ---------- */
.vs-c-hero__title--small { font-size: 22px; }
.vs-c-back {
  background: transparent;
  border: 1px solid var(--vs-border-strong);
  color: var(--vs-text-muted);
  padding: 6px 12px;
  border-radius: var(--vs-r-md);
  font-size: var(--vs-fs-sm);
  cursor: pointer;
  margin-bottom: 12px;
  transition: all 120ms var(--vs-ease);
}
.vs-c-back:hover { color: var(--vs-text); background: var(--vs-surface-2); }

.vs-admin-edit {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
}
@media (max-width: 1024px) {
  .vs-admin-edit { grid-template-columns: 1fr; }
}

.vs-admin-edit__form {
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-lg);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.vs-admin-edit__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 600px) { .vs-admin-edit__grid { grid-template-columns: 1fr; } }

.vs-admin-edit__field { display: flex; flex-direction: column; gap: 5px; }
.vs-admin-edit__field span {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  font-weight: 500;
}
.vs-admin-edit__field input,
.vs-admin-edit__field select,
.vs-admin-edit__field textarea {
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 8px 11px;
  color: var(--vs-text);
  font-size: var(--vs-fs-md);
  font-family: inherit;
  outline: none;
  resize: vertical;
  transition: border-color 120ms var(--vs-ease), box-shadow 120ms var(--vs-ease);
}
.vs-admin-edit__field input:focus,
.vs-admin-edit__field select:focus,
.vs-admin-edit__field textarea:focus {
  border-color: var(--vs-border-focus);
  box-shadow: var(--vs-glow);
}
.vs-admin-edit__field--full { grid-column: 1 / -1; }
.vs-admin-edit__field--check {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}
.vs-admin-edit__field--check input { margin: 0; cursor: pointer; }
.vs-admin-edit__field--check span { font-weight: 400; color: var(--vs-text); }

/* ============================================================
   Bloc bilingue FR / EN (Phase 6 i18n DB)
   - Section .vs-admin-edit__bilingual : encadre les 3 champs name /
     description / tags dupliqués FR vs EN, avec un badge "Origine : FR/EN"
     (meta_lang_origin) et 2 boutons de traduction globale.
   - Chaque champ a 2 boutons →/← pour traduire isolément vers l'autre lang.
   - Responsive : 2 colonnes desktop, 1 colonne mobile (<900px).
   ============================================================ */
.vs-admin-edit__bilingual {
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 14px 16px 6px;
  background: var(--vs-surface-2);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.vs-admin-edit__bilingual-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.vs-admin-edit__bilingual-title {
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  color: var(--vs-text);
}
.vs-admin-edit__bilingual-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.vs-admin-edit__origin-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
}
.vs-admin-edit__origin-badge.is-fr {
  color: var(--vs-info, #2563eb);
  background: var(--vs-info-low, rgba(37, 99, 235, 0.12));
  border-color: rgba(37, 99, 235, 0.30);
}
.vs-admin-edit__origin-badge.is-en {
  color: var(--vs-warn, #d97706);
  background: var(--vs-warn-low, rgba(217, 119, 6, 0.12));
  border-color: rgba(217, 119, 6, 0.30);
}
.vs-admin-edit__bilingual-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
@media (max-width: 900px) {
  .vs-admin-edit__bilingual-grid { grid-template-columns: 1fr; }
}
.vs-admin-edit__bilingual-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.vs-admin-edit__bilingual-col .vs-admin-edit__field {
  /* override : on reste 1 col à l'intérieur de chaque demi-grille */
  grid-column: auto;
}
.vs-admin-edit__code-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.vs-admin-edit__code-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
}
.vs-admin-edit__hint { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }
.vs-admin-edit__code {
  width: 100%;
  min-height: 320px;
  max-height: 60vh;
  background: var(--vs-surface-2);
  color: var(--vs-text);
  font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
  font-size: 12px;
  line-height: 1.5;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 12px 14px;
  white-space: pre;
  overflow: auto;
  outline: none;
  resize: vertical;
}
.vs-admin-edit__code:focus { border-color: var(--vs-border-focus); box-shadow: var(--vs-glow); }

.vs-admin-edit__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--vs-border);
  padding-top: 14px;
}
.vs-admin-edit__buttons .vs-c-btn { padding: 9px 14px; font-size: var(--vs-fs-sm); }

.vs-admin-edit__status {
  font-size: var(--vs-fs-sm);
  padding: 8px 12px;
  border-radius: var(--vs-r-sm);
  min-height: 16px;
  background: transparent;
}
.vs-admin-edit__status.is-info    { background: var(--vs-surface-2); color: var(--vs-text-muted); }
.vs-admin-edit__status.is-success { background: var(--vs-success-low); color: var(--vs-success); }
.vs-admin-edit__status.is-error   { background: var(--vs-danger-low); color: var(--vs-danger); }

.vs-admin-edit__preview {
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-lg);
  overflow: hidden;
  position: sticky;
  top: 80px;
  display: flex;
  flex-direction: column;
}
.vs-admin-edit__canvas {
  aspect-ratio: 1 / 1;
  background: var(--vs-surface-2);
  position: relative;
}
.vs-admin-edit__canvas canvas { display: block; width: 100% !important; height: 100% !important; }
.vs-admin-edit__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  padding: 10px 14px;
  background: var(--vs-surface-2);
  border-top: 1px solid var(--vs-border);
}
.vs-admin-edit__meta {
  margin: 0;
  padding: 14px 16px;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 14px;
  font-size: var(--vs-fs-sm);
  border-top: 1px solid var(--vs-border);
}
.vs-admin-edit__meta div { display: contents; }
.vs-admin-edit__meta dt {
  color: var(--vs-text-muted);
  font-weight: 500;
}
.vs-admin-edit__meta dd {
  margin: 0;
  color: var(--vs-text);
  word-break: break-word;
}
.vs-admin-edit__meta .dim { color: var(--vs-text-dim); font-style: italic; }

/* ---------- Nav globale en mode OVERLAY (utilisée sur /edit) ---------- */
.vs-c-nav.is-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  background: var(--vs-surface-1);
  border-bottom: 1px solid var(--vs-border);
  height: 52px;
  padding: 0 28px;
}

/* ---------- Admin import modal (multi-fichiers) ---------- */
.vs-admin-import { width: min(720px, 100%); }
.vs-admin-import__body {
  padding: 16px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: auto;
}
.vs-admin-import__intro {
  margin: 0;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  line-height: 1.55;
}
.vs-admin-import__intro code {
  background: var(--vs-surface-2);
  padding: 1px 6px;
  border-radius: var(--vs-r-sm);
  font-size: 11px;
}
.vs-admin-import__pick {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px;
  border: 1px dashed var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  background: var(--vs-surface-2);
}
.vs-admin-import__check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  cursor: pointer;
}
.vs-admin-import__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow: auto;
}
.vs-admin-import__empty {
  padding: 14px;
  text-align: center;
  color: var(--vs-text-dim);
  font-size: var(--vs-fs-sm);
  border: 1px dashed var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-admin-import__item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  gap: 4px 12px;
  align-items: center;
  padding: 10px 12px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-admin-import__name {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.vs-admin-import__name strong { font-size: var(--vs-fs-md); }
.vs-admin-import__name code {
  font-size: 11px;
  color: var(--vs-text-muted);
  background: var(--vs-surface-2);
  padding: 1px 6px;
  border-radius: var(--vs-r-sm);
}
.vs-admin-import__file {
  grid-column: 1;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
}
.vs-admin-import__state {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
}
.vs-admin-import__err {
  grid-column: 1 / -1;
  font-size: var(--vs-fs-xs);
  color: var(--vs-danger);
  background: var(--vs-danger-low);
  padding: 6px 8px;
  border-radius: var(--vs-r-sm);
}
.vs-admin-import__badge {
  display: inline-block;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  padding: 3px 9px;
  border-radius: var(--vs-r-pill);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.vs-admin-import__badge.is-ready { background: var(--vs-accent-low); color: var(--vs-accent-hi); }
.vs-admin-import__badge.is-busy  { background: var(--vs-warn-low);   color: var(--vs-warn); }
.vs-admin-import__badge.is-ok    { background: var(--vs-success-low); color: var(--vs-success); }
.vs-admin-import__badge.is-err   { background: var(--vs-danger-low);  color: var(--vs-danger); }
.vs-admin-import__actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  padding-top: 6px;
  border-top: 1px solid var(--vs-border);
}
.vs-admin-import__errors {
  background: var(--vs-danger-low);
  border: 1px solid var(--vs-danger);
  border-radius: var(--vs-r-md);
  padding: 12px 14px;
  max-height: 220px;
  overflow-y: auto;
}
.vs-admin-import__errors-title {
  margin: 0 0 8px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-danger);
  font-weight: 700;
}
.vs-admin-import__errors-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.vs-admin-import__errors-list li {
  font-size: var(--vs-fs-xs);
  line-height: 1.45;
}
.vs-admin-import__errors-list code {
  background: var(--vs-surface-2);
  padding: 1px 5px;
  border-radius: var(--vs-r-sm);
  font-size: 11px;
}
.vs-admin-import__errors-msg {
  color: var(--vs-danger);
  margin-top: 2px;
}

/* ===========================================================
   RESPONSIVE - ajustements mobile globaux
   Bloc consolidé en fin de fichier pour ne pas disperser les
   overrides ; chaque règle est un fix ciblé d'un débordement
   ou d'un padding qui mangeait l'écran sur < 720px.
   =========================================================== */
@media (max-width: 720px) {
  /* Shell principal : padding latéral 28px → 16px (gain ~24px utile
     sur viewport 360px) ; padding-bottom 80px → 48px. */
  .vs-c-main { padding: 24px 16px 48px; }

  /* Hero des sous-pages (galerie / modération / my-models) : on
     réduit le padding et la taille du titre - sinon ça occupe
     ~40% de l'écran avant même de voir le contenu. */
  .vs-c-hero { padding: 24px 8px 18px; margin-bottom: 24px; }
  .vs-c-hero__title { font-size: 26px; }
  .vs-c-hero__sub { font-size: var(--vs-fs-md); }

  /* Filtres galerie : passer la barre en colonne sur mobile pour
     éviter le wrap chaotique (search + sort + tags). */
  .vs-c-filters__row { gap: 10px; }
  .vs-c-filters__row .vs-c-search { width: 100%; min-width: 0; }
  .vs-c-sort { width: 100%; flex-wrap: wrap; }
  .vs-c-sort__btn { flex: 1; min-width: 0; }
  .vs-c-perpage { width: 100%; justify-content: space-between; padding: 4px 10px 4px 14px; }

  /* Grid de cards : minmax 244 → 160px pour avoir 2 colonnes
     dès 360px (au lieu d'une seule colonne géante). Le thumb 1:1
     reste lisible à cette taille. */
  .vs-c-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }

  /* Pagination mobile : chaque bloc (info / nav / goto) prend 100% de
     largeur centré pour rester lisible sans débordement horizontal.
     Numéros plus compacts. */
  .vs-c-pagination { gap: 12px; flex-wrap: wrap; }
  .vs-c-paginate__info,
  .vs-c-paginate__nav,
  .vs-c-paginate__goto {
    width: 100%;
    justify-content: center;
  }
  .vs-c-paginate__num,
  .vs-c-paginate__btn { min-width: 32px; height: 32px; padding: 0 6px; }
  .vs-c-paginate__total { margin-left: 0; flex-basis: 100%; text-align: center; }
}

@media (max-width: 480px) {
  /* Très petit : 1 colonne complète, mais on garde des paddings
     pour éviter les cards en pleine largeur écran. */
  .vs-c-grid { grid-template-columns: 1fr; }
}

/* =========================================================
   SPRITE GALLERY - variant /sprites/ avec sprite sheet en
   aperçu (au lieu de la mini-thumbnail 3D). Page detail dédiée
   focus pixel art, sans player WebGL.
   ========================================================= */
body.vs-sprites {
  overflow: auto;
  background: var(--vs-bg);
  color: var(--vs-text);
  min-height: 100vh;
}
.vs-c-link {
  color: var(--vs-accent);
  text-decoration: none;
  font-weight: 500;
}
.vs-c-link:hover { text-decoration: underline; }

/* Grille sprites : cards plus larges car les sprite sheets sont rectangulaires
   (largeur >> hauteur). Mieux d'avoir 2-3 colonnes max sur desktop.
   /!\ Override .vs-c-grid (auto-fill 244/160) -- DOIT garder ses propres
   media queries mobile sinon `minmax(360px, 1fr)` cause un overflow horizontal
   en dessous de 360px de viewport (1 colonne forcée à 360px). */
.vs-c-grid--sprites {
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
@media (max-width: 720px) {
  .vs-c-grid--sprites { grid-template-columns: 1fr; }
}
.vs-c-card--sprite {
  display: flex;
  flex-direction: column;
}

/* ---------- Split preview : 3D thumb (gauche) | sprite sheet (droite)
 * Grid 50/50 pour que l'user identifie le modèle d'un coup d'œil ET voie la
 * structure de la sprite. La sheet seule était trop petite pour être lisible.
 * Hauteur fixe (200px) pour homogénéiser les cards (sinon une sprite 528×3696
 * donnerait une card 4× plus haute qu'une 256×256). object-fit: contain pour
 * que rien ne soit écrasé/croppé.
 * ---------- */
.vs-c-card__split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--vs-border);
  height: 200px;
  overflow: hidden;
}
.vs-c-card__split-half {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  overflow: hidden;
  min-width: 0;
}
.vs-c-card__split-half--3d {
  background: linear-gradient(135deg, var(--vs-surface-1, #fafbfc), var(--vs-surface-2, #f1f3f7));
  border-right: 1px solid var(--vs-border);
}
.vs-c-card__split-half--sheet {
  /* Damier de transparence classique pour signaler le PNG transparent. */
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 12px 12px;
}
.vs-c-card__split-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
.vs-c-card__split-img--sheet {
  /* Sprites = pixel art, on garde les pixels nets sans antialiasing. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.vs-c-card__split-img--3d {
  /* Thumbnails 3D = rendus rasterisés normaux, on laisse l'antialiasing natif. */
  image-rendering: auto;
}
.vs-c-card__split-label {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 2;
  padding: 2px 6px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(15, 23, 42, 0.55);
  border-radius: 3px;
  pointer-events: none;
}
.vs-c-card__split-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 22px;
  font-weight: 600;
  color: var(--vs-text-dim);
  letter-spacing: 0.05em;
}
.vs-c-card__sprite-info {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--vs-border);
}

/* =========================================================
   SPRITE DETAIL - /sprites/<slug> page focus 2D
   ========================================================= */
.vs-c-detail--sprite {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 28px;
  padding: 24px 28px;
  /* Plein écran utile sous la nav + back link (~120px). align-items:stretch
     (default) → la sheet-area s'étire pour matcher la sidebar et on peut
     filer `height:100%` aux enfants ci-dessous pour cover la viewport. */
  min-height: calc(100vh - 140px);
  align-items: stretch;
}
@media (max-width: 880px) {
  .vs-c-detail--sprite { grid-template-columns: 1fr; min-height: 0; }
}
.vs-c-detail__sheet-area {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  height: 100%;
  min-height: 0;
}
.vs-c-detail__animated,
.vs-c-detail__static {
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* Le pane actif prend tout l'espace dispo sous l'onglet → la sheet/grid
     remplit la viewport. min-height:0 = flex item peut shrink en-dessous
     de son contenu, sinon les images forcent la hauteur. */
  flex: 1;
  min-height: 0;
  height: 100%;
}
/* Onglets sprite/animated au-dessus du sheet-area : reprend les styles
   `.vs-sheet__tabs` / `.vs-sheet__tab` du squelette. Léger override pour
   coller au padding de la page detail (pas de margin verticale extra). */
.vs-c-detail__tabs {
  margin: 0;
}
.vs-c-detail__static[hidden],
.vs-c-detail__animated[hidden] { display: none; }
.vs-c-detail__section-title {
  font-size: var(--vs-fs-md);
  font-weight: 600;
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.vs-c-detail__count {
  font-weight: 400;
  color: var(--vs-text-muted);
  font-size: var(--vs-fs-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Page /sprites/<slug> : même pattern groupé que .vs-sheet__anim-grid (cf.
   app.css), avec une cellule un peu plus grande (140px min vs 120px) parce
   que le focus produit ici est l'aperçu animé, pas un onglet secondaire.
   Le rendu et la classe enfant (.vs-sheet__anim-group__cells) sont communs. */
.vs-c-detail__anim-grid {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 12px;
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 16px 16px;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  min-height: 140px;
}
.vs-c-detail__anim-grid .vs-sheet__anim-group__cells {
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.vs-c-detail__sheet {
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 16px 16px;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 16px;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Remplit le pane (qui lui-même remplit le sheet-area étiré par le grid).
     min-height couvre les viewports courts pour garder un zoom utilisable. */
  flex: 1;
  min-height: 320px;
  height: 100%;
}
.vs-c-detail__sheet img {
  display: block;
  max-width: 100%;
  height: auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
/* Override en mode zoom : la classe `.vs-sheet__preview--zoomable` (app.css)
   pose `padding:0; overflow:hidden` mais avec une spécificité (0,1,0) égale
   à `.vs-c-detail__sheet`. community.css est chargé APRÈS app.css → notre
   règle gagne. On combine les deux classes pour pousser la spécificité
   (0,2,0) et garantir l'override quel que soit l'ordre de cascade futur. */
.vs-c-detail__sheet.vs-sheet__preview--zoomable {
  padding: 0;
  overflow: hidden;
  height: 100%;
}
.vs-c-detail__how {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--vs-border);
}
.vs-c-detail__how h3 {
  font-size: var(--vs-fs-md);
  font-weight: 600;
  margin: 0 0 10px;
}
.vs-c-detail__how p {
  margin: 0 0 8px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  line-height: 1.55;
}
.vs-c-detail__how code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  background: var(--vs-surface-2);
  padding: 1px 6px;
  border-radius: var(--vs-r-sm);
  color: var(--vs-text);
}
.vs-c-detail__hint {
  font-size: var(--vs-fs-xs) !important;
  color: var(--vs-text-dim) !important;
  font-style: italic;
}

/* Reusing AnimatedPreview classes - référence stylée plus haut sous
   .vs-sheet__anim-* dans app.css. La page detail utilise les mêmes
   classes (.vs-sheet__anim-cell etc) puisque le composant les pose. */

/* Filtres anim/vue dans le dialog Sprite Sheet community.
   Réutilise les .vs-sheet__chip(s)-group(-head|-toggle|-title) déjà
   définies dans app.css. La règle [hidden] est explicite parce que
   `display: flex` ci-dessous battrait sinon le `[hidden] { display:none }`
   du user-agent (même spécificité, mais l'auteur gagne en cascade). */
.vs-sheet__filters {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 4px 0 10px;
}
.vs-sheet__filters[hidden] { display: none; }

/* Dropdown "Télécharger" du footer du dialog Sprite Sheet community.
   Menu absolu ancré au bouton, qui s'ouvre vers le haut (le dialog est
   au-dessus du bouton dans la viewport). Click outside / Escape gérés en JS. */
.vs-sheet__dl {
  position: relative;
}
.vs-sheet__dl-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  right: 0;
  display: flex;
  flex-direction: column;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
  min-width: 280px;
  overflow: hidden;
  z-index: 10;
}
.vs-sheet__dl-menu[hidden] { display: none; }
.vs-sheet__dl-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  padding: 10px 14px;
  background: transparent;
  border: 0;
  color: var(--vs-text);
  cursor: pointer;
  font-family: inherit;
}
.vs-sheet__dl-item:hover:not(:disabled) { background: var(--vs-surface-2); }
.vs-sheet__dl-item:disabled { opacity: 0.45; cursor: not-allowed; }
.vs-sheet__dl-item + .vs-sheet__dl-item { border-top: 1px solid var(--vs-border); }
.vs-sheet__dl-item-title {
  font-size: var(--vs-fs-sm);
  font-weight: 600;
}
.vs-sheet__dl-item-hint {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
}

/* =========================================================
   Sprite Sheet dialog - layout 2-pane (community).
   Hierarchy : header éditorial → sidebar filtres + main avec tabs
   sheet/animated → footer split (close / admin / download).
   On surcharge `.vs-sheet__head` / `.vs-sheet__body` du squelette
   app.css quand le panel porte la classe `--two-pane`. Le mode
   plein écran (`--max`) compose proprement avec `--two-pane`.
   ========================================================= */
.vs-sheet__panel--two-pane {
  width: min(1180px, 100%);
  /* Le squelette `.vs-sheet__panel` (app.css) ne pose qu'un `max-height: 92vh`
     et le panel est `display:flex; flex-direction:column` → sans hauteur
     forcée il se shrink à wrapping sur le contenu et la zone preview ne
     prend que ~30 % du viewport (visible quand la sheet a peu de lignes).
     On fixe une hauteur cible élevée pour que la modale "remplisse" l'écran
     par défaut sans avoir à cliquer le toggle plein écran. */
  height: min(900px, 92vh);
}

/* Header - eyebrow + titre + sous-ligne meta + actions à droite. */
.vs-sheet__panel--two-pane .vs-sheet__head {
  align-items: flex-start;
  gap: 16px;
}
.vs-sheet__head-titles {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
.vs-sheet__head-titles .vs-eyebrow { margin: 0 0 2px; }
.vs-sheet__head-titles h3 {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 600;
  color: var(--vs-text);
  letter-spacing: -0.01em;
  /* asset au nom long → ellipsis plutôt que pousser la sidebar */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-sheet__head-meta {
  margin: 0;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* tronque proprement les messages d'erreur longs (ex : stack JSON) */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-sheet__panel--two-pane .vs-sheet__head-actions { padding-top: 4px; }

/* Body 2-pane : sidebar filtres à gauche, main à droite.
   IMPORTANT : on FORCE display:flex pour OVERRIDE le `display:grid`
   du squelette `.vs-sheet__body` (app.css). Raison : en grid, masquer
   la sidebar (mode cache admin) laisse la colonne 304px réservée et
   le main est placé dedans → 800px de blanc à droite. En flex, un
   item `display:none` disparaît complètement et le main s'étale. */
.vs-sheet__body--two-pane {
  display: flex;
  padding: 18px 20px 20px;
  gap: 18px;
  /* Le panel est flex-column → on doit prendre toute la hauteur entre
     header et footer pour que la sidebar et le main remplissent verticalement. */
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
@media (max-width: 720px) {
  .vs-sheet__body--two-pane { flex-direction: column; }
}

/* Sidebar : largeur fixe, scrollable indépendamment du main quand le
   contenu déborde (chips d'anim longues). `flex: 0 0 304px` = pas de
   grow, pas de shrink, base 304px. */
.vs-sheet__sidebar {
  flex: 0 0 304px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  min-height: 0;
  overflow: auto;
  padding-right: 2px;
}
.vs-sheet__sidebar[hidden] { display: none; }
@media (max-width: 720px) {
  .vs-sheet__sidebar { flex: 0 0 auto; max-height: 40vh; }
}

/* Section <details> : on retire le marqueur natif et on stylise le
   summary comme une row "title + actions" cliquable. La rotation du
   chevron utilise .vs-sheet__section[open] côté CSS. */
.vs-sheet__section {
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 10px 12px;
}
.vs-sheet__section[open] { padding-bottom: 12px; }
.vs-sheet__section-summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  cursor: pointer;
  padding: 2px 0;
  user-select: none;
}
.vs-sheet__section-summary::-webkit-details-marker { display: none; }
.vs-sheet__section-summary::before {
  content: "▸";
  display: inline-block;
  font-size: 10px;
  color: var(--vs-text-dim);
  width: 10px;
  flex: 0 0 10px;
  transition: transform 120ms var(--vs-ease);
}
.vs-sheet__section[open] .vs-sheet__section-summary::before {
  transform: rotate(90deg);
}
.vs-sheet__section-summary .vs-sheet__section-title {
  flex: 1;
  margin: 0;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text);
}
.vs-sheet__section-summary .vs-sheet__count {
  margin-left: 6px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.vs-sheet__section-actions {
  display: inline-flex;
  gap: 4px;
}
/* Le contenu d'une section (chips, seg, view-groups) est animé en
   reveal simple via le toggle natif de <details>. */
.vs-sheet__section > :not(summary) {
  margin-top: 10px;
}

/* Segmented control "Presets de vues" (4 / 8 / 10 / 14).
   Boutons côte-à-côte, l'actif sera marqué via [data-active]. */
.vs-sheet__seg {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  overflow: hidden;
}
.vs-sheet__seg-btn {
  background: transparent;
  border: 0;
  padding: 6px 8px;
  font-size: var(--vs-fs-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--vs-text-muted);
  cursor: pointer;
  transition: background 100ms var(--vs-ease), color 100ms var(--vs-ease);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.vs-sheet__seg-btn + .vs-sheet__seg-btn {
  border-left: 1px solid var(--vs-border);
}
.vs-sheet__seg-btn:hover {
  background: var(--vs-surface-3);
  color: var(--vs-text);
}
.vs-sheet__seg-btn[data-active="1"] {
  background: var(--vs-accent-low);
  color: var(--vs-text);
  font-weight: 600;
}

.vs-sheet__view-groups {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ---------- Mobile (≤ 600px) : sprite sheet dialog tweaks
   - Preset seg en grid 4 colonnes : labels "4 directions / 8 directions /
     10 (8 + dessus/dessous) / Tout (14 vues)" ne tiennent pas en 4×~85px
     sur mobile, ils ellipsisent ("4 dir…", "8 dir…", "10 (8…", "Tout …").
     On bascule en grid 2×2 : ~170px par cellule = labels lisibles.
   - Dropdown menus du footer (Admin ▾ / Télécharger ▾) : ancrés `right: 0`
     du wrap avec `min-width: 280px`. Le bouton Admin est au milieu du
     footer mobile → le menu de 280px s'étend vers la gauche et déborde
     hors viewport. Solution : sur mobile, le menu sort en `position: fixed`
     ancré aux 2 bords du viewport (au-dessus du footer).
   ---------- */
@media (max-width: 600px) {
  .vs-sheet__seg {
    grid-template-columns: repeat(2, 1fr);
    gap: 1px;
    background: var(--vs-border);
  }
  .vs-sheet__seg-btn {
    background: var(--vs-surface-1);
    padding: 9px 10px;
    font-size: var(--vs-fs-sm);
    text-overflow: clip;
  }
  /* En grid 2x2 le `+` adjacent ne reflète plus le voisinage spatial,
     on retire la border-left et on laisse le gap:1px sur fond border faire
     les séparateurs entre cellules (look quadrillage propre). */
  .vs-sheet__seg-btn + .vs-sheet__seg-btn { border-left: 0; }

  .vs-sheet__dl-menu {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: 76px;
    top: auto;
    min-width: 0;
    max-width: none;
    z-index: 230;
  }
}

/* Main pane droit : tabs + panes scrollables + statbar fixe en bas.
   `flex: 1` pour qu'il prenne tout l'espace restant après la sidebar
   (304px). Quand la sidebar est masquée, il prend toute la largeur. */
.vs-sheet__main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  gap: 10px;
}
.vs-sheet__tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--vs-border);
  padding-bottom: 0;
}
.vs-sheet__tab {
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 8px 14px;
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  color: var(--vs-text-muted);
  cursor: pointer;
  margin-bottom: -1px;
  transition: color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.vs-sheet__tab:hover { color: var(--vs-text); }
.vs-sheet__tab.is-active {
  color: var(--vs-text);
  border-bottom-color: var(--vs-accent);
  font-weight: 600;
}
.vs-sheet__tab .vs-sheet__count {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
}

.vs-sheet__panes {
  flex: 1;
  min-height: 220px;
  position: relative;
  display: flex;
}
.vs-sheet__pane {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: none;
}
.vs-sheet__pane.is-active { display: flex; flex-direction: column; }
.vs-sheet__pane[hidden] { display: none !important; }

/* Le `.vs-sheet__preview` (canvas/img) reste stylé par app.css ;
   on s'assure juste qu'il prend tout l'espace de son pane. */
.vs-sheet__pane[data-pane="sheet"] .vs-sheet__preview { flex: 1; min-height: 220px; }

/* La grid d'aperçus animés vit dans son pane et scroll si nécessaire.
   On garde `auto-fill 120px` du squelette mais en full-height. */
.vs-sheet__pane[data-pane="animated"] .vs-sheet__anim-grid {
  flex: 1;
  align-content: flex-start;
  overflow: auto;
}

/* Statbar discrète en pied du main pane (dimensions + counts).
   On remplace l'ancien .vs-sheet__hint volant. */
.vs-sheet__statbar {
  margin: 0;
  padding: 8px 12px;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  min-height: 30px;
  line-height: 1.4;
}
.vs-sheet__statbar:empty { display: none; }

/* Plein écran : la sidebar peut s'élargir à 340px pour aérer les chips
   (plus de place pour les noms d'anims longs). Adapté au flex layout. */
.vs-sheet__panel--two-pane.vs-sheet__panel--max .vs-sheet__sidebar {
  flex: 0 0 340px;
}

/* Plein écran : on FORCE 100vw × 100vh sur le panel --two-pane.
   Sans cette règle plus spécifique (0,2,0), `.vs-sheet__panel--two-pane`
   (0,1,0) gagne la cascade sur `.vs-sheet__panel--max` (0,1,0) côté
   `width` (community.css après app.css en source order) → le panel
   reste plafonné à 1180px et le toggle-max ne fait visuellement rien
   d'autre que retirer la border-radius. */
.vs-sheet__panel--two-pane.vs-sheet__panel--max {
  width: 100vw;
  height: 100vh;
  max-width: 100vw;
  max-height: 100vh;
  border-radius: 0;
}
/* Mode plein écran : on retire aussi le padding du wrap `.vs-sheet`
   (24px par défaut) pour que le panel touche les bords. */
.vs-sheet:has(.vs-sheet__panel--max) {
  padding: 0;
}

/* =========================================================
   Sub-modal "Ligne sprite" (drill-down sur une cellule animée).
   Ouvert au-dessus du main dialog (z-index 240 > 220) quand
   l'user clique sur un .vs-sheet__anim-cell. Affiche : aperçu
   animé en grand, canvas PNG de la ligne, bouton Télécharger.
   ========================================================= */
.vs-sheet-line {
  position: fixed;
  inset: 0;
  z-index: 240;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.vs-sheet-line__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
}
.vs-sheet-line__panel {
  position: relative;
  width: min(760px, 100%);
  max-height: 92vh;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--vs-shadow-3);
}
.vs-sheet-line__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
}
.vs-sheet-line__head-titles {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
.vs-sheet-line__head-titles .vs-eyebrow { margin: 0 0 2px; }
.vs-sheet-line__head-titles h3 {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 600;
  color: var(--vs-text);
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-sheet-line__meta {
  margin: 0;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.vs-sheet-line__body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 18px 20px;
  overflow: auto;
  min-height: 0;
}
.vs-sheet-line__section-title {
  margin: 0 0 8px;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-muted);
}
.vs-sheet-line__animated {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 18px;
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 16px 16px;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  min-height: 220px;
}
/* On réutilise le rendu .vs-sheet__anim-cell mais on neutralise les
   bordures pour qu'il flotte propre sur le checkerboard. */
.vs-sheet-line__animated .vs-sheet__anim-cell {
  background: transparent;
  border: 0;
  padding: 0;
}
.vs-sheet-line__animated .vs-sheet__anim-label { display: none; }
.vs-sheet-line__preview {
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 16px 16px;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 12px;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.vs-sheet-line__preview canvas {
  display: block;
  max-width: 100%;
  height: auto;
  image-rendering: pixelated;
}
.vs-sheet-line__foot {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--vs-border);
  background: var(--vs-surface-1);
}

/* ---------- Lang switch (drapeaux + code) dans la nav globale ----------
   Vit ici parce que les autres .vs-c-nav__* y vivent déjà. Drapeaux faits
   en CSS pur (pas de SVG, pas d'emoji) pour rendu identique sur toutes les
   plateformes. Inclus partout (community.css est chargé sur landing,
   community, sprites, et toutes les pages SEO outils). */
.vs-c-nav__lang {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  height: 32px;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  font-size: 11px;
  font-weight: 700;
  color: var(--vs-text-muted);
  text-decoration: none;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  transition: border-color 120ms var(--vs-ease), color 120ms var(--vs-ease);
  flex-shrink: 0;
}
.vs-c-nav__lang:hover { border-color: var(--vs-text); color: var(--vs-text); }
.vs-c-nav__lang-code { line-height: 1; }
.vs-c-nav__flag {
  display: inline-block;
  width: 18px;
  height: 12px;
  border-radius: 1px;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.08) inset;
}
/* France : 3 bandes verticales bleu / blanc / rouge */
.vs-c-nav__flag--fr {
  background: linear-gradient(to right,
    #002395 0 33.3%,
    #FFFFFF 33.3% 66.7%,
    #ED2939 66.7% 100%);
}
/* UK simplifié : fond bleu marine, croix blanche + diagonales blanches +
   superpositions rouges. Reconnaissable à 18×12 px. */
.vs-c-nav__flag--uk { background: #012169; }
.vs-c-nav__flag--uk .vs-c-nav__flag-d1,
.vs-c-nav__flag--uk .vs-c-nav__flag-d2 {
  position: absolute; left: 50%; top: 50%;
  width: 24px; height: 2px; background: #fff;
  transform-origin: 0 0;
}
.vs-c-nav__flag--uk .vs-c-nav__flag-d1 { transform: translate(-50%, -50%) rotate(33.7deg); }
.vs-c-nav__flag--uk .vs-c-nav__flag-d2 { transform: translate(-50%, -50%) rotate(-33.7deg); }
.vs-c-nav__flag--uk .vs-c-nav__flag-v {
  position: absolute; left: 50%; top: 0; bottom: 0;
  width: 3px; margin-left: -1.5px; background: #fff;
}
.vs-c-nav__flag--uk .vs-c-nav__flag-h {
  position: absolute; top: 50%; left: 0; right: 0;
  height: 3px; margin-top: -1.5px; background: #fff;
}
.vs-c-nav__flag--uk .vs-c-nav__flag-vr {
  position: absolute; left: 50%; top: 0; bottom: 0;
  width: 1.5px; margin-left: -0.75px; background: #C8102E;
}
.vs-c-nav__flag--uk .vs-c-nav__flag-hr {
  position: absolute; top: 50%; left: 0; right: 0;
  height: 1.5px; margin-top: -0.75px; background: #C8102E;
}

/* ---------- Lang dropdown (refonte 2026-05-09) ----------
   Le trigger affiche maintenant la langue COURANTE (drapeau + code + caret).
   Click → menu déroulant avec les 2 langues : courante cochée (non-cliquable)
   + autre comme lien. Esc / click hors wrap → ferme. JS dans _main_nav.php. */
.vs-c-nav__lang-wrap { position: relative; flex-shrink: 0; }
/* Reset des defaults user-agent du <button> (le sélecteur .vs-c-nav__lang
   ci-dessus est partagé avec l'ancienne version <a> ; sur button, fond +
   font-family ne sont pas hérités). */
button.vs-c-nav__lang {
  background: transparent;
  cursor: pointer;
  font-family: inherit;
}
.vs-c-nav__lang[aria-expanded="true"] {
  border-color: var(--vs-text);
  color: var(--vs-text);
}
.vs-c-nav__lang-caret {
  width: 10px; height: 10px;
  margin-left: -2px;
  transition: transform 120ms var(--vs-ease);
  flex-shrink: 0;
}
.vs-c-nav__lang[aria-expanded="true"] .vs-c-nav__lang-caret {
  transform: rotate(180deg);
}
.vs-c-nav__lang-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 180px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  box-shadow: 0 12px 32px -16px rgba(10, 10, 10, 0.2);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  z-index: 50;
}
.vs-c-nav__lang-menu[hidden] { display: none; }
.vs-c-nav__lang-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: 4px;
  color: var(--vs-text);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
.vs-c-nav__lang-item-name { flex: 1 1 auto; }
a.vs-c-nav__lang-item:hover { background: var(--vs-surface-3); }
.vs-c-nav__lang-item.is-current {
  background: var(--vs-surface-3);
  color: var(--vs-text-muted);
  cursor: default;
}
.vs-c-nav__lang-check {
  width: 14px; height: 14px;
  color: var(--vs-text);
  flex-shrink: 0;
}

/* ============================================================
 * AdminApproveDialog - modal "Approuver & Publier" (refonte 2026-05-04).
 * Ouverte depuis /admin/community quand l'admin clique "Publier" sur un
 * asset pending. Layout : preview 3D à gauche + form bilingue à droite,
 * sticky footer avec actions.
 * ============================================================ */
.vs-approve-modal {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.vs-approve-modal__backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.65);
  backdrop-filter: blur(2px);
}
.vs-approve-modal__panel {
  position: relative;
  display: flex; flex-direction: column;
  width: min(1100px, 100%); max-height: calc(100vh - 48px);
  background: var(--vs-surface);
  border: 1px solid var(--vs-border);
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.vs-approve-modal__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--vs-border);
  background: var(--vs-surface-2);
}
.vs-approve-modal__head h3 {
  margin: 0; font-size: var(--vs-fs-lg); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vs-approve-modal__close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: transparent; border: 1px solid transparent;
  color: var(--vs-text-muted); border-radius: 6px;
  cursor: pointer;
}
.vs-approve-modal__close:hover { color: var(--vs-text); background: var(--vs-surface-3); }

.vs-approve-modal__body {
  display: grid; grid-template-columns: 1fr 1.2fr;
  gap: 18px;
  padding: 18px;
  overflow-y: auto;
  flex: 1 1 auto;
}
@media (max-width: 900px) {
  .vs-approve-modal__body { grid-template-columns: 1fr; }
}

.vs-approve-modal__preview {
  display: flex; flex-direction: column; gap: 10px;
}
.vs-approve-modal__preview-canvas {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 8px;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.vs-approve-modal__preview-canvas canvas {
  display: block; width: 100% !important; height: 100% !important;
}
.vs-approve-modal__pills {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.vs-approve-modal__meta-info {
  display: grid; grid-template-columns: auto 1fr;
  gap: 4px 10px; margin: 0;
  font-size: var(--vs-fs-sm);
}
.vs-approve-modal__meta-info div { display: contents; }
.vs-approve-modal__meta-info dt { color: var(--vs-text-dim); font-weight: 500; }
.vs-approve-modal__meta-info dd { margin: 0; color: var(--vs-text); }

.vs-approve-modal__lang-badge {
  display: inline-block;
  padding: 2px 8px;
  font-size: var(--vs-fs-xs); font-weight: 600;
  border-radius: 4px;
}
.vs-approve-modal__lang-badge.is-fr { background: #2b3a6b; color: #cbd5ff; }
.vs-approve-modal__lang-badge.is-en { background: #6b2b3a; color: #ffcbd5; }

.vs-approve-modal__badge {
  display: inline-block;
  padding: 2px 8px;
  font-size: var(--vs-fs-xs); font-weight: 500;
  border-radius: 4px;
  background: var(--vs-surface-3); color: var(--vs-text-muted);
}
.vs-approve-modal__badge.is-official {
  background: var(--vs-accent-low, #2b4a6b); color: var(--vs-accent-hi, #9ec5ff);
}

.vs-approve-modal__form {
  display: flex; flex-direction: column; gap: 12px;
}
.vs-approve-modal__form-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.vs-approve-modal__form-head h4 {
  margin: 0; font-size: var(--vs-fs-md); font-weight: 600;
}

.vs-approve-modal__llm-status {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  border-radius: 6px;
  background: var(--vs-surface-2);
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
}
.vs-approve-modal__llm-status.is-loading { color: var(--vs-accent-hi); }
.vs-approve-modal__llm-status.is-success {
  background: var(--vs-success-low, #1e3a2b); color: var(--vs-success, #6ed68b);
}
.vs-approve-modal__llm-status.is-error {
  background: var(--vs-danger-low, #4a1e1e); color: var(--vs-danger, #ff8b8b);
}
.vs-approve-modal__llm-spinner {
  width: 14px; height: 14px;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%;
  animation: vs-approve-spin 0.8s linear infinite;
  flex-shrink: 0;
}
.vs-approve-modal__llm-status:not(.is-loading) .vs-approve-modal__llm-spinner {
  display: none;
}
@keyframes vs-approve-spin {
  to { transform: rotate(360deg); }
}

.vs-approve-modal__grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.vs-approve-modal__field { display: flex; flex-direction: column; gap: 4px; }
.vs-approve-modal__field span {
  font-size: var(--vs-fs-sm); color: var(--vs-text-muted); font-weight: 500;
}
.vs-approve-modal__field input,
.vs-approve-modal__field textarea {
  padding: 8px 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 6px;
  color: var(--vs-text);
  font-family: inherit; font-size: var(--vs-fs-sm);
  resize: vertical;
}
.vs-approve-modal__field input:focus,
.vs-approve-modal__field textarea:focus {
  outline: none;
  border-color: var(--vs-border-focus, var(--vs-accent-hi));
  box-shadow: var(--vs-glow, 0 0 0 3px rgba(120, 180, 255, 0.15));
}
.vs-approve-modal__field--full { grid-column: 1 / -1; }

.vs-approve-modal__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 12px 18px;
  border-top: 1px solid var(--vs-border);
  background: var(--vs-surface-2);
}
.vs-approve-modal__status {
  flex: 1 1 auto;
  font-size: var(--vs-fs-sm);
  padding: 6px 10px;
  border-radius: 6px;
  min-height: 28px;
}
.vs-approve-modal__status.is-info    { background: var(--vs-surface-3); color: var(--vs-text-muted); }
.vs-approve-modal__status.is-success { background: var(--vs-success-low, #1e3a2b); color: var(--vs-success, #6ed68b); }
.vs-approve-modal__status.is-error   { background: var(--vs-danger-low, #4a1e1e); color: var(--vs-danger, #ff8b8b); }
.vs-approve-modal__actions {
  display: flex; gap: 8px; flex-shrink: 0;
}

/* ---------- SEO hero (SSR pre-mount) ----------
   Bloc rendu côté serveur sur /community/<slug> et /sprites/<slug> AVANT que
   le JS hydrate la page. Sert deux choses :
     1) crawlers (Google + IA non-JS) voient un H1 + paragraphe + tags avec
        les bons keywords ("voxel", "animated", "GLB"…)
     2) utilisateur réel voit un état de chargement plus utile que "…".
   Le SPA écrase root.innerHTML au mount → ce bloc disparait dès que le rendu
   interactif arrive. Style volontairement sobre, doit pouvoir cohabiter avec
   le shimmer loading qui suit. */
.vs-c-seo-hero {
  max-width: 880px;
  margin: 32px auto 16px;
  padding: 0 20px;
  text-align: center;
}
.vs-c-seo-hero__title {
  font-size: clamp(22px, 3vw, 32px);
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 12px;
  color: var(--vs-text);
}
.vs-c-seo-hero__lead {
  font-size: var(--vs-fs-md, 15px);
  line-height: 1.55;
  color: var(--vs-text-muted);
  margin: 0 0 14px;
}
.vs-c-seo-hero__tags {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
}
.vs-c-seo-hero__tags li a {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--vs-surface-2);
  color: var(--vs-text-muted);
  text-decoration: none;
  font-size: var(--vs-fs-sm, 13px);
}
.vs-c-seo-hero__tags li a:hover { color: var(--vs-text); }
.vs-c-seo-hero__crosslink {
  margin: 0;
  font-size: var(--vs-fs-sm, 13px);
}
.vs-c-seo-hero__crosslink a { color: var(--vs-accent); text-decoration: none; }
.vs-c-seo-hero__crosslink a:hover { text-decoration: underline; }

