/* Contrôle « machine GPU » (sidebar studio) : pastille d'état cliquable + panneau d'infos/actions
   au survol. */
.js-gpu-button { position: relative; display: inline-block; }

.gpu-status-btn {
  display: inline-flex; align-items: center; gap: .45rem;
  border: 1px solid var(--bs-border-color-translucent);
  background: var(--bs-tertiary-bg); color: var(--bs-body-color);
}
.gpu-status-btn:hover { background: var(--bs-secondary-bg); color: var(--bs-body-color); }

/* Pastille d'état (colorée selon la classe gpu-<state> portée par la racine) */
.gpu-dot { display: inline-block; width: .55rem; height: .55rem; border-radius: 50%; background: #6c757d; flex: 0 0 auto; }
.gpu-needs_key    .gpu-dot { background: #fd7e14; }
.gpu-off          .gpu-dot { background: #6c757d; }
.gpu-ready        .gpu-dot { background: #198754; }
.gpu-busy         .gpu-dot { background: #198754; box-shadow: 0 0 .35rem #198754; }
.gpu-provisioning .gpu-dot { background: #198754; animation: gpu-blink 1s steps(2, start) infinite; }
.gpu-error        .gpu-dot { background: #dc3545; }
/* après .gpu-ready : à spécificité égale, la dernière règle gagne (warn = bientôt éteinte) */
.gpu-warn         .gpu-dot { background: #fd7e14; animation: gpu-blink .7s steps(2, start) infinite; }

@keyframes gpu-blink { 50% { opacity: .15; } }

/* Panneau d'infos/actions au survol / clic (ancré sous la pastille, ouvert vers la gauche) */
.gpu-panel {
  position: absolute; left: 0; top: calc(100% + .4rem); z-index: 1040;
  width: 19rem; max-width: 90vw; display: none; overflow: hidden;
  background: var(--bs-body-bg); color: var(--bs-body-color);
  border: 1px solid var(--bs-border-color); border-radius: .6rem;
  box-shadow: 0 .75rem 1.5rem rgba(0, 0, 0, .35);
  text-align: left; white-space: normal;
}
.gpu-panel--open { display: block; }

.gpu-panel__head {
  display: flex; align-items: center; gap: .5rem;
  padding: .7rem .9rem; font-weight: 600;
  border-bottom: 1px solid var(--bs-border-color);
}
.gpu-panel__head .gpu-dot { width: .65rem; height: .65rem; }

.gpu-panel__msg { margin: 0; padding: .7rem .9rem; font-size: .85rem; color: var(--bs-secondary-color); }

.gpu-panel__info {
  margin: 0; padding: 0 .9rem .5rem;
  display: grid; grid-template-columns: auto 1fr; gap: .2rem .75rem; font-size: .82rem;
}
.gpu-panel__info dt { color: var(--bs-secondary-color); }
.gpu-panel__info dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

.gpu-panel__actions { padding: .65rem .9rem; }

/* Barre de progression du démarrage (~95 % à 10 min, puis de plus en plus lent) */
.gpu-progress-line {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--bs-border-color-translucent); overflow: hidden; border-radius: 0 0 .375rem .375rem;
}
.gpu-progress-line__bar { display: block; height: 100%; width: 0; background: #198754; transition: width 1s linear; }

.gpu-progress {
  display: flex; align-items: center; gap: .55rem;
  padding: 1rem .9rem .75rem;
}
.gpu-progress__track { flex: 1; height: 6px; background: var(--bs-tertiary-bg); border-radius: 3px; overflow: hidden; }
.gpu-progress__bar { display: block; height: 100%; width: 0; background: #198754; transition: width 1s linear; }
.gpu-progress__pct {
  font-size: .78rem; color: var(--bs-secondary-color);
  font-variant-numeric: tabular-nums; min-width: 2.6em; text-align: right;
}

/* Petit bouton réglages (clé RunPod), posé en haut à droite du contrôle machine */
.gpu-cog { line-height: 1; }

/* Bouton Generate grisé : laisse le survol atteindre l'enveloppe (tooltip « machine off ») */
[data-generate-button]:disabled { pointer-events: none; }
/* Studio de génération : sidebar en panneau plus sombre, collante sur desktop. */
.gen-sidebar {
  background-color: rgba(0, 0, 0, .22);
  border: 1px solid var(--bs-border-color-translucent);
}

@media (min-width: 992px) {
  .gen-sidebar {
    position: sticky;
    top: 80px;
    /* Plus haut que l'écran → scroll INTERNE au panneau (sinon le sticky reste épinglé, le bas du
       form — dont Generate — est inatteignable, et le scroll est capté par le feed à droite). */
    max-height: calc(100vh - 80px - 1rem);
    overflow-y: auto;
    /* Arrivé en bas du panneau, le scroll ne « déborde » PAS sur la page → le feed (et donc les
       images du haut) ne bouge plus quand on scrolle les réglages. */
    overscroll-behavior: contain;
  }
  /* Sidebar un peu plus étroite (entre l'ancien ~33% et le col-lg-5 ~42%). */
  .gen-aside { flex: 0 0 auto; width: 38%; }
  .gen-main  { flex: 0 0 auto; width: 62%; }
}

/* Barre de progression indéterminée sous le bouton Generate (pendant la génération) : un segment
   qui glisse en boucle (durée inconnue → pas de % réel, juste « ça travaille »). */
.gen-progress {
  height: 6px;
  background: rgba(255, 255, 255, .12);
  border-radius: 3px;
  overflow: hidden;
}
.gen-progress__bar {
  height: 100%;
  width: 40%;
  background: var(--bs-primary);
  border-radius: 3px;
  animation: gen-progress-slide 1.2s ease-in-out infinite;
}
@keyframes gen-progress-slide {
  from { margin-left: -40%; }
  to   { margin-left: 100%; }
}

/* Feed du Studio : chaque génération est un bloc empilé verticalement, séparé d'un filet. */
.gen-block + .gen-block {
  border-top: 1px solid var(--bs-border-color-translucent);
  padding-top: 1rem;
}

/* Vignettes du feed : ratio carré réservé (comme Home) → l'espace est pris avant le chargement
   de l'image, donc pas de saut de mise en page. */
.gen-grid img {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

/* Sélecteur d'aspect ratio : trois cartes radio horizontales, chacune montrant la forme. */
.aspect-radios {
  display: flex;
  gap: .5rem;
}
.aspect-radio {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  margin: 0;
  padding: .6rem .4rem;
  border: 1px solid var(--bs-border-color-translucent);
  border-radius: var(--bs-border-radius);
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}
.aspect-radio:hover {
  border-color: var(--bs-primary);
}
/* Zone de hauteur fixe : les trois boîtes partagent la même ligne de base. */
.aspect-radio__shape {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.aspect-radio__box {
  border: 2px solid var(--bs-secondary-color);
  border-radius: 3px;
  transition: border-color .12s ease, background-color .12s ease;
}
/* Variante « icône » (picker d'upscaling) : le SVG suit la couleur d'état comme les boîtes. */
.aspect-radio__shape svg {
  color: var(--bs-secondary-color);
  transition: color .12s ease;
}
.aspect-radio__label {
  font-size: .8rem;
  color: var(--bs-secondary-color);
}
.aspect-radio:has(input:checked) {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), .12);
}
.aspect-radio:has(input:checked) .aspect-radio__box {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), .28);
}
.aspect-radio:has(input:checked) .aspect-radio__shape svg {
  color: var(--bs-primary);
}
.aspect-radio:has(input:checked) .aspect-radio__label {
  color: var(--bs-body-color);
  font-weight: 600;
}
.aspect-radio:has(input:focus-visible) {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Zone de résultats : cellule placeholder grise carrée (état vide / pendant la génération). */
.gen-placeholder {
  aspect-ratio: 1;
  width: 100%;
  background-color: rgba(0, 0, 0, .28); /* plus sombre que le fond */
}

/* Pendant la génération : les placeholders « respirent », chacun déphasé (delay négatif par
   position) pour que la grille ne pulse pas à l'unisson. */
.gen-pulse .gen-placeholder {
  animation: gen-pulse 1.4s ease-in-out infinite;
}
.gen-pulse .col:nth-child(1) .gen-placeholder { animation-delay: 0s; }
.gen-pulse .col:nth-child(2) .gen-placeholder { animation-delay: -.5s; }
.gen-pulse .col:nth-child(3) .gen-placeholder { animation-delay: -1s; }
.gen-pulse .col:nth-child(4) .gen-placeholder { animation-delay: -.3s; }
.gen-pulse .col:nth-child(5) .gen-placeholder { animation-delay: -.8s; }
.gen-pulse .col:nth-child(6) .gen-placeholder { animation-delay: -1.2s; }
.gen-pulse .col:nth-child(7) .gen-placeholder { animation-delay: -.6s; }
.gen-pulse .col:nth-child(8) .gen-placeholder { animation-delay: -.1s; }
.gen-pulse .col:nth-child(9) .gen-placeholder { animation-delay: -.9s; }
@keyframes gen-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .4; }
}

/* Nombre d'images : petits carrés radio (1 à 9). */
.count-radios {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}
.count-radio {
  margin: 0;
  cursor: pointer;
}
.count-radio__box {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--bs-border-color-translucent);
  border-radius: var(--bs-border-radius);
  font-size: .9rem;
  color: var(--bs-secondary-color);
  transition: border-color .12s ease, background-color .12s ease, color .12s ease;
}
.count-radio:hover .count-radio__box {
  border-color: var(--bs-primary);
}
.count-radio:has(input:checked) .count-radio__box {
  border-color: var(--bs-primary);
  background-color: rgba(var(--bs-primary-rgb), .2);
  color: var(--bs-body-color);
  font-weight: 600;
}
.count-radio:has(input:focus-visible) .count-radio__box {
  outline: 2px solid var(--bs-primary);
  outline-offset: 2px;
}

/* Recherche discrète du feed du studio : compacte, largeur bornée (ne s'étale pas sur tout le feed). */
.gen-feed-search {
  max-width: 22rem;
}
/* Modale de tri d'image (studio + drafts) : grande image, flèches latérales, barre d'actions. */

/* En-tête : le prompt (tags nettoyés, tronqué) à gauche + un badge de statut, la croix à droite.
   `width: 0; min-width: 100%` : la modale est en `fit-content` → sans ça, la largeur « max-content »
   du prompt (nowrap) élargirait la modale au-delà de l'image (marges latérales). Là, le header
   REMPLIT la largeur fixée par l'image sans jamais la pousser, et le prompt tronque dedans. */
.triage-modal__head { gap: .75rem; width: 0; min-width: 100%; }
.triage-modal__meta {
  display: flex;
  flex-direction: column; /* ligne 1 : prompt + badge ; ligne 2 : date en gris */
  gap: 1px;
  min-width: 0; /* autorise l'ellipsis du prompt */
  flex: 1;
}
.triage-modal__title-row { display: flex; align-items: center; gap: .5rem; min-width: 0; }
/* Plafonné à ~un tiers de la largeur du header : le prompt ne prend pas toute la place (le badge
   vient se coller juste après). text-truncate tronque à cette largeur. */
.triage-modal__prompt { min-width: 0; max-width: 33%; font-size: .95rem; color: var(--bs-body-color); }
.triage-modal__sub { display: flex; align-items: center; gap: .5rem; font-size: .8rem; color: var(--bs-secondary-color); }
/* font-size explicite : sinon le badge, désormais dans la sous-ligne (.8rem), rétrécirait trop. */
.modal-status-badge { flex-shrink: 0; font-weight: 500; font-size: .72rem; border-radius: 20px; padding: .28em .7em; }
.modal-status-badge--draft { background: rgba(255, 255, 255, .09); color: var(--bs-secondary-color); }
.modal-status-badge--saved { background: rgba(45, 164, 120, .18); color: #6fd39c; }

/* La modale épouse la largeur de l'image (au lieu d'une largeur xl fixe) → plus de barres
   sur les côtés. fit-content se cale sur le plus large enfant (le stage, ou la barre d'actions).
   `margin-inline: auto` = centrage horizontal : sans ça, sur mobile Bootstrap met `margin: .5rem`
   FIXE (pas auto) → la modale fit-content se colle à gauche et laisse un vide à droite. */
.triage-modal .modal-dialog {
  width: fit-content;
  max-width: 95vw;
  margin-inline: auto;
}

.triage__stage {
  position: relative;
  margin-inline: auto;
  /* Cadre au ratio EXACT de l'image : hauteur plafonnée (75vh / largeur dispo), largeur déduite
     via aspect-ratio. L'espace est donc réservé avant chargement (pas de saut) et le cadre colle
     à l'image (pas de letterbox, pas de barres). La variante image est bornée à 900px. */
  aspect-ratio: var(--triage-ratio, 1);
  height: min(75vh, calc(min(90vw, 1080px) / var(--triage-ratio, 1)));
  /* Coins hauts carrés : le .modal-header arrondit désormais le haut de la modale. */
  overflow: hidden;
}
.triage__img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Flèches prev/next en overlay sur l'image, révélées au survol — même style que la navigation
   de la page show (.img-navigation), pour une expérience cohérente entre les deux. */
.triage__nav {
  position: absolute; top: 0; bottom: 0; width: 8rem;
  display: flex; align-items: center;
  text-decoration: none; color: #fff;
  opacity: 0; transition: opacity .2s ease;
}
.triage__nav:hover { opacity: 1; }
.triage__nav--prev { left: 0; justify-content: flex-start; padding-left: 1rem; }
.triage__nav--next { right: 0; justify-content: flex-end; padding-right: 1rem; }
/* Le chevron (pastille .nav-chevron) est partagé avec images#show → défini dans application.css. */
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *


 */

/* ── Couleur primaire : INDIGO (remplace le bleu #0d6efd de Bootstrap). Bootstrap arrive du CDN en
   CSS compilé → en plus des variables :root, on ré-override les composants qui figent la couleur
   (btn-primary, badge, switch, focus, slider). ── */
:root {
  --in-primary: #6366f1;
  --in-hover: #4f46e5;
  --in-active: #4338ca;
  --in-light: #a5a8f8;

  --bs-primary: var(--in-primary);
  --bs-primary-rgb: 99, 102, 241;
  --bs-primary-text-emphasis: #a5a8f8;
  --bs-primary-bg-subtle: #1c1d3a;
  --bs-primary-border-subtle: #3b3ea3;
  --bs-link-color-rgb: 139, 142, 247;
  --bs-link-hover-color-rgb: 165, 168, 248;
}
.btn-primary {
  --bs-btn-bg: var(--in-primary);
  --bs-btn-border-color: var(--in-primary);
  --bs-btn-hover-bg: var(--in-hover);
  --bs-btn-hover-border-color: var(--in-hover);
  --bs-btn-active-bg: var(--in-active);
  --bs-btn-active-border-color: var(--in-active);
  --bs-btn-disabled-bg: var(--in-primary);
  --bs-btn-disabled-border-color: var(--in-primary);
  --bs-btn-focus-shadow-rgb: 99, 102, 241;
}
.btn-outline-primary {
  --bs-btn-color: var(--in-light);
  --bs-btn-border-color: var(--in-primary);
  --bs-btn-hover-bg: var(--in-primary);
  --bs-btn-hover-border-color: var(--in-primary);
  --bs-btn-active-bg: var(--in-primary);
  --bs-btn-active-border-color: var(--in-primary);
  --bs-btn-focus-shadow-rgb: 99, 102, 241;
}
.text-bg-primary {
  background-color: var(--in-primary) !important;
}
.form-check-input:checked {
  background-color: var(--in-primary);
  border-color: var(--in-primary);
}
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
  border-color: var(--in-light);
  box-shadow: 0 0 0 0.25rem rgba(99, 102, 241, 0.25);
}
.form-range::-webkit-slider-thumb {
  background-color: var(--in-primary);
}
.form-range::-moz-range-thumb {
  background-color: var(--in-primary);
}

/* Largeur de page : on occupe plus l'horizontale que le défaut Bootstrap (s'applique à la navbar,
   la galerie et le footer, qui partagent `.container`).
   - Tablettes / petits laptops (576–1199px) : les paliers Bootstrap (540/720/960) laissent beaucoup
     de marge (ex. iPad portrait ~760px plafonné à 540px) → on passe à ~94 % de la largeur. Sous
     576px on garde la pleine largeur par défaut des téléphones.
   - Grands écrans : caps plus larges que le défaut. */
@media (min-width: 576px) and (max-width: 1199.98px) {
  .container { max-width: 94%; }
}
@media (min-width: 1200px) {
  .container { max-width: 1320px; }
}
@media (min-width: 1400px) {
  .container { max-width: 1560px; }
}


.sd-image-grid {
  & img {
    /* Miniatures carrées qui suivent la largeur de colonne. Le recadrage (380x380, haut
       de l'image pour les portraits) est déjà fait côté serveur dans la variante, donc
       l'image affichée correspond pile à la variante générée — pas de re-crop CSS. */
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }

  & a {
    display: block;
    position: relative;
    .badges {
      position: absolute;
      bottom: 0.5rem;
      right: 1rem;
    }
  }
}

/* Sélecteur de mode du studio : les 3 segments à largeur égale (base 0 → partage à parts égales,
   sinon chaque bouton grandit selon la longueur de son libellé). */
.gen-mode > .btn {
  flex: 1 1 0;
}

.btn-tag-bool {
  width: 28px !important;
  height: 24px !important;
  line-height: 16px !important;
  padding: 2px;
}

/* Boutons « icône + texte » : on centre verticalement l'icône avec le texte (sinon le SVG inline
   s'aligne sur la baseline → trop bas) et on met un petit écart constant entre les deux. */
.btn:has(> svg) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  /* En flex, un bouton icône-seule retombe à la hauteur du SVG (16px) au lieu de la line-box
     du bouton → il paraît plus petit que les boutons texte voisins. On force la hauteur d'une
     ligne de texte (calc à partir des variables Bootstrap → marche aussi pour btn-sm/btn-lg). */
  min-height: calc(var(--bs-btn-line-height, 1.5) * 1em + var(--bs-btn-padding-y) * 2 + var(--bs-btn-border-width, 1px) * 2);
}

/* Items de menu « icône + texte » : même logique que les boutons, mais on reste en `flex`
   (pas inline-flex) pour garder l'item pleine largeur. Ne cible que les items dont le SVG est
   enfant direct → les dropdowns navbar (icône dans un <span>) ne sont pas affectés. */
.dropdown-item:has(> svg) {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Menus déroulants plus aérés (global) : padding menu généreux + items plus hauts, items arrondis
   (le survol ne touche pas les bords), séparateurs insérés grâce au padding-x du menu. */
.dropdown-menu {
  /* Horizontal uniquement (le vertical reste au défaut Bootstrap → items pas trop espacés).
     Retrait latéral : un petit padding du MENU (le « pill » de survol ne touche pas les bords) +
     le padding interne des items. Le header ET les séparateurs reçoivent le MÊME retrait interne
     → icônes, titre et barres s'alignent tous sur la même verticale. */
  --bs-dropdown-padding-x: 0.5rem;
  --bs-dropdown-item-padding-x: 1rem;
  --bs-dropdown-header-padding-x: 1rem;
  /* Menu plus large (comme le mockup ~232px) : les items s'étalent au lieu de coller au texte. */
  --bs-dropdown-min-width: 14.5rem;
}
.dropdown-item {
  border-radius: var(--bs-border-radius);
}
.dropdown-divider {
  margin-left: var(--bs-dropdown-item-padding-x);
  margin-right: var(--bs-dropdown-item-padding-x);
}

/* Bouton « fantôme » : sans fond ni bordure, texte atténué, léger fond au survol. */
.btn-ghost {
  --bs-btn-color: var(--bs-secondary-color);
  --bs-btn-bg: transparent;
  --bs-btn-border-color: transparent;
  --bs-btn-hover-color: var(--bs-body-color);
  --bs-btn-hover-bg: rgba(255, 255, 255, 0.08);
  --bs-btn-hover-border-color: transparent;
  --bs-btn-active-color: var(--bs-body-color);
  --bs-btn-active-bg: rgba(255, 255, 255, 0.12);
  --bs-btn-active-border-color: transparent;
  --bs-btn-box-shadow: none;
}
/* Home (navbar) : libellé + logo en blanc (même clair que les voisins Upload/Characters en
   `btn-outline-light`), pas atténué comme un ghost standard. Ciblé à la navbar → n'affecte pas les
   autres boutons fantômes (ex. menu « personnages présents »). */
.navbar .btn-ghost { --bs-btn-color: var(--bs-light); }

/* Taille « extra-small » manquante dans Bootstrap 5 : définie EXACTEMENT comme `.btn-sm` (surcharge
   des mêmes variables), en un cran plus petit. Réutilisable partout. */
.btn-xs {
  --bs-btn-padding-y: 0.2rem;
  --bs-btn-padding-x: 0.35rem;
  --bs-btn-font-size: 0.8rem;
  --bs-btn-border-radius: var(--bs-border-radius-sm);
}

/* En-tête d'un bloc de génération (studio, design A) : prompt (court) en tête + sous-ligne discrète,
   et à droite des actions en boutons outline blancs (copier / relancer / supprimer). */
.gen-block__meta { min-width: 0; } /* autorise l'ellipsis (text-truncate) du prompt */
.gen-block__prompt { font-size: .9rem; color: var(--bs-body-color); }
.gen-block__sub { font-size: .8rem; margin-top: .05rem; }
/* Les actions du header sont de simples `btn btn-sm btn-outline-light` (comme le bouton download du
   modal), avec des icônes 16px uniformes → carrées via le min-height de `.btn:has(> svg)`. Aucune
   règle custom : on colle au standard Bootstrap de l'app. */

.img-navigation-container {
  position: relative;

  & img {
    object-fit: contain;
  }

  --img-navigation-width: 8rem;

  .img-navigation {
    position: absolute;
    top: 0;
    height: 100%;
    width: var(--img-navigation-width);
    transition: opacity 0.2s;
    opacity: 0;
    color: white;
    text-decoration: none;
    display: flex;
    align-items: center;
  }

  /* Zone centrale cliquable (entre les flèches prev/next) : ouvre le visualisateur plein écran. */
  .img-navigation-zoom {
    position: absolute;
    top: 0;
    width: calc(100% - (var(--img-navigation-width) * 2));
    left: 8rem;
    height: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: zoom-in;
    z-index: 2;
  }

  .img-navigation-left {
    left: 0;
    justify-content: start;
    padding-left: 1rem;

    &:hover {
      opacity: 1;
    }
  }

  .img-navigation-right {
    right: 0;
    justify-content: end;
    padding-right: 1rem;

    &:hover {
      opacity: 1;
    }
  }
}

/* Chevron de navigation (show + modale de tri) : pastille sombre arrondie, façon PhotoSwipe. */
.nav-chevron {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.5);
  transition: background 0.15s ease;
}
.img-navigation:hover .nav-chevron,
.triage__nav:hover .nav-chevron {
  background: rgba(0, 0, 0, 0.65);
}

.caption-container {
  & .caption-container-image {
    position: relative;
    overflow: hidden;
    user-select: none;
    width: 100%;
    aspect-ratio: var(--img-ratio);
    /* Query container : les captions s'expriment en cqw (% de la largeur image). */
    container-type: inline-size;

    & img {
      display: block;
      width: 100%;
      height: 100%;
      object-fit: contain;
      -webkit-user-drag: none;
      user-select: none;
    }

    & button {
      position: absolute;
      bottom: 8px;
      right: 8px;
      z-index: 5;
    }
  }

  /* Mode "adaptive" (page show) : on plafonne la LARGEUR via le ratio pour que la hauteur
     ne dépasse pas le viewport, sans letterboxer (le conteneur colle à l'image). */
  & .caption-container-image--adaptive {
    max-width: calc((100vh - 120px) * var(--img-ratio));
    margin-inline: auto;
  }

  /* Mode ÉDITION (le form éditable de la modale) : curseurs, contour de sélection et poignées.
     En lecture (page show), les bulles sont inertes. touch-action: none → les Pointer Events
     des drags ne se font pas voler par le scroll tactile. */
  &.caption-container--editing {
    /* Image (colonne de droite) légèrement arrondie, comme le panneau d'outils de gauche. */
    .caption-container-image {
      border-radius: 0.5rem;
    }

    .caption {
      cursor: move;
      touch-action: none;
    }

    .caption-resize-handle,
    .caption-tail-handle {
      touch-action: none;
    }

    .caption:hover, .caption.selected {
      outline: 4px solid rgba(255, 255, 255, 0.35);
    }

    /* Poignée de redimensionnement de la largeur : à CHEVAL sur le bord droit de la bulle
       (moitié dedans, moitié sur l'image), visible au survol / à la sélection. */
    .caption-resize-handle {
      position: absolute;
      top: 50%;
      right: -6px;
      transform: translateY(-50%);
      width: 12px;
      height: 26px;
      border-radius: 4px;
      background: var(--bs-primary, #0d6efd);
      border: 2px solid #fff;
      box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
      cursor: ew-resize;
      opacity: 0;
      transition: opacity 0.15s;
      z-index: 6;
    }

    .caption:hover .caption-resize-handle,
    .caption.selected .caption-resize-handle {
      opacity: 1;
    }
  }

  /* Poignée de la QUEUE (pointe de la bulle speech/thought) : pastille draggable posée sur le
     stage aux coordonnées --tx/--ty ; montrée par le contrôleur pour la bulle sélectionnée. */
  .caption-tail-handle {
    position: absolute;
    left: calc(var(--tx, 0.5) * 100%);
    top: calc(var(--ty, 0.8) * 100%);
    transform: translate(-50%, -50%);
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--bs-primary, #0d6efd);
    border: 2px solid #fff;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
    cursor: grab;
    z-index: 7;
  }

  .caption {
    position: absolute;
    left: calc(var(--x) * 100%);
    top: calc(var(--y) * 100%);
    width: calc(var(--w, 0.3) * 100cqw);
    /* La police scale avec la largeur d'image (cqw) ; padding/border en em → suivent. */
    font-size: calc(var(--fs, 0.05) * 100cqw);
    box-sizing: border-box;
    padding: 0.15em 0.3em;
    border-radius: 0.15em;
    z-index: 4;
    user-select: none;
    text-align: left;
    /* Couleurs par variables (posées inline par le partial/l'éditeur) → les kinds spéciaux
       (whisper translucide…) les dérivent sans !important. */
    background-color: var(--bg, #ffffff);
    color: var(--fg, #000000);

    font-family: "Dekko", cursive;
    font-weight: 400;
    font-style: normal;

    .caption-body:empty:before {
      content: "Empty caption";
      opacity: 0.3;
    }
  }

  /* ── Vocabulaire BD (kind) ── */
  .caption[data-kind="speech"]  { border-radius: 0.6em; }
  .caption[data-kind="thought"] { border-radius: 1.4em; }
  .caption[data-kind="shout"]   { border-radius: 0.3em; }
  .caption[data-kind="whisper"] {
    background-color: color-mix(in srgb, var(--bg, #ffffff) 55%, transparent);
    border: 0.08em dashed var(--fg, #000000);
    border-radius: 0.8em;
  }
  .caption[data-kind="box"] {
    border-radius: 0;
    border: 0.06em solid var(--fg, #000000);
  }

  /* ── Typo (font/graisse/italique/alignement) ── */
  .caption[data-font="comic"]   { font-family: "Comic Neue", cursive; }
  .caption[data-font="bangers"] { font-family: "Bangers", cursive; letter-spacing: 0.04em; }
  .caption[data-bold="true"]    { font-weight: 700; }
  .caption[data-italic="true"]  { font-style: italic; }
  .caption[data-align="center"] { text-align: center; }
  .caption[data-align="right"]  { text-align: right; }

  /* Décor SVG d'une bulle (queue, nuage, étoile) : couvre tout le stage, SOUS les bulles. */
  .caption-shape {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 3;
    pointer-events: none;
  }

  /* Sidebar d'outils de l'éditeur de bulles : même langage visuel que la sidebar du studio
     (panneau assombri, bord translucide). À gauche de l'image en desktop, dessous en mobile. */
  /* La sidebar est un simple conteneur transparent : la matière (fond + bord) vit dans SES DEUX
     BLOCS (Bubbles / outils), nettement séparés par le gap. */
  .caption-sidebar {
    text-align: left;
  }

  /* Outils contextuels : colonne verticale (toujours affichés — désactivés quand rien n'est
     sélectionné → hauteur du bloc constante). */
  .caption-sidebar__tools {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  /* Bloc-carte (Bubbles, outils) : même look que la sidebar du studio. */
  .caption-block {
    background-color: rgba(0, 0, 0, .22);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: 0.5rem;
    padding: 1rem;
  }

  /* En desktop : la sidebar remplit la hauteur. Le bloc OUTILS garde sa hauteur naturelle (tout
     visible, aucun scroll) ; le bloc BUBBLES prend le reste et SA LISTE défile s'il y a trop de
     bulles. */
  @media (min-width: 768px) {
    .caption-sidebar {
      flex: 0 0 340px;
      width: 340px;
      /* Si les deux blocs dépassent (bloc outils grand + bulles), c'est la SIDEBAR qui défile —
         jamais le bloc outils (qui reste entièrement visible). */
      overflow-y: auto;
    }
    /* Bloc Bubbles : prend l'espace restant (remplit quand rien n'est sélectionné → outils réduits
       à leur placeholder), mais ne descend jamais sous ~4 lignes ; au-delà, SA liste défile. */
    .caption-sidebar__list-pane {
      flex: 1 1 auto;
      min-height: 8rem;
    }
    .caption-sidebar__list-pane .caption-bubble-list {
      flex: 1 1 0;
      min-height: 0;
      max-height: none;
    }
    /* Bloc outils : hauteur naturelle, toujours entièrement visible (aucun scroll interne). */
    .caption-sidebar__config {
      flex: 0 0 auto;
    }
  }

  /* Boutons de style (B/I, alignements) : largeur fixe uniforme (sinon chacun suit son contenu). */
  .caption-sidebar .btn-group > .btn {
    width: 2.5rem;
    padding-left: 0;
    padding-right: 0;
  }

  /* Indication atténuée (liste vide). */
  .caption-sidebar__hint {
    color: var(--bs-secondary-color);
    font-size: 0.875rem;
  }

  /* Liste « calques » des bulles : lignes cliquables (sélection), « × » au survol (suppression),
     ligne active surlignée. Défile si beaucoup de bulles. */
  .caption-bubble-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 30vh;
    overflow-y: auto;
  }
  .caption-bubble-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: var(--bs-border-radius);
    padding: 5px 8px;
    color: var(--bs-body-color);
    font-size: 0.85rem;
  }
  .caption-bubble-row:hover {
    background: rgba(255, 255, 255, 0.06);
  }
  .caption-bubble-row.active {
    background: rgba(120, 150, 240, 0.18);
  }
  .caption-bubble-row > svg {
    flex: 0 0 auto;
    color: var(--bs-secondary-color);
  }
  .caption-bubble-row.active > svg {
    color: #9ec5fe;
  }
  .caption-bubble-row__text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .caption-bubble-row__empty {
    color: var(--bs-secondary-color);
    font-style: italic;
  }
  .caption-bubble-row__del {
    flex: 0 0 auto;
    color: var(--bs-secondary-color);
    font-size: 1.15rem;
    line-height: 1;
    padding: 0 3px;
    opacity: 0;
  }
  .caption-bubble-row:hover .caption-bubble-row__del {
    opacity: 1;
  }
  .caption-bubble-row__del:hover {
    color: #ea868f;
  }
}

/* Dans la modale (XL, non plein écran) : l'image tient dans la hauteur restante — le calc
   adaptive standard (100vh - 120px) ne compte pas le chrome de la modale (marges + header). */
#captionsEditModal .caption-container-image--adaptive {
  max-width: calc((100vh - 240px) * var(--img-ratio));
}

/* Réouverture du bloc .caption-container (le reste des règles ci-dessous lui appartient). */
.caption-container {

  input[type='color'] {
    height: 32px;
    margin: 0;
    padding: 0;
  }

  input[name='font_size'] {
    width: 3rem;
    text-align: center;
  }

  textarea {
    width: 100%;
  }
}

/* Split du show (≥ lg) : sidebar ~21% / image + more results ~79% (compromis col-9 ↔ col-10). */
@media (min-width: 992px) {
  .show-sidebar { flex: 0 0 auto; width: 25%; }
  .show-main { flex: 0 0 auto; width: 75%; }
}

/* Panneau de détails (prompt / negative prompt / settings) de la colonne de gauche du show. */
.image-details h6 {
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.75rem;
}

/* Labels « petits titres » (fiche perso) : mêmes majuscules atténuées que les titres du panneau
   de détails du show. */
.form-label-caps {
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.75rem;
}

/* Avatar cliquable de la fiche perso : label du champ fichier caché (clic → sélecteur natif),
   pastille caméra en coin pour signaler l'action. */
.avatar-picker {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  cursor: pointer;
}
.avatar-picker:hover {
  filter: brightness(1.08);
}
.avatar-picker__badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--bs-primary);
  border: 2px solid var(--bs-body-bg);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.image-settings {
  margin: 0;
  font-size: 0.85rem;
}

.image-settings .image-setting {
  padding: 0.2rem 0;
}

/* Clé en petit label, valeur en dessous. */
.image-settings dt {
  font-weight: 600;
  opacity: 0.55;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.image-settings dd {
  margin: 0;
  word-break: break-word;
}

/* ── Éditeur de prompt à pastilles (studio) — prompt_editor_controller.js ── */
/* Switch « Expert » (ON = textarea brute) : compact, aligné à droite du label du champ. */
/* Petites icônes à côté du label du champ : « copier le prompt » et « vider les tags » (clear). */
.prompt-copy,
.prompt-clear {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem; /* icône ↔ texte */
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--bs-border-color-translucent);
  border-radius: var(--bs-border-radius);
  background: rgba(255, 255, 255, 0.04);
  color: var(--bs-secondary-color);
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}
.prompt-copy:hover,
.prompt-clear:hover {
  color: var(--bs-body-color);
  background: rgba(255, 255, 255, 0.09);
  border-color: var(--bs-border-color);
}
/* Icônes discrètes à côté du label : plus petites que le 16px partagé (elles décorent le label,
   pas des boutons d'action). Ciblé ici → n'affecte pas les mêmes icônes ailleurs (header de bloc). */
.prompt-copy svg,
.prompt-clear svg {
  width: 12px;
  height: 12px;
}

.prompt-editor-switch {
  min-height: auto;
  padding-left: 2.6em;
}
.prompt-editor-switch .form-check-label {
  font-size: 0.85rem;
  color: var(--bs-secondary-color);
  cursor: pointer;
}
.prompt-editor-switch .form-check-input {
  cursor: pointer;
}

/* Boîte qui imite un .form-control mais s'étend en hauteur et wrap les pastilles. */
.prompt-chipbox {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  height: auto;
  min-height: calc(1.5em + 0.75rem + 2px);
  padding: 0.4rem 0.5rem;
  position: relative;
  cursor: text;
}
/* Halo de focus UNIQUEMENT quand on tape dans la saisie (classe posée par le contrôleur) — pas
   quand on sélectionne une pastille, sinon toute la boîte s'illumine, c'est lourd. */
.prompt-chipbox--focus {
  border-color: var(--bs-primary);
  box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.2);
}

/* Pastilles plates et douces (on n'utilise PAS .badge : trop petit + gris massif). */
.prompt-chip {
  --chip-rgb: 148, 155, 165; /* gris neutre par défaut → sélection des tags sans catégorie (les catégories surchargent) */
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.65rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 500;
  line-height: 1.25;
  color: var(--bs-body-color);
  background: var(--bs-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  cursor: pointer; /* cliquable → sélectionne pour la barre du bas */
  user-select: none;
}
.prompt-chip:hover {
  background: var(--bs-secondary-bg);
}
/* Couleurs de pastille par catégorie E621 (symbole résolu par TagCategory ; le général reste neutre).
   Chaque catégorie ne définit qu'une variable `--chip-rgb` → réutilisée pour le fond, le survol ET la
   sélection. L'artiste garde l'indigo. */
.prompt-chip--cat-artist    { --chip-rgb: var(--bs-primary-rgb); }
.prompt-chip--cat-character { --chip-rgb: 74, 222, 128; }
.prompt-chip--cat-species   { --chip-rgb: 251, 146, 60; }
.prompt-chip--cat-copyright { --chip-rgb: 232, 121, 249; }
.prompt-chip--cat-meta,
.prompt-chip--cat-lore      { --chip-rgb: 148, 163, 184; }
.prompt-chip[class*="prompt-chip--cat-"] {
  border-color: rgba(var(--chip-rgb), 0.55);
  background: rgba(var(--chip-rgb), 0.12);
}
.prompt-chip[class*="prompt-chip--cat-"]:hover:not(.selected) {
  background: rgba(var(--chip-rgb), 0.2);
}
/* Sélection par CLASSE (pas :focus) → tient quand on clique − / + dans la barre. Le chip sélectionné
   GARDE sa couleur (`--chip-rgb` : couleur de catégorie, ou gris neutre par défaut), en plus vif
   (bord plein + fond plus dense). Largeur de bord constante → aucun décalage des autres pastilles. */
.prompt-chip.selected {
  border-color: rgb(var(--chip-rgb));
  background: rgba(var(--chip-rgb), 0.3);
}
.prompt-chip:focus-visible {
  outline: 2px solid var(--bs-primary);
  outline-offset: 1px;
}
.prompt-chip--opaque {
  font-family: var(--bs-font-monospace);
  font-weight: 400;
  font-size: 0.8rem;
}
/* Poids en chiffre simple (pas de « × »), affiché en permanence mais discret (petit + atténué). */
.prompt-chip__wnum {
  font-variant-numeric: tabular-nums;
  font-size: 0.8em;
  font-weight: 400;
  opacity: 0.55;
}
/* Petite croix de suppression sur chaque pastille (le poids étant un chiffre nu → aucune ambiguïté). */
.prompt-chip__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1em; /* boîte alignée sur la ligne de texte → croix centrée verticalement */
  border: 0;
  background: transparent;
  color: inherit;
  padding: 0;
  margin-left: 0.05rem;
  line-height: 1;
  font-size: 1.05em;
  opacity: 0.4;
  cursor: pointer;
}
.prompt-chip__x:hover {
  opacity: 0.75; /* juste plus clair, pas de rouge */
}

/* ── Barre de poids (sous la boîte) : réglages de la pastille sélectionnée. ── */
.prompt-weightbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.25rem;
  margin-top: 0.5rem;
  padding: 0.35rem 0.6rem;
  border: 0.5px solid var(--bs-border-color);
  border-radius: 0.375rem;
  background: rgba(0, 0, 0, 0.18);
}
.prompt-weightbar__name {
  flex: 0 0 auto; /* ne se laisse PAS écraser par le slider (sinon « badger » → « ba… ») */
  font-weight: 500;
  max-width: 35%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prompt-weightbar__cat {
  flex: 0 0 auto; /* cartouche de catégorie (« artist », « species »…) à côté du nom */
}
/* Slider compact, poussé à droite (le num suit). */
.prompt-weightbar__slider {
  flex: 0 0 auto;
  width: 7.5rem;
  margin: 0 0 0 auto; /* margin-left auto → slider + num filent à droite */
}
.prompt-weightbar__num {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  min-width: 2.2em;
  text-align: center;
}

.prompt-chip-input {
  flex: 1 1 6rem;
  min-width: 6rem;
  border: 0;
  background: transparent;
  color: inherit;
  outline: 0;
  padding: 0.15rem 0.1rem;
}

/* Menu d'autocomplétion posé sous la boîte. */
.prompt-suggest {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 20;
  margin-top: 3px;
  max-height: 14rem;
  overflow-y: auto;
  background: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.375rem;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4);
}
.prompt-suggest__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.6rem;
  white-space: nowrap;
  cursor: pointer;
}
.prompt-suggest__item.active {
  background: rgba(var(--bs-primary-rgb), 0.3);
}
/* Suggestion issue des tags de l'utilisateur : léger liseré à gauche (« à toi »). */
.prompt-suggest__item--personal {
  box-shadow: inset 2px 0 0 var(--in-light);
}
.prompt-suggest__name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Cartouche de catégorie à droite du nom (comme sur images#show). */
.prompt-suggest__cat {
  flex: 0 0 auto;
}
/* Antécédent d'alias affiché avant la cible (« tits → breasts »). */
.prompt-suggest__alias {
  flex: 0 0 auto;
  opacity: 0.5;
  font-size: 0.85em;
}
/* Option « ajouter exactement ce que j'ai tapé » (tag custom, en bas du menu). */
.prompt-suggest__item--custom .prompt-suggest__name {
  font-style: italic;
  opacity: 0.85;
}
.prompt-suggest__plus {
  flex: 0 0 auto;
  font-weight: 700;
  color: var(--in-light);
}

/* ── Cartouches de catégorie E621, partagées par le menu, la barre de poids et images#show. ──
   L'artiste garde l'indigo ; les autres reprennent les teintes des chips (texte sombre pour le
   contraste sur fond clair). */
.cat-badge--artist    { background-color: var(--in-primary); color: #fff; }
.cat-badge--species   { background-color: #fb923c; color: #1b1b1b; }
.cat-badge--character { background-color: #4ade80; color: #1b1b1b; }
.cat-badge--copyright { background-color: #e879f9; color: #1b1b1b; }
.cat-badge--meta,
.cat-badge--lore      { background-color: #94a3b8; color: #1b1b1b; }
.cat-badge--lora      { background-color: var(--bs-danger); color: #fff; }
.cat-badge--rating    { background-color: var(--bs-warning); color: #1b1b1b; }

/* Compteur de filtres actifs dans le bouton « Filter » : centré verticalement avec le libellé
   (le badge Bootstrap est en vertical-align:baseline → sinon il flotte trop haut) et chiffre centré. */
.filter-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  min-width: 1.2rem;
  height: 1.2rem;
  padding: 0 0.3rem;
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1;
  border-radius: 0.5rem;
}

