/* =========================================================
   VoxelSprites - Design System & UI
   Tokens, primitives, then components.
   Namespace: .vs-*

   Tokens (--vs-*), @font-face Superstar et accents rotatifs vivent dans
   _design-tokens.css (partagé avec account.css qui est chargé seul sur
   /account/*). admin.css / community.css / landing.css en héritent via
   ce @import - aucun token n'est redéclaré ailleurs.
   ========================================================= */
@import url('/assets/css/_design-tokens.css');

/* ---------- Reset ---------- */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
  width: 100%;
  background: var(--vs-bg);
  color: var(--vs-text);
  font-family: var(--vs-font);
  font-size: var(--vs-fs-md);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
/* L'éditeur veut un layout fullscreen sans scroll natif (canvas + panneaux
   floating). Les autres pages (landing/tools/community/account) scrollent
   normalement comme des pages web standards. */
html:has(> body.vs-edit) { height: 100vh; overflow: hidden; }

button, input, textarea, select {
  font-family: inherit;
  color: inherit;
}

button { background: none; border: none; cursor: pointer; }

/* ::selection - source unique dans _design-tokens.css (chargé par
 * app.css ET account.css). Refonte 2026-05-14 : vert foncé fixe partout
 * avec alpha 0.38 - avant rgba(22,163,74,0.10) quasi invisible. */

/* Scrollbar */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(15, 23, 42, 0.18);
  border-radius: var(--vs-r-pill);
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(15, 23, 42, 0.32); background-clip: padding-box; }

/* Focus visible */
*:focus { outline: none; }
*:focus-visible {
  outline: 2px solid var(--vs-accent);
  outline-offset: 2px;
  border-radius: var(--vs-r-sm);
}

/* ---------- Icons (SVG inline, currentColor, 1em scale) ---------- */
.vs-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  flex-shrink: 0;
  display: inline-block;
}
/* ---------- Layout ----------
 * Sur /edit, la nav globale (.vs-c-nav rendue par _main_nav.php) vit en
 * flow normal au-dessus de .vs-layout. Pour que les deux cohabitent sans
 * débordement vertical, on passe le body en flex column avec
 * height: 100vh, et .vs-layout devient flex: 1 (height: auto) - le canvas
 * occupe automatiquement le reste de la hauteur après la nav.
 */
.vs-layout {
  position: relative;
  height: 100vh;
  width: 100vw;
}
body.vs-edit {
  display: flex;
  flex-direction: column;
  height: 100vh;
  margin: 0;
  overflow: hidden;
}
/* `position: relative` (et pas `static`) sinon le drawer mobile
   `.vs-c-nav__drawer` (`position: absolute; top: 100%`) tombe sur
   l'initial containing block (viewport) et part 100vh sous l'écran.
   z-index 100 pour passer au-dessus de #editor-root (stacking context z:30)
   et de .vs-prompt (z:50) - le menu burger doit dominer tout le studio. */
body.vs-edit > .vs-c-nav { flex: 0 0 auto; position: relative; z-index: 100; }
body.vs-edit > .vs-layout {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}

/* Touch-friendly : élimine le délai 300ms du double-tap-zoom natif sur tous
   les éléments interactifs de l'éditeur. Le canvas WebGL a déjà
   `touch-action: none` posé en JS dans SceneManager (gestion gestes complète
   via Pointer Events) ; ici on traite les boutons/liens autour pour qu'un tap
   tablette réponde instantanément au lieu d'attendre un éventuel second tap. */
body.vs-edit button,
body.vs-edit a,
body.vs-edit [role="button"] {
  touch-action: manipulation;
}

.vs-canvas-area {
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--vs-surface-2);
}

#voxel-canvas { width: 100%; height: 100%; }

#editor-root {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 30;
  color: var(--vs-text);
  font-family: var(--vs-font);
}

/* =========================================================
   PRIMITIVES
   ========================================================= */

/* Buttons */
.vs-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  color: var(--vs-text);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease), transform 80ms var(--vs-ease);
  white-space: nowrap;
}
.vs-btn:hover:not(:disabled) {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-strong);
}
.vs-btn:active:not(:disabled) { transform: translateY(1px); }
.vs-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.vs-btn--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--vs-text-muted);
}
.vs-btn--ghost:hover:not(:disabled) {
  background: var(--vs-surface-2);
  color: var(--vs-text);
  border-color: var(--vs-border);
}

/* Primary = noir solide. L'accent coloré est réservé aux eyebrows /
   underlines / dots signature, pas aux gros aplats de boutons (sinon
   chaque page change de couleur dominante = le drapeau qu'on évite). */
.vs-btn--primary {
  background: var(--vs-text);
  border-color: var(--vs-text);
  color: #fff;
  font-weight: 600;
}
.vs-btn--primary:hover:not(:disabled) {
  background: #1f1f1f;
  border-color: #1f1f1f;
}

.vs-btn--icon {
  width: 32px;
  height: 32px;
  padding: 0;
  flex: 0 0 auto;
}

.vs-btn--sm { height: 26px; padding: 0 8px; font-size: var(--vs-fs-xs); border-radius: var(--vs-r-sm); }
.vs-btn--sm.vs-btn--icon { width: 26px; padding: 0; }

.vs-btn--danger {
  color: var(--vs-danger);
}
.vs-btn--danger:hover:not(:disabled) {
  background: var(--vs-danger-low);
  border-color: rgba(220, 38, 38, 0.3);
}

/* Vert "valider et continuer" - utilisé sur les boutons de transition
   d'étape du flow guidé quand un candidat est déjà sélectionné. */
.vs-btn--success {
  background: var(--vs-success);
  border-color: var(--vs-success);
  color: #fff;
  font-weight: 600;
}
.vs-btn--success:hover:not(:disabled) {
  background: #15803d;
  border-color: #15803d;
}

/* Icon size in buttons */
.vs-btn svg { width: 16px; height: 16px; flex: 0 0 auto; stroke-width: 1.75; }
.vs-btn--sm svg { width: 14px; height: 14px; }

/* Inputs */
.vs-input {
  height: 32px;
  padding: 0 10px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  transition: border-color 120ms var(--vs-ease), box-shadow 120ms var(--vs-ease);
}
.vs-input:hover { border-color: var(--vs-border-strong); }
.vs-input:focus { border-color: var(--vs-border-focus); box-shadow: var(--vs-glow); }
.vs-input::placeholder { color: var(--vs-text-dim); }
.vs-input--sm { height: 26px; font-size: var(--vs-fs-xs); padding: 0 8px; }

.vs-input--ghost {
  background: transparent;
  border-color: transparent;
  font-weight: 600;
  font-size: var(--vs-fs-lg);
  padding: 0 6px;
}
.vs-input--ghost:hover { background: var(--vs-surface-2); }
.vs-input--ghost:focus { background: var(--vs-surface-1); border-color: var(--vs-border-focus); box-shadow: var(--vs-glow); }

/* Number input arrows hidden */
.vs-input[type="number"]::-webkit-outer-spin-button,
.vs-input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.vs-input[type="number"] { -moz-appearance: textfield; text-align: center; }

/* Surfaces */
.vs-surface {
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid var(--vs-border);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}

/* Divider */
.vs-divider { height: 1px; background: var(--vs-border); margin: 8px 0; }

/* =========================================================
   TOPBAR
   ========================================================= */
.vs-edit-subnav {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--vs-topbar-h);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 14px;
  background: rgba(255, 255, 255, 0.85);
  border-bottom: 1px solid var(--vs-border);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  pointer-events: all;
  z-index: 40;
}

.vs-edit-subnav__name {
  flex: 0 1 220px;
}

.vs-edit-subnav__center {
  display: flex; align-items: center; gap: 4px;
  margin: 0 auto;
}

.vs-edit-subnav__actions {
  display: flex; align-items: center; gap: 6px;
}

/* History group (undo/redo) */
.vs-history {
  display: flex;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 2px;
  gap: 2px;
}
.vs-history .vs-btn--icon {
  height: 26px; width: 26px;
  background: transparent;
  border-color: transparent;
  border-radius: var(--vs-r-sm);
}
.vs-history .vs-btn--icon:hover:not(:disabled) {
  background: var(--vs-surface-3);
}

/* Export dropdown menu */
.vs-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 240px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  padding: 6px;
  box-shadow: var(--vs-shadow-3);
  z-index: 200;
  display: none;
}
.vs-menu.is-open { display: block; }
.vs-menu__item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px;
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  transition: background 120ms var(--vs-ease);
}
.vs-menu__item:hover { background: var(--vs-surface-3); }
.vs-menu__item-title { font-size: var(--vs-fs-sm); font-weight: 500; color: var(--vs-text); }
.vs-menu__item-desc { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }
.vs-menu__sep { height: 1px; background: var(--vs-border); margin: 4px 0; }

/* ---------- Topbar : bloc Publier ---------- */
.vs-edit-subnav__publish {
  font-weight: 600;
}
.vs-edit-subnav__publish:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Avatar menu (connected). <details>/<summary> natif → toggle gratuit. */
.vs-account-menu { position: relative; }
.vs-account-menu summary {
  list-style: none;
  cursor: pointer;
}
.vs-account-menu summary::-webkit-details-marker { display: none; }

.vs-account-menu__trigger {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 3px 10px 3px 3px;
  border-radius: var(--vs-r-pill);
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-account-menu[open] .vs-account-menu__trigger,
.vs-account-menu__trigger:hover {
  background: var(--vs-surface-2);
  border-color: var(--vs-accent-line);
}
.vs-account-menu__avatar {
  width: 22px; height: 22px;
  display: inline-grid; place-items: center;
  border-radius: 50%;
  background: var(--vs-accent);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  flex: 0 0 auto;
}
.vs-account-menu__name {
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  color: var(--vs-text);
  max-width: 110px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-account-menu__pill {
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 5px;
  border-radius: var(--vs-r-pill);
  background: var(--vs-accent-low, rgba(98,68,255,0.10));
  color: var(--vs-accent-hi);
}

.vs-account-menu__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  min-width: 180px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  padding: 4px;
  box-shadow: var(--vs-shadow-3);
  display: flex; flex-direction: column;
  z-index: 60;
}
.vs-account-menu__item {
  padding: 8px 10px;
  border-radius: var(--vs-r-sm);
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  text-decoration: none;
  cursor: pointer;
  background: transparent;
  border: 0;
  text-align: left;
  font-family: inherit;
}
.vs-account-menu__item:hover { background: var(--vs-surface-3); }
.vs-account-menu__sep { height: 1px; background: var(--vs-border); margin: 4px 0; }

/* Hide the topbar account name when very narrow (avatar still visible). */
@media (max-width: 1100px) {
  .vs-account-menu__name { display: none; }
}

/* =========================================================
   LEFT TOOLBAR
   ========================================================= */
.vs-toolbar {
  position: absolute;
  top: var(--vs-topbar-h);
  left: 0;
  bottom: var(--vs-status-h);
  width: var(--vs-toolbar-w);
  background: rgba(255, 255, 255, 0.85);
  border-right: 1px solid var(--vs-border);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  display: flex; flex-direction: column;
  padding: 10px 8px;
  gap: 8px;
  pointer-events: all;
  z-index: 31;
  overflow-y: auto;
}

.vs-toolbar__group {
  display: flex; flex-direction: column;
  gap: 4px;
}

/* button.vs-tool (et pas juste .vs-tool) : la classe `vs-tool` est aussi
   posée sur le <body> des pages SEO outils (cf. tools/index.php, etc.).
   Sans la contrainte `button`, ces règles écrasaient le body et lui
   filaient padding/flex/border/cursor cassant tout le layout (header
   décollé du top, etc.). */
button.vs-tool {
  position: relative;
  width: 100%;
  padding: 7px 4px 6px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 3px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--vs-r-md);
  color: var(--vs-text-muted);
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
}
button.vs-tool:hover {
  background: var(--vs-surface-2);
  color: var(--vs-text);
}
button.vs-tool.is-active {
  background: var(--vs-accent-low);
  border-color: var(--vs-accent);
  color: var(--vs-accent-hi);
}
button.vs-tool svg { width: 20px; height: 20px; stroke-width: 1.75; }
.vs-tool__label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.1;
  color: inherit;
}
/* Tooltip */
button.vs-tool[data-tooltip]:hover::after {
  content: attr(data-tooltip);
  position: absolute;
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  background: var(--vs-surface-hi);
  color: var(--vs-text);
  padding: 5px 10px;
  border-radius: var(--vs-r-sm);
  font-size: var(--vs-fs-xs);
  font-weight: 500;
  box-shadow: var(--vs-shadow-2);
  border: 1px solid var(--vs-border-strong);
  z-index: 200;
  pointer-events: none;
  letter-spacing: 0.01em;
}

/* Color block at bottom of toolbar */
.vs-toolbar__color {
  margin-top: auto;
  display: flex; flex-direction: column;
  gap: 8px;
  width: 100%;
  padding-top: 12px;
  border-top: 1px solid var(--vs-border);
}
/* Wrapper recents+palettes : sur desktop il ne change rien (display:contents
   = ses enfants se comportent comme s'il n'existait pas dans le layout). Le
   toggle mobile est masqué. C'est uniquement sur ≤600px que la mécanique
   bottom sheet s'enclenche (cf. responsive plus bas). */
.vs-toolbar__color-panel { display: contents; }
.vs-toolbar__color-toggle { display: none; }
.vs-color-swatch-current {
  width: 100%;
  height: 56px;
  border-radius: var(--vs-r-md);
  border: 2px solid var(--vs-border-strong);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: border-color 120ms var(--vs-ease);
}
.vs-color-swatch-current:hover {
  border-color: var(--vs-accent);
}
.vs-color-swatch-current input[type="color"] {
  position: absolute; inset: 0;
  opacity: 0; cursor: pointer;
  border: none; padding: 0;
}

.vs-color-recents {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  width: 100%;
}
.vs-color-recents__swatch {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 5px;
  cursor: pointer;
  border: 1.5px solid var(--vs-border);
  transition: border-color 100ms var(--vs-ease);
  padding: 0;
  position: relative;
}
.vs-color-recents__swatch:hover {
  border-color: var(--vs-border-strong);
}
.vs-color-recents__swatch.is-active {
  border-color: var(--vs-accent);
}

/* =========================================================
   RIGHT PANEL
   ========================================================= */
.vs-rpanel {
  position: absolute;
  top: var(--vs-topbar-h);
  right: 0;
  bottom: var(--vs-status-h);
  width: var(--vs-rpanel-w);
  background: rgba(255, 255, 255, 0.85);
  border-left: 1px solid var(--vs-border);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  display: flex; flex-direction: column;
  pointer-events: all;
  z-index: 31;
  overflow: hidden;
}

.vs-rpanel__section {
  border-bottom: 1px solid var(--vs-border);
  display: flex; flex-direction: column;
  min-height: 0;
}
.vs-rpanel__section:last-child { border-bottom: none; }

.vs-rpanel__header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px 4px;
}
.vs-rpanel__title {
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--vs-text-muted);
}
.vs-rpanel__count {
  font-size: 10px;
  color: var(--vs-text-dim);
  font-weight: 500;
}
.vs-rpanel__body {
  padding: 4px 12px 12px;
  display: flex; flex-direction: column;
  gap: 6px;
  overflow-y: auto;
  min-height: 0;
}

/* Assets section (smaller, scrollable list).
   Quand le rpanel ne porte QUE les assets (--solo, depuis la migration vers
   le timeline dock 2026-05-10), on laisse la liste prendre toute la hauteur
   pour éviter le double scroll panel/liste. */
.vs-rpanel--assets .vs-rpanel__body { max-height: 220px; }
.vs-rpanel--solo { flex: 1 1 auto; min-height: 0; }
.vs-rpanel--solo .vs-rpanel__body { max-height: none; flex: 1 1 auto; }
.vs-rpanel__asset-list {
  display: flex; flex-direction: column;
  gap: 4px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.vs-asset-row {
  display: flex; align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-asset-row:hover { background: var(--vs-surface-2); border-color: var(--vs-border-strong); }
.vs-asset-row__name {
  flex: 1;
  font-size: var(--vs-fs-sm);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--vs-text);
}
.vs-asset-row__del {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: var(--vs-r-sm);
  color: var(--vs-text-dim);
  transition: all 120ms var(--vs-ease);
}
.vs-asset-row__del:hover { color: var(--vs-danger); background: var(--vs-danger-low); }
.vs-asset-row__del svg { width: 12px; height: 12px; }

/* Slots synthétiques "Brouillon courant" / "Brouillon précédent" : tag visuel
   accent + label italique pour les distinguer des assets nommés sauvegardés
   par l'user (cf. VoxelEditorApp.SPECIAL_CURRENT / SPECIAL_PREVIOUS). */
.vs-asset-row--special {
  background: var(--vs-accent-low);
  border-color: var(--vs-accent);
  border-style: dashed;
}
.vs-asset-row--special:hover {
  background: var(--vs-accent-low);
  border-color: var(--vs-accent);
}
.vs-asset-row--special .vs-asset-row__name {
  font-style: italic;
  color: var(--vs-accent);
  font-weight: 500;
}

/* Note: les anciens blocs `.vs-anim-row*`, `.vs-frames`, `.vs-frame*`,
   `.vs-playback`, `.vs-duration*` (legacy rpanel pré-timeline) ont été
   supprimés 2026-05-10 - les sélecteurs effectifs vivent maintenant dans
   le bloc TIMELINE DOCK plus bas. `.vs-play-btn` est conservé car réutilisé
   par la transport bar du dock. */
.vs-play-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  background: var(--vs-accent);
  color: #fff;
  border-radius: var(--vs-r-md);
  border: none;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), transform 80ms var(--vs-ease);
}
.vs-play-btn:hover { background: var(--vs-accent-hi); }
.vs-play-btn:active { transform: scale(0.95); }
.vs-play-btn svg { width: 14px; height: 14px; }

/* Empty states */
.vs-empty {
  text-align: center;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  padding: 14px 8px;
  font-style: italic;
}

/* Mini button row (for + Dup Del Up Down) */
.vs-btn-row {
  display: flex;
  gap: 4px;
  margin-top: 4px;
}
.vs-btn-row .vs-btn {
  flex: 1;
  min-width: 0;
}
.vs-btn-row .vs-btn--icon { flex: 0 0 32px; width: 32px; }
.vs-btn-row .vs-btn--sm.vs-btn--icon { flex: 0 0 26px; width: 26px; }

/* Palette section (in sidebar, comfortable) */
.vs-palette-section {
  display: flex; flex-direction: column;
  gap: 6px;
  width: 100%;
  padding-top: 8px;
  border-top: 1px solid var(--vs-border);
  /* Les badges .vs-palette-swatch__remove / __swap (et les SVG des tabs en
     mobile 52px) débordent en right:-5px de la 4e colonne du grid. Sans clip,
     ce débordement remonte au .vs-toolbar parent (overflow-y: auto -> overflow-x
     devient implicitement auto par spec) et déclenche un scroll horizontal
     fantôme sur l'onglet Modèle / Personnalisée. `clip` plutôt que `hidden` :
     pas de scroll container creation, pas d'impact sur les overlays positionnés. */
  overflow-x: clip;
}

.vs-palette-tabs {
  display: flex;
  gap: 3px;
  width: 100%;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 3px;
}
.vs-palette-tab {
  flex: 1;
  height: 28px;
  display: grid; place-items: center;
  background: transparent;
  border: none;
  border-radius: 5px;
  color: var(--vs-text-dim);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
  min-width: 0;
}
.vs-palette-tab:hover:not(.is-active) {
  background: var(--vs-surface-3);
  color: var(--vs-text);
}
.vs-palette-tab.is-active {
  background: var(--vs-accent-low);
  color: var(--vs-accent-hi);
}
.vs-palette-tab svg { width: 15px; height: 15px; }

.vs-palette-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  width: 100%;
}
.vs-palette-grid:empty { display: none; }
.vs-palette-grid .vs-color-recents__swatch {
  width: 100%;
  aspect-ratio: 1;
  height: auto;
  border-radius: 5px;
  position: relative;
}

/* Cell wrapper utilisé uniquement par la palette custom : permet de poser
   un bouton × en absolute pour retirer la couleur (la palette builtin n'a
   pas ce wrap, ses swatches sont placés direct dans le grid). */
.vs-palette-cell {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
}
.vs-palette-cell .vs-color-recents__swatch {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;  /* la cell impose déjà 1:1, on évite la double-contrainte */
}
.vs-palette-swatch__remove {
  position: absolute;
  top: -5px; right: -5px;
  width: 14px; height: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--vs-danger, #dc2626);
  color: #fff;
  border: 1.5px solid var(--vs-surface, #fff);
  border-radius: 50%;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 100ms var(--vs-ease), transform 100ms var(--vs-ease), background 100ms var(--vs-ease);
  z-index: 2;
  font-family: inherit;
}
.vs-palette-cell:hover .vs-palette-swatch__remove,
.vs-palette-swatch__remove:focus-visible {
  opacity: 1;
  transform: scale(1);
}
.vs-palette-swatch__remove:hover {
  background: #b91c1c;
}
/* Écrans tactiles : pas de hover, on garde le × visible mais discret pour
   que l'user puisse retirer une couleur (sinon il reste bloqué dessus). */
@media (hover: none) {
  .vs-palette-swatch__remove {
    opacity: 0.7;
    transform: scale(0.85);
  }
}

/* Variante "swap" du badge : utilisée par la palette "Modèle" pour ouvrir
   le picker de remplacement global. Hérite du positionnement de __remove
   (haut-droite, scale au hover) mais coloré en accent au lieu de danger. */
.vs-palette-swatch__swap {
  position: absolute;
  top: -5px; right: -5px;
  width: 14px; height: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--vs-accent, #4fc3f7);
  color: #fff;
  border: 1.5px solid var(--vs-surface, #fff);
  border-radius: 50%;
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 100ms var(--vs-ease), transform 100ms var(--vs-ease), background 100ms var(--vs-ease);
  z-index: 2;
  font-family: inherit;
}
.vs-palette-cell:hover .vs-palette-swatch__swap,
.vs-palette-swatch__swap:focus-visible {
  opacity: 1;
  transform: scale(1);
}
.vs-palette-swatch__swap:hover {
  background: var(--vs-accent-hi, #1e88e5);
}
@media (hover: none) {
  .vs-palette-swatch__swap {
    opacity: 0.7;
    transform: scale(0.85);
  }
}

.vs-palette-empty {
  /* Le parent .vs-palette-grid est en grid 4 cols ; sans cette directive,
     le message empty n'occupe qu'1 cellule sur 4 et les 3 autres restent
     vides → aspect "4 blocs". On force le span sur toute la ligne. */
  grid-column: 1 / -1;
  width: 100%;
  font-size: 11px;
  text-align: center;
  color: var(--vs-text-dim);
  padding: 12px 8px;
  line-height: 1.4;
  font-style: italic;
  background: var(--vs-surface-1);
  border: 1px dashed var(--vs-border);
  border-radius: var(--vs-r-sm);
}

.vs-palette-add {
  width: 100%;
  height: 28px;
  display: flex; align-items: center; justify-content: center;
  gap: 5px;
  background: var(--vs-surface-1);
  border: 1px dashed var(--vs-border-strong);
  border-radius: var(--vs-r-sm);
  color: var(--vs-text-muted);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;          /* empêche le label de wrap si la sidebar est étroite */
  cursor: pointer;
  font-family: inherit;
  margin-top: 4px;              /* petit air entre la grille et le bouton */
  transition: all 120ms var(--vs-ease);
}
.vs-palette-add:hover {
  background: var(--vs-surface-2);
  color: var(--vs-text);
  border-style: solid;
  border-color: var(--vs-accent);
}
.vs-palette-add svg { width: 12px; height: 12px; }

/* Search input */
.vs-search {
  position: relative;
  margin-bottom: 6px;
}
.vs-search input {
  width: 100%;
  padding-left: 30px;
}
.vs-search svg {
  position: absolute;
  left: 9px;
  top: 50%;
  transform: translateY(-50%);
  width: 13px; height: 13px;
  color: var(--vs-text-dim);
  pointer-events: none;
}

/* =========================================================
   STATUS BAR
   ========================================================= */
.vs-status {
  position: absolute;
  bottom: 0; left: 0; right: 0;
  height: var(--vs-status-h);
  display: flex; align-items: center;
  padding: 0 14px;
  gap: 14px;
  background: rgba(255, 255, 255, 0.85);
  border-top: 1px solid var(--vs-border);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  pointer-events: none;
  z-index: 30;
  font-variant-numeric: tabular-nums;
}
.vs-status__item {
  display: flex; align-items: center; gap: 6px;
}
.vs-status__item svg { width: 12px; height: 12px; color: var(--vs-text-dim); }
.vs-status__sep { width: 1px; height: 12px; background: var(--vs-border); }
.vs-status__brand {
  margin-left: auto;
  color: var(--vs-text-dim);
  font-size: 10px;
  letter-spacing: 0.04em;
}

/* =========================================================
   PROMPT (AI bar)  - saisie pure, single-row, style ChatGPT/Lovable
   La pill crédits / publier / compte vivent désormais dans la vs-topbar.
   ========================================================= */
.vs-prompt {
  position: fixed;
  /* Empilé : status bar | timeline dock | prompt qui flotte juste au-dessus.
     Le `--vs-timeline-h` est 0 quand le dock est masqué (mobile <600px). */
  bottom: calc(var(--vs-status-h) + var(--vs-timeline-h, 0px) + 14px);
  left: calc(var(--vs-toolbar-w) + (100vw - var(--vs-toolbar-w) - var(--vs-rpanel-w)) / 2);
  transform: translateX(-50%);
  width: min(680px, calc(100vw - var(--vs-toolbar-w) - var(--vs-rpanel-w) - 32px));
  z-index: 50;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-xl);
  padding: 6px 6px 6px 8px;
  box-shadow: 0 6px 24px -8px rgba(15, 23, 42, 0.18);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  transition: border-color 160ms var(--vs-ease), box-shadow 160ms var(--vs-ease);
  pointer-events: all;
}
.vs-prompt:focus-within {
  border-color: var(--vs-accent);
  box-shadow: 0 6px 28px -6px rgba(98, 68, 255, 0.22);
}

/* Credit pill (réutilisé par la topbar) */
.vs-credit-pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px;
  font-size: 11px;
  font-weight: 700;
  border-radius: var(--vs-r-pill);
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
  letter-spacing: 0.01em;
}
.vs-credit-pill svg { width: 11px; height: 11px; }
.vs-credit-pill.is-ok     { color: var(--vs-accent-hi); border-color: rgba(98,68,255,0.25); background: rgba(98,68,255,0.08); }
.vs-credit-pill.is-warn   { color: var(--vs-warn);     border-color: rgba(217,119,6,0.30); background: rgba(217,119,6,0.10); }
.vs-credit-pill.is-danger { color: var(--vs-danger);   border-color: rgba(220,38,38,0.30); background: rgba(220,38,38,0.10); }

/* ---------- Mode toggle (segmented control empty state)
 * Affiché au-dessus de la bar uniquement quand pas d'asset chargé. Permet à
 * l'user de choisir entre une génération mesh+anim (default) et une mesh-only
 * (statique, plus rapide pour chaise/statue). Même prix dans les 2 cas.
 * ---------- */
.vs-prompt__mode {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px;
  margin: 0 0 8px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
  width: max-content;
  max-width: 100%;
}
.vs-prompt__mode-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--vs-text-muted);
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
  white-space: nowrap;
}
.vs-prompt__mode-pill:hover:not(.is-active) {
  color: var(--vs-text);
  background: var(--vs-surface-3);
}
.vs-prompt__mode-pill.is-active {
  background: var(--vs-surface-1);
  color: var(--vs-text);
  border-color: var(--vs-accent-line);
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}
.vs-prompt__mode-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--vs-text-dim);
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease);
}
.vs-prompt__mode-pill.is-active .vs-prompt__mode-dot {
  background: var(--vs-accent, #6244ff);
}

/* ---------- Top row : toggle + bouton "Nouveau projet"
 * En asset state, le toggle Modifier/Animation et le bouton "Nouveau projet"
 * partagent la même ligne via flex space-between. Le toggle garde sa marge
 * interne mais on neutralise sa margin-bottom (récupérée par .top-row). */
.vs-prompt__top-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 8px;
  flex-wrap: wrap;
}
.vs-prompt__top-row > .vs-prompt__mode {
  margin: 0;
  flex: 0 1 auto;
}
.vs-prompt__new-btn {
  flex: 0 0 auto;
  white-space: nowrap;
  padding: 6px 10px;
  font-size: 12px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.vs-prompt__new-btn svg {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
}
/* Mobile : si l'espace manque, le bouton wrap sous le toggle plutôt que
 * de pousser la pill "Animation" hors viewport. Le toggle prend 100% et
 * le bouton va à la ligne en pleine largeur. */
@media (max-width: 520px) {
  .vs-prompt__top-row > .vs-prompt__mode { flex: 1 1 100%; }
  .vs-prompt__new-btn { align-self: flex-end; }
}

/* Pill désactivée (outils IA bloqués sur import/handbuilt/AI-modifié). On
 * garde la pill visible pour que l'user comprenne ce qui EST normalement
 * disponible, mais inerte et grisée. Cf. _buildAiBlockedBanner pour le
 * message explicatif au-dessus. */
.vs-prompt__mode-pill.is-disabled,
.vs-prompt__mode-pill:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* ---------- AI-info chip (asset import / handbuilt / IA modifié)
 * Refonte 2026-05-14 : avant ça, c'était un banner inline dans le PromptPanel
 * (.vs-prompt__ai-blocked) qui occupait ~60px de hauteur en bas de l'écran -
 * volumineux et bloquant. Maintenant un petit chip rond "i" en position fixed
 * top-right, click ouvre une modal (vsConfirm / vsAlert) avec le message
 * complet + bouton de restore en mode ai-modified.
 *
 * 2 variantes via classe `.is-modified` :
 *   - default (non-ai)     : amber léger, info coming soon
 *   - .is-modified         : orange plus saturé, warn modif main
 *
 * Position : sous la topbar éditeur (top ~64px). z-index 100 = au-dessus du
 * canvas / panels mais SOUS les modaux dialogs (qui sont à 9000+).
 * ---------- */
.vs-ai-info-chip {
  position: fixed;
  top: 175px;
  /* À gauche du rpanel (= dans le coin haut-droite du canvas éditeur).
     Suit la largeur du panneau via la var --vs-rpanel-w (260/220/0px selon bp). */
  right: calc(var(--vs-rpanel-w, 260px) + 16px);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  background: rgba(255, 184, 76, 0.16);
  color: rgb(180, 100, 8);
  border: 1px solid rgba(255, 184, 76, 0.48);
  cursor: pointer;
  z-index: 100;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
  transition: background 120ms var(--vs-ease, ease),
              transform 80ms var(--vs-ease, ease),
              border-color 120ms var(--vs-ease, ease);
}
.vs-ai-info-chip:hover {
  background: rgba(255, 184, 76, 0.28);
  border-color: rgba(255, 184, 76, 0.7);
  transform: translateY(-1px);
}
.vs-ai-info-chip:active { transform: translateY(0); }
.vs-ai-info-chip:focus-visible {
  outline: 2px solid rgb(255, 215, 130);
  outline-offset: 2px;
}
.vs-ai-info-chip[hidden] { display: none; }
.vs-ai-info-chip svg {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
}
/* Variante "asset IA modifié à la main" : orange plus saturé pour signaler
 * une action utile derrière (Recharger la version générée), pas juste de
 * l'info passive. */
.vs-ai-info-chip.is-modified {
  background: rgba(255, 140, 50, 0.20);
  color: rgb(180, 78, 4);
  border-color: rgba(255, 140, 50, 0.60);
}
.vs-ai-info-chip.is-modified:hover {
  background: rgba(255, 140, 50, 0.32);
  border-color: rgba(255, 140, 50, 0.80);
}
/* Mobile/portrait : on remonte légèrement et on réduit la taille pour ne
 * pas masquer le contenu sous une topbar plus haute. */
@media (max-width: 540px) {
  .vs-ai-info-chip { top: 174px; right: 10px; width: 28px; height: 28px; }
  .vs-ai-info-chip svg { width: 14px; height: 14px; }
}

/* ---------- Tier toggles (Fast / Medium par axe mesh & anim)
 * Sélecteurs compacts au-dessus de la bar, mêmes pills que le mode toggle
 * mais plus petits + label inline ("Mesh:" / "Anim:" devant les pills).
 * Persistés en localStorage (cf. shared/genTiers.js).
 * ---------- */
.vs-prompt__tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 8px;
}
.vs-prompt__tier {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
}
.vs-prompt__tier-axis {
  font-size: 11px;
  font-weight: 600;
  color: var(--vs-text-muted);
  padding: 0 6px 0 8px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.vs-prompt__tier-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--vs-text-muted);
  font-size: 11px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
  white-space: nowrap;
}
.vs-prompt__tier-pill:hover:not(.is-active) {
  color: var(--vs-text);
  background: var(--vs-surface-3);
}
.vs-prompt__tier-pill.is-active {
  background: var(--vs-surface-1);
  color: var(--vs-text);
  border-color: var(--vs-accent-line);
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
}
/* Le SVG inline (cf. icons.js : .vs-icon → 1em × 1em) s'aligne déjà tout seul
 * via currentColor + line-height. On force juste une taille un peu plus
 * petite que le label pour rester compact dans la pill. */
.vs-prompt__tier-pill .vs-icon {
  width: 12px;
  height: 12px;
}

/* Hint inline qui apparaît quand au moins un axe est en Medium. flex-basis 100%
 * pour passer à la ligne sous les pills (le parent .vs-prompt__tiers est en
 * flex-wrap). Visuel discret : couleur warn léger, pas un bandeau alarmant. */
.vs-prompt__tier-hint {
  flex-basis: 100%;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--vs-text-muted);
  background: rgba(234, 179, 8, 0.08);
  border: 1px solid rgba(234, 179, 8, 0.28);
  border-radius: 8px;
}
.vs-prompt__tier-hint .vs-icon {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  color: #a16207;
}

/* Badge read-only dans le header des dialogs (GuidedDialog + AddAnimCandidates)
 * indiquant le tier actif pour le run en cours. Cf. _buildTierBadgeHtml. */
.vs-guided__tier-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 2px 8px;
  margin-left: 6px;
  font-size: 11px;
  font-weight: 500;
  color: var(--vs-text-muted);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
  white-space: nowrap;
}
.vs-guided__tier-badge-part {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.vs-guided__tier-badge-part + .vs-guided__tier-badge-part::before {
  content: "·";
  margin-right: 4px;
  color: var(--vs-text-dim);
}
.vs-guided__tier-badge .vs-icon {
  width: 11px;
  height: 11px;
}

/* ---------- Bar layout : [+] [textarea] [📎] [send]
 * Tous les contrôles à 32px ; boutons collés au bas du textarea quand il
 * passe en multi-ligne (style ChatGPT/Slack).
 * ---------- */
.vs-prompt__bar {
  display: flex; align-items: flex-end; gap: 6px;
}
.vs-prompt__plus-wrap { position: relative; flex: 0 0 auto; }
.vs-prompt__plus {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  color: var(--vs-text-muted);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
  flex: 0 0 auto;
}
.vs-prompt__plus:hover { background: var(--vs-surface-3); color: var(--vs-text); }
.vs-prompt__plus svg { width: 14px; height: 14px; }
.vs-prompt__plus-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  min-width: 280px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  padding: 4px;
  box-shadow: var(--vs-shadow-3);
  z-index: 60;
  display: none;
}
.vs-prompt__plus-menu.is-open { display: block; }
.vs-prompt__plus-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border-radius: var(--vs-r-sm);
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  color: var(--vs-text);
}
.vs-prompt__plus-item:hover { background: var(--vs-surface-3); }
.vs-prompt__plus-item svg { width: 18px; height: 18px; color: var(--vs-text-muted); flex: 0 0 auto; }
.vs-prompt__plus-item span { display: flex; flex-direction: column; gap: 2px; }
.vs-prompt__plus-item strong { font-size: var(--vs-fs-sm); font-weight: 600; }
.vs-prompt__plus-item em { font-style: normal; font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }

.vs-prompt__textarea {
  flex: 1 1 auto !important;
  min-width: 0 !important;
  min-height: 32px !important;
  max-height: 160px !important;
  background: #a4a4a41a !important;
  border-radius: 10px !important;
  border: none !important;
  color: var(--vs-text) !important;
  font-size: var(--vs-fs-md) !important;
  font-family: inherit !important;
  line-height: 1.45 !important;
  resize: none !important;
  outline: none !important;
  padding: 6px 4px !important;
}
.vs-prompt__textarea::placeholder { color: var(--vs-text-dim); }

/* Compaction de l'ImageAttachment dans le contexte de la prompt bar :
 * on cache le label "Image" et le hint texte ("coller - déposer - cliquer")
 * pour ne garder qu'un bouton-icône trombone à 32px. La preview (vignette
 * ronde) reste visible quand une image est attachée. */
.vs-prompt__attach { flex: 0 0 auto; align-self: center; }
.vs-prompt .vs-img-attach { gap: 4px; }
.vs-prompt .vs-img-attach__btn {
  width: 32px; height: 32px;
  padding: 0;
  border-radius: 50%;
  gap: 0;
}
.vs-prompt .vs-img-attach__btn svg { width: 14px; height: 14px; }
.vs-prompt .vs-img-attach__btn-label,
.vs-prompt .vs-img-attach__hint { display: none; }
.vs-prompt .vs-img-attach__preview { width: 28px; height: 28px; border-radius: 6px; }

.vs-prompt__send {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  background: var(--vs-accent);
  border: 1px solid transparent;
  border-radius: var(--vs-r-pill);
  color: #fff;
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: background 140ms var(--vs-ease), transform 100ms var(--vs-ease);
}
.vs-prompt__send:active:not(:disabled) { transform: scale(0.97); }
.vs-prompt__send:hover:not(:disabled) { background: var(--vs-accent-hi); }
.vs-prompt__send:disabled {
  background: var(--vs-surface-2);
  color: var(--vs-text-dim);
  cursor: not-allowed;
  border-color: var(--vs-border);
}
.vs-prompt__send.is-unaffordable {
  background: var(--vs-danger-low, rgba(220,38,38,0.10));
  color: var(--vs-danger, #dc2626);
  cursor: not-allowed;
  border-color: rgba(220,38,38,0.3);
}
.vs-prompt__send svg { width: 14px; height: 14px; }
.vs-prompt__send-cost {
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: var(--vs-r-pill);
  background: rgba(255,255,255,0.22);
  letter-spacing: 0.02em;
}
.vs-prompt__send.is-unaffordable .vs-prompt__send-cost { background: transparent; }

/* Flash rouge bordure quand setStatus(..., 'error') (ex: prompt vide,
 * crédits 0). Court, juste pour signaler "y a un souci, regarde le bon
 * endroit" - la zone de status texte a été supprimée car redondante. */
.vs-prompt.is-error-flash {
  border-color: var(--vs-danger, #dc2626) !important;
  box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12);
  animation: vs-prompt-shake 320ms var(--vs-ease) both;
}
@keyframes vs-prompt-shake {
  0%, 100% { transform: translateX(-50%); }
  25%      { transform: translateX(calc(-50% - 4px)); }
  75%      { transform: translateX(calc(-50% + 4px)); }
}

@media (max-width: 1100px) {
  .vs-prompt {
    width: min(560px, calc(100vw - var(--vs-toolbar-w) - 32px));
    left: calc(var(--vs-toolbar-w) + (100vw - var(--vs-toolbar-w)) / 2);
  }
}
@media (max-width: 720px) {
  /* Tient compte de la toolbar (116/52px selon breakpoint) ET du rpanel
     (220/0px selon breakpoint), via les vars `--vs-toolbar-w` et
     `--vs-rpanel-w` re-déclarées plus bas dans les bp 900/600. Sinon le
     prompt centré 50% chevauche la toolbar à gauche. */
  .vs-prompt {
    width: calc(100vw - var(--vs-toolbar-w) - var(--vs-rpanel-w) - 24px);
    left: calc(var(--vs-toolbar-w) + (100vw - var(--vs-toolbar-w) - var(--vs-rpanel-w)) / 2);
  }
}

/* =========================================================
   TIMELINE DOCK (bottom) - v2 (refonte 2026-05-10)
   3 rows horizontales empilées (Aseprite/sprite editor style) :
     1. Transport (play/step/dur/fps + dup/del frame)
     2. Anim tabs (pills horizontales scrollables, +anim/IA à droite)
     3. Frame rail full-width (drag&drop, +frame en bout)
   Palette light cohérente avec le reste de l'éditeur (rgba blanc + blur),
   accent indigo `--vs-accent` pour les éléments actifs.
   ========================================================= */
.vs-timeline {
  position: absolute;
  left: var(--vs-toolbar-w);
  right: var(--vs-rpanel-w);
  bottom: var(--vs-status-h);
  height: var(--vs-timeline-h);
  display: flex;
  flex-direction: column;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-top: 1px solid var(--vs-border);
  box-shadow: 0 -6px 22px -16px rgba(15, 23, 42, 0.18);
  pointer-events: all;
  z-index: 31;
  overflow: visible;
  transition: height 180ms var(--vs-ease);
  /* Vars tick : recalculées par JS quand le zoom change (pxPerMs × {100,500,1000}).
     Defaults = pxPerMs 0.25 (ne servent qu'au flash initial avant que JS pose). */
  --vs-tick-minor: 25px;
  --vs-tick-major: 125px;
  --vs-tick-second: 250px;
  --vs-px-per-ms: 0.25;
}

/* Spacer générique : pousse les éléments suivants vers la droite. */
.vs-timeline__spacer { flex: 1 1 auto; min-width: 0; }

/* ============ Resize handle (drag bord supérieur) + collapse / expand ============
   Pattern Blender / VSCode panel : l'user attrape la bordure haute du dock
   et drag pour ajuster la hauteur. En dessous d'un seuil → snap collapsed
   (mode badges only). État persisté dans localStorage. */
.vs-timeline__body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.vs-timeline__resize-handle {
  position: absolute;
  top: -4px; left: 0; right: 0;
  height: 8px;
  cursor: ns-resize;
  background: transparent;
  z-index: 32;
  touch-action: none;
}
.vs-timeline__resize-handle::before {
  content: '';
  position: absolute;
  top: 3px; left: 50%;
  transform: translateX(-50%);
  width: 36px; height: 3px;
  border-radius: 2px;
  background: var(--vs-border);
  opacity: 0;
  transition: opacity 120ms var(--vs-ease), background 120ms var(--vs-ease);
}
.vs-timeline__resize-handle:hover::before,
.vs-timeline.is-resizing .vs-timeline__resize-handle::before { opacity: 1; background: var(--vs-accent); }
.vs-timeline.is-resizing { transition: none; user-select: none; }

/* Bouton fold dans le transport row (à droite, après les frame actions). */
.vs-timeline__collapse-btn {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  background: transparent;
  color: var(--vs-text-muted);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-timeline__collapse-btn:hover { background: var(--vs-surface-3); color: var(--vs-text); border-color: var(--vs-text-muted); }
.vs-timeline__collapse-btn svg { width: 14px; height: 14px; }

/* Compact bar : visible uniquement en is-collapsed. Affiche play + badges
   d'animations pour permettre le switch sans déplier la timeline. */
.vs-timeline__compact {
  display: none;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  height: 100%;
  background: var(--vs-surface-2);
}
.vs-timeline.is-collapsed { height: 48px !important; }
.vs-timeline.is-collapsed .vs-timeline__body { display: none; }
.vs-timeline.is-collapsed .vs-timeline__compact { display: flex; }

.vs-timeline__compact .vs-play-btn {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  background: var(--vs-accent);
  color: #fff;
  border-radius: var(--vs-r-md);
  border: none;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), transform 80ms var(--vs-ease);
}
.vs-timeline__compact .vs-play-btn:hover { background: var(--vs-accent-hi); }
.vs-timeline__compact .vs-play-btn:active { transform: scale(0.95); }
.vs-timeline__compact .vs-play-btn svg { width: 12px; height: 12px; }

.vs-timeline__expand-btn {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  background: transparent;
  color: var(--vs-text-muted);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-timeline__expand-btn:hover { background: var(--vs-surface-3); color: var(--vs-text); border-color: var(--vs-text-muted); }
.vs-timeline__expand-btn svg { width: 14px; height: 14px; }

.vs-timeline__compact-anims {
  display: flex; align-items: center;
  gap: 4px;
  flex: 1 1 auto;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: rgba(15, 23, 42, 0.20) transparent;
  padding: 0 2px;
}
.vs-timeline__compact-anims::-webkit-scrollbar { height: 5px; }
.vs-timeline__compact-anims::-webkit-scrollbar-thumb { background: rgba(15, 23, 42, 0.20); border-radius: 3px; }

.vs-anim-badge {
  display: inline-flex; align-items: center;
  gap: 5px;
  padding: 5px 10px;
  background: var(--vs-surface-1);
  color: var(--vs-text-muted);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-anim-badge:hover { background: var(--vs-surface-3); color: var(--vs-text); border-color: var(--vs-text-muted); }
.vs-anim-badge.is-active {
  background: var(--vs-accent);
  color: #fff;
  border-color: var(--vs-accent);
}
.vs-anim-badge__meta { font-size: 10px; opacity: 0.7; font-variant-numeric: tabular-nums; }
.vs-timeline__compact-empty {
  font-size: 11.5px; color: var(--vs-text-dim); padding: 0 8px;
}

/* ============ Row 1 : Transport ============ */
.vs-timeline__transport {
  flex: 0 0 auto;
  display: flex; align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--vs-border);
  background: var(--vs-surface-2);
}
.vs-timeline__transport .vs-play-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  background: var(--vs-accent);
  color: #fff;
  border-radius: var(--vs-r-md);
  border: none;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 120ms var(--vs-ease), transform 80ms var(--vs-ease);
}
.vs-timeline__transport .vs-play-btn:hover { background: var(--vs-accent-hi); }
.vs-timeline__transport .vs-play-btn:active { transform: scale(0.95); }
.vs-timeline__transport .vs-play-btn svg { width: 14px; height: 14px; }

.vs-transport__steps {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 2px 4px;
}
.vs-transport__step {
  width: 26px; height: 24px;
  display: grid; place-items: center;
  background: transparent;
  color: var(--vs-text-muted);
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-transport__step:hover { background: var(--vs-surface-3); color: var(--vs-text); }
.vs-transport__step svg { width: 13px; height: 13px; }
.vs-transport__position {
  font-size: 11px;
  font-weight: 700;
  color: var(--vs-text);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  min-width: 48px;
  text-align: center;
}
.vs-transport__duration {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 4px 8px;
}
.vs-transport__duration-label {
  font-size: 9px;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--vs-text-dim);
  font-weight: 700;
}
.vs-transport__duration-input {
  width: 50px; height: 22px;
  padding: 0 4px;
  text-align: right;
  background: transparent;
  border: none;
  color: var(--vs-text);
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-family: inherit;
  outline: none;
  -moz-appearance: textfield;
  appearance: textfield;
}
.vs-transport__duration-input::-webkit-outer-spin-button,
.vs-transport__duration-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.vs-transport__duration-unit {
  font-size: 9px;
  color: var(--vs-text-dim);
  font-weight: 700;
  text-transform: uppercase;
}
.vs-transport__fps {
  font-size: 11px;
  color: var(--vs-text-muted);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}

/* Group d'actions dans la transport bar (anim actions / frame actions). */
.vs-transport__group {
  display: inline-flex; align-items: center;
  gap: 6px;
  padding-left: 10px;
  margin-left: 4px;
  border-left: 1px solid var(--vs-border);
}
/* Override `.vs-ai-btn` (default = full-width 100%) pour la transport :
   bouton compact en ligne, accent indigo, juste plus mis en avant que les
   autres `.vs-btn--sm` pour qu'on voie l'option IA. */
.vs-timeline__transport .vs-ai-btn {
  width: auto;
  height: 28px;
  padding: 0 10px;
  font-size: var(--vs-fs-xs);
  font-weight: 600;
  border-radius: var(--vs-r-md);
}
.vs-timeline__transport .vs-ai-btn .vs-ai-cost {
  font-size: 10px;
  padding: 1px 6px;
  margin-left: 4px;
}

/* ============ Row 2 : Anim list (pills horizontales scrollables) ============
   Liste directement visible (plus de dropdown). Si beaucoup d'anims,
   scroll-x natif. Chaque anim = pill compacte avec dot active + name +
   meta `Nf` + duration ms + delete hover-only. */
.vs-timeline__anims {
  flex: 0 0 auto;
  display: flex; align-items: center;
  padding: 6px 14px;
  border-bottom: 1px solid var(--vs-border);
  background: var(--vs-surface-1);
  overflow: hidden;
  min-height: 0;
}
.vs-anim-list {
  display: flex; align-items: center;
  gap: 6px;
  flex: 1 1 auto;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(15, 23, 42, 0.20) transparent;
  min-width: 0;
}
.vs-anim-list::-webkit-scrollbar { height: 6px; }
.vs-anim-list::-webkit-scrollbar-thumb { background: rgba(15, 23, 42, 0.20); border-radius: 3px; }
.vs-anim-list__empty {
  font-size: 11px;
  color: var(--vs-text-dim);
  font-style: italic;
  padding: 4px 8px;
}

/* Pill minimaliste : `Name  Nf · Nms  [🗑]`. Volontairement épurée :
   - Pas de "dot" décoratif (l'indigo plein de l'active suffit).
   - Pas d'input de durée (édition via la transport bar pour l'active).
   - Trash hover-only sur inactive, semi-visible sur active. */
.vs-anim-row {
  display: inline-flex; align-items: center;
  gap: 10px;
  padding: 0 6px 0 14px;
  height: 32px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
  cursor: pointer;
  flex: 0 0 auto;
  transition: background 140ms var(--vs-ease), border-color 140ms var(--vs-ease), color 140ms var(--vs-ease), box-shadow 140ms var(--vs-ease), transform 140ms var(--vs-ease);
  position: relative;
  font-family: inherit;
  color: var(--vs-text);
  user-select: none;
  white-space: nowrap;
}
.vs-anim-row:hover {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-strong);
  transform: translateY(-1px);
}
.vs-anim-row:active { transform: translateY(0); }
.vs-anim-row.is-active {
  background: var(--vs-accent);
  border-color: var(--vs-accent);
  color: #fff;
  box-shadow: 0 4px 14px -4px rgba(98, 68, 255, 0.45);
}
.vs-anim-row.is-active:hover {
  background: var(--vs-accent-hi);
  border-color: var(--vs-accent-hi);
  transform: translateY(-1px);
}
.vs-anim-row__name {
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
  overflow: hidden; text-overflow: ellipsis;
  max-width: 160px;
}
.vs-anim-row__meta {
  font-size: 10.5px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  opacity: 0.50;
  /* Sépare visuellement le name du meta avec un point mid-pill. */
  padding-left: 10px;
  border-left: 1px solid currentColor;
  /* le border est tracé en currentColor mais on l'opacifie via opacity */
}
.vs-anim-row.is-active .vs-anim-row__meta {
  opacity: 0.78;
}
.vs-anim-row__del {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  border-radius: 50%;
  color: currentColor;
  background: transparent;
  opacity: 0;
  transition: opacity 140ms var(--vs-ease), background 140ms var(--vs-ease);
  cursor: pointer;
  flex: 0 0 auto;
  border: none;
  font: inherit;
}
.vs-anim-row:hover .vs-anim-row__del,
.vs-anim-row:focus-within .vs-anim-row__del { opacity: 0.55; }
.vs-anim-row.is-active .vs-anim-row__del { opacity: 0.65; }
.vs-anim-row__del:hover { opacity: 1 !important; background: var(--vs-danger); color: #fff; }
.vs-anim-row.is-active .vs-anim-row__del:hover { background: rgba(0, 0, 0, 0.22); color: #fff; }
.vs-anim-row__del svg { width: 12px; height: 12px; }

/* ============ Row 3 : Frame rail temporelle ============
   Vraie timeline style montage vidéo (After Effects / Premiere) :
   - Largeur des tuiles proportionnelle au temps (frameDuration × pxPerMs)
   - Graduations au sol via CSS gradient (1 tick / 100ms, 1 majeur / 500ms)
   - Règle de temps en bas avec labels (0, 0.5s, 1s, 1.5s ...)
   - Playhead vertical accent qui suit la frame active
   - Click sur la zone vide = scrub (gérée en JS) */
.vs-timeline__rail-wrap {
  flex: 1 1 auto;
  position: relative;
  display: block;
  padding: 10px 14px 12px;
  /* Graduations de fond, dimensionnées via CSS vars recalculées par JS au
     zoom change. 4 couches superposées (style After Effects / Premiere) :
       - couche 1 : alternance subtile 1s/1s (rythme visuel "secondes")
       - couche 2 : tick fin 100ms (cadrage rapide)
       - couche 3 : tick majeur 500ms (sub-second markers)
       - couche 4 : tick second 1s (ancrage temporel principal) */
  background:
    repeating-linear-gradient(to right,
      transparent 0 var(--vs-tick-second),
      rgba(15, 23, 42, 0.022) var(--vs-tick-second),
      rgba(15, 23, 42, 0.022) calc(var(--vs-tick-second) * 2)),
    repeating-linear-gradient(to right,
      rgba(15, 23, 42, 0.05) 0 1px,
      transparent 1px var(--vs-tick-minor)),
    repeating-linear-gradient(to right,
      rgba(15, 23, 42, 0.13) 0 1px,
      transparent 1px var(--vs-tick-major)),
    repeating-linear-gradient(to right,
      rgba(15, 23, 42, 0.22) 0 1px,
      transparent 1px var(--vs-tick-second)),
    var(--vs-surface-1);
  background-position: 14px 0, 14px 0, 14px 0, 14px 0, 0 0;
  overflow-x: auto;
  overflow-y: hidden;
  min-height: 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(15, 23, 42, 0.20) transparent;
}
.vs-timeline__rail-wrap::-webkit-scrollbar { height: 8px; }
.vs-timeline__rail-wrap::-webkit-scrollbar-thumb { background: rgba(15, 23, 42, 0.20); border-radius: 4px; }

/* Canvas qui contient frames + ruler + playhead. Flex column avec
   `min-width: max-content` pour que sa width = somme des tuiles (donc le
   ruler vide stretch en column flexbox, et le playhead absolute peut se
   positionner sur toute la longueur). */
.vs-timeline__rail {
  display: flex;
  flex-direction: column;
  min-width: max-content;
  position: relative;
}
.vs-timeline__rail-frames {
  display: flex;
  align-items: center;
  gap: 0;          /* alignement temps/pixel parfait : pas de gap */
  padding: 4px 0 6px;
  position: relative;
  flex: 0 0 auto;
  min-height: 60px;
}

/* Règle de temps SOUS les frames : flex item qui stretch en column-flex,
   donc width = celle du parent rail (= somme des tuiles). Labels positionnés
   en absolute selon le timestamp réel (left = ms × pxPerMs). */
.vs-timeline__rail-ruler {
  position: relative;
  height: 18px;
  margin-top: 2px;
  flex: 0 0 auto;
  border-top: 1px solid rgba(15, 23, 42, 0.10);
}
.vs-timeline__rail-tick {
  position: absolute;
  top: 2px;
  font-size: 9.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--vs-text-dim);
  letter-spacing: 0.02em;
  transform: translateX(-50%);
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
}
.vs-timeline__rail-tick:first-child { transform: translateX(0); }

/* Playhead = curseur de lecture draggable (style montage vidéo). Le
   wrapper fait 14 px de large (zone de grab cliquable) avec margin-left
   négatif pour que le visuel soit centré sur la position X demandée par
   le JS (transform: translateX). La ligne 2 px et le handle au sommet
   sont rendus via ::after / ::before. */
.vs-timeline__rail-playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 14px;
  margin-left: -6px;          /* visuel centré sur transformX */
  cursor: ew-resize;
  pointer-events: auto;       /* override : on veut grabber */
  background: transparent;
  transform: translateX(0);
  transition: transform 80ms linear, opacity 120ms var(--vs-ease);
  z-index: 4;
  touch-action: none;
}
/* La ligne verticale 2 px (purement décorative). */
.vs-timeline__rail-playhead::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 6px;
  width: 2px;
  background: var(--vs-accent);
  border-radius: 1px;
  box-shadow: 0 0 8px rgba(98, 68, 255, 0.55);
  pointer-events: none;
}
/* Le handle drapeau au sommet (visuel "tu peux me grabber"). */
.vs-timeline__rail-playhead::before {
  content: '';
  position: absolute;
  top: -3px;
  left: 1px;
  width: 12px;
  height: 10px;
  background: var(--vs-accent);
  border-radius: 2px 2px 1px 1px;
  box-shadow: 0 2px 6px rgba(98, 68, 255, 0.45);
  pointer-events: none;
}
/* Pendant le drag : pas de transition pour suivre le pointer en temps réel. */
.vs-timeline__rail-playhead.is-dragging {
  transition: none;
  cursor: grabbing;
}
.vs-timeline__rail-playhead.is-dragging::before {
  transform: scale(1.15);
  background: var(--vs-accent-hi);
}

/* Tuile frame : largeur inline-stylée par JS = `frameDuration × pxPerMs`
   pour alignement temps/pixel parfait. Bordure droite seule (pas de full
   border) pour séparer visuellement les tuiles collées (gap=0).
   `transition: transform` = clé du shift smooth pendant le drag&drop des
   voisines (cf. _attachFrameDrag.applyShift). Easing avec léger overshoot
   donne un settling "snap" rappel des UIs DCC. */
.vs-timeline .vs-frame {
  flex: 0 0 auto;
  width: 50px;                                /* fallback ; JS override inline */
  height: 60px;
  display: grid;
  place-items: center;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-right: 1px solid rgba(15, 23, 42, 0.18);
  border-radius: 0;                            /* tuiles collées */
  color: var(--vs-text-muted);
  font-size: 13px;
  font-weight: 700;
  cursor: grab;
  position: relative;
  transition:
    transform 220ms cubic-bezier(0.22, 0.78, 0.18, 1.04),
    background 120ms var(--vs-ease),
    border-color 120ms var(--vs-ease),
    color 120ms var(--vs-ease),
    box-shadow 120ms var(--vs-ease);
  font-variant-numeric: tabular-nums;
  user-select: none;
  touch-action: none;
  margin-left: -1px;                           /* éviter le double-border */
  will-change: transform;
}
.vs-timeline .vs-frame:first-of-type {
  border-radius: var(--vs-r-sm) 0 0 var(--vs-r-sm);
  margin-left: 0;
}
.vs-timeline .vs-frame:hover {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-strong);
  color: var(--vs-text);
  z-index: 1;
}
.vs-timeline .vs-frame:active { cursor: grabbing; }
.vs-timeline .vs-frame.is-active {
  background: var(--vs-accent);
  border-color: var(--vs-accent);
  color: #fff;
  box-shadow: 0 4px 14px -4px rgba(98, 68, 255, 0.45);
  z-index: 2;
}
.vs-timeline .vs-frame.is-dragging {
  opacity: 0.30;
  cursor: grabbing;
  /* Source : pas de transform (reste en place visuel sous le ghost). On
     désactive juste la transition pour ne pas avoir d'effet sur le reset
     d'un drag-cancel. */
  transition: opacity 120ms var(--vs-ease);
}
.vs-timeline .vs-frame__num {
  font-size: 13px;
  line-height: 1;
}
.vs-timeline .vs-frame--add {
  background: transparent;
  border: 1px dashed var(--vs-border-strong);
  color: var(--vs-text-dim);
  width: 44px;
  cursor: pointer;
  border-radius: var(--vs-r-sm);
  margin-left: 8px;                            /* détache du dernier frame */
}
.vs-timeline .vs-frame--add:hover {
  background: var(--vs-surface-2);
  border-color: var(--vs-accent);
  color: var(--vs-accent-hi);
}
.vs-timeline .vs-frame--add svg { width: 14px; height: 14px; }
/* Ghost qui suit le pointer pendant le drag (clone position:fixed). Le JS
   set transform inline avec rotate(1.5deg) + scale(1.05) → impression
   "carte soulevée". Shadow forte suggère la profondeur. */
.vs-frame--ghost {
  opacity: 0.94;
  border-radius: var(--vs-r-md);
  background: var(--vs-accent) !important;
  border-color: var(--vs-accent) !important;
  color: #fff !important;
  box-shadow:
    0 18px 42px -8px rgba(98, 68, 255, 0.55),
    0 6px 18px -2px rgba(15, 23, 42, 0.32);
  transition: none;
}
/* Indicator vertical de drop. La transition `left` smoothise les
   déplacements quand l'user balaye le rail (le 1er positionnement reste
   instantané : géré en JS via style.transition='none' au mount). */
.vs-rail-indicator {
  position: absolute;
  top: 6px; bottom: 6px;
  width: 3px;
  background: var(--vs-accent);
  border-radius: 2px;
  pointer-events: none;
  box-shadow: 0 0 12px rgba(98, 68, 255, 0.65);
  margin-left: -1.5px;
  transition: left 200ms cubic-bezier(0.22, 0.78, 0.18, 1.04);
  z-index: 3;
}
.vs-rail-indicator::before,
.vs-rail-indicator::after {
  /* Petits "caps" triangle/diamant aux extrémités pour rappel After
     Effects "in/out point" markers. */
  content: '';
  position: absolute;
  left: 50%;
  width: 9px;
  height: 9px;
  background: var(--vs-accent);
  transform: translateX(-50%) rotate(45deg);
  border-radius: 1px;
  box-shadow: 0 0 6px rgba(98, 68, 255, 0.55);
}
.vs-rail-indicator::before { top: -4px; }
.vs-rail-indicator::after  { bottom: -4px; }

/* Spacer transparent à droite du bouton "+ frame" : étend le rail jusqu'à
   _effectiveRulerMs pour que le ruler ait toute sa longueur visible et
   qu'on puisse drag-drop "dans le vide" à droite. Pas de pointer-events
   pour ne pas voler les clicks scrub du wrap parent. */
.vs-timeline__rail-tail {
  flex: 0 0 auto;
  height: 60px;
  pointer-events: none;
  align-self: stretch;
}

/* ============ Zoom controls (transport bar) ============
   Compact group -/label/+ à droite du fps. Le label affiche la largeur en
   px d'une frame de référence (200ms) — repère plus parlant qu'un % abstrait. */
.vs-transport__zoom {
  display: inline-flex; align-items: center;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 2px 4px;
  gap: 1px;
  flex: 0 0 auto;
}
.vs-transport__zoom-btn {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: transparent;
  border: none;
  color: var(--vs-text-muted);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  font-family: inherit;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-transport__zoom-btn:hover {
  background: var(--vs-surface-3);
  color: var(--vs-text);
}
.vs-transport__zoom-btn:active { transform: scale(0.94); }
.vs-transport__zoom-label {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--vs-text);
  font-variant-numeric: tabular-nums;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0 8px;
  height: 22px;
  white-space: nowrap;
  border-radius: var(--vs-r-sm);
  font-family: inherit;
  letter-spacing: 0.01em;
  transition: background 120ms var(--vs-ease);
}
.vs-transport__zoom-label:hover { background: var(--vs-surface-3); }

/* ============ Context menu Windows-style ============
   Singleton géré par VoxelEditorUI._showContextMenu. Position fixed,
   shadow soft, items hover indigo plein. Ouvre sur right-click frame /
   anim row. Ferme sur outside-click, Escape, scroll, resize. */
.vs-context-menu {
  position: fixed;
  z-index: 90;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  box-shadow:
    0 12px 32px -10px rgba(15, 23, 42, 0.30),
    0 2px 6px -2px rgba(15, 23, 42, 0.18);
  padding: 4px;
  min-width: 196px;
  font-family: inherit;
  user-select: none;
  animation: vs-context-menu-in 100ms var(--vs-ease);
}
@keyframes vs-context-menu-in {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.vs-context-menu__item {
  display: flex; align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 10px 7px 8px;
  background: transparent;
  border: none;
  color: var(--vs-text);
  font-size: 12.5px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  border-radius: var(--vs-r-sm);
  font-family: inherit;
  transition: background 80ms var(--vs-ease), color 80ms var(--vs-ease);
}
.vs-context-menu__item:hover:not(:disabled),
.vs-context-menu__item:focus:not(:disabled) {
  background: var(--vs-accent);
  color: #fff;
  outline: none;
}
.vs-context-menu__item:disabled {
  opacity: 0.40;
  cursor: not-allowed;
}
.vs-context-menu__icon {
  width: 14px; height: 14px;
  display: grid; place-items: center;
  flex: 0 0 14px;
  color: var(--vs-text-muted);
}
.vs-context-menu__item:hover:not(:disabled) .vs-context-menu__icon,
.vs-context-menu__item:focus:not(:disabled) .vs-context-menu__icon { color: rgba(255, 255, 255, 0.88); }
.vs-context-menu__icon svg { width: 14px; height: 14px; }
.vs-context-menu__label { flex: 1 1 auto; }
.vs-context-menu__shortcut {
  font-size: 10px;
  color: var(--vs-text-dim);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  margin-left: 12px;
}
.vs-context-menu__item:hover:not(:disabled) .vs-context-menu__shortcut,
.vs-context-menu__item:focus:not(:disabled) .vs-context-menu__shortcut { color: rgba(255, 255, 255, 0.65); }
.vs-context-menu__sep {
  height: 1px;
  margin: 4px 0;
  background: var(--vs-border);
}
.vs-context-menu__item--danger { color: var(--vs-danger); }
.vs-context-menu__item--danger .vs-context-menu__icon { color: var(--vs-danger); }
.vs-context-menu__item--danger:hover:not(:disabled),
.vs-context-menu__item--danger:focus:not(:disabled) {
  background: var(--vs-danger);
  color: #fff;
}
.vs-context-menu__item--danger:hover:not(:disabled) .vs-context-menu__icon,
.vs-context-menu__item--danger:focus:not(:disabled) .vs-context-menu__icon { color: rgba(255, 255, 255, 0.92); }

/* =========================================================
   LOADING OVERLAY
   ========================================================= */
.vs-loading-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background: rgba(246, 247, 250, 0.78);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms var(--vs-ease);
}
.vs-loading-overlay.visible {
  opacity: 1;
  pointer-events: auto;
}
.vs-loader-content {
  text-align: center;
  perspective: 600px;
}
.vs-loader-cube {
  position: relative;
  width: 72px; height: 72px;
  margin: 0 auto 28px;
  transform-style: preserve-3d;
  /* 3.2s : tour plus lent qu'avant (2.4s), donne un rythme plus posé pour
     l'overlay plein-écran de l'éditeur - on contemple la rotation au lieu
     de subir un strobing. */
  animation: vs-cube-spin 3.2s infinite linear;
}
.vs-cube-face {
  position: absolute;
  width: 72px; height: 72px;
  background: linear-gradient(135deg, var(--vs-accent), #4fc3f7);
  border: 1px solid rgba(255, 255, 255, 0.18);
}
.vs-cube-front  { transform: translateZ(36px); }
.vs-cube-back   { transform: rotateY(180deg) translateZ(36px); opacity: 0.6; }
.vs-cube-right  { transform: rotateY(90deg)  translateZ(36px); opacity: 0.8; }
.vs-cube-left   { transform: rotateY(-90deg) translateZ(36px); opacity: 0.8; }
.vs-cube-top    { transform: rotateX(90deg)  translateZ(36px); opacity: 0.9; }
.vs-cube-bottom { transform: rotateX(-90deg) translateZ(36px); opacity: 0.5; }

@keyframes vs-cube-spin {
  0%   { transform: rotateX(-25deg) rotateY(0deg); }
  100% { transform: rotateX(-25deg) rotateY(360deg); }
}

.vs-loader-text {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--vs-text);
  margin-bottom: 6px;
  letter-spacing: -0.01em;
}
.vs-loader-subtext {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  min-height: 1.2em;
  font-variant-numeric: tabular-nums;
}

body.loading .vs-prompt,
body.loading .vs-edit-subnav,
body.loading .vs-toolbar,
body.loading .vs-rpanel { pointer-events: none; }

/* =========================================================
   RESPONSIVE
   ========================================================= */
@media (max-width: 900px) {
  :root {
    --vs-rpanel-w: 220px;
    --vs-timeline-h: 208px;
  }
  .vs-edit-subnav__name { flex-basis: 140px; }
}

@media (max-width: 600px) {
  :root {
    --vs-toolbar-w: 52px;
    --vs-rpanel-w: 0px;
    --vs-timeline-h: 0px;
  }
  .vs-rpanel,
  .vs-timeline { display: none; }
  button.vs-tool { padding: 6px 2px; }
  button.vs-tool svg { width: 16px; height: 16px; }
  .vs-tool__label { display: none; }

  /* ----- Subnav éditeur compactée -----
     Sur 360-600px on a (publish + sprite sheet + export + help + name + undo/redo)
     dans une seule rangée. Sans cette règle, ça wrap ou ça déborde derrière
     la toolbar. On cache les labels texte des boutons d'action et on garde
     uniquement les icônes. Le bouton Export garde son chevron. */
  .vs-edit-subnav { gap: 6px; padding: 0 8px; }
  .vs-edit-subnav__name {
    flex: 1 1 auto;
    min-width: 0;
    flex-basis: auto;
    font-size: var(--vs-fs-md);
  }
  .vs-edit-subnav__publish span,
  .vs-edit-subnav__spritesheet span,
  .vs-edit-subnav__actions .vs-btn--primary span:not(.vs-prompt__send-cost) {
    display: none;
  }
  .vs-edit-subnav__actions .vs-btn { padding: 0 8px; }
  /* Le wrap export = container du dropdown menu, on garde l'icône + chevron. */
  .vs-edit-subnav__actions .vs-btn--primary svg + svg { margin-left: 2px; }
  /* Center (undo/redo) reste utile au tap, juste un poil plus petit. */
  .vs-history { padding: 1px; }
  .vs-history .vs-btn--icon { width: 24px; height: 24px; }

  /* Dropdown menu Télécharger (et autres .vs-menu) sur mobile :
     min-width 240px + items title+desc 2 lignes = menu qui mange ~70% du
     viewport et bouffe la lecture du canvas + prompt panel derrière. On
     contraint la largeur, on serre le padding des items, et on cap le titre
     à 1 ligne (ellipsis). La desc reste sur 2 lignes max. */
  .vs-menu {
    min-width: 0;
    width: max-content;
    max-width: calc(100vw - 16px);
    padding: 4px;
  }
  .vs-menu__item { padding: 7px 10px; gap: 1px; }
  .vs-menu__item-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .vs-menu__item-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.3;
  }

  /* ----- PromptPanel : compactage des pills mobiles. La largeur/position
     est posée par le bp 720px plus haut (calc avec --vs-toolbar-w/--vs-rpanel-w
     qui sont 52/0 à ce breakpoint). On ne touche qu'au padding et bottom. */
  .vs-prompt {
    bottom: calc(var(--vs-status-h) + 8px);
    padding: 4px 4px 4px 6px;
    border-radius: var(--vs-r-lg);
  }
  /* Mode toggle (Mesh+anim / Mesh statique ou Modifier / Animation) :
     pills plus serrées, gap réduit. */
  .vs-prompt__mode { padding: 3px; margin: 0 0 6px; gap: 4px; }
  .vs-prompt__mode-pill { padding: 3px 8px; font-size: 11px; }
  .vs-prompt__mode-dot { width: 6px; height: 6px; }
  /* Tier toggles : empilés sur 1 ligne mais pills micro. */
  .vs-prompt__tiers { gap: 4px; margin: 0 0 6px; }
  .vs-prompt__tier { padding: 2px; gap: 2px; }
  .vs-prompt__tier-axis { font-size: 10px; padding: 0 4px 0 6px; }
  .vs-prompt__tier-pill { padding: 2px 7px; font-size: 10px; }
  /* Bar : boutons à 28px (vs 32 desktop). Sur mobile on wrap : textarea full
     width au-dessus, rangée [+] 📎 ... [send] en dessous (pattern ChatGPT).
     Sinon les 3 boutons fixes mangent ~150px sur ~280px de bar et le textarea
     wrappe mot par mot. order:-1 force le textarea en haut sans toucher au DOM
     (le menu du `+` reste positionné par rapport à son wrap). */
  .vs-prompt__bar { gap: 4px; flex-wrap: wrap; row-gap: 6px; }
  .vs-prompt__plus,
  .vs-prompt .vs-img-attach__btn { width: 28px; height: 28px; }
  .vs-prompt__send { height: 28px; padding: 0 10px; }
  .vs-prompt__textarea {
    flex: 1 1 100% !important;
    order: -1;
    min-height: 36px !important;
    max-height: 120px !important;
    padding: 8px 10px !important;
  }
  /* Pousse le send tout à droite de la rangée d'actions. */
  .vs-prompt__send { margin-left: auto; }

  /* ----- Bloc couleurs en bottom sheet -----
     Sur 52px de toolbar mobile, recents (4 cols) + palette tabs (3 onglets)
     + palette grid (4 cols) tassent en swatches ~10px tactilement morts.
     On masque ce bloc et on en fait un panneau bottom sheet ouvert via le
     toggle chevron sous le big swatch. Le picker natif (big swatch) reste
     accessible direct dans la toolbar - le sheet n'est qu'un raccourci
     vers les couleurs prédéfinies / récentes. */
  .vs-toolbar__color-panel {
    display: none;
  }
  .vs-toolbar__color-toggle {
    display: grid;
    place-items: center;
    width: 100%;
    height: 28px;
    background: var(--vs-surface-2);
    border: 1px solid var(--vs-border);
    border-radius: var(--vs-r-sm);
    color: var(--vs-text-muted);
    cursor: pointer;
    transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), transform 180ms var(--vs-ease);
    padding: 0;
  }
  .vs-toolbar__color-toggle svg { width: 14px; height: 14px; }
  .vs-toolbar__color-toggle:hover { background: var(--vs-surface-3); color: var(--vs-text); }
  .vs-toolbar__color.is-panel-open .vs-toolbar__color-toggle {
    transform: rotate(180deg);
    color: var(--vs-accent);
    border-color: var(--vs-accent);
  }
  .vs-toolbar__color.is-panel-open .vs-toolbar__color-panel {
    display: flex;
    flex-direction: column;
    gap: 12px;
    position: fixed;
    left: calc(var(--vs-toolbar-w) + 8px);
    right: 8px;
    bottom: calc(var(--vs-status-h) + 8px);
    max-height: 60vh;
    overflow-y: auto;
    padding: 14px;
    background: var(--vs-surface-1);
    border: 1px solid var(--vs-border-strong);
    border-radius: var(--vs-r-lg);
    box-shadow: var(--vs-shadow-3);
    z-index: 210;
    /* Petit slide-in depuis le bas pour signaler l'ouverture. */
    animation: vs-color-sheet-in 160ms var(--vs-ease) both;
  }
  /* Recents et palette grid : passent en grid 8 colonnes pour profiter de
     toute la largeur disponible (≈ viewport - toolbar - 16px de marges). */
  .vs-toolbar__color.is-panel-open .vs-color-recents,
  .vs-toolbar__color.is-panel-open .vs-palette-grid {
    grid-template-columns: repeat(8, 1fr);
    gap: 6px;
  }
  /* Palette tabs : un peu plus grandes au tap. */
  .vs-toolbar__color.is-panel-open .vs-palette-tab {
    height: 36px;
  }
  .vs-toolbar__color.is-panel-open .vs-palette-tab svg { width: 18px; height: 18px; }

  /* ----- Anti-overlap : la subnav globale (vs-c-nav) a son drawer
     `position: absolute; top: 100%`. Quand il est ouvert il doit
     dominer le canvas et le prompt-panel. z-index 100 déjà posé sur
     `body.vs-edit > .vs-c-nav` plus haut, rien à ajouter ici - juste
     un rappel pour les futurs lecteurs. */
}
@keyframes vs-color-sheet-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}

/* Override media query 720 pour la subnav (entre 601 et 720 on garde le RPanel
   donc on n'est pas dans le bloc 600px ci-dessus). Les boutons subnav avaient
   déjà tendance à overflow vers 700px. */
@media (max-width: 720px) and (min-width: 601px) {
  .vs-edit-subnav { gap: 8px; padding: 0 10px; }
  .vs-edit-subnav__publish span,
  .vs-edit-subnav__spritesheet span { display: none; }
  .vs-edit-subnav__actions .vs-btn { padding: 0 10px; }
}

/* =========================================================
   COMPTE / AUTH (PromptPanel top-bar)
   ========================================================= */
.vs-account {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.vs-account__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  font-size: 11px;
  font-weight: 600;
  background: var(--vs-surface-2);
  color: var(--vs-text);
  border: 1px solid var(--vs-border);
  border-radius: 999px;
  text-decoration: none;
  transition: background .15s;
}
.vs-account__link:hover {
  background: var(--vs-surface-3);
}
.vs-account__pill {
  background: var(--vs-accent);
  color: #fff;
  font-size: 9px;
  padding: 1px 6px;
  border-radius: 999px;
  text-transform: uppercase;
  font-weight: 700;
  letter-spacing: .04em;
}

/* ---------- Publish row (PromptPanel → bouton Publier galerie) ---------- */
.vs-publish-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-publish-row__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  text-decoration: none;
  padding: 6px 10px;
  border-radius: var(--vs-r-sm);
  transition: color 120ms var(--vs-ease), background 120ms var(--vs-ease);
}
.vs-publish-row__link:hover { color: var(--vs-text); background: var(--vs-surface-3); }

/* ---------- Publish modal (PublishDialog.js) ---------- */
.vs-publish {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.vs-publish__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(6px);
  cursor: pointer;
}
.vs-publish__panel {
  position: relative;
  width: min(720px, 100%);
  max-height: 90vh;
  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-publish__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
}
.vs-publish__head h3 { margin: 0; font-size: var(--vs-fs-lg); font-weight: 600; }
.vs-publish__close {
  font-size: 18px;
  color: var(--vs-text-muted);
  padding: 4px 10px;
  border-radius: var(--vs-r-sm);
  background: transparent;
}
.vs-publish__close:hover { color: var(--vs-text); background: var(--vs-surface-2); }

.vs-publish__body {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 20px;
  padding: 20px;
  overflow: auto;
}
@media (max-width: 600px) {
  .vs-publish__body { grid-template-columns: 1fr; }
}
.vs-publish__thumb-block { display: flex; flex-direction: column; gap: 8px; }
.vs-publish__thumb {
  aspect-ratio: 1 / 1;
  background: var(--vs-surface-2);
  border-radius: var(--vs-r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--vs-border);
}
.vs-publish__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vs-publish__thumb-loading {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-dim);
  text-align: center;
  padding: 14px;
}
.vs-publish__refresh {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  padding: 6px 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  transition: background 120ms var(--vs-ease);
}
.vs-publish__refresh:hover { background: var(--vs-surface-3); color: var(--vs-text); }
.vs-publish__form { display: flex; flex-direction: column; gap: 14px; }

/* ---------- Suggest row (auto-fill via Flash Lite) ----------
 * Petite ligne discrète au-dessus des champs : à gauche, un label
 * "✨ L'IA propose…" avec mini-spinner pendant le call ; à droite,
 * un bouton "↻ Re-suggérer" pour relancer la suggestion.
 * Quand pas en loading et message != stale : auto-fade après 3s
 * (cf. _showSuggestText). */
.vs-publish__suggest-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 22px;
}
.vs-publish__suggest-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  font-style: italic;
}
.vs-publish__suggest-label.is-loading { color: var(--vs-accent-hi); }
.vs-publish__suggest-spinner {
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 2px solid var(--vs-border);
  border-top-color: var(--vs-accent);
  animation: vs-publish-suggest-spin 700ms linear infinite;
  display: inline-block;
  flex: 0 0 auto;
}
.vs-publish__suggest-label:not(.is-loading) .vs-publish__suggest-spinner {
  display: none;
}
@keyframes vs-publish-suggest-spin {
  to { transform: rotate(360deg); }
}
.vs-publish__resuggest {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  font-size: var(--vs-fs-xs);
  font-weight: 500;
  color: var(--vs-accent-hi);
  background: rgba(98, 68, 255, 0.08);
  border: 1px solid rgba(98, 68, 255, 0.20);
  border-radius: var(--vs-r-pill);
  cursor: pointer;
  font-family: inherit;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-publish__resuggest:hover:not(:disabled) {
  background: rgba(98, 68, 255, 0.14);
  border-color: rgba(98, 68, 255, 0.35);
}
.vs-publish__resuggest:disabled { opacity: 0.5; cursor: wait; }
.vs-publish__resuggest svg { width: 11px; height: 11px; }

.vs-publish__field { display: flex; flex-direction: column; gap: 5px; }
.vs-publish__field span { font-size: var(--vs-fs-sm); color: var(--vs-text-muted); font-weight: 500; }
.vs-publish__field input,
.vs-publish__field textarea {
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 9px 12px;
  font-size: var(--vs-fs-md);
  color: var(--vs-text);
  font-family: inherit;
  outline: none;
  resize: vertical;
  transition: border-color 120ms var(--vs-ease), box-shadow 120ms var(--vs-ease);
}
.vs-publish__field input:focus,
.vs-publish__field textarea:focus {
  border-color: var(--vs-border-focus);
  box-shadow: var(--vs-glow);
}
.vs-publish__hint {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  margin: 0;
  line-height: 1.5;
}
.vs-publish__foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 14px 20px;
  border-top: 1px solid var(--vs-border);
  background: var(--vs-surface-1);
}
.vs-publish__btn {
  padding: 9px 18px;
  font-size: var(--vs-fs-md);
  font-weight: 600;
  border-radius: var(--vs-r-md);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), filter 120ms var(--vs-ease);
}
.vs-publish__btn:disabled { opacity: 0.5; cursor: not-allowed; }
.vs-publish__btn--primary {
  background: var(--vs-accent);
  color: white;
  border: none;
}
.vs-publish__btn--primary:hover:not(:disabled) { background: var(--vs-accent-hi); }
.vs-publish__btn--ghost {
  background: transparent;
  color: var(--vs-text);
  border: 1px solid var(--vs-border-strong);
}
.vs-publish__btn--ghost:hover { background: var(--vs-surface-2); }
.vs-publish__status {
  padding: 10px 20px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  border-top: 1px solid var(--vs-border);
}
.vs-publish__status:empty { display: none; }
.vs-publish__status.is-error { color: var(--vs-danger); background: var(--vs-danger-low); }
.vs-publish__status.is-success { color: var(--vs-success); background: var(--vs-success-low); }
.vs-publish__status.is-info { color: var(--vs-text-muted); }

/* =========================================================
   SPRITE SHEET DIALOG - export 2D pixel-art (8 directions
   + top/bottom × animations × frames) en PNG transparent.
   Layout : settings à gauche, preview large à droite.
   ========================================================= */
.vs-sheet {
  position: fixed;
  inset: 0;
  z-index: 220;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.vs-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.5);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
}
.vs-sheet__panel {
  position: relative;
  width: min(1100px, 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__panel--narrow { width: min(820px, 100%); }
.vs-sheet__panel--max {
  width: 100%;
  height: 100vh;
  max-width: 100vw;
  max-height: 100vh;
  border-radius: 0;
}
.vs-sheet__panel--max .vs-sheet__settings { display: none; }
.vs-sheet__panel--max .vs-sheet__body { grid-template-columns: 1fr; }
.vs-sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
}
.vs-sheet__head h3 {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 600;
}
.vs-sheet__close {
  font-size: 22px;
  line-height: 1;
  color: var(--vs-text-muted);
  padding: 2px 10px;
  border-radius: var(--vs-r-sm);
  background: transparent;
  border: none;
  cursor: pointer;
}
.vs-sheet__close:hover {
  color: var(--vs-text);
  background: var(--vs-surface-2);
}
.vs-sheet__head-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}
.vs-sheet__icon-btn {
  font-size: 16px;
  line-height: 1;
  color: var(--vs-text-muted);
  padding: 4px 10px;
  border-radius: var(--vs-r-sm);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-sheet__icon-btn:hover {
  color: var(--vs-text);
  background: var(--vs-surface-3);
}
.vs-sheet__body {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 20px;
  padding: 20px;
  overflow: auto;
  min-height: 0;
}
.vs-sheet__body--single {
  grid-template-columns: 1fr;
}
@media (max-width: 720px) {
  .vs-sheet__body { grid-template-columns: 1fr; }
}
.vs-sheet__hint {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-dim);
  margin: 4px 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.vs-sheet__settings {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.vs-sheet__group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.vs-sheet__label {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  font-weight: 600;
}
.vs-sheet__radios,
.vs-sheet__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.vs-sheet__chips-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
}
.vs-sheet__chips-group + .vs-sheet__chips-group { margin-top: 6px; }
.vs-sheet__chips-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.vs-sheet__chips-group-title {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.vs-sheet__chips-toggle {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  padding: 2px 8px;
  cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  transition: background 120ms var(--vs-ease);
}
.vs-sheet__chips-toggle:hover {
  background: var(--vs-surface-3);
  color: var(--vs-text);
}
.vs-sheet__chips-group .vs-sheet__chip {
  background: var(--vs-surface-1);
}
.vs-sheet__chips-group .vs-sheet__chip:hover {
  background: var(--vs-surface-2);
}
.vs-sheet__radio,
.vs-sheet__chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  cursor: pointer;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  user-select: none;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-sheet__radio:hover,
.vs-sheet__chip:hover {
  background: var(--vs-surface-3);
}
.vs-sheet__radio input,
.vs-sheet__chip input {
  margin: 0;
  accent-color: var(--vs-accent);
}
.vs-sheet__radio:has(input:checked),
.vs-sheet__chip:has(input:checked) {
  background: var(--vs-accent-low, rgba(79, 195, 247, 0.15));
  border-color: var(--vs-accent);
  color: var(--vs-text);
}
.vs-sheet__preview-wrap {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  min-height: 0;
}
.vs-sheet__progress {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.vs-sheet__section-title {
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  color: var(--vs-text);
  margin: 0 0 8px;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.vs-sheet__count {
  font-weight: 400;
  color: var(--vs-text-muted);
  font-size: var(--vs-fs-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.vs-sheet__animated,
.vs-sheet__static {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
/* Grille des aperçus animés - GROUPÉE PAR ANIMATION. La grille est un flex
   column qui contient un .vs-sheet__anim-group par anim ; chaque groupe a
   un titre discret (pixel font, dim) puis une sous-grille auto-fill des
   cellules de vues. Cf. shared/AnimatedPreviewGrid.js pour le markup. */
.vs-sheet__anim-grid {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 10px;
  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: 100px;
}
.vs-sheet__anim-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.vs-sheet__anim-group__title {
  font-family: var(--vs-font-pixel);
  font-size: var(--vs-fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--vs-text-dim);
  padding: 2px 4px 0;
}
.vs-sheet__anim-group__cells {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 10px;
}
.vs-sheet__panel--max .vs-sheet__anim-group__cells {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}
.vs-sheet__anim-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  transition: border-color 120ms var(--vs-ease);
}
.vs-sheet__anim-cell.is-clickable { cursor: pointer; }
.vs-sheet__anim-cell.is-clickable:hover { border-color: var(--vs-accent); }
.vs-sheet__anim-canvas {
  display: block;
  background:
    repeating-conic-gradient(#e2e8f0 0% 25%, #f8fafc 0% 50%) 50% / 8px 8px;
  border-radius: var(--vs-r-sm);
}
.vs-sheet__anim-label {
  font-size: var(--vs-fs-xs);
  color: var(--vs-text-muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  text-align: center;
  line-height: 1.3;
  word-break: break-word;
  max-width: 100%;
}
.vs-sheet__preview {
  flex: 1;
  min-height: 200px;
  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: flex-start;
  justify-content: center;
}
.vs-sheet__preview canvas,
.vs-sheet__preview img {
  display: block;
  background: transparent;
  max-width: 100%;
  height: auto;
}

/* ---- Mode "zoomable" (cf. CommunitySpriteSheetDialog._mountZoom) ----
   Quand ZoomPan est actif, le preview devient un viewport overflow:hidden
   et son enfant `.vs-sheet__zoom-content` est transformé en CSS pour le
   zoom/pan. On override les règles de centrage flex + le max-width:100%
   sur l'image/canvas, qui empêcherait le zoom > 100%. */
.vs-sheet__preview--zoomable {
  position: relative;
  overflow: hidden;
  display: block;
  padding: 0;
  cursor: grab;
  user-select: none;
  touch-action: none;
}
.vs-sheet__preview--zoomable.is-panning {
  cursor: grabbing;
}
.vs-sheet__zoom-content {
  position: absolute;
  top: 0; left: 0;
  display: inline-block;
  transform-origin: 0 0;
  /* will-change posé par ZoomPan en JS pour que la 1re frame ne lague pas */
}
.vs-sheet__preview--zoomable .vs-sheet__zoom-content > canvas,
.vs-sheet__preview--zoomable .vs-sheet__zoom-content > img {
  display: block;
  max-width: none;       /* ZoomPan gère la taille via transform */
  max-height: none;
  width: auto;
  height: auto;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  pointer-events: none;  /* le drag du parent n'est pas mangé par <img> */
}

/* Overlay barre de contrôles : zoom in / out / fit / 100% + readout. Fixé
   en bas-droite du viewport, semi-translucide, n'absorbe pas le scroll. */
.vs-sheet__zoom-ctrl {
  position: absolute;
  bottom: 10px;
  right: 10px;
  display: inline-flex;
  align-items: stretch;
  gap: 1px;
  padding: 0;
  background: rgba(15, 23, 42, 0.78);
  border-radius: var(--vs-r-md);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  z-index: 2;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  user-select: none;
}
.vs-sheet__zoom-btn {
  appearance: none;
  background: transparent;
  border: none;
  color: #fff;
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  width: 32px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 100ms var(--vs-ease);
}
.vs-sheet__zoom-btn:hover {
  background: rgba(255, 255, 255, 0.12);
}
.vs-sheet__zoom-btn:active {
  background: rgba(255, 255, 255, 0.22);
}
.vs-sheet__zoom-btn:first-child { border-radius: var(--vs-r-md) 0 0 var(--vs-r-md); }
.vs-sheet__zoom-btn:last-child  { border-radius: 0 var(--vs-r-md) var(--vs-r-md) 0; }
.vs-sheet__zoom-btn--readout {
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-width: 50px;
  letter-spacing: 0.02em;
}
.vs-sheet__zoom-btn--readout::after {
  /* Petit séparateur visuel à gauche/droite du readout, lisible quand le
     fond du panel/dialog change. */
}
.vs-sheet__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);
}
.vs-sheet__spacer { flex: 1; }
.vs-sheet__btn {
  padding: 9px 18px;
  font-size: var(--vs-fs-md);
  font-weight: 600;
  border-radius: var(--vs-r-md);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), filter 120ms var(--vs-ease);
  border: 1px solid transparent;
}
.vs-sheet__btn:disabled { opacity: 0.45; cursor: not-allowed; }
.vs-sheet__btn--primary {
  background: var(--vs-accent);
  color: white;
}
.vs-sheet__btn--primary:hover:not(:disabled) { background: var(--vs-accent-hi); }
.vs-sheet__btn--ghost {
  background: transparent;
  color: var(--vs-text);
  border-color: var(--vs-border-strong);
}
.vs-sheet__btn--ghost:hover:not(:disabled) { background: var(--vs-surface-2); }

/* Mobile : footer sprite sheet a 3 boutons (Fermer + Admin▾ + Télécharger▾)
   qui se touchent ric-rac sur ~360px. On serre padding/font pour qu'ils
   tiennent confortablement sans wrap. Le menu déroulant lui-même devient
   `position: fixed` (cf. community.css @media 600). */
@media (max-width: 600px) {
  .vs-sheet__foot {
    padding: 10px 12px;
    gap: 6px;
  }
  .vs-sheet__btn {
    padding: 8px 12px;
    font-size: var(--vs-fs-sm);
  }
}

/* =========================================================
   EDITORIAL - composants signature partagés (landing, community,
   account, admin). Le langage visuel : eyebrow uppercase mono +
   carré coloré, titre noir massif, italique serif souligné pour
   l'accent éditorial, badges code mono.
   ========================================================= */

/* Eyebrow - petite étiquette uppercase mono avec carré coloré
   accent devant. Ex : "■ WORKFLOW", "■ VOXELS ANIMÉS - GÉNÉRATION".
   Le carré reprend l'accent courant via var(--vs-accent), donc
   change avec [data-accent="..."]. */
.vs-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--vs-font-pixel);
  font-size: var(--vs-fs-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--vs-text-muted);
  margin: 0;
}
.vs-eyebrow::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  background: var(--vs-accent);
  flex: 0 0 auto;
}

/* Headline - titre éditorial massif. À mettre directement comme
   classe sur <h1>/<h2>. <em> à l'intérieur passe en pixel display
   (Superstar) avec un soulignement à l'accent (effet "marker pen"). */
.vs-headline {
  font-family: var(--vs-font);
  font-size: clamp(40px, 6vw, 80px);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.035em;
  color: var(--vs-text);
  margin: 0;
}
.vs-headline em {
  font-family: var(--vs-font-pixel);
  font-style: normal;          /* pixel font : pas de vraie italic */
  font-weight: 400;
  letter-spacing: 0;           /* tracking neutre, le bitmap se gère seul */
  /* Désactive l'AA pour préserver le rendu pixel-perfect ; sur Firefox
     ce n'est pas honoré mais reste lisible. */
  -webkit-font-smoothing: none;
  -moz-osx-font-smoothing: unset;
  /* Soulignement marker - barre solide à l'accent en bas du texte.
     Décalée à 6px (vs 4px avant) car la pixel font a un peu plus d'air
     sous la baseline. */
  background-image: linear-gradient(transparent calc(100% - 6px), var(--vs-accent) calc(100% - 6px));
  background-repeat: no-repeat;
  background-size: 100% 100%;
  padding: 0 0.04em;
}
.vs-headline--md { font-size: clamp(28px, 4vw, 44px); letter-spacing: -0.025em; }

/* Body lead - paragraphe d'intro sous le titre */
.vs-lead {
  font-size: var(--vs-fs-xl);
  line-height: 1.55;
  color: var(--vs-text-muted);
  max-width: 56ch;
  margin: 0;
}

/* Code badge - pastille pixel inline pour les snippets éditoriaux,
   façon "une vache qui marche" / chicken.glb / ~12s - 32×32×32 */
.vs-code {
  font-family: var(--vs-font-pixel);
  font-size: 1em;
  padding: 2px 7px;
  background: var(--vs-surface-3);
  color: var(--vs-text);
  border-radius: var(--vs-r-sm);
  white-space: nowrap;
  font-weight: 400;
  letter-spacing: 0;
}

/* Stat number - "01", "02", "03" en pixel petit gris au-dessus
   d'un titre de step. */
.vs-stepnum {
  display: block;
  font-family: var(--vs-font-pixel);
  font-size: var(--vs-fs-sm);
  font-weight: 500;
  color: var(--vs-text-dim);
  letter-spacing: 0.04em;
}

/* Rule - divider horizontal pleine largeur, fin */
.vs-rule {
  border: 0;
  height: 1px;
  background: var(--vs-border);
  margin: 0;
}

/* Stripe voxel - bandeau de 6 carrés couleur (red/orange/yellow/
   green/blue/purple) qui rappelle qu'on est sur un site de voxel.
   À utiliser PARCIMONIEUSEMENT (footer, fin de section) pour ne
   pas tomber dans l'effet drapeau. */
.vs-stripe {
  display: flex;
  height: 4px;
  width: 100%;
}
.vs-stripe > span { flex: 1 1 0; display: block; }
.vs-stripe > span:nth-child(1) { background: var(--vs-red); }
.vs-stripe > span:nth-child(2) { background: var(--vs-orange); }
.vs-stripe > span:nth-child(3) { background: var(--vs-yellow); }
.vs-stripe > span:nth-child(4) { background: var(--vs-green); }
.vs-stripe > span:nth-child(5) { background: var(--vs-blue); }
.vs-stripe > span:nth-child(6) { background: var(--vs-purple); }

/* Brand cube - petite mark voxel pixelisée 2x2 multicolore.
   Remplace le losange gradient purple/cyan partout (logos,
   topbar, account, etc.). Pose-le dans n'importe quel <span>. */
.vs-mark {
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  width: 18px;
  height: 18px;
  gap: 1px;
  flex: 0 0 auto;
}
.vs-mark > i { display: block; background: currentColor; }
.vs-mark > i:nth-child(1) { background: var(--vs-text); }
.vs-mark > i:nth-child(2) { background: var(--vs-red); }
.vs-mark > i:nth-child(3) { background: var(--vs-blue); }
.vs-mark > i:nth-child(4) { background: var(--vs-yellow); }

/* ---------- Import 3D button (compact, sur la même ligne que les actions
 *  primaires via .vs-actions-row > .vs-import3d-btn) ---------- */
.vs-import3d-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 12px;
  border: 1px dashed var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  background: transparent;
  color: var(--vs-text-muted);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-import3d-btn:hover {
  background: var(--vs-surface-2);
  border-color: var(--vs-border-focus);
  color: var(--vs-text);
}
.vs-import3d-btn .vs-icon,
.vs-import3d-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.vs-import3d-btn__free {
  font-size: 10px;
  color: var(--vs-green);
  background: rgba(34, 197, 94, 0.12);
  padding: 1px 6px;
  border-radius: 9999px;
  letter-spacing: 0.04em;
}
/* Sur petit écran (panneau côté étroit), on peut masquer le label "Importer 3D"
 * et garder juste l'icône + "gratuit" - sinon les 3 boutons asset-state se
 * retrouvent serrés. Désactivé par défaut, le wrap suffit dans la plupart
 * des cas via flex-wrap sur .vs-actions-row. */
@media (max-width: 480px) {
  .vs-import3d-btn__label { display: none; }
}

/* Generic spinner - used by `.vs-spin` SVG icons project-wide.
   Durée 1.4s (au lieu de 1s) : un peu plus calme à l'œil, surtout quand
   plusieurs spinners cohabitent dans la même vue. */
@keyframes vs-rotate { to { transform: rotate(360deg); } }
.vs-spin { animation: vs-rotate 1.4s linear infinite; transform-origin: center; }
/* Variante encore plus lente pour les loaders d'arrière-plan (suggestions IA,
   refresh passifs) - défilement plus long, ton plus posé. */
.vs-spin--slow { animation: vs-rotate 2.2s linear infinite; transform-origin: center; }

/* =========================================================
   LOADERS INLINE (.vs-c-loading)
   Bloc texte centré + un viz CSS optionnel via modifier (--cubes, --dots,
   --bar, --pulse, --shimmer). Chaque variante a sa propre durée pour
   éviter l'effet "tout le site spinne pareil".
   Distribution dans le projet :
     --cubes   (1.4s)  : Community detail (page modèle), Admin preview voxel
     --dots    (1.4s)  : Community gallery (liste de modèles)
     --bar     (2.6s)  : Sprite detail, modale détail bench-ratings
     --pulse   (2.0s)  : Player community (sous le canvas 3D)
     --shimmer (2.2s)  : Sprite gallery, Admin moderation list
   ========================================================= */
.vs-c-loading {
  text-align: center;
  padding: 60px 20px;
  color: var(--vs-text-muted);
  font-size: var(--vs-fs-sm);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.vs-c-loading::before {
  content: "";
  display: block;
}

/* --- "cubes" : 4 cubes voxel en cascade ---
       1 carré réel + 3 box-shadows décalés ; l'animation transfère l'accent
       du cube #1 au cube #4. margin-right compense l'overflow droite des
       shadows pour centrer visuellement (la margin-box est centrée par
       align-items: center). */
.vs-c-loading--cubes::before {
  width: 14px;
  height: 14px;
  background: rgba(99,102,241,0.25);
  border-radius: 2px;
  box-shadow: 20px 0 0 rgba(99,102,241,0.25),
              40px 0 0 rgba(99,102,241,0.25),
              60px 0 0 rgba(99,102,241,0.25);
  margin-right: 60px;
  animation: vs-loader-cubes-cascade 1.4s ease-in-out infinite;
}
@keyframes vs-loader-cubes-cascade {
  0%, 100% { background: var(--vs-accent);
             box-shadow: 20px 0 0 rgba(99,102,241,0.25),
                         40px 0 0 rgba(99,102,241,0.25),
                         60px 0 0 rgba(99,102,241,0.25); }
  25%      { background: rgba(99,102,241,0.25);
             box-shadow: 20px 0 0 var(--vs-accent),
                         40px 0 0 rgba(99,102,241,0.25),
                         60px 0 0 rgba(99,102,241,0.25); }
  50%      { background: rgba(99,102,241,0.25);
             box-shadow: 20px 0 0 rgba(99,102,241,0.25),
                         40px 0 0 var(--vs-accent),
                         60px 0 0 rgba(99,102,241,0.25); }
  75%      { background: rgba(99,102,241,0.25);
             box-shadow: 20px 0 0 rgba(99,102,241,0.25),
                         40px 0 0 rgba(99,102,241,0.25),
                         60px 0 0 var(--vs-accent); }
}

/* --- "dots" : 3 dots qui se relaient ---
       Durée 1.4s. Même technique que cubes mais en disques. */
.vs-c-loading--dots::before {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(99,102,241,0.30);
  box-shadow: 16px 0 0 rgba(99,102,241,0.30), 32px 0 0 rgba(99,102,241,0.30);
  margin-right: 32px;
  animation: vs-loader-dots 1.4s ease-in-out infinite;
}
@keyframes vs-loader-dots {
  0%, 100% { background: var(--vs-accent);
             box-shadow: 16px 0 0 rgba(99,102,241,0.30), 32px 0 0 rgba(99,102,241,0.30); }
  33%      { background: rgba(99,102,241,0.30);
             box-shadow: 16px 0 0 var(--vs-accent), 32px 0 0 rgba(99,102,241,0.30); }
  66%      { background: rgba(99,102,241,0.30);
             box-shadow: 16px 0 0 rgba(99,102,241,0.30), 32px 0 0 var(--vs-accent); }
}

/* --- "bar" : barre indéterminée qui slide ---
       Durée 2.6s - la plus longue, "défilement plus long" demandé par le user. */
.vs-c-loading--bar::before {
  width: 180px;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg,
    transparent 0%, transparent 30%,
    var(--vs-accent) 30%, var(--vs-accent) 70%,
    transparent 70%, transparent 100%
  );
  background-size: 300% 100%;
  background-color: rgba(99,102,241,0.12);
  background-position: 200% 0;
  animation: vs-loader-bar 2.6s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite;
}
@keyframes vs-loader-bar {
  0%   { background-position: 200% 0; }
  100% { background-position: -100% 0; }
}

/* --- "pulse" : un disque qui s'évase ---
       Durée 2.0s, calme - idéal pour un canvas 3D qui se charge dessous. */
.vs-c-loading--pulse::before {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--vs-accent);
  opacity: 0.5;
  animation: vs-loader-pulse 2.0s ease-out infinite;
}
@keyframes vs-loader-pulse {
  0%   { transform: scale(0.4); opacity: 0.55; box-shadow: 0 0 0 0 rgba(99,102,241,0.4); }
  70%  { transform: scale(1);   opacity: 0;    box-shadow: 0 0 0 18px rgba(99,102,241,0); }
  100% { transform: scale(1);   opacity: 0;    box-shadow: 0 0 0 0 rgba(99,102,241,0); }
}

/* --- "shimmer" : skeleton bar qui glisse en dégradé ---
       Durée 2.2s - la plus apaisée, pour listes (gallery / moderation). */
.vs-c-loading--shimmer::before {
  width: 200px;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(90deg,
    var(--vs-surface-2) 0%,
    var(--vs-surface-3) 40%,
    rgba(99,102,241,0.18) 50%,
    var(--vs-surface-3) 60%,
    var(--vs-surface-2) 100%);
  background-size: 200% 100%;
  animation: vs-loader-shimmer 2.2s linear infinite;
}
@keyframes vs-loader-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ---------- Import 3D modal (Import3dDialog.js) ---------- */
@keyframes vs-import3d-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes vs-import3d-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.vs-import3d {
  position: fixed;
  inset: 0;
  z-index: 220;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: vs-import3d-fade 140ms var(--vs-ease) both;
}
.vs-import3d__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.55);
  backdrop-filter: blur(6px);
  cursor: pointer;
}
.vs-import3d__panel {
  position: relative;
  width: min(560px, 100%);
  max-height: 90vh;
  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);
  animation: vs-import3d-in 180ms var(--vs-ease) both;
}

.vs-import3d__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
  flex: 0 0 auto;
}
.vs-import3d__head h3 {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.vs-import3d__head h3 svg { width: 20px; height: 20px; color: var(--vs-text-muted); }
.vs-import3d__close {
  font-size: 18px;
  color: var(--vs-text-muted);
  padding: 4px 10px;
  border-radius: var(--vs-r-sm);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-import3d__close:hover:not(:disabled) { color: var(--vs-text); background: var(--vs-surface-2); }
.vs-import3d__close:disabled { opacity: 0.4; cursor: not-allowed; }
.vs-import3d__close svg { width: 16px; height: 16px; }

.vs-import3d__body {
  padding: 18px 20px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1 1 auto;
  min-height: 0;
}

/* --- Notice (avertissement import vs IA) --- */
.vs-import3d__notice {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  background: rgba(234, 179, 8, 0.10);
  border: 1px solid rgba(234, 179, 8, 0.35);
  border-radius: var(--vs-r-md);
  padding: 12px 14px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  line-height: 1.45;
}
.vs-import3d__notice-icon {
  flex: 0 0 auto;
  font-size: 18px;
  line-height: 1;
  color: #eab308;
  margin-top: 1px;
}
.vs-import3d__notice-text { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.vs-import3d__notice-text strong { color: var(--vs-text); font-size: var(--vs-fs-md); }
.vs-import3d__notice-text p { margin: 0; color: var(--vs-text-muted); }
.vs-import3d__notice-text p strong { color: var(--vs-text); font-size: inherit; font-weight: 600; }

/* --- Drop zone --- */
.vs-import3d__drop {
  border: 2px dashed var(--vs-border-strong);
  border-radius: var(--vs-r-md);
  padding: 28px 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  background: var(--vs-surface-2);
  transition: background 140ms var(--vs-ease), border-color 140ms var(--vs-ease), transform 140ms var(--vs-ease);
  text-align: center;
  outline: none;
  /* Reset <button> defaults so it looks like a panel, not a button. */
  font: inherit;
  color: inherit;
  width: 100%;
}
.vs-import3d__drop:hover,
.vs-import3d__drop:focus-visible {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-focus);
}
.vs-import3d__drop.is-drag {
  background: var(--vs-surface-3);
  border-color: var(--vs-border-focus);
  transform: scale(1.01);
  box-shadow: var(--vs-glow);
}
.vs-import3d__drop.is-locked { pointer-events: none; opacity: 0.55; }
.vs-import3d__drop-icon { color: var(--vs-text-muted); }
.vs-import3d__drop-icon svg { width: 36px; height: 36px; }
.vs-import3d__drop-text { display: flex; flex-direction: column; gap: 4px; align-items: center; }
.vs-import3d__drop-text strong { font-size: var(--vs-fs-md); color: var(--vs-text); }
.vs-import3d__drop-text > span:not(.dim) { font-size: var(--vs-fs-sm); color: var(--vs-text-muted); }
.vs-import3d__drop-text .dim { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); margin-top: 2px; }

/* --- File info card --- */
.vs-import3d__file-info {
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 10px 14px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-muted);
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.vs-import3d__file-info strong { color: var(--vs-text); }
.vs-import3d__file-info .dim { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }

/* --- Options fieldset --- */
.vs-import3d__opts {
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 12px 14px 14px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: opacity 120ms var(--vs-ease);
}
.vs-import3d__opts:disabled,
.vs-import3d__opts[disabled] { opacity: 0.55; pointer-events: none; }
.vs-import3d__opts legend {
  font-size: var(--vs-fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-dim);
  padding: 0 6px;
  font-weight: 600;
}

.vs-import3d__field { display: flex; flex-direction: column; gap: 5px; }
.vs-import3d__field > span:first-child {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  font-weight: 500;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.vs-import3d__field em {
  color: var(--vs-text);
  font-style: normal;
  font-weight: 600;
  background: var(--vs-surface-3);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: var(--vs-fs-xs);
  font-variant-numeric: tabular-nums;
}
.vs-import3d__field input[type="range"],
.vs-import3d__field select {
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  padding: 7px 10px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  font-family: inherit;
  outline: none;
}
.vs-import3d__field input[type="range"] {
  padding: 0;
  height: 22px;
  background: transparent;
  border: 0;
  accent-color: var(--vs-accent, #6366f1);
}
.vs-import3d__field .dim { font-size: var(--vs-fs-xs); color: var(--vs-text-dim); }

.vs-import3d__check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  cursor: pointer;
  user-select: none;
}
.vs-import3d__check input[type="checkbox"] { accent-color: var(--vs-accent, #6366f1); width: 15px; height: 15px; }

.vs-import3d__check--feature {
  align-items: flex-start;
  padding: 10px 12px;
  border: 1px solid var(--vs-accent, #6366f1);
  border-radius: 8px;
  background: color-mix(in srgb, var(--vs-accent, #6366f1) 8%, transparent);
}
.vs-import3d__check--feature > span {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.vs-import3d__check--feature strong { font-size: var(--vs-fs-sm); }
.vs-import3d__check--feature .dim { font-size: var(--vs-fs-xs); line-height: 1.4; }

/* --- Error block --- */
.vs-import3d__error {
  background: rgba(239, 68, 68, 0.12);
  border: 1px solid rgba(239, 68, 68, 0.3);
  color: var(--vs-red, #ef4444);
  padding: 9px 12px;
  border-radius: var(--vs-r-md);
  font-size: var(--vs-fs-sm);
}

/* --- Footer (sticky actions) --- */
.vs-import3d__foot {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 20px;
  border-top: 1px solid var(--vs-border);
  background: var(--vs-surface-1);
  flex: 0 0 auto;
}

.vs-import3d__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 18px;
  font-size: var(--vs-fs-md);
  font-weight: 500;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  background: var(--vs-surface-2);
  color: var(--vs-text);
  cursor: pointer;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease), filter 120ms var(--vs-ease);
  font-family: inherit;
}
.vs-import3d__btn:hover:not(:disabled) { background: var(--vs-surface-3); }
.vs-import3d__btn:disabled { opacity: 0.5; cursor: not-allowed; }
.vs-import3d__btn--primary {
  background: var(--vs-accent, #6366f1);
  border-color: var(--vs-accent, #6366f1);
  color: #fff;
  font-weight: 600;
  min-width: 200px;
  justify-content: center;
}
.vs-import3d__btn--primary:hover:not(:disabled) {
  filter: brightness(1.1);
  background: var(--vs-accent, #6366f1);
}
.vs-import3d__btn--primary.is-loading { filter: brightness(0.85); cursor: progress; }
.vs-import3d__btn svg { width: 16px; height: 16px; }
.vs-import3d__btn-icon { display: inline-flex; align-items: center; }
.vs-import3d__btn-icon .vs-spin { width: 16px; height: 16px; }

/* ============================================================
   AddAnimationDialog - modal d'ajout d'animation (preset + custom).
   Layout calqué sur Import3dDialog : backdrop + panel + head/body/foot.
   ============================================================ */
.vs-addanim {
  position: fixed; inset: 0; z-index: 8500;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.vs-addanim__backdrop {
  position: absolute; inset: 0;
  background: rgba(8,12,24,0.62);
  backdrop-filter: blur(2px);
  cursor: pointer;
}
.vs-addanim__panel {
  position: relative;
  background: var(--vs-surface, #fff);
  border-radius: 14px;
  width: min(520px, calc(100vw - 48px));
  max-height: calc(100vh - 48px);
  display: flex; flex-direction: column;
  box-shadow: 0 24px 60px rgba(0,0,0,0.35);
  overflow: hidden;
  animation: vs-import3d-in 180ms var(--vs-ease) both;
}
.vs-addanim__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--vs-border);
  background: var(--vs-surface-2);
}
.vs-addanim__head h3 {
  margin: 0;
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--vs-text);
}
.vs-addanim__head h3 svg { width: 18px; height: 18px; color: var(--vs-accent-hi); }
.vs-addanim__close {
  display: grid; place-items: center;
  width: 30px; height: 30px;
  background: transparent;
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  color: var(--vs-text-muted);
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
}
.vs-addanim__close:hover { background: var(--vs-surface-2); color: var(--vs-text); }
.vs-addanim__close svg { width: 14px; height: 14px; }

.vs-addanim__mode {
  display: flex;
  gap: 4px;
  padding: 8px 18px 0;
}
.vs-addanim__mode-tab {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid var(--vs-border, #e5e7eb);
  background: var(--vs-surface-1);
  color: var(--vs-text-dim);
  font: inherit; font-size: 13px; font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
}
.vs-addanim__mode-tab:hover { background: var(--vs-surface-2); color: var(--vs-text); }
.vs-addanim__mode-tab.is-active {
  background: var(--vs-accent, #2563eb);
  border-color: var(--vs-accent, #2563eb);
  color: #fff;
}
.vs-addanim__body {
  padding: 16px 18px;
  display: flex; flex-direction: column;
  gap: 16px;
  overflow-y: auto;
}
.vs-addanim__section {
  display: flex; flex-direction: column;
  gap: 8px;
}
/* Le mode "multi" pose hidden=true sur [data-section="name"] (cf.
   AddAnimationDialog._setMode). La règle display:flex ci-dessus écrase
   l'attribut HTML hidden, donc on le ré-applique explicitement. */
.vs-addanim__section[hidden] { display: none; }
.vs-addanim__title {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--vs-text-dim);
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.vs-addanim__title .dim { font-weight: 500; letter-spacing: 0; text-transform: none; }
.vs-addanim__custom-toggle {
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--vs-accent-hi);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
}
.vs-addanim__custom-toggle:hover { text-decoration: underline; }

.vs-addanim__chips {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.vs-addanim__chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 11px;
  font-size: 12px;
  font-weight: 600;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-pill);
  color: var(--vs-text);
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
  font-family: inherit;
}
.vs-addanim__chip:hover:not(:disabled) {
  background: var(--vs-surface-2);
  border-color: var(--vs-accent-line);
  color: var(--vs-accent-hi);
}
.vs-addanim__chip.is-selected {
  background: var(--vs-accent-low);
  border-color: var(--vs-accent-hi);
  color: var(--vs-accent-hi);
}
.vs-addanim__chip.is-taken,
.vs-addanim__chip:disabled {
  opacity: 0.40;
  cursor: not-allowed;
  text-decoration: line-through;
}
.vs-addanim__chip svg { width: 11px; height: 11px; color: var(--vs-success); }

.vs-addanim__custom { display: flex; flex-direction: column; gap: 4px; }
.vs-addanim__input {
  font-family: inherit;
  font-size: 13px;
  padding: 8px 10px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  color: var(--vs-text);
}
.vs-addanim__input:focus {
  outline: none;
  border-color: var(--vs-accent-hi);
  box-shadow: 0 0 0 3px var(--vs-accent-low);
}
.vs-addanim__custom-hint,
.vs-addanim__hint {
  margin: 0;
  font-size: 11px;
  color: var(--vs-text-dim);
}
.vs-addanim__hint code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
  background: var(--vs-surface-2);
  padding: 1px 4px;
  border-radius: var(--vs-r-sm);
}

/* Bloc "Suggestions :" sous l'input nom - petit label inline + chips
   en row. Visuellement secondaire vs l'input qui est la source de
   vérité du nom. */
.vs-addanim__suggest {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}
.vs-addanim__suggest-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-muted);
  font-weight: 600;
}

.vs-addanim__notes {
  font-family: inherit;
  font-size: 13px;
  padding: 8px 10px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-sm);
  color: var(--vs-text);
  resize: vertical;
  min-height: 56px;
  line-height: 1.4;
}
.vs-addanim__notes:focus {
  outline: none;
  border-color: var(--vs-accent-hi);
  box-shadow: 0 0 0 3px var(--vs-accent-low);
}

.vs-addanim__error {
  padding: 8px 10px;
  background: rgba(220,38,38,0.08);
  border: 1px solid rgba(220,38,38,0.30);
  border-radius: var(--vs-r-sm);
  color: var(--vs-danger, #dc2626);
  font-size: 12px;
}

.vs-addanim__foot {
  display: flex; justify-content: flex-end;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--vs-border);
  background: var(--vs-surface-2);
}
.vs-addanim__btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  color: var(--vs-text);
  cursor: pointer;
  transition: all 120ms var(--vs-ease);
}
.vs-addanim__btn:hover:not(:disabled) { background: var(--vs-surface-3, var(--vs-surface-2)); }
.vs-addanim__btn:disabled { opacity: 0.45; cursor: not-allowed; }
.vs-addanim__btn--primary {
  background: var(--vs-accent);
  border-color: transparent;
  color: #fff;
}
.vs-addanim__btn--primary:hover:not(:disabled) { background: var(--vs-accent-hi); }
.vs-addanim__btn svg { width: 14px; height: 14px; }
.vs-addanim__cost {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 11px;
  padding: 2px 7px;
  border-radius: var(--vs-r-pill);
  background: rgba(255,255,255,0.22);
  letter-spacing: 0.04em;
}

/* ============================================================
   GuidedDialog - flow user "Mesh animé" en 2 étapes (4 mesh
   candidats → 4 anim candidats + ratings).
   Le DOM interne (.bench-grid, .bench-candidate, .vs-rate-btn)
   est stylé par admin.css qui est aussi chargé sur /edit.php.
   Ici on style juste le shell (overlay + tabs + decision banner).
   ============================================================ */
.vs-guided-overlay {
  position: fixed;
  inset: 0;
  background: rgba(8, 12, 24, 0.62);
  backdrop-filter: blur(2px);
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
body.vs-guided-open { overflow: hidden; }

.vs-guided-modal {
  background: var(--vs-surface, #fff);
  border-radius: 14px;
  width: min(1280px, calc(100vw - 48px));
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  box-shadow: 0 24px 60px rgba(0,0,0,0.35);
  overflow: hidden;
}

.vs-guided__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border, #e3e6ec);
  background: var(--vs-surface-2, #f6f7fa);
}
.vs-guided__head h2 {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
}
.vs-guided__close {
  background: transparent;
  border: 0;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  color: var(--vs-text-dim, #6a7385);
  padding: 4px 10px;
  border-radius: 6px;
}
.vs-guided__close:hover { background: var(--vs-border, #e3e6ec); color: var(--vs-text, #0f172a); }

.vs-guided__prompt-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 20px;
  background: var(--vs-surface-2, #f6f7fa);
  border-bottom: 1px solid var(--vs-border, #e3e6ec);
  font-size: 13px;
}
.vs-guided__prompt-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-dim, #6a7385);
  font-weight: 600;
}
.vs-guided__prompt-text {
  font-weight: 500;
  color: var(--vs-text, #0f172a);
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-guided__status {
  font-size: 12px;
  color: var(--vs-text-dim, #6a7385);
  text-align: right;
  flex-shrink: 0;
  max-width: 50%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-guided__status.is-error { color: #c33; }
.vs-guided__status.is-info  { color: #2a8c4a; }

.vs-guided__tabs {
  display: flex;
  gap: 4px;
  padding: 8px 20px 0;
  border-bottom: 1px solid var(--vs-border, #e3e6ec);
}
.vs-guided__tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--vs-text-dim, #6a7385);
  cursor: pointer;
  position: relative;
  bottom: -1px;
  font-family: inherit;
}
.vs-guided__tab:disabled { opacity: 0.5; cursor: not-allowed; }
.vs-guided__tab.is-active {
  background: var(--vs-surface, #fff);
  border-color: var(--vs-border, #e3e6ec);
  color: var(--vs-text, #0f172a);
  font-weight: 600;
}
.vs-guided__tab-num {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--vs-surface-2, #f6f7fa);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
}
.vs-guided__tab.is-active .vs-guided__tab-num {
  background: #2a8c4a;
  color: #fff;
}
.vs-guided__tab-status {
  font-size: 11px;
  color: var(--vs-text-dim, #6a7385);
  font-family: 'JetBrains Mono', monospace;
}

.vs-guided__panels {
  flex: 1;
  overflow: auto;
  padding: 16px 20px 20px;
  background: var(--vs-surface, #fff);
}
.vs-guided__panels .bench-panel {
  border: none;
  padding: 0;
  background: transparent;
  border-radius: 0;
}
/* Le head de chaque panel reprend la même mise en forme qu'en /admin/bench. */
.vs-guided__panels .bench-panel__head {
  margin-bottom: 8px;
}

/* === Animations d'entrée - UX guidée magique ============================
   100 % CSS, scopé à `.vs-guided-overlay` pour ne PAS toucher /admin/bench-test
   (qui réutilise le même DOM .bench-candidate--card mais sans overlay parent).
   Tout tourne sur le compositor (opacity + transform uniquement) → indépendant
   du main thread bloqué pendant l'init Three.js (CanvasGrid crée 4 contextes
   WebGL + SceneManager + OrbitControls par panel = 100-400 ms de freeze).
   Avant on faisait via WAAPI mais le commit ne s'enregistrait pas avant le
   freeze → animations avalées, modal qui pop instantanément.
   ====================================================================== */

@keyframes vs-guided-overlay-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Note anti-flou texte : les keyframes finissent toutes sur `transform: none`
   (et PAS `transform: scale(1)`/`translateY(0)`). Sans ça, l'élément reste
   "composité" sur GPU layer après animation à cause du fill: both → le
   subpixel-antialiasing du texte est désactivé en permanence et tout le
   contenu textuel paraît flou. `none` = identity matrix → le browser peut
   dépromouvoir la layer dès la fin de l'animation. Aucun `will-change` non
   plus pour la même raison. */
@keyframes vs-guided-modal-in {
  0%   { opacity: 0; transform: translateY(24px) scale(0.86); }
  60%  { opacity: 1; transform: translateY(-3px) scale(1.02); }
  100% { opacity: 1; transform: none;                          }
}
/* Card entry : fade-in doux 1 s avec micro slide + scale subtil. Pas de
   overshoot/bounce - l'effet "magique" vient de la cascade des 4 cartes,
   pas d'un pop violent par carte. */
@keyframes vs-guided-card-in {
  0%   { opacity: 0; transform: translateY(14px) scale(0.96); }
  100% { opacity: 1; transform: none;                          }
}
/* Canvas reveal : fade-in pur 1 s avec un scale très léger (0.97 → 1) qui
   accompagne le fade-out du LoadingProgress. Le mesh "apparaît" plutôt que
   d'être collé d'un coup, sans le pop brusque qu'on avait avant. */
@keyframes vs-guided-canvas-in {
  0%   { opacity: 0; transform: scale(0.97); }
  100% { opacity: 1; transform: none;        }
}
/* Overlay fade-in (320 ms) + modal box pop spring (540 ms) - déclenchés
   automatiquement à la création du DOM par GuidedDialog.open(). `both` fill
   garde l'état initial (opacity 0) avant que le compositor ne lance l'anim,
   évitant le flash blanc d'une frame. */
.vs-guided-overlay {
  animation: vs-guided-overlay-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.vs-guided-overlay .vs-guided-modal {
  animation: vs-guided-modal-in 540ms cubic-bezier(0.16, 1, 0.3, 1) both;
  transform-origin: center center;
}

/* Cards cascade - déclenchée quand un panel devient `.is-active` (toggle
   par _switchTab JS). Stagger 180 ms × :nth-child pour rester 100 % CSS.
   Fade-in doux 1 s, easing out-quint - pas de pop brusque, l'élégance
   vient de la cascade décalée des 4 cartes. */
.vs-guided-overlay .bench-panel.is-active .bench-candidate--card {
  animation: vs-guided-card-in 1000ms cubic-bezier(0.16, 1, 0.3, 1) both;
  transform-origin: center center;
}
.vs-guided-overlay .bench-panel.is-active .bench-candidate--card:nth-child(1) { animation-delay:   0ms; }
.vs-guided-overlay .bench-panel.is-active .bench-candidate--card:nth-child(2) { animation-delay: 180ms; }
.vs-guided-overlay .bench-panel.is-active .bench-candidate--card:nth-child(3) { animation-delay: 360ms; }
.vs-guided-overlay .bench-panel.is-active .bench-candidate--card:nth-child(4) { animation-delay: 540ms; }

/* Note grid wrapping : la card racine du grid 2×2 est .bench-grid > article,
   donc :nth-child cible bien chaque article. Si jamais le markup ajoute un
   sibling (ex. : un placeholder), faudra repasser à un système d'index custom. */

/* Canvas reveal - fade-in doux 1 s sur le canvas WebGL quand la card passe
   à .is-done (mesh généré et appliqué par Three.js). Accompagne le fade-out
   du LoadingProgress overlay → l'asset apparaît en fondu plutôt qu'avec un
   pop brusque (changement vs ancienne version qui faisait scale 0.9 → 1.05
   en 480 ms - trop agressif sur le canvas). */
.vs-guided-overlay .bench-candidate--card.is-done .bench-candidate__canvas-host canvas {
  animation: vs-guided-canvas-in 1000ms cubic-bezier(0.16, 1, 0.3, 1) both;
  transform-origin: center center;
}

/* Decision banner - pas d'animation : l'apparition spring (pop scale) faisait
   un effet bizarre au clic ✓ Choisir parce que la bannière est INSIDE le panel
   actif qui a déjà sa propre animation `vs-guided-card-in` héritée par les
   children, donc les deux se combinaient en glitch visuel. La bannière
   apparaît maintenant instantanément (toggle [hidden]). */

/* Côté user : on cache le `<details class="bench-candidate__code">` qui
   expose le code JS du voxel (utile en /admin/bench-test pour debug provider,
   intimidant + bruyant pour le grand public). Le scope `.vs-guided-overlay`
   préserve la visibilité côté admin (qui n'a pas cet overlay). */
.vs-guided-overlay .bench-candidate__code { display: none; }

/* Note volontaire : pas de @media (prefers-reduced-motion: reduce) ici.
   L'effet "magique" de la modal guidée est une partie centrale de l'UX du
   produit - on accepte de l'imposer même aux users qui ont activé la préf
   OS de réduction de mouvement (sinon la modal pop instantanément, et on
   perd tout le côté élégant que l'user attend). Si on veut respecter la
   préf un jour, on remplacera par des animations plus courtes (~120 ms)
   plutôt qu'un skip total. */

@media (max-width: 720px) {
  /* Sur mobile, on replie le 2×2 en colonne - chaque carte occupe toute
     la largeur, l'user scrolle entre les 4 candidats. */
  .vs-guided__panels .bench-grid--2x2 {
    grid-template-columns: 1fr !important;
  }
  .vs-guided__panels .bench-candidate__canvas-host { height: 200px; }
  .vs-guided__head h2 { font-size: 14px; }
  .vs-guided-modal { width: 100vw; max-height: 100vh; border-radius: 0; }
  .vs-guided-overlay { padding: 0; }
}

/* ============================================================
 * Tablette+ / portable / desktop : fit-to-viewport SANS scroll.
 *
 * Refonte 2026-05-14 : avant ça, .vs-guided__panels avait overflow: auto
 * + .bench-candidate__canvas-host avait height: 220px fixe (cf. admin.css).
 * Sur PC portable 1366×768, après chrome (head 48 + prompt 36 + tabs 44 +
 * panels padding 36 + bench-panel__head 50 = ~214px), il restait ~470px
 * pour la grille 2×2 alors qu'une seule rangée de cards faisait déjà ~310px
 * → scroll vertical obligatoire pour atteindre les boutons "Choisir" du bas.
 *
 * Solution : chaîne flex column avec min-height: 0 du modal jusqu'au
 * canvas-host. Le canvas-host devient flex: 1 sans hauteur fixe, la grille
 * 2×2 utilise grid-template-rows: 1fr 1fr pour partager 50/50, le panel
 * remplit le panels area, panels devient overflow: hidden (au lieu de auto).
 *
 * Scopé à .vs-guided-overlay : /admin/bench-test garde son comportement
 * (canvas-host 220px fixe, scroll OK puisque la page entière scroll). Le
 * mode mobile <720px ci-dessus reste actif et écrase ces règles via le
 * media query suivant (overflow:auto reactivated, canvas-host fixed 200px,
 * grid-template-rows: none pour les 4 cards stacked).
 * ============================================================ */
@media (min-width: 721px) {
  /* La modal n'avait que `max-height: calc(100vh - 48px)` (cf. .vs-guided-modal
   * ligne ~4046), donc en l'absence de height explicite elle se contractait
   * sur la hauteur minimale du contenu et la chaîne flex `panels → bench-panel
   * → bench-grid → canvas-host` n'avait que ~120px à distribuer. Résultat :
   * canvases à 118-122px sur 1080p alors qu'il restait 200px+ de vide en bas.
   * Fix 2026-05-14 : on force la modal à la pleine hauteur dispo. */
  .vs-guided-overlay .vs-guided-modal {
    height: calc(100vh - 48px);
  }
  .vs-guided-overlay .vs-guided__panels {
    /* overflow: auto au lieu de hidden : sur les écrans courts (PC portable
     * paysage 1024×600, téléphone paysage), les candidates refusent désormais
     * de se contracter en dessous de leur min-height (240px). Sans scroll,
     * les boutons "Choisir" passent sous le fold. */
    overflow: auto;
    display: flex;
    flex-direction: column;
    padding: 12px 18px 14px;
    min-height: 0;
  }
  .vs-guided-overlay .vs-guided__panels .bench-panel {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  /* Le `display: flex` ci-dessus (specificity 0,0,3,0) écrasait le
     `.bench-panel[hidden] { display: none }` de admin.css (0,0,2,1),
     donc step1 ET step2 s'affichaient simultanément sur desktop.
     On rétablit la priorité avec une règle de specificity ≥ 0,0,3,1. */
  .vs-guided-overlay .vs-guided__panels .bench-panel[hidden] { display: none; }
  .vs-guided-overlay .vs-guided__panels .bench-panel__head {
    flex: 0 0 auto;
    margin-bottom: 6px;
  }
  .vs-guided-overlay .vs-guided__panels .bench-panel__head-info strong { font-size: 13px; }
  .vs-guided-overlay .vs-guided__panels .bench-panel__hint { font-size: 11px; }
  .vs-guided-overlay .vs-guided__panels .bench-grid {
    flex: 1 1 auto;
    min-height: 0;
  }
  .vs-guided-overlay .vs-guided__panels .bench-grid--2x2  { grid-template-rows: 1fr 1fr; }
  .vs-guided-overlay .vs-guided__panels .bench-grid--2x1  { grid-template-rows: 1fr; }
  .vs-guided-overlay .vs-guided__panels .bench-grid--single { grid-template-rows: 1fr; }
  .vs-guided-overlay .vs-guided__panels .bench-candidate {
    /* On garde un plancher de 240px pour que sur écran trop court (paysage
     * portable, téléphone paysage) le candidat reste lisible : le scroll
     * de .vs-guided__panels prend le relais quand la grid 2×2 déborde.
     * Sur PC standard, `flex: 1 1 auto` (hérité de admin.css) prend toute
     * la hauteur dispo donc on dépasse largement ce plancher. */
    min-height: 240px;
    padding: 8px 10px;
    gap: 6px;
  }
  .vs-guided-overlay .vs-guided__panels .bench-candidate__canvas-host {
    flex: 1 1 0;
    height: auto;
    min-height: 180px;  /* en dessous l'aperçu devient illisible */
  }
  /* Chrome compacté pour récupérer de la hauteur pour la grille (gain ~26px
   * cumulés : head 8 + prompt 6 + tabs 4 + panels padding 8). */
  .vs-guided-overlay .vs-guided__head { padding: 10px 18px; }
  .vs-guided-overlay .vs-guided__head h2 { font-size: 15px; }
  .vs-guided-overlay .vs-guided__prompt-row { padding: 7px 18px; }
  .vs-guided-overlay .vs-guided__tabs { padding: 4px 18px 0; }
  .vs-guided-overlay .vs-guided__tab { padding: 6px 12px; font-size: 12px; }
}

/* ============================================================
 * LoadingProgress - overlay réutilisable affiché par CandidateSlot
 * pendant les fetch de génération. Cf. shared/LoadingProgress.js.
 * S'attend à un parent en `position: relative` ; couvre 100 % du
 * parent en absolute. Le canvas WebGL sous-jacent reste vivant en
 * dessous, le loader se contente d'occulter le rendu pendant le run.
 * ============================================================ */
.vs-loadprog {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg,
    var(--vs-surface, #fff) 0%,
    var(--vs-surface-2, #f6f7fa) 100%);
  z-index: 2;
  border-radius: inherit;
  transition: opacity 220ms var(--vs-ease, ease);
}
.vs-loadprog.is-complete { opacity: 0; pointer-events: none; }
.vs-loadprog__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  width: min(86%, 280px);
  text-align: center;
}
.vs-loadprog__pulse {
  display: inline-flex;
  gap: 5px;
  margin-bottom: 2px;
}
.vs-loadprog__pulse span {
  width: 8px;
  height: 8px;
  background: var(--vs-text-dim, #6a7385);
  border-radius: 1px;
  animation: vs-loadprog-pulse 1.05s ease-in-out infinite;
}
.vs-loadprog__pulse span:nth-child(2) { animation-delay: 0.18s; }
.vs-loadprog__pulse span:nth-child(3) { animation-delay: 0.36s; }
@keyframes vs-loadprog-pulse {
  0%, 100% { transform: translateY(0); opacity: 0.45; background: var(--vs-text-dim, #6a7385); }
  50%      { transform: translateY(-3px); opacity: 1; background: var(--vs-text, #0f172a); }
}
.vs-loadprog__eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--vs-text-dim, #6a7385);
}
.vs-loadprog__message {
  font-size: 13px;
  line-height: 1.45;
  color: var(--vs-text, #0f172a);
  font-weight: 500;
  min-height: 2.4em;
  transition: opacity 180ms ease;
}
.vs-loadprog__message.is-fading { opacity: 0; }
.vs-loadprog__bar {
  width: 100%;
  height: 4px;
  background: var(--vs-border, #e3e6ec);
  border-radius: 999px;
  overflow: hidden;
  position: relative;
}
.vs-loadprog__bar-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  background: var(--vs-text, #0f172a);
  border-radius: inherit;
  transition: width 120ms linear;
}
.vs-loadprog.is-error .vs-loadprog__bar-fill { background: #c33; }
.vs-loadprog__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 10px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  color: var(--vs-text-dim, #6a7385);
  font-variant-numeric: tabular-nums;
}
.vs-loadprog__overdue {
  font-family: inherit;
  font-style: italic;
  color: var(--vs-text-dim, #6a7385);
  text-align: right;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-loadprog.is-overdue .vs-loadprog__bar-fill {
  /* Subtle stripe pour signaler que la barre est en attente. */
  background: repeating-linear-gradient(
    -45deg,
    var(--vs-text, #0f172a) 0 6px,
    var(--vs-text-dim, #6a7385) 6px 12px
  );
  animation: vs-loadprog-shift 1.2s linear infinite;
}
@keyframes vs-loadprog-shift {
  from { background-position: 0 0; }
  to   { background-position: 24px 0; }
}
.vs-loadprog.is-error .vs-loadprog__message {
  color: #c33;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  word-break: break-word;
}

/* ============================================================
 * VsModal - modal de confirmation/alerte custom (remplace alert/confirm)
 * Utilisé par : BenchTest admin (Run all, Regen 4, Clear, Push to /admin),
 * GuidedDialog (close), AddAnimCandidatesDialog (close).
 * Cf. /assets/js/shared/VsModal.js - vsConfirm() / vsAlert().
 * ============================================================ */
.vs-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;          /* au-dessus de vs-guided-overlay (z=9999 typique) */
  background: rgba(15, 23, 42, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: vs-modal-fade 120ms ease-out;
}
@keyframes vs-modal-fade { from { opacity: 0; } to { opacity: 1; } }

.vs-modal {
  background: var(--vs-surface, #fff);
  border: 1px solid var(--vs-border, #e3e6ec);
  border-radius: 12px;
  box-shadow: 0 20px 48px rgba(15, 23, 42, 0.25);
  max-width: 460px;
  width: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: vs-modal-pop 160ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes vs-modal-pop {
  from { transform: translateY(8px) scale(0.96); opacity: 0; }
  to   { transform: translateY(0)   scale(1);    opacity: 1; }
}

.vs-modal__head {
  padding: 18px 22px 12px;
}
.vs-modal__title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--vs-text, #0f172a);
  line-height: 1.35;
}

.vs-modal__body {
  padding: 0 22px 16px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--vs-text, #0f172a);
}
.vs-modal__body:empty { padding: 0; }
.vs-modal__body code {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  background: var(--vs-surface-2, #f3f5f9);
  padding: 1px 5px;
  border-radius: 3px;
}

.vs-modal__actions {
  padding: 12px 22px 18px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  flex-wrap: wrap;
  background: var(--vs-surface, #fff);
  border-top: 1px solid var(--vs-border, #e9ecf2);
}

/* Variante danger : titre rouge + bouton primaire rouge (cf. .vs-btn--danger) */
.vs-modal-overlay--danger .vs-modal__title {
  color: #c33;
}

/* Variante prompt (vsPrompt) : remplace window.prompt() natif par un modal
 * stylé avec input texte single-line + boutons OK / Annuler. Hint optionnel
 * au-dessus de l'input. Cf. shared/VsModal.js exportant vsPrompt(). */
.vs-modal__hint {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--vs-text-dim, #5a6478);
  line-height: 1.45;
}
.vs-modal__input {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 11px;
  font-size: 14px;
  font-family: inherit;
  color: var(--vs-text, #0f172a);
  background: var(--vs-surface, #fff);
  border: 1px solid var(--vs-border, #e9ecf2);
  border-radius: var(--vs-radius-sm, 6px);
  outline: 0;
  transition: border-color 120ms var(--vs-ease, ease), box-shadow 120ms var(--vs-ease, ease);
}
.vs-modal__input:focus {
  border-color: var(--vs-accent, #6244ff);
  box-shadow: 0 0 0 3px rgba(98, 68, 255, 0.15);
}
.vs-modal__input::placeholder {
  color: var(--vs-text-faint, #94a0b6);
}

/* Bouton danger - utilisé pour confirmations destructives (Clear, écraser). */
.vs-btn--danger {
  background: #d83434;
  color: #fff;
  border: 1px solid #b21c1c;
}
.vs-btn--danger:hover { background: #b81e1e; }

/* ============================================================
 * DownloadProgressModal - stepper unifié pour les exports lourds
 * (palette + GLB par frame + sprite sheet WebGL + ZIP). Cf.
 * /assets/js/shared/DownloadProgressModal.js. Style sobre, aligné
 * sur vs-modal de base : on ne réinvente pas l'overlay/fade.
 * ============================================================ */
.vs-dlprog__modal { max-width: 460px; }
.vs-dlprog__body {
  padding: 6px 22px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.vs-dlprog__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.vs-dlprog__step {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 13px;
  line-height: 1.35;
  color: var(--vs-text-muted, #94a3b8);
  transition: color 160ms var(--vs-ease, ease);
}
.vs-dlprog__step-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--vs-text-muted, #cbd5e1);
}
.vs-dlprog__step-icon svg { width: 100%; height: 100%; }
.vs-dlprog__step.is-pending .vs-dlprog__step-icon { color: var(--vs-border, #e3e6ec); }
.vs-dlprog__step.is-active {
  color: var(--vs-text, #0f172a);
  font-weight: 600;
}
.vs-dlprog__step.is-active .vs-dlprog__step-icon {
  color: var(--vs-accent, #16a34a);
  animation: vs-dlprog-spin 1.1s linear infinite;
}
@keyframes vs-dlprog-spin { to { transform: rotate(360deg); } }
.vs-dlprog__step.is-done {
  color: var(--vs-text-dim, #475569);
}
.vs-dlprog__step.is-done .vs-dlprog__step-icon {
  color: #16a34a;
}
.vs-dlprog__step.is-failed {
  color: #c33;
  font-weight: 600;
}
.vs-dlprog__step.is-failed .vs-dlprog__step-icon { color: #c33; }

.vs-dlprog__bar {
  height: 6px;
  width: 100%;
  background: var(--vs-surface-2, #f3f5f9);
  border-radius: 999px;
  overflow: hidden;
}
.vs-dlprog__bar-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--vs-accent, #16a34a), var(--vs-accent-hi, #15803d));
  border-radius: 999px;
  transition: width 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.vs-dlprog__sub {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  font-size: 12px;
  color: var(--vs-text-muted, #94a3b8);
  min-height: 16px;
}
.vs-dlprog__sub-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vs-dlprog__percent {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--vs-text-dim, #475569);
}
.vs-dlprog__overlay.is-complete .vs-dlprog__bar-fill {
  background: linear-gradient(90deg, #16a34a, #22c55e);
}
.vs-dlprog__overlay.is-error .vs-dlprog__bar-fill {
  background: #c33;
}
.vs-dlprog__overlay.is-error .vs-dlprog__sub-label {
  color: #c33;
  white-space: normal;
}
.vs-dlprog__foot { justify-content: flex-end; }

/* Modale de décision step1 (Garder mesh statique / Animer / Fermer). Ouverte
   au clic ✓ Choisir sur un slot mesh. Variante visuelle : header avec X de
   fermeture, body élargi pour le markup HTML du message, backdrop "permissif"
   (clic backdrop ferme = re-choisir un autre mesh, cf. _askStep1Decision). */
.vs-guided-decision-modal .vs-modal { max-width: 520px; }
.vs-guided-decision-modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.vs-guided-decision-modal__close {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--vs-text-muted, #64748b);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 8px;
  margin: -4px -6px 0 0;
  border-radius: 6px;
  transition: color 120ms var(--vs-ease, ease), background 120ms var(--vs-ease, ease);
}
.vs-guided-decision-modal__close:hover {
  color: var(--vs-text, #0f172a);
  background: var(--vs-surface-2, #f3f5f9);
}
.vs-guided-decision-modal__msg { margin: 0; }
.vs-guided-decision-modal__anim-label {
  display: block;
  margin: 14px 0 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--vs-text-dim, #475569);
  letter-spacing: 0.02em;
}
.vs-guided-decision-modal__anim-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  font-size: 13px;
  font-family: inherit;
  line-height: 1.4;
  color: var(--vs-text, #0f172a);
  background: var(--vs-surface-1, #f8fafc);
  border: 1px solid var(--vs-border, #e2e8f0);
  border-radius: 6px;
  resize: vertical;
  min-height: 48px;
  transition: border-color 120ms var(--vs-ease, ease), box-shadow 120ms var(--vs-ease, ease);
}
.vs-guided-decision-modal__anim-input:focus {
  outline: none;
  border-color: var(--vs-accent, #4fc3f7);
  box-shadow: 0 0 0 3px rgba(79, 195, 247, 0.18);
}
.vs-guided-decision-modal__anim-input::placeholder {
  color: var(--vs-text-muted, #94a3b8);
  opacity: 1;
}
/* Hint info sous le textarea : prévient le user du cap soft 5-6 animations
 * par run pour qu'il ne soit pas surpris de la qualité s'il en demande 10.
 * Icône ronde "i" accent + texte dim, layout flex pour alignement propre. */
.vs-guided-decision-modal__anim-max-hint {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 8px 0 0;
  padding: 0;
  font-size: 12px;
  color: var(--vs-text-dim, #5a6478);
  line-height: 1.45;
}
.vs-guided-decision-modal__anim-max-hint svg {
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
  margin-top: 2px;
  color: var(--vs-accent, #6244ff);
}

@media (max-width: 540px) {
  .vs-modal-overlay { padding: 12px; }
  .vs-modal { max-width: 100%; }
  .vs-modal__head, .vs-modal__body, .vs-modal__actions { padding-left: 16px; padding-right: 16px; }
}

/* --- VsRatingPicker (note 0..10 nuancée) --- */
.vs-modal-overlay--rating .vs-modal--rating { max-width: 520px; }
.vs-rating-picker__hint {
  margin: 0 0 14px 0;
  font-size: 13px;
  color: var(--vs-text-dim, #6b7280);
}
.vs-rating-picker__row {
  display: grid;
  grid-template-columns: repeat(11, minmax(0, 1fr));
  gap: 4px;
  margin: 8px 0 12px;
}
.vs-rating-pill {
  appearance: none;
  background: var(--vs-surface-2, #f3f5f9);
  border: 2px solid var(--vs-border, #e3e6ec);
  border-radius: 6px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 10px 0;
  cursor: pointer;
  color: var(--vs-text, #0f172a);
  transition: background 80ms, border-color 80ms, color 80ms, transform 80ms;
  user-select: none;
}
.vs-rating-pill:hover  { transform: translateY(-1px); }
.vs-rating-pill:focus  { outline: 2px solid var(--vs-accent, #2a8c4a); outline-offset: 1px; }
.vs-rating-pill.is-zero    { color: var(--vs-text-dim, #6b7280); }
.vs-rating-pill.is-bad     { color: #c33; }
.vs-rating-pill.is-good    { color: #2a8c4a; }
.vs-rating-pill.is-active.is-zero {
  background: #6b7280; color: white; border-color: #4b5563;
}
.vs-rating-pill.is-active.is-bad {
  background: rgba(220, 60, 60, 0.92); color: white; border-color: #a82020;
}
.vs-rating-pill.is-active.is-good {
  background: rgba(40, 180, 90, 0.92); color: white; border-color: #1f6e36;
}
.vs-rating-picker__legend {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--vs-text-dim, #6b7280);
  padding: 0 2px;
}
.vs-rating-picker__legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.vs-rating-picker__legend-item .vs-icon { width: 12px; height: 12px; }
.vs-rating-picker__legend-item.is-good { color: #1f6e36; font-weight: 600; }
.vs-rating-picker__legend-item.is-bad  { color: #a82020; font-weight: 600; }

/* --- Textarea notes (sous les pills, optionnel mais utile pour entraîner) --- */
.vs-rating-picker__notes-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 16px;
}
.vs-rating-picker__notes-label > span {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vs-text-dim, #6b7280);
}
.vs-rating-picker__notes {
  width: 100%;
  min-height: 72px;
  resize: vertical;
  padding: 10px 12px;
  background: var(--vs-surface-2, #f3f5f9);
  border: 1px solid var(--vs-border, #e3e6ec);
  border-radius: 6px;
  font: inherit;
  font-size: 13px;
  line-height: 1.45;
  color: var(--vs-text, #0f172a);
  outline: none;
  transition: border-color 100ms, box-shadow 100ms;
  box-sizing: border-box;
  font-family: inherit;
}
.vs-rating-picker__notes:focus {
  border-color: var(--vs-accent, #2a8c4a);
  box-shadow: 0 0 0 3px rgba(42, 140, 74, 0.12);
}
.vs-rating-picker__notes::placeholder {
  color: var(--vs-text-dim, #9ca3af);
  font-style: italic;
}

@media (max-width: 540px) {
  .vs-rating-picker__row {
    grid-template-columns: repeat(6, minmax(0, 1fr));
    /* 11 pills sur 2 rangées de 6 (la dernière a 1 seul item, OK). */
  }
  .vs-rating-pill { padding: 8px 0; font-size: 13px; }
}

/* =========================================================
   RIGHT PANEL - ligne Save + New (section Assets)
   Save (primary, flex:1) + New (ghost, compact) côte à côte au-dessus
   du search bar.
   ========================================================= */
.vs-rpanel__collection-row {
  display: flex;
  align-items: stretch;
  gap: 6px;
  margin-bottom: 8px;
}
.vs-rpanel__collection-save {
  flex: 1 1 auto;
  justify-content: center;
}
.vs-rpanel__collection-new {
  flex: 0 0 auto;
}

/* =========================================================
   RIGHT PANEL - actions IA (section Animations)
   "Animation IA" + "Refaire la mesh" rendus directement à côté
   de la liste des animations, pour rester proches du contexte
   asset au lieu d'être perdus dans le PromptPanel.
   ========================================================= */
.vs-rpanel__ai-actions {
  display: flex; flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--vs-border);
}
.vs-ai-btn {
  display: inline-flex; align-items: center; justify-content: flex-start;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border);
  border-radius: var(--vs-r-md);
  color: var(--vs-text);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--vs-fs-sm);
  font-weight: 600;
  transition: background 120ms var(--vs-ease), border-color 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-ai-btn:hover:not(:disabled) {
  background: var(--vs-surface-2);
  border-color: var(--vs-accent-line);
  color: var(--vs-accent-hi);
}
.vs-ai-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.vs-ai-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.vs-ai-btn--primary {
  background: var(--vs-accent);
  border-color: transparent;
  color: #fff;
}
.vs-ai-btn--primary:hover:not(:disabled) {
  background: var(--vs-accent-hi);
  color: #fff;
}
.vs-ai-cost {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: var(--vs-r-pill);
  background: rgba(0,0,0,0.10);
}
.vs-ai-btn--primary .vs-ai-cost { background: rgba(255,255,255,0.22); }

/* ============================================================
   EditorHelpDialog (vs-help-*) - modale tutoriel éditeur.
   Pattern shell calqué sur .vs-publish (overlay + backdrop + panel
   + head/body). Contenu = sections statiques outils/raccourcis.
   ============================================================ */
.vs-help {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.vs-help__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  cursor: pointer;
}
.vs-help__panel {
  position: relative;
  width: min(720px, 100%);
  max-height: 90vh;
  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-help__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--vs-border);
}
.vs-help__head h3 {
  margin: 0;
  font-size: var(--vs-fs-lg);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.vs-help__head h3 .vs-icon {
  width: 1.1em;
  height: 1.1em;
  color: var(--vs-accent);
}
.vs-help__close {
  font-size: 18px;
  color: var(--vs-text-muted);
  padding: 4px 8px;
  border-radius: var(--vs-r-sm);
  background: transparent;
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.vs-help__close:hover { color: var(--vs-text); background: var(--vs-surface-2); }
.vs-help__close .vs-icon { width: 18px; height: 18px; }

.vs-help__body {
  padding: 18px 22px 22px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.vs-help__intro,
.vs-help__outro {
  margin: 0;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-dim);
  line-height: 1.5;
}
.vs-help__outro {
  border-top: 1px dashed var(--vs-border);
  padding-top: 14px;
  margin-top: 4px;
}

/* Callout "bienvenue" affiché uniquement à la première ouverture (ouverture
   automatique au boot de /edit). Pointe vers le bouton "?" subnav pour que
   l'user sache où retrouver l'aide ensuite. */
.vs-help__welcome {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  background: color-mix(in srgb, var(--vs-accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--vs-accent) 30%, var(--vs-border));
  border-radius: var(--vs-r-md);
  margin: 0;
}
.vs-help__welcome-icon {
  flex: 0 0 auto;
  color: var(--vs-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.vs-help__welcome-icon svg {
  width: 22px;
  height: 22px;
}
.vs-help__welcome-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--vs-fs-sm);
  line-height: 1.5;
  color: var(--vs-text-dim);
}
.vs-help__welcome-body strong {
  font-size: var(--vs-fs-md);
  font-weight: 600;
  color: var(--vs-text);
}

.vs-help__section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.vs-help__section h4 {
  margin: 0;
  font-size: var(--vs-fs-md);
  font-weight: 600;
  color: var(--vs-text);
  letter-spacing: 0.01em;
}
.vs-help__lead {
  margin: 0;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text-dim);
  line-height: 1.45;
}

/* Grille 2-col : libellé / combo. Sur petit écran, on empile. */
.vs-help__rows {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 16px;
  row-gap: 4px;
  margin: 4px 0 0;
}
.vs-help__row {
  display: contents;
}
.vs-help__row dt {
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  padding: 5px 0;
}
.vs-help__row dd {
  margin: 0;
  text-align: right;
  font-size: var(--vs-fs-xs);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 4px;
  flex-wrap: wrap;
  padding: 5px 0;
  color: var(--vs-text-muted);
}

.vs-help__list {
  margin: 4px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--vs-fs-sm);
  color: var(--vs-text);
  line-height: 1.5;
}
.vs-help__list li strong { color: var(--vs-accent); }

/* Style touche clavier dans la modale aide. Scoped à .vs-help pour ne pas
   péter les <kbd> si on en utilise ailleurs un jour. */
.vs-help kbd {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, "Courier New", monospace;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  color: var(--vs-text);
  background: var(--vs-surface-2);
  border: 1px solid var(--vs-border-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 3px 6px;
  white-space: nowrap;
}

@media (max-width: 600px) {
  /* En mobile l'overlay collait au top (padding 24 → encore visible) et la
     modale prenait 95vh = quasi tout l'écran. On compacte head+welcome+body
     pour que la modale ressemble à une feuille tap-to-scroll, plus à un mur
     qui mange tout l'écran. */
  .vs-help { padding: 8px; }
  .vs-help__panel { max-height: 88vh; }
  .vs-help__head { padding: 10px 14px; }
  .vs-help__head h3 { font-size: var(--vs-fs-md); gap: 6px; }
  .vs-help__head h3 .vs-icon { width: 1em; height: 1em; }
  .vs-help__close { padding: 2px 6px; }
  .vs-help__close .vs-icon { width: 16px; height: 16px; }

  .vs-help__body {
    padding: 12px 14px 16px;
    gap: 12px;
  }
  .vs-help__intro,
  .vs-help__outro { font-size: var(--vs-fs-xs); }
  .vs-help__outro { padding-top: 10px; margin-top: 0; }

  /* Welcome callout : icone en plus petit, padding réduit, gap serré. */
  .vs-help__welcome { padding: 10px 12px; gap: 10px; }
  .vs-help__welcome-icon svg { width: 18px; height: 18px; }
  .vs-help__welcome-body { font-size: var(--vs-fs-xs); gap: 2px; }
  .vs-help__welcome-body strong { font-size: var(--vs-fs-sm); }

  .vs-help__section { gap: 6px; }
  .vs-help__section h4 { font-size: var(--vs-fs-sm); }
  .vs-help__lead { font-size: var(--vs-fs-xs); }

  .vs-help__rows {
    grid-template-columns: 1fr;
    row-gap: 2px;
  }
  .vs-help__row dt { padding: 4px 0 0; font-size: var(--vs-fs-xs); }
  .vs-help__row dd {
    justify-content: flex-start;
    padding: 0 0 6px;
    font-size: 10px;
  }
  .vs-help kbd { font-size: 10px; padding: 2px 5px; }

  .vs-help__list { font-size: var(--vs-fs-xs); gap: 4px; padding-left: 16px; }
}

/* Bouton "?" dans la subnav éditeur - icône-only, neutre, toujours actif. */
.vs-edit-subnav__help {
  flex: 0 0 auto;
}

/* =========================================================
   Cookie consent banner + modal (cf. cookie-consent.js)
   Posé 2026-05-12. Style autoportant (les tokens viennent
   de _design-tokens.css importé en tête).
   ========================================================= */
.vs-cc-banner {
  position: fixed;
  left: 16px; right: 16px; bottom: 16px;
  z-index: 9000;
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-lg);
  box-shadow: var(--vs-shadow-3);
  padding: 18px 20px;
  max-width: 720px;
  margin-left: auto;
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  animation: vs-cc-slide-in 220ms var(--vs-ease) both;
}
@keyframes vs-cc-slide-in {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}
.vs-cc-banner__inner { display: flex; flex-direction: column; gap: 12px; }
.vs-cc-banner__title {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--vs-text);
}
.vs-cc-banner__body {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--vs-text-muted);
}
.vs-cc-banner__more {
  color: var(--vs-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.vs-cc-banner__more:hover { color: var(--vs-accent-hi); }
.vs-cc-banner__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.vs-cc-btn {
  padding: 10px 18px;
  border-radius: var(--vs-r-md);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid var(--vs-border-strong);
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease), border-color 120ms var(--vs-ease);
}
.vs-cc-btn--ghost {
  background: transparent;
  color: var(--vs-text-muted);
}
.vs-cc-btn--ghost:hover { color: var(--vs-text); border-color: var(--vs-text); }
.vs-cc-btn--primary {
  background: var(--vs-text);
  color: #fff;
  border-color: var(--vs-text);
}
.vs-cc-btn--primary:hover { background: var(--vs-accent); border-color: var(--vs-accent); }

/* Modal de préférences */
.vs-cc-modal__backdrop {
  position: fixed; inset: 0;
  z-index: 9100;
  background: rgba(10, 10, 10, 0.45);
  backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  animation: vs-cc-fade-in 160ms var(--vs-ease) both;
}
@keyframes vs-cc-fade-in { from { opacity: 0; } to { opacity: 1; } }
.vs-cc-modal__panel {
  background: var(--vs-surface-1);
  border: 1px solid var(--vs-border-strong);
  border-radius: var(--vs-r-lg);
  box-shadow: var(--vs-shadow-3);
  padding: 22px 24px;
  max-width: 520px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
.vs-cc-modal__title {
  margin: 0 0 16px;
  font-size: 17px;
  font-weight: 700;
  color: var(--vs-text);
}
.vs-cc-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid var(--vs-border);
}
.vs-cc-row:last-of-type { border-bottom: 1px solid var(--vs-border); margin-bottom: 16px; }
.vs-cc-row__main { flex: 1; min-width: 0; }
.vs-cc-row__main b { display: block; font-size: 13.5px; color: var(--vs-text); margin-bottom: 4px; }
.vs-cc-row__main small { display: block; font-size: 12.5px; color: var(--vs-text-muted); line-height: 1.45; }
.vs-cc-row--locked .vs-cc-row__main b { color: var(--vs-text-muted); }

/* Toggle switch (boutons-bascule analytics) */
.vs-cc-toggle {
  flex: 0 0 auto;
  position: relative;
  width: 42px;
  height: 24px;
  border-radius: 999px;
  background: var(--vs-surface-hi);
  border: 1px solid var(--vs-border);
  cursor: pointer;
  padding: 0;
  transition: background 160ms var(--vs-ease);
  margin-top: 4px;
}
.vs-cc-toggle::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  transition: transform 160ms var(--vs-ease);
}
.vs-cc-toggle.is-on { background: var(--vs-accent); border-color: var(--vs-accent); }
.vs-cc-toggle.is-on::after { transform: translateX(18px); }
.vs-cc-toggle.is-locked { opacity: 0.7; cursor: not-allowed; }

.vs-cc-modal__actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

@media (max-width: 600px) {
  .vs-cc-banner { left: 8px; right: 8px; bottom: 8px; padding: 14px 16px; }
  .vs-cc-banner__title { font-size: 14px; }
  .vs-cc-banner__body { font-size: 12.5px; }
  .vs-cc-banner__actions .vs-cc-btn { flex: 1 1 100px; }
}

/* =========================================================
   Signup nudge banner — rappel inscription pour anonymes.
   Rendu server-side dans _main_nav.php, juste AVANT le <header>.
   Sticky-haut (au-dessus de la nav) pour rester visible pendant le scroll.
   Dismissable via [data-vs-signup-nudge-close] + localStorage TTL 30j.
   ========================================================= */
.vs-signup-nudge {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 16px;
  background: linear-gradient(90deg, rgba(99, 102, 241, 0.10), rgba(168, 85, 247, 0.10));
  border-bottom: 1px solid rgba(99, 102, 241, 0.22);
  font-size: 13px;
  color: var(--vs-text);
  position: relative;
  z-index: 99;
}
.vs-signup-nudge[hidden] { display: none; }
.vs-signup-nudge__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: #6366f1;
}
.vs-signup-nudge__icon svg { width: 16px; height: 16px; }
.vs-signup-nudge__text {
  flex: 1 1 auto;
  line-height: 1.4;
}
.vs-signup-nudge__text strong { color: var(--vs-text); font-weight: 700; }
.vs-signup-nudge__cta {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--vs-text);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: transform 120ms var(--vs-ease), box-shadow 120ms var(--vs-ease);
}
.vs-signup-nudge__cta:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(10, 10, 10, 0.18);
  color: #fff;
}
.vs-signup-nudge__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--vs-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 120ms var(--vs-ease), color 120ms var(--vs-ease);
}
.vs-signup-nudge__close svg { width: 14px; height: 14px; }
.vs-signup-nudge__close:hover {
  background: rgba(10, 10, 10, 0.06);
  color: var(--vs-text);
}

@media (max-width: 600px) {
  .vs-signup-nudge { padding: 7px 12px; gap: 8px; font-size: 12px; }
  .vs-signup-nudge__icon { width: 18px; height: 18px; }
  .vs-signup-nudge__icon svg { width: 13px; height: 13px; }
  .vs-signup-nudge__cta { padding: 5px 10px; font-size: 11.5px; }
}
