/*
 * Styles du Team Builder (/team_2/).
 * Toutes les classes commencent par "gtb-" (Gifr Team Builder) pour éviter
 * tout conflit avec les classes du thème Divi ou des autres plugins du site.
 */

/* ---------- Conteneur général + variables de couleur ---------- */

.gtb-app {
  /* Variables CSS centralisées : changer une valeur ici la change partout
     dans le fichier, pas besoin de chercher chaque occurrence.

     Chaque variable --gtb-* est désormais une réexpression d'un jeton
     --gi-* du design system global (plugin gifr-design, gi-tokens.css),
     avec l'ancienne valeur codée en dur comme repli. Le repli n'est pas
     décoratif : si gifr-design est un jour désactivé, --gi-* devient
     invalide et var() retombe automatiquement sur la couleur d'origine,
     donc le builder reste utilisable et identique à avant plutôt que de
     casser silencieusement. */
  --gtb-bg: var(--gi-ink-900, #1c1c22);
  --gtb-panel: var(--gi-ink-800, #26262e);
  --gtb-border: var(--gi-line, #3a3a45);
  --gtb-text: var(--gi-text, #eee);
  --gtb-muted: var(--gi-text-muted, #9a9aa5);
  --gtb-accent: var(--gi-accent, #c9a35c);

  /* Dérivées : mêmes rôles que sur le reste du site (fond opaque pour les
     pastilles, texte sombre sur fond doré, teinte dorée diluée pour l'état
     "possédé/sélectionné", voile de fond de modale), plutôt que des
     couleurs isolées propres au builder. */
  --gtb-badge-bg: var(--gi-ink-700, #3a3a45);
  --gtb-ink: var(--gi-ink-950, #1c1c22);
  --gtb-owned-bg: color-mix(in srgb, var(--gi-gold-500, #c9a35c) 18%, var(--gtb-panel));
  --gtb-overlay: color-mix(in srgb, var(--gi-ink-950, black) 70%, transparent);

  /* Rayons de bordure alignés sur l'échelle --r-* du site plutôt que des
     pixels isolés : mêmes proportions que les cartes, boutons et étiquettes
     du reste du site. */
  --gtb-radius-sm: var(--r-xs, 4px);
  --gtb-radius-md: var(--r-sm, 6px);
  --gtb-radius-lg: var(--r-md, 8px);
  --gtb-radius-pill: var(--r-pill, 999px);

  /* Transition commune aux éléments interactifs (survol souris ET classes
     togglées en JS type gtb-active/gtb-owned) : mêmes durée et courbe que le
     reste du site, pour que les changements d'état ne soient pas plus
     abrupts ici qu'ailleurs. */
  --gtb-transition: border-color var(--t-fast, 160ms) var(--ease-out, ease),
    background-color var(--t-fast, 160ms) var(--ease-out, ease),
    color var(--t-fast, 160ms) var(--ease-out, ease);

  color: var(--gtb-text);
  /* PAS de background opaque ici (retiré, ancienne valeur var(--gi-ink-950) —
     exactement la même teinte de base que gi-cat-fond, posée en JS sur
     .entry-content, voir team-builder.js). Un fond plein sur .gtb-app
     couvrirait entièrement les halos/semis d'étoiles/vignetage de
     gi-cat-fond dans le padding et les espacements internes (.gtb-layout,
     grille de personnages) : ils ne resteraient visibles qu'en dehors de
     .gtb-app, comme sur la page avant ce fond. Chaque élément interne
     (boutons, cartes, modale) a déjà son propre fond (--gtb-panel/--gtb-bg),
     donc rien ne perd en lisibilité. */
  /* !important nécessaire : Divi pose lui-même du font-family en !important
     sur le contenu de ses modules (le shortcode [gtb_team_builder] est
     intégré dans un module Divi sur /team_2/), donc une règle "normale" ici
     perd la cascade et le builder retombe sur la police par défaut de Divi
     au lieu d'Inter comme le reste du site. Même remède déjà utilisé ailleurs
     sur le site pour la même raison, voir gi-divi.css (recherche "!important
     parce que Divi utilise lui-même !important"). Repli sur "inherit" si
     gi-tokens est absent (--gi-font-body invalide) : le builder retombe
     alors sur la police héritée de Divi/du thème, comme avant cette règle. */
  font-family: var(--gi-font-body, inherit) !important;
  border-radius: var(--gtb-radius-lg);

  /* Mêmes écarts droite/gauche que .gi-container--wide (gi-tokens.css),
     utilisé par la ligne Divi de /personnages/ : largeur pleine jusqu'à
     --gi-container-wide (1760px), centrée, avec le même gouttière latérale
     --gi-gutter qui se resserre progressivement sur petit écran (clamp) au
     lieu d'un padding fixe. Repli en dur si gifr-design est absent. */
  width: 100%;
  max-width: var(--gi-container-wide, 1760px);
  margin-inline: auto;
  padding-block: 16px;
  padding-inline: var(--gi-gutter, clamp(1rem, 0.6rem + 2vw, 2.5rem));
}

/* --cat-r0 à --cat-r5 (teintes de rareté : gris, vert, bleu, violet, or) ne
   sont normalement déclarées QUE sur les éléments portant la classe .gi-cat
   (voir gi-database.css). Sur /personnages/, gi-database.js pose toujours
   .gi-cat AVANT d'ajouter .gi-cat--sans-lien, jamais l'un sans l'autre. Ici,
   .gtb-persos-grid porte gi-cat--sans-lien SEUL (voir Shortcode.php) : .gi-cat
   impose sa propre mise en page en deux colonnes (grille + colonne de
   filtres), incompatible avec la grille du builder, donc on ne peut pas
   l'ajouter telle quelle. Sans .gi-cat, aucune des six variables n'est
   héritée : .gi-cat-card[data-r] pose --rar: var(--cat-rX), qui devient
   invalide, et tout ce qui en dépend (fond des portraits, anneau "possédé")
   retombe silencieusement sur ses valeurs de repli. Même bug, déjà documenté
   dans gi-database.css, pour le panneau de survol qui vit hors de .gi-cat.
   On redéclare donc ici les six mêmes teintes (valeurs recopiées de
   gi-database.css : à resynchroniser si elles changent là-bas). */
.gtb-app {
  --cat-r0: #7d8083;
  --cat-r1: #7d8083;
  --cat-r2: #599479;
  --cat-r3: #5985ab;
  --cat-r4: #926eb9;
  --cat-r5: #c57b24;
}

/* ============================================================================
   HABILLAGE DES TUILES DE PERSONNAGES — recopié de gi-database.css (gifr-design)
   ----------------------------------------------------------------------------
   Recopié ici le 2026-08-31. Depuis gifr-design 2.12.113, gi-database.css
   n'est plus chargée sur toutes les pages du site mais seulement sur celles
   que gifr_besoin_db() reconnaît (fiches gi_*, archives gi_*, contenu portant
   un marqueur data-gi-*). La page du builder n'en fait pas partie : les tuiles
   .gi-cat-card* que rend Shortcode.php se retrouvaient donc sans style.

   On rapatrie ici les seules règles nécessaires à la grille (cadre du portrait,
   fond de rareté, badges élément/arme, étoiles), portées par .gtb-app et avec
   un repli en dur sur chaque jeton --gi-*, exactement comme le reste de ce
   fichier. Le builder est ainsi autonome : il s'affiche correctement quoi que
   fasse gifr-design (feuille non chargée, plugin désactivé). Le hook
   gifr_pages_db (voir Core/Plugin.php) reste en place en complément : quand
   gifr-design est présent et à jour, sa version fait toujours foi.

   À RESYNCHRONISER si le dessin des tuiles de /personnages/ change dans
   gi-database.css. Non repris volontairement : la grille .gi-cat__grid et la
   colonne de filtres .gi-cat (mise en page propre à /personnages/, le builder
   a la sienne), les pastilles d'axe vide (le shortcode ne rend un <li> que
   s'il y a une icône) et le cadrage .gi-cat--objet (pages armes/objets).
   ============================================================================ */
.gtb-app {
  /* Masque de l'étoile de rareté : le repli garantit que les étoiles restent
     visibles même si gi-tokens.css n'est pas chargé (--gi-star invalide). */
  --gtb-star: var(--gi-star, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 1.8c.6 4.3 1.6 6.9 3.4 8.6 1.7 1.7 4.3 2.7 8.6 3.3v.6c-4.3.6-6.9 1.6-8.6 3.3-1.8 1.7-2.8 4.3-3.4 8.6h-.6c-.6-4.3-1.6-6.9-3.3-8.6-1.7-1.7-4.3-2.7-8.6-3.3v-.6c4.3-.6 6.9-1.6 8.6-3.3C9.8 8.7 10.8 6.1 11.4 1.8z'/%3E%3C/svg%3E"));
}

/* Couleur d'élément posée par data-v sur le badge peint (--chip) et le point
   de repli (--chip-on). Valeurs recopiées de gi-tokens.css. */
.gtb-app [data-v="anemo"]   { --chip: var(--el-anemo, #74C6AC);   --chip-on: 1; }
.gtb-app [data-v="geo"]     { --chip: var(--el-geo, #E5B13A);     --chip-on: 1; }
.gtb-app [data-v="electro"] { --chip: var(--el-electro, #B189DD); --chip-on: 1; }
.gtb-app [data-v="dendro"]  { --chip: var(--el-dendro, #8FC93A);  --chip-on: 1; }
.gtb-app [data-v="hydro"]   { --chip: var(--el-hydro, #4FA6E3);   --chip-on: 1; }
.gtb-app [data-v="pyro"]    { --chip: var(--el-pyro, #E4694A);    --chip-on: 1; }
.gtb-app [data-v="cryo"]    { --chip: var(--el-cryo, #93D4E8);    --chip-on: 1; }

.gtb-app .gi-sym {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  object-fit: contain;
  border-radius: 50%;
}
.gtb-app .gi-sym--peint {
  background: var(--chip, var(--gi-sand-100, #F1E9DA));
  -webkit-mask: var(--ico) center / contain no-repeat;
          mask: var(--ico) center / contain no-repeat;
  border-radius: 0;
}

.gtb-app .gi-etoile {
  width: 1em;
  height: 1em;
  flex: none;
  background: var(--et, var(--gi-gold-400, #DBC084));
  -webkit-mask: var(--gtb-star) center / contain no-repeat;
          mask: var(--gtb-star) center / contain no-repeat;
}

.gtb-app .gi-cat-card {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  width: 100%;
  overflow: hidden;
  text-decoration: none;
  background: var(--gi-ink-850, #121D29);
  border: 1px solid var(--gi-line-soft, rgba(241, 233, 218, 0.09));
  border-radius: 4px;
  box-shadow:
    0 14px 30px -18px rgba(0, 0, 0, 0.85),
    0 3px 8px -4px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(241, 233, 218, 0.07);
  transition: border-color var(--t-base, 280ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
              translate var(--t-base, 280ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1)),
              box-shadow var(--t-base, 280ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
  /* Valeur explicite plutôt que le mot-clé implicite "none" : sinon la
     transition translate ne se fait que dans un sens (bug Firefox connu,
     documenté dans gi-database.css). */
  translate: 0 0;
}
/* Les tuiles du builder sont des <div> (jamais des <a>) et le conteneur porte
   gi-cat--sans-lien : c'est ce sélecteur-là qui pilote le survol, pas :hover
   sur un lien. */
.gtb-app .gi-cat--sans-lien .gi-cat-card:hover {
  border-color: var(--gi-line-strong, rgba(198, 165, 93, 0.42));
  translate: 0 -3px;
  box-shadow:
    0 22px 42px -18px rgba(0, 0, 0, 0.9),
    0 6px 14px -6px rgba(0, 0, 0, 0.6),
    inset 0 1px 0 rgba(241, 233, 218, 0.12);
}
.gtb-app .gi-cat-card--muet { cursor: default; }

.gtb-app .gi-cat-card__portrait {
  position: relative;
  isolation: isolate;
  aspect-ratio: 1;
  overflow: hidden;
  /* Fond dégradé à la couleur de la rareté (--rar, posée par [data-r]
     ci-dessous ; --cat-r0 est le repli, déclaré plus haut sur .gtb-app). */
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--rar, var(--cat-r0)) 78%, #060b12) 0%,
      var(--rar, var(--cat-r0)) 62%,
      color-mix(in srgb, var(--rar, var(--cat-r0)) 88%, #f1e9da) 100%);
}
.gtb-app .gi-cat-card__portrait::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.22;
  mix-blend-mode: overlay;
  /* Grain purement décoratif : pas de repli en dur, il disparaît simplement
     si gi-tokens.css est absent. */
  background-image: var(--gi-noise), var(--gi-noise);
  background-size: 158px 158px, 780px 780px;
}
.gtb-app .gi-cat-card__portrait::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent 62%, rgba(6, 11, 18, 0.28) 100%);
}
.gtb-app .gi-cat-card__portrait > .gi-cat-card__fili {
  position: absolute;
  inset: -12% -12% auto -12%;
  aspect-ratio: 1;
  z-index: 0;
  pointer-events: none;
  opacity: 0.13;
  background: #05090f;
  /* --gi-fili est écrite en style inline sur le <span> par Shortcode.php
     (url de l'icône d'élément), pas besoin de jeton global ici. */
  -webkit-mask: var(--gi-fili) center / contain no-repeat;
          mask: var(--gi-fili) center / contain no-repeat;
}
.gtb-app .gi-cat-card[data-r="1"] { --rar: var(--cat-r1); }
.gtb-app .gi-cat-card[data-r="2"] { --rar: var(--cat-r2); }
.gtb-app .gi-cat-card[data-r="3"] { --rar: var(--cat-r3); }
.gtb-app .gi-cat-card[data-r="4"] { --rar: var(--cat-r4); }
.gtb-app .gi-cat-card[data-r="5"] { --rar: var(--cat-r5); }

.gtb-app .gi-cat-card__portrait img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  display: block;
  transition: scale var(--t-slow, 620ms) var(--ease-out, cubic-bezier(0.22, 1, 0.36, 1));
  scale: 1;
}
.gtb-app .gi-cat--sans-lien .gi-cat-card:hover .gi-cat-card__portrait img { scale: 1.06; }

.gtb-app .gi-cat-card__ico {
  position: absolute;
  inset: 0.3rem auto auto 0.3rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: fit-content;
  gap: 0.28rem;
  margin: 0;
  padding: 0;
  list-style: none;
  z-index: 2;
}
/* Divi ré-applique un padding aux <ul> via #et-boc : se hisser d'une classe
   suffit à repasser devant (même parade que dans gi-database.css). */
.gtb-app ul.gi-cat-card__ico.gi-cat-card__ico { padding: 0; margin: 0; }
.gtb-app .gi-cat-card__ico li {
  display: grid;
  place-items: center;
  width: 1.65rem;
  height: 1.65rem;
  font-size: 0.9rem;
  background: rgba(8, 14, 20, 0.6);
  border: 1px solid color-mix(in srgb, var(--chip, var(--gi-gold-600, #A4813D)) 42%, transparent);
  border-radius: 50%;
  backdrop-filter: blur(3px);
}
.gtb-app .gi-cat-card__ico li[data-v] .gi-sym {
  background: var(--chip, var(--gi-gold-400, #DBC084));
  -webkit-mask: var(--ico) center / contain no-repeat;
          mask: var(--ico) center / contain no-repeat;
  border-radius: 0;
}
.gtb-app .gi-cat-card__ico li[data-v] img.gi-sym { background: var(--chip, var(--gi-gold-400, #DBC084)); }

.gtb-app .gi-cat-card__rank {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 1px;
  padding: 1.2rem 0 0.35rem;
  background: linear-gradient(180deg, transparent, rgba(8, 14, 20, 0.8));
}
.gtb-app .gi-cat-card__rank .gi-etoile { width: 1.2rem; height: 1.2rem; }

.gtb-app .gi-cat-card__name {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  min-height: 3.4rem;
  padding: var(--sp-2, 0.5rem) var(--sp-3, 0.75rem);
  /* font-family reste sur "inherit" via .gtb-app * (voir plus bas) : conservé
     ici pour rester fidèle à gi-database.css lors d'une resynchro. */
  font-family: var(--gi-font-title, inherit);
  font-size: var(--fs-t-sm, 1.06rem);
  font-weight: 600;
  letter-spacing: -0.014em;
  line-height: var(--lh-snug, 1.28);
  text-align: center;
  text-wrap: balance;
  color: var(--gi-sand-50, #F8F4EC);
  background: linear-gradient(180deg, var(--gi-ink-800, #172533), var(--gi-ink-850, #121D29));
  border-top: 1px solid var(--gi-line, rgba(198, 165, 93, 0.20));
  overflow: hidden;
  isolation: isolate;
}
.gtb-app .gi-cat-card__name::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0.12;
  mix-blend-mode: overlay;
  background-image: var(--gi-noise), var(--gi-noise);
  background-size: 128px 128px, 620px 620px;
}
.gtb-app .gi-cat-card__name::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(120% 160% at 50% 0%, transparent 34%, rgba(6, 11, 18, 0.42) 100%);
}
.gtb-app .gi-cat-card__name > span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* gi-hover.js (gifr-design, chargé partout) attache sa lampe et son
   soulèvement générique à .gi-card--hover. Sur une tuile de portrait la lampe
   se lit comme une tache et le soulèvement générique (transform) entre en
   conflit avec le translate ci-dessus : on ne garde que les étincelles, comme
   sur /personnages/. */
.gtb-app .gi-cat-card.gi-card--hover { --lamp: 0 !important; }
.gtb-app .gi-cat-card.gi-card--hover:hover { transform: none; }

/* box-sizing: border-box partout à l'intérieur du builder, pour que
   padding/border n'agrandissent jamais la taille déclarée d'un élément
   (évite des surprises de mise en page). font-family: inherit en plus
   (même raison que le !important ci-dessus sur .gtb-app) : Divi cible
   parfois directement certaines balises (button, input...) avec son propre
   font-family en !important, la seule règle posée sur .gtb-app ne suffit
   alors pas — la cascade normale ne "traverse" pas une règle plus précise
   posée sur le descendant lui-même. */
.gtb-app * {
  box-sizing: border-box;
  font-family: inherit !important;
}

/* ---------- Mise en page générale : contenu à gauche, colonne d'actions/filtres à droite ---------- */

.gtb-layout {
  display: flex;
  /* Même espacement que .gi-cat (gi-database.css : gap: var(--sp-6) var(--sp-7)),
     colonne + aside sur /personnages/. */
  gap: var(--sp-6, 2rem) var(--sp-7, 2.5rem);
  align-items: flex-start;
}

.gtb-sidebar {
  flex: 0 0 220px;
  /* order: 2 fait passer la colonne d'actions/filtres APRÈS .gtb-main dans
     l'ordre visuel (à droite), sans changer l'ordre du HTML (Shortcode.php) :
     même emplacement que la colonne de filtres de /personnages/ (.gi-cat__aside,
     seconde colonne de la grille .gi-cat). */
  order: 2;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* "sticky" : la sidebar reste visible à l'écran quand on scrolle dans les
     résultats/la grille de personnages, pratique vu la hauteur de la page.
     top fixe à 20px auparavant : ça ne tenait pas compte de la nav du site
     (position sticky elle aussi), qui passait par-dessus la sidebar une fois
     collée. --gi-header-h (mesurée en JS, gi-ui.js) donne la vraie hauteur
     de la nav à l'instant présent, admin bar WP comprise : même mécanique
     que .gi-cat__aside sur /personnages/ (gi-database.css). */
  position: sticky;
  top: calc(var(--gi-header-h, 74px) + var(--sp-3, 12px));

  /* Défilement interne, comme .gi-cat__aside (gi-database.css) : sur un
     écran court, la sidebar (générateur + filtres + tri) peut dépasser la
     fenêtre ; étant sticky, ses derniers boutons deviendraient sinon
     inatteignables. max-height bornée à la fenêtre (moins top + une même
     respiration en bas) plus overflow-y: auto rend la main. overflow-x:
     clip évite qu'une bordure d'1px ne déclenche une barre horizontale. */
  max-height: calc(100dvh - var(--gi-header-h, 74px) - var(--sp-6, 32px));
  overflow-y: auto;
  overflow-x: clip;

  /* Scrollbar volontairement DIFFÉRENTE de celle, dorée, du reste de la page
     (voir gi-tokens.css : ::-webkit-scrollbar, 10px, dégradé --gi-gold-700/800) :
     même recette que .gi-cat__aside sur /personnages/, plus fine (9px) et
     plus claire, pour rester lisible sur le panneau sombre de la sidebar
     sans se confondre avec la barre de défilement de la fenêtre. */
  scrollbar-width: thin;
  scrollbar-color: var(--gi-gold-600, #a4813d) rgba(8, 14, 20, 0.5);
  scrollbar-gutter: stable;
}

.gtb-sidebar::-webkit-scrollbar {
  width: 9px;
}

.gtb-sidebar::-webkit-scrollbar-track {
  background: rgba(8, 14, 20, 0.5);
  border-radius: 5px;
}

.gtb-sidebar::-webkit-scrollbar-thumb {
  background: var(--gi-gold-600, #a4813d);
  border-radius: 5px;
  border: 2px solid var(--gi-ink-900, #0d1620);
}

.gtb-sidebar::-webkit-scrollbar-thumb:hover {
  background: var(--gi-gold-500, #c6a55d);
}

.gtb-main {
  order: 1;
  flex: 1;
  min-width: 0;
}

/* Sous 1080px, même palier que .gi-cat (gi-database.css, "C6. PALIERS") :
   la colonne d'actions/filtres repasse au-dessus de la grille plutôt qu'à
   côté, où elle mangerait plus de place qu'elle n'en rend. */
@media (max-width: 1080px) {
  .gtb-layout {
    flex-direction: column;
    /* .gtb-layout a align-items:flex-start (plus haut) pour ne pas forcer
       la sidebar et .gtb-main à la même hauteur en layout normal (ligne).
       Une fois empilés en colonne, les axes s'inversent : flex-start
       s'applique alors à la LARGEUR, et .gtb-main se retrouvait réduit à la
       largeur de son contenu (grille de résultats) au lieu de remplir la
       colonne — d'où le "moitié de la largeur" avec du vide à droite.
       stretch restaure le remplissage complet une fois en colonne. */
    align-items: stretch;
  }

  .gtb-sidebar {
    order: -1;
    position: static;
    flex: 1 1 auto;
    width: 100%;
    /* Plus sticky ni contrainte en hauteur une fois empilée au-dessus de la
       grille : le défilement interne n'a de sens que pour une colonne
       collante à côté du contenu, pas pour un bloc en flux normal. */
    max-height: none;
    overflow: visible;

    /* Colonne unique (display:flex, direction:column, voir la règle de base
       .gtb-sidebar) devenue trop haute une fois empilée au-dessus de la
       grille : chaque bouton/filtre s'étirait sur toute la largeur et se
       retrouvait seul sur sa ligne (Exporter/Importer/Proposer en 3 lignes
       pleine largeur, Trier par et Éléments chacun sur la sienne). Grille à
       6 colonnes plutôt qu'un simple flex-wrap : ça permet d'exprimer aussi
       bien un partage 50/50 (span 3 + span 3, Trier par / Éléments) qu'un
       partage en 3 (span 2 x3, Exporter/Importer/Proposer) avec une seule
       et même grille, sans wrapper supplémentaire dans le HTML — les
       éléments qui doivent rester pleine largeur (Générer,
       groupe de filtres...) couvrent simplement les 6 colonnes. */
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 8px;
  }

  /* Pleine largeur (une ligne à eux seuls) : tout ce qui n'a pas vocation à
     partager sa ligne avec un autre élément. */
  .gtb-sidebar>.gtb-generate-btn,
  .gtb-sidebar>.gtb-refresh-btn,
  .gtb-sidebar>#gtb-select-all,
  .gtb-sidebar>.gtb-filter-group {
    grid-column: 1 / -1;
  }

  /* <br> servaient à forcer des sauts de ligne en flux normal (voir
     Shortcode.php) : sans objet dans une grille, où chaque enfant occupe
     déjà sa propre cellule — display:none plutôt que de les laisser
     consommer une cellule vide. */
  .gtb-sidebar>br {
    display: none;
  }

  /* "Trier par" et "Éléments" chacun sur leur propre ligne (pleine largeur,
     1/-1) : partagés moitié-moitié auparavant, mais devenus trop à l'étroit
     l'un contre l'autre. .gtb-filter-group (la liste des boutons
     Anémo/Cryo/...) reste pleine largeur juste en dessous (voir plus haut). */
  .gtb-sidebar>.gtb-sort {
    grid-column: 1 / -1;
    margin-bottom: 0;
  }

  .gtb-sidebar>.gtb-filter-label {
    grid-column: 1 / -1;
    margin: 0;
  }

  /* Exporter / Importer / Proposer une team sur une même ligne (span 2 sur
     6 colonnes chacun, soit 1/3) plutôt qu'empilés pleine largeur. Police
     réduite : à ce tiers de largeur sur un écran de téléphone, le texte
     original (14px) forçait un retour à la ligne disgracieux dans certains
     boutons ("Exporter mon code" notamment) alors que les deux autres
     restaient sur une ligne. */
  .gtb-sidebar>#gtb-export,
  .gtb-sidebar>#gtb-import,
  .gtb-sidebar>#gtb-open-submit {
    grid-column: span 2;
    padding-inline: 6px;
    font-size: 12px;
    text-align: center;
  }
}

/* En dessous de 560px (vrai mobile, pas juste la tablette qui déclenche déjà
   le palier 1080px ci-dessus), le texte "Éléments" n'apporte plus grand-chose
   une fois les boutons de filtre juste en dessous et bien visibles : on le
   retire pour ne garder que les boutons cliquables, qui gagnent d'autant plus
   de place à l'écran. */
@media (max-width: 560px) {
  .gtb-sidebar>.gtb-filter-label {
    display: none;
  }
}

/* ---------- Boutons génériques de la sidebar ---------- */

.gtb-btn {
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  color: var(--gtb-text);
  padding: 8px 12px;
  border-radius: var(--gtb-radius-md);
  cursor: pointer;
  font-size: var(--fs-xs);
  transition: var(--gtb-transition);
}

.gtb-btn:hover {
  border-color: var(--gtb-accent);
}

/* Classe ajoutée/retirée par le JS (classList.toggle) pour marquer un
   bouton comme actif : mode sélectionné, filtre actif, etc. */
.gtb-btn.gtb-active {
  background: var(--gtb-accent);
  color: var(--gtb-ink);
  border-color: var(--gtb-accent);
}

/* Style "mis en avant" pour l'action principale (le bouton Générer). */
.gtb-btn-primary {
  background: var(--gtb-accent);
  color: var(--gtb-ink);
  font-weight: bold;
}

/* ---------- Filtres Éléments / Armes ---------- */

.gtb-filter-label {
  margin: 4px 0 2px;
  font-size: 12px;
  font-weight: 700;
  /* Même style que le badge "Impératif" des cartes (.gtb-imperatif-btn) :
     doré + letter-spacing "wider" au lieu du gris muet "widest" utilisé avant. */
  letter-spacing: var(--ls-wider);
  color: var(--gi-gold-400);
  text-transform: uppercase;
  /* !important nécessaire pour l'emporter sur .gtb-app * (ligne ~113), qui
     force font-family: inherit !important sur tous les descendants. */
  font-family: var(--gi-font-display, 'Cinzel') !important;
}

.gtb-filter-group {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.gtb-filter-btn {
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  color: var(--gtb-text);
  padding: 4px 8px;
  border-radius: var(--gtb-radius-sm);
  cursor: pointer;
  font-size: 12px;
  transition: var(--gtb-transition);
}

.gtb-filter-btn.gtb-active {
  background: var(--gtb-accent);
  color: var(--gtb-ink);
}

/* Boutons du filtre "Éléments" (data-el, voir Shortcode.php) : colorés par
   élément dès l'affichage (--el-* définies dans gi-tokens.css, chargé avant
   ce fichier). Scopé à #gtb-filter-element pour ne pas toucher le filtre
   "Armes", qui partage la classe .gtb-filter-btn mais reste neutre/doré. */
#gtb-filter-element .gtb-filter-btn[data-el] {
  border-color: color-mix(in srgb, var(--el-c, var(--gtb-border)) 55%, transparent);
  background: color-mix(in srgb, var(--el-c, var(--gtb-panel)) 30%, var(--gtb-panel));
  color: var(--gtb-text);
}

#gtb-filter-element .gtb-filter-btn[data-el="anemo"] {
  --el-c: var(--el-anemo);
}

#gtb-filter-element .gtb-filter-btn[data-el="cryo"] {
  --el-c: var(--el-cryo);
}

#gtb-filter-element .gtb-filter-btn[data-el="dendro"] {
  --el-c: var(--el-dendro);
}

#gtb-filter-element .gtb-filter-btn[data-el="geo"] {
  --el-c: var(--el-geo);
}

#gtb-filter-element .gtb-filter-btn[data-el="hydro"] {
  --el-c: var(--el-hydro);
}

#gtb-filter-element .gtb-filter-btn[data-el="pyro"] {
  --el-c: var(--el-pyro);
}

#gtb-filter-element .gtb-filter-btn[data-el="electro"] {
  --el-c: var(--el-electro);
}

/* Élément sélectionné : couleur pleine (au lieu de l'accent doré générique
   utilisé par le filtre Armes) pour rester lisible comme "cet élément est actif". */
#gtb-filter-element .gtb-filter-btn.gtb-active {
  background: var(--el-c, var(--gtb-accent));
  border-color: var(--el-c, var(--gtb-accent));
  color: var(--gtb-ink);
}

/* Classe ajoutée par le JS (syncElementFilterButtons()) sur les boutons
   élément non sélectionnés dès qu'au moins un élément est actif : même
   traitement que .gtb-perso.gtb-dim (opacity 0.3) pour rester cohérent avec
   l'estompage déjà utilisé ailleurs dans la grille. */
#gtb-filter-element .gtb-filter-btn.gtb-dim {
  opacity: 0.3;
}

/* ---------- Zone de résultats (générée dynamiquement par le JS après "Générer") ---------- */

/* Une seule colonne, toujours : chaque team (.gtb-team) est un enfant
   direct de cette grille et doit occuper toute la largeur (grande carte,
   une par ligne — esthétique d'origine). */
.gtb-results {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  margin-bottom: 24px;
}

/* Tant qu'aucune team n'est générée, #gtb-results est vide mais garde son
   margin-bottom : ça décale la grille de personnages 24px plus bas que le
   haut de .gtb-sidebar (tous deux alignés en haut via .gtb-layout), donnant
   l'impression que "gestion" (sidebar) et "sélection des personnages"
   (grille) ne démarrent pas à la même hauteur en desktop. On annule cette
   marge uniquement quand la zone est vide (voir team-builder.js ligne ~500,
   qui la vide avec innerHTML = '' au clic sur "Retirer") ; dès qu'une team
   est générée, la marge de séparation avec la grille réapparaît normalement. */
.gtb-results:empty {
  margin-bottom: 0;
}

/* Une "carte team" individuelle : titre en en-tête pleine largeur, puis
   avatars des 4 persos + rôles à gauche, note à droite. Depuis que
   .gtb-team occupe toute la largeur (une seule colonne, voir .gtb-results
   plus haut), justify-content:space-between avec une colonne de droite
   figée à 130px créait un grand vide au milieu de la carte : les deux blocs
   étaient plaqués chacun à un bord, sans rien pour remplir l'espace entre
   les deux. .gtb-team-right est maintenant flexible (flex:1) et suit
   directement les avatars, comme dans la maquette d'origine. */
.gtb-team {
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-lg);
  padding: 18px;
  display: flex;
  /* Colonne : le titre (pleine largeur) au-dessus, puis .gtb-team-body (la
     ligne avatars/note) en dessous. Titre sorti de .gtb-team-left pour ça
     (voir teamCard() dans team-builder.js) : autrement le texte de la note,
     aligné en haut de .gtb-team-right, démarrait au niveau du titre au lieu
     des avatars, puisque tous deux partageaient la même ligne flex. */
  flex-direction: column;
}

/* La ligne avatars (gauche) / note (droite) proprement dite, sous le titre.
   Porte l'ancien display:flex + gap de .gtb-team. */
.gtb-team-body {
  display: flex;
  gap: 16px;
}

.gtb-team h4 {
  margin: 0;
  /* padding: 0 nécessaire pour annuler le padding-bottom (~10px) posé
     globalement sur les h1-h6 par la configuration Divi (Personnaliser >
     Typographie des titres) : sans ça, le texte du h4 n'est pas centré au
     même niveau que le bouton de téléchargement dans .gtb-team-title
     (le padding, invisible, décale la boîte du h4 mais pas visuellement
     son texte, donc align-items:center centre la boîte, pas le texte). */
  padding: 0;
  /* Pas de font-size ici : --fs-xs (voir gi-tokens.css) le rendait plus
     petit que la taille par défaut du h4, à l'inverse de l'effet recherché
     (un titre qui se distingue visuellement de la description en dessous).
     Sans override, le h4 garde sa taille par défaut, plus grande. */
}

/* Regroupe le nom de la team et le bouton de téléchargement, pour que le
   bouton reste collé à droite du titre quel que soit la longueur du nom. */
.gtb-team-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 6px;
}

/* Bouton de téléchargement JPG d'une team (voir exportTeamCardAsImage() dans
   team-builder.js) : même habillage que les boutons de la sidebar
   (.gtb-btn), en version compacte carrée pour tenir à côté du titre. */
.gtb-team-download {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-sm);
  color: var(--gtb-text);
  cursor: pointer;
  transition: var(--gtb-transition);
}

.gtb-team-download:hover,
.gtb-team-download:focus-visible {
  background: var(--gtb-accent);
  border-color: var(--gtb-accent);
  color: var(--gtb-ink);
}

/* Pendant la génération de l'image (voir loadHtml2Canvas()) : évite un
   double clic et signale visuellement que quelque chose se passe. */
.gtb-team-download--busy {
  opacity: 0.5;
  pointer-events: none;
}

/* Colonne de gauche (titre + avatars) : 1/3 de la carte, le reste (note)
   prend les 2/3 restants (.gtb-team-right) — flex:1/flex:2 avec
   flex-basis 0 répartit l'espace disponible (une fois le gap de .gtb-team
   déjà retiré) exactement selon ce ratio 1:2, quelle que soit la largeur
   réelle des avatars. Ratio ramené à 1:1 sous 1080px (voir plus bas). */
.gtb-team-left {
  flex: 1 1 0;
  min-width: 0;
}

.gtb-team-avatars {
  display: flex;
  gap: 4px;
}

.gtb-avatar-wrap {
  position: relative;
  display: block;
  width: 80px;
  height: 80px;
}

.gtb-team-avatars .gtb-avatar {
  width: 70px;
  height: 70px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}

/* Icônes de rôle (2 max) : chacune dans sa propre bulle ronde, à cheval
   (50% de sa hauteur) sur le bas de l'avatar. La 1ère est centrée sur la
   moitié gauche de l'avatar (35px), la 2e (si présente) sur la moitié
   droite — d'où la grille à 2 colonnes de 35px plutôt qu'un flex groupé. */
.gtb-avatar-roles {
  position: absolute;
  left: 0;
  bottom: -4px;
  width: 70px;
  display: grid;
  grid-template-columns: 35px 35px;
  justify-items: center;
  line-height: 0;
}

.gtb-avatar-roles .gtb-role {
  width: 23px;
  height: 23px;
  object-fit: contain;
  /* Médaillon crème dégradé façon "papier" : les pictos de rôle sont
     désormais sombres et se noyaient sur l'ancien fond encre. Unités cqw
     (largeur de conteneur) pour rester proportionnel quand la carte est
     mise à l'échelle, notamment à l'export image. */
  border-radius: 0.35cqw;
  background: linear-gradient(180deg, #F1E9DA, #CCBCA4);
  color: #16212D;
  box-shadow: 0 0.1cqw 0.25cqw rgba(0, 0, 0, 0.5);
  padding: 2px;
  box-sizing: content-box;
}

/* Pseudo du proposant (voir teamCard() dans team-builder.js), sous les
   avatars — absent du HTML plutôt que vide quand la team n'a pas de pseudo
   (team importée/migrée), donc pas besoin de gérer un cas "caché" ici. */
.gtb-team-author {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--gtb-muted);
}

.gtb-team-author strong {
  color: var(--gtb-text);
  font-weight: 600;
}

.gtb-team-right {
  flex: 2 1 0;
  min-width: 0;
}

/* Sous 1080px (même palier "mobile" que le reste du builder, voir plus
   haut) : répartition 50/50 plutôt que 1/3-2/3, les avatars ont besoin de
   plus de place relative sur un écran étroit. */
@media (max-width: 1080px) {
  .gtb-team-right {
    flex: 1 1 0;
  }
}

/* Sous 560px seulement (vrai mobile, pas la tablette qui garde encore le
   50/50 ci-dessus) : la ligne avatars/note passe en colonne, texte sous les
   avatars, plutôt que les deux blocs côte à côte qui finissaient trop à
   l'étroit sur un écran de téléphone (texte tronqué dès les premiers mots).
   flex:1 1 auto sur les deux enfants annule le ratio hérité (flex-basis:0)
   qui n'a plus de sens une fois empilé : chacun retrouve sa hauteur
   naturelle. (.gtb-team lui-même est déjà en colonne à toutes les tailles,
   voir plus haut : seule .gtb-team-body, la ligne avatars/note, a besoin de
   ce basculement.) */
@media (max-width: 560px) {
  .gtb-team-body {
    flex-direction: column;
  }

  .gtb-team-left,
  .gtb-team-right {
    flex: 1 1 auto;
  }
}

.gtb-team-note {
  font-size: 15px;
  line-height: 1.5;
  color: var(--gtb-muted);
  margin: 0;
}

/* Version complète masquée tant que la note n'est pas dépliée (bouton
   .gtb-note-toggle, voir team-builder.js). */
.gtb-team-note .gtb-note-full {
  display: none;
}

.gtb-team-note.gtb-note-expanded .gtb-note-short {
  display: none;
}

.gtb-team-note.gtb-note-expanded .gtb-note-full {
  display: inline;
}

/* Petit bouton "..." pour déplier une note tronquée. */
.gtb-note-toggle {
  display: inline-block;
  margin-left: 2px;
  padding: 0 4px;
  border: none;
  border-radius: var(--gtb-radius-sm);
  background: var(--gtb-badge-bg);
  color: var(--gtb-accent);
  font-size: 12px;
  font-weight: bold;
  line-height: 1.4;
  cursor: pointer;
  transition: var(--gtb-transition);
}

.gtb-note-toggle:hover,
.gtb-note-toggle:focus-visible {
  background: var(--gtb-accent);
  color: var(--gtb-ink);
}

/* Messages d'info/erreur au-dessus des résultats (ex: "Aucune équipe ne
   correspond...", limite d'affichage atteinte). */
.gtb-notice {
  grid-column: 1 / -1;
  color: var(--gtb-accent);
  font-weight: bold;
}

/* ---------- Tri de la grille (select #gtb-sort-select, voir Shortcode.php) ---------- */

.gtb-sort {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 13px;
  color: var(--gtb-muted);
}

.gtb-sort label {
  /* !important nécessaire pour l'emporter sur .gtb-app * (ligne ~113), qui
     force font-family: inherit !important sur tous les descendants. */
  font-family: var(--gi-font-display, 'Cinzel') !important;
}

.gtb-sort select {
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  color: var(--gtb-text);
  padding: 6px 10px;
  border-radius: var(--gtb-radius-md);
  font-size: 13px;
  cursor: pointer;
  transition: var(--gtb-transition);
}

.gtb-sort select:hover,
.gtb-sort select:focus-visible {
  border-color: var(--gtb-accent);
}

/* ---------- Grille principale des personnages (rendue en PHP, jamais reconstruite en JS) ---------- */

.gtb-persos-grid {
  display: grid;
  /* 132px : assez large pour le portrait carré + les badges élément/arme +
     le nom sur une ligne, sans tomber sous les proportions de la tuile
     gi-cat-card d'origine (--cat-tuile: 10.5rem sur /personnages/, un peu
     réduite ici car le builder affiche beaucoup plus de tuiles à la fois). */
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 8px;
}

/* .gtb-perso porte aussi gi-cat-card + gi-card--hover + gi-cat-card--muet
   (voir Shortcode.php) : le portrait, les icônes élément/arme, le rang en
   étoiles et l'effet de survol viennent de gi-database.css, chargé sur tout
   le site. On ne redéfinit ici que ce qui est propre au builder (état
   possédé/estompé, bouton Impératif). */

/* Contrairement à /personnages/, où CHAQUE tuile affiche en permanence le
   fond teinté par rareté (gi-database.css, .gi-cat-card__portrait), le
   builder ne doit le montrer QUE pour un perso "possédé" : c'est le signal
   de sélection. On annule donc ce fond par défaut (le fond neutre --gi-ink-850
   de la carte, posé juste derrière le portrait, reste visible) et on ne le
   laisse réapparaître que sous .gtb-owned, où la règle de gi-database.css
   s'applique alors normalement. Sélecteur à trois classes (spécificité 0,3,0)
   pour l'emporter sur `.gi-cat-card__portrait` (0,1,0) quel que soit l'ordre
   de chargement des deux feuilles de style. */
.gtb-perso:not(.gtb-owned) .gi-cat-card__portrait {
  background: transparent;
}

/* Classe ajoutée par le JS quand un filtre Élément/Arme est actif et que ce
   personnage n'y correspond pas : on l'estompe plutôt que de le masquer
   complètement, pour garder la grille stable visuellement. order: 1 (au lieu
   du 0 par défaut des autres tuiles) les fait passer après les personnages
   correspondant au filtre : grid-auto-flow replace les tuiles selon "order"
   avant la position DOM, donc ça ne touche pas au tri A→Z/rareté/sortie déjà
   appliqué par le JS (sortPersoCards()) — juste un regroupement en plus,
   par-dessus. */
.gtb-perso.gtb-dim {
  opacity: 0.3;
  order: 1;
}

.gtb-imperatif-btn {
  font-size: 0.625rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-wider);
  padding: 2px 4px;
  background: transparent;
  /* !important nécessaire pour l'emporter sur .gtb-app * (ligne ~113), qui
     force font-family: inherit !important sur tous les descendants. */
  font-family: var(--gi-font-display, 'Cinzel') !important;
  /* Pas de bordure latérale/basse : la carte (overflow: hidden + border-radius
     via gi-cat-card) découpe déjà proprement les bords du bouton, une bordure
     complète dessinerait un rectangle visible par-dessus ce découpage. */
  border: 1px solid var(--gtb-border);
  border-left: none;
  border-right: none;
  border-bottom: none;
  color: var(--gi-gold-400);
  border-radius: 0;
  cursor: pointer;
  width: 100%;
  transition: var(--gtb-transition);
}

/* Classe ajoutée par le JS quand ce personnage est marqué "impératif". */
.gtb-imperatif-btn.gtb-mandatory {
  background: var(--gtb-accent);
  color: var(--gtb-ink);
  border-color: var(--gtb-accent);
}

/* ---------- Modale "Proposer une team" ----------
   Rappel important (voir Frontend/Shortcode.php) : cette modale vit
   maintenant À L'INTÉRIEUR de #gtb-app, pas juste après sa fermeture comme
   avant — les variables --gtb-* ci-dessus ne sont déclarées QUE sur .gtb-app,
   un élément en dehors ne les voit tout simplement pas (var() sans valeur de
   repli retombe sur "aucune valeur", pas sur du noir/blanc par défaut), ce
   qui rendait la modale quasi invisible (fond transparent, texte illisible).
   Ce bloc n'a donc plus besoin de redéclarer quoi que ce soit : il hérite
   normalement, comme le reste du builder. */

/* inset : top décalé de la hauteur du header (--gi-header-h, mesurée par
   gi-ui.js sur <html>, voir gi-divi.css/.gi-drawer-overlay pour la même
   recette) plutôt que 0 partout. La modale reste techniquement sous la nav
   dans l'empilement (voir plus haut, ce contexte d'empilement est imbriqué
   trop profondément côté Divi pour qu'un z-index l'en sorte), donc on évite
   simplement tout chevauchement avec elle au lieu de chercher à passer
   par-dessus. */
.gtb-modal {
  position: fixed;
  /* + --sp-3 : un top calé pile sur --gi-header-h collait la modale contre
     le bas de la nav, sans respiration visuelle. */
  inset: calc(var(--gi-header-h, 74px) + var(--sp-3, 12px)) 0 0 0;
  background: var(--gtb-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 16px;
}

/* Classe ajoutée/retirée par le JS pour ouvrir/fermer la modale (aucune
   reconstruction de HTML : seule cette classe change). */
.gtb-modal.gtb-hidden {
  display: none;
}

.gtb-modal-content {
  background: var(--gtb-panel);
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-lg);
  padding: 24px;
  max-width: 600px;
  width: 90%;
  max-height: 85vh;
  overflow-y: auto;
  position: relative;
  color: var(--gtb-text);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
}

.gtb-submit-modal-content {
  /* Plus large que la modale générique par défaut : la grille de personnages
     a besoin de place pour rester lisible avec une barre de recherche.
     960px (au lieu de 720px) : sur un écran desktop large, 720px plafonnait
     bien avant les 90% de largeur autorisés par .gtb-modal-content, laissant
     la pop-in visiblement petite au milieu de l'écran. */
  max-width: 960px;
}

.gtb-modal-close {
  position: absolute;
  top: 12px;
  right: 16px;
  background: none;
  border: none;
  color: var(--gtb-muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
  transition: var(--gtb-transition);
}

.gtb-modal-close:hover {
  color: var(--gtb-text);
}

.gtb-modal-content h3 {
  margin: 0 0 4px;
  padding-right: 28px;
}

.gtb-submit-intro {
  color: var(--gtb-muted);
  font-size: 13px;
  margin: 0 0 18px;
}

.gtb-modal-content form label {
  display: block;
  margin-bottom: 4px;
  font-size: 13px;
  font-weight: 600;
}

.gtb-submit-field {
  margin-bottom: 14px;
}

/* Nom de la team + pseudo côte à côte sur desktop : deux champs courts liés
   (identité de la proposition), pas besoin de leur donner une ligne entière
   chacun. Repasse en une seule colonne sous 560px (voir media query plus bas). */
.gtb-submit-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.gtb-modal-content input[type="text"],
.gtb-modal-content input[type="search"],
.gtb-modal-content input[type="number"],
.gtb-modal-content textarea {
  width: 100%;
  padding: 8px 10px;
  margin-top: 4px;
  background: var(--gtb-bg);
  border: 1px solid var(--gtb-border);
  color: var(--gtb-text);
  border-radius: var(--gtb-radius-sm);
  font: inherit;
}

.gtb-modal-content input[type="text"]:focus,
.gtb-modal-content input[type="search"]:focus,
.gtb-modal-content input[type="number"]:focus,
.gtb-modal-content textarea:focus {
  outline: none;
  border-color: var(--gtb-accent);
}

/* Pseudo désactivé pour un membre de l'équipe du site (identifié via son
   compte WordPress, voir Shortcode.php) : grisé plutôt que stylé comme un
   champ normal, pour que ce soit visuellement évident qu'il n'y a rien à
   saisir ici. */
.gtb-modal-content input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.gtb-modal-content fieldset {
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-md);
  padding: 8px 10px;
}

.gtb-modal-content fieldset legend {
  padding: 0 4px;
  font-size: 13px;
  font-weight: 600;
}

.gtb-modal-content fieldset label {
  display: inline-block;
  margin: 4px 12px 0 0;
  font-size: 13px;
  font-weight: normal;
}

/* Un bloc par personnage sélectionné (voir renderSubmitRoles() dans
   team-builder.js), affiché sous la grille de personnages : chaque fieldset
   hérite déjà de l'habillage générique ".gtb-modal-content fieldset"
   ci-dessus, on ne pose ici que la mise en grille de l'ensemble. */
.gtb-submit-roles {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.gtb-submit-roles-perso {
  flex: 1 1 160px;
  margin: 0;
}

.gtb-submit-hint {
  color: var(--gtb-muted);
  font-weight: normal;
  font-size: 11px;
}

/* En-tête de la grille de sélection : libellé "Personnages (x/4)" à gauche,
   champ de recherche à droite — la grille complète (~90 personnages) est
   difficile à parcourir sans un moyen de filtrer par nom. */
.gtb-submit-persos-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.gtb-submit-persos-header label {
  margin: 0;
}

.gtb-submit-persos-header input[type="search"] {
  width: auto;
  min-width: 180px;
  margin-top: 0;
  padding: 6px 10px;
  font-size: 12px;
}

/* Aperçu des personnages déjà sélectionnés (avatars + nom), au-dessus de la
   grille complète : repère visuel rapide de "qui est déjà choisi" sans avoir
   à repérer les 4 tuiles surlignées au milieu de toutes les autres. Rempli
   par le JS (syncSubmitSelection()), caché (attribut hidden) tant que rien
   n'est sélectionné. */
.gtb-submit-selected {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
  padding: 8px;
  background: var(--gtb-bg);
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-md);
}

.gtb-submit-selected-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 3px;
  background: var(--gtb-owned-bg);
  border-radius: var(--gtb-radius-pill);
  font-size: 12px;
}

.gtb-submit-selected-chip img {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
}

/* Croix de retrait à droite de la pastille : discrète au repos, plus
   marquée au survol. Cliquable partout grâce à un padding suffisant. */
.gtb-submit-selected-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-left: 2px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}

.gtb-submit-selected-remove:hover,
.gtb-submit-selected-remove:focus-visible {
  opacity: 1;
  background: rgba(0, 0, 0, 0.25);
}

/* Grille de sélection des 4 personnages à l'intérieur de la modale (grille
   séparée de .gtb-persos-grid : ici on choisit les membres de la nouvelle
   team, pas "ce qu'on possède"). Les tuiles elles-mêmes sont désormais les
   mêmes .gi-cat-card que la grille principale (voir Shortcode.php) ; ne
   restent ici que la mise en page de la grille (plus dense que la grille
   principale, la modale étant bien plus étroite) et l'état "sélectionné". */
.gtb-submit-persos-grid {
  display: grid;
  /* 100px et non 132px (grille principale) : la modale reste plafonnée à
     960px (voir .gtb-submit-modal-content) quelle que soit la largeur de
     l'écran, donc les tuiles doivent rester plus compactes qu'en pleine page. */
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 8px;
  max-height: 420px;
  overflow-y: auto;
  /* Même cause que .gtb-sidebar plus haut : la barre de défilement verticale
     (overflow-y: auto) grignote parfois 1px sur la largeur utile, suffisant
     pour faire déborder la dernière colonne de la grille et déclencher une
     barre horizontale en plus. overflow-x: clip supprime ce débordement sans
     toucher au défilement vertical. */
  overflow-x: clip;
  padding: 4px;
  margin-bottom: 4px;
  background: var(--gtb-bg);
  border: 1px solid var(--gtb-border);
  border-radius: var(--gtb-radius-md);
}

/* Cette grille reste display:none (voir .gtb-modal.gtb-hidden) tant que la
   modale n'est pas ouverte : les 122 tuiles ne coûtent donc rien tant qu'on
   n'a pas cliqué sur "Proposer une team", mais TOUTES apparaissent d'un coup
   à l'ouverture (mise en page + peinture + chargement des images lazy encore
   jamais déclenché), très sensible sur mobile — surtout que chaque icône de
   tuile porte un backdrop-filter (gi-database.css, plugin gifr-design),
   propriété coûteuse en GPU, 122 fois d'un coup.
   content-visibility: auto reporte le calcul de mise en page/peinture des
   tuiles hors de la zone visible de ce conteneur (il défile lui-même, voir
   overflow-y ci-dessus) : au lieu des 122 d'un coup, seule la poignée
   réellement visible à l'ouverture est calculée immédiatement, le reste au
   fur et à mesure du défilement. contain-intrinsic-size réserve une hauteur
   approximative aux tuiles pas encore mesurées, pour que la barre de
   défilement ne saute pas. Propriété ignorée sans effet sur les navigateurs
   qui ne la supportent pas (dégradation gracieuse, comportement actuel inchangé). */
.gtb-submit-perso {
  content-visibility: auto;
  contain-intrinsic-size: auto 150px;
}

/* Classe ajoutée par le JS quand ce personnage fait partie des 4 choisis
   pour la nouvelle team proposée (max 4, voir team-builder.js). Anneau en
   plus de la tuile plutôt qu'un changement de sa bordure/fond (déjà utilisés
   par gi-database.css pour la rareté) : même recette que le focus-visible
   des vraies gi-cat-card ailleurs sur le site (outline doré, 2px). */
.gtb-submit-perso.selected {
  outline: 2px solid var(--gtb-accent);
  outline-offset: 2px;
}

/* Classe ajoutée par le JS (filtre de recherche #gtb-submit-search) : masque
   les tuiles ne correspondant pas au texte tapé, sans jamais retirer une
   sélection existante du DOM (un perso choisi puis filtré hors de vue reste
   sélectionné). */
.gtb-submit-perso.gtb-search-hidden {
  display: none;
}

/* Message d'état de disponibilité, rempli par le JS dès que 4 personnages
   sont sélectionnés (voir checkSubmitAvailability() dans team-builder.js) :
   3 variantes selon la réponse de GET /wp-json/gtb/v1/teams/check. */
.gtb-submit-status {
  margin: 12px 0;
  padding: 10px 12px;
  border-radius: var(--gtb-radius-md);
  font-size: 13px;
  border: 1px solid transparent;
}

.gtb-submit-status--available {
  background: color-mix(in srgb, #2e7d32 22%, var(--gtb-panel));
  border-color: color-mix(in srgb, #2e7d32 55%, transparent);
  color: #b7e4b9;
}

.gtb-submit-status--conflict {
  background: color-mix(in srgb, #b45309 22%, var(--gtb-panel));
  border-color: color-mix(in srgb, #b45309 55%, transparent);
  color: #ffd9a8;
}

.gtb-submit-status--error {
  background: color-mix(in srgb, #b3261e 22%, var(--gtb-panel));
  border-color: color-mix(in srgb, #b3261e 55%, transparent);
  color: #ffb4ab;
}

#gtb-submit-btn:disabled,
#gtb-submit-publish-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* "Envoyer la proposition" + "Enregistrer l'équipe (admin)" (ce dernier réservé à
   l'équipe du site, voir Shortcode.php) côte à côte plutôt qu'un simple
   bouton isolé comme avant. */
.gtb-submit-actions {
  display: flex;
  gap: 8px;
}

.gtb-submit-actions .gtb-btn {
  flex: 1;
}

@media (max-width: 560px) {
  .gtb-submit-row {
    grid-template-columns: 1fr;
  }

  .gtb-submit-actions {
    flex-direction: column;
  }
}

/* ---------- Version mobile : sidebar en haut, sur toute la largeur ----------
   Tout le comportement (empilement au-dessus de la grille, mise en grille à
   6 colonnes des filtres/boutons) vit maintenant dans le palier @media
   (max-width: 1080px) ci-dessus : ancien palier séparé à 800px supprimé, il
   ne faisait plus que répéter des règles déjà posées à 1080px (flex-wrap
   remplacé par la grille explicite). Un seul palier plutôt que deux évite
   l'état intermédiaire (entre 800 et 1080px) où la sidebar restait empilée
   en une seule colonne étirée sur toute la largeur — exactement le
   problème signalé. */

/* ---------- Barre flottante Générer/Retirer (mobile/tablette) ----------
   Doublon des boutons "Générer"/"Retirer" de la sidebar (mêmes classes
   .gtb-generate-btn/.gtb-refresh-btn, voir Shortcode.php et team-builder.js
   pour la synchronisation clic + affichage), fixé en bas de l'écran sous
   1400px : sous cette largeur, la sidebar peut être scrollée loin des
   boutons ou (sous 1080px) passée au-dessus d'une longue grille de
   personnages — cette barre évite d'avoir à remonter tout en haut de page
   pour lancer/retirer une génération. Les boutons d'origine ne sont PAS
   déplacés, celle-ci s'ajoute simplement en plus. */
.gtb-mobile-bar {
  display: none;
}

@media (max-width: 1400px) {
  .gtb-mobile-bar {
    display: flex;
    gap: 6px;
    /* Petit bloc flottant centré, PAS une barre pleine largeur : width
       auto/inline-flex + left:50%/translateX pour se dimensionner au
       contenu des deux boutons plutôt qu'à toute la largeur de l'écran. */
    width: max-content;
    position: fixed;
    left: 50%;
    bottom: max(10px, env(safe-area-inset-bottom));
    transform: translateX(-50%);
    z-index: 500;
    padding: 4px;
    background: var(--gtb-ink);
    border: 1px solid var(--gtb-border);
    border-radius: var(--gtb-radius-pill);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  }

  .gtb-mobile-bar .gtb-btn {
    border-radius: var(--gtb-radius-pill);
    padding: 6px 14px;
    font-size: 13px;
  }

  /* Le petit bloc flotte par-dessus le bas de la page (pas de barre pleine
     largeur qui pousserait le contenu) : une marge suffit pour que la
     dernière carte ne se retrouve pas juste en dessous, cachée derrière. */
  .gtb-app {
    padding-bottom: 48px;
  }
}

/* ---------- Page hôte : gabarit Divi "Sidebar à droite" ----------
   /team_2/ n'est pas une page 100% Divi Builder : Divi y ajoute son
   habillage "classique" (#main-content, #left-area, #sidebar), qui reste
   sur son blanc par défaut et affiche une colonne de widgets (Articles
   récents, Catégories) à droite — réglage Divi "Sidebar à droite" propre à
   cette page. On corrige ça ici plutôt que de changer le réglage de mise
   en page dans Divi (un réglage de contenu, pas du code) : cette feuille
   n'est chargée QUE sur les pages portant le shortcode [gtb_team_builder]
   (voir Shortcode.php), donc sans impact sur le reste du site.

   Spécificité montée d'un cran (ancêtre body) plutôt que !important, même
   logique que gi-database.css face à Divi : ça bat le "#sidebar" /
   "#left-area" tout court de Divi, quel que soit l'ordre de chargement des
   feuilles de style. */
body #main-content,
#main-content .container {
  background-color: var(--gi-ink-950, #0d1620);
}

/* Divi encadre encore le contenu dans .container (width:80%, max-width:1080px,
   margin:auto, padding-top:58px, style.min.css) : une boîte en plus de
   .gtb-app, qui gère déjà elle-même sa largeur/son centrage (voir plus haut,
   même logique que .gi-container--wide). On neutralise cette boîte pour que
   .gtb-app se retrouve directement sous #main-content, comme sur le reste du
   site (page-container > main-content > contenu), sans conteneur Divi
   intermédiaire. */
body #main-content .container {
  width: 100%;
  max-width: none;
  margin: 0;
  padding-top: 0;
}

body #sidebar {
  display: none;
}

body #left-area {
  width: 100%;
  float: none;
}

/* Filet vertical que Divi (style.min.css) dessine entre #left-area et
   #sidebar en layout "Sidebar à droite" (#main-content .container:before,
   1px, #e2e2e2 clair) : sans sidebar visible, ce trait clair flotte seul
   sur le fond sombre. */
body #main-content .container::before {
  display: none;
}

/* Titre "Team Builder" posé par Divi (page.php, h1.entry-title.main_title
   généré à partir du titre WordPress de la page) : nom obsolète, retiré
   uniquement de l'affichage ici (le titre de page reste inchangé côté
   admin/SEO) plutôt que de toucher page.php, qui sert aussi à toutes les
   autres pages du site. */
body h1.entry-title.main_title {
  display: none;
}

/* .entry-content reçoit gi-cat-fond/gi-grain/gi-vignette--soft en JS (voir
   team-builder.js, "Démarrage du script") pour le même fond que /personnages/.
   Le grain (::before, z-index 2) et le vignetage (::after, z-index 3) de ces
   classes sont posés en position absolute sur .entry-content ; sans relever
   #gtb-app au-dessus, le voile assombri des bords du vignetage passerait
   par-dessus toute l'interface du builder. Même recette que la règle du site
   ".gi-vignette--soft > .gi-container { z-index: 4 }" (gi-tokens.css),
   .gtb-app jouant ici le rôle de .gi-container. */
body .entry-content>.gtb-app {
  position: relative;
  z-index: 4;
}

/* surcharge les iconnes personnages car pas la même taille*/
.gtb-perso .gi-cat-card__ico,
.gtb-submit-perso .gi-cat-card__ico {
  inset: 0.3rem auto auto -0.7rem
}

/* Nom du personnage plus petit dans la grille "Proposer une team" (17px par
   défaut via --fs-t-sm, trop imposant vu le nombre de tuiles affichées côte
   à côte dans la modale) : uniquement ici, la grille principale du builder
   garde la taille d'origine. */
.gtb-submit-perso .gi-cat-card__name {
  font-size: 14px;
}
/* ============================================================================
   NOTIFICATIONS (TOASTS) — remplacent les alert() natifs du navigateur
   ----------------------------------------------------------------------------
   Bandeaux non bloquants empilés en bas au centre de la fenêtre, créés à la
   volée par notify() dans team-builder.js.

   Le conteneur #gtb-toasts est attaché à <body>, pas à .gtb-app (voir notify()
   pour le pourquoi : caches et ancêtres Divi). Les variables --gtb-* ne sont
   définies que sur .gtb-app et ne seraient donc pas résolues ici : on utilise
   directement les jetons --gi-* (globaux, gi-tokens.css) avec le MÊME repli en
   dur que les --gtb-* correspondantes en tête de fichier, pour que les toasts
   restent habillés même sans gifr-design. La position est aussi posée en
   inline par notify() ; les règles ci-dessous ne font que l'habillage.
   ============================================================================ */
.gtb-toasts {
  position: fixed;
  left: 50%;
  /* En haut, centré, juste sous l'en-tête fixe du site (même repère que
     .gtb-modal). La position est aussi posée en inline par notify() : garder
     les deux en phase. */
  top: calc(var(--gi-header-h, 74px) + 12px);
  transform: translateX(-50%);
  z-index: 100000;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  width: max-content;
  max-width: min(92vw, 460px);
  /* Le conteneur ne capte pas les clics ; chaque toast les réactive pour lui. */
  pointer-events: none;
}

.gtb-toast {
  pointer-events: auto;
  cursor: pointer;
  padding: 12px 16px;
  border-radius: var(--r-sm, 6px);
  background: var(--gi-ink-800, #26262e);
  color: var(--gi-text, #eee);
  border: 1px solid var(--gi-line, #3a3a45);
  border-left: 3px solid var(--gi-accent, #c9a35c);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  font-size: 0.9rem;
  line-height: 1.4;
  text-align: left;
  /* État de départ (avant .gtb-toast--visible, ajoutée à la frame suivante) :
     décalé vers le HAUT et transparent — le bandeau descend en entrant, puis
     qu'il apparaît en haut de l'écran. */
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity 200ms var(--ease-out, ease), transform 200ms var(--ease-out, ease);
}

.gtb-toast--visible {
  opacity: 1;
  transform: translateY(0);
}

/* Ajoutée par notify() juste avant de retirer le nœud : rejoue l'animation
   d'entrée en sens inverse (remonte en disparaissant). */
.gtb-toast--leaving {
  opacity: 0;
  transform: translateY(-12px);
}

.gtb-toast--success { border-left-color: var(--el-dendro, #8FC93A); }
.gtb-toast--info { border-left-color: var(--gi-accent, #c9a35c); }

/* Erreur : fond légèrement teinté de rouge (pas rouge plein) + bord rouge
   assumé, pour que le problème saute aux yeux sans agresser. Teintes dérivées
   de --el-pyro (rouge d'élément de la charte) mélangé au fond sombre habituel.
   color-mix est déjà utilisé ailleurs dans ce fichier. */
.gtb-toast--error {
  background: color-mix(in srgb, var(--el-pyro, #E4694A) 16%, var(--gi-ink-800, #26262e));
  border-color: color-mix(in srgb, var(--el-pyro, #E4694A) 45%, var(--gi-line, #3a3a45));
  border-left-color: var(--el-pyro, #E4694A);
}

@media (prefers-reduced-motion: reduce) {
  .gtb-toast,
  .gtb-toast--visible,
  .gtb-toast--leaving {
    transform: none;
    transition: opacity 120ms linear;
  }
}

/* Fenêtre "code d'équipe" (export/import), remplace les prompt() natifs :
   réutilise .gtb-modal/.gtb-modal-content, juste un peu plus étroite et un
   champ en police à chasse fixe pour que le code reste lisible. */
.gtb-code-modal-content {
  max-width: 460px;
}

.gtb-code-input {
  width: 100%;
  margin-top: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

.gtb-code-actions {
  margin-top: 16px;
}
