@charset "UTF-8";
/* PostForMe -- systeme de design (feuille unique, vendorisee : pas de build front,
   architecture.md §2). CSP stricte (web/middleware.py) : aucun style inline, aucune
   police distante -- pile de polices systeme uniquement.

   Sommaire
     1. Jetons (couleurs, rayons, ombres, espacements, typographie) + themes
     2. Base / typographie
     3. Coquille : barre laterale, en-tete mobile, contenu, pages non connectees
     4. En-tete de page
     5. Composants : cartes, boutons, formulaires, tableaux, badges, messages,
        progression, etats vides, audio, images, blocs de prompt, journal
     6. Utilitaires
     7. Responsive / preferences de mouvement
*/

/* ------------------------------------------------------------------------ */
/* 1. Jetons                                                                  */
/* ------------------------------------------------------------------------ */

:root {
  color-scheme: light;

  /* Typographie : pile systeme (Segoe UI Variable sous Windows, SF sur macOS). */
  --font-sans: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system,
    BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif;
  --font-display: "Segoe UI Variable Display", "Segoe UI", system-ui, -apple-system,
    BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Code", "Cascadia Mono", "SF Mono", Menlo,
    Consolas, "Liberation Mono", monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.8125rem;
  --text-base: 0.9375rem;
  --text-md: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.375rem;
  --text-2xl: 1.75rem;

  /* Espacements (echelle 4 px). */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;

  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-pill: 999px;

  --control-h: 2.5rem; /* 40 px : cible tactile minimale */
  --sidebar-w: 264px;
  /* Retour utilisateur du 2026-09-20 : « Dans les jobs, le tableau est tronqué.
     Augmente la largeur du contenu de toutes les pages pour afficher plus
     d'informations. » -- 1180 px coupait la colonne « Créé le » de /jobs en plein
     milieu. Les pages de TEXTE ne suivent pas cet élargissement : leur mesure de
     lecture reste plafonnée séparément (`.page-lead` 72ch, `.prose` 80ch, `.form`
     560px, `.auth-wrap` 440px, et `.form-grid`/`.form-grid-3` ci-dessous) -- le but
     est « plus d'informations dans les tableaux », jamais « du texte sur 1600 px ». */
  --content-max: 1600px;
  /* Plafond de MESURE DE LECTURE (formulaires, texte suivi) : l'ancienne largeur de
     contenu, pour que ces blocs gardent exactement l'aspect qu'ils avaient. */
  --reading-max: 1180px;

  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur: 160ms;

  /* Theme clair (defaut). */
  --bg: #f5f6f8;
  --surface: #ffffff;
  --surface-2: #f1f3f6;
  --surface-3: #e6e9ee;
  --border: #e2e5ea;
  --border-strong: #c9ced7;
  --text: #151920;
  --text-muted: #566070;
  --text-subtle: #646d7c;

  --accent: #0f766e;
  --accent-hover: #0c5f58;
  --accent-soft: #e3f3f0;
  --accent-soft-text: #0b5a53;
  --on-accent: #ffffff;
  --focus-ring: #0f766e;

  --success: #157f3b;
  --success-soft: #e8f5ec;
  --success-border: #b7dfc4;
  --warning: #8a4b06;
  --warning-soft: #fff5e3;
  --warning-border: #f1d29a;
  --danger: #b42318;
  --danger-soft: #fdeeec;
  --danger-border: #f3c4be;
  --on-danger: #ffffff;
  --info: #1c5bb8;
  --info-soft: #eef4fd;
  --info-border: #c3d6f4;
  --neutral-soft: #eceef2;
  --neutral-text: #4a5261;

  --code-bg: #f6f7f9;
  --code-text: #1e2430;
  --log-bg: #0f1319;
  --log-text: #d4dae3;

  --shadow-sm: 0 1px 2px rgb(16 24 40 / 0.06);
  --shadow-md: 0 1px 2px rgb(16 24 40 / 0.05), 0 4px 12px rgb(16 24 40 / 0.06);
  --shadow-lg: 0 12px 32px rgb(16 24 40 / 0.14);

  /* Barre laterale : encre sombre dans les deux themes (ambiance "studio"). */
  --side-bg: #0f1319;
  --side-border: #1d232d;
  --side-text: #c3cad5;
  --side-muted: #8a94a3;
  --side-hover: rgb(255 255 255 / 0.06);
  --side-active-bg: rgb(45 212 191 / 0.13);
  --side-indicator: #2dd4bf;
  --side-focus: #5eead4;
}

/* Theme sombre : preference systeme, sauf forcage explicite data-theme="light".
   Les memes valeurs sont repetees pour le forcage data-theme="dark" (pas de
   preprocesseur). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0e1116;
    --surface: #151920;
    --surface-2: #1b2029;
    --surface-3: #252c37;
    --border: #262d38;
    --border-strong: #38414f;
    --text: #e6e9ee;
    --text-muted: #a0a9b6;
    --text-subtle: #8f99a7;

    --accent: #2dd4bf;
    --accent-hover: #5eead4;
    --accent-soft: rgb(45 212 191 / 0.13);
    --accent-soft-text: #7ce8d8;
    --on-accent: #04201e;
    --focus-ring: #5eead4;

    --success: #4ade80;
    --success-soft: rgb(74 222 128 / 0.11);
    --success-border: rgb(74 222 128 / 0.3);
    --warning: #fbbf24;
    --warning-soft: rgb(251 191 36 / 0.11);
    --warning-border: rgb(251 191 36 / 0.3);
    --danger: #f98080; /* 4,47:1 -> 5:1 sur fond « Échec » du calendrier (audit 2026-10-01) */
    --danger-soft: rgb(248 113 113 / 0.11);
    --danger-border: rgb(248 113 113 / 0.32);
    --on-danger: #1a0605;
    --info: #7cb4ff;
    --info-soft: rgb(124 180 255 / 0.11);
    --info-border: rgb(124 180 255 / 0.3);
    --neutral-soft: #232a35;
    --neutral-text: #b7c0cc;

    --code-bg: #10141a;
    --code-text: #dce2ea;
    --log-bg: #0a0d11;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
    --shadow-md: 0 1px 2px rgb(0 0 0 / 0.35), 0 4px 14px rgb(0 0 0 / 0.3);
    --shadow-lg: 0 16px 40px rgb(0 0 0 / 0.55);

    --side-bg: #0a0d11;
    --side-border: #1a2029;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0e1116;
  --surface: #151920;
  --surface-2: #1b2029;
  --surface-3: #252c37;
  --border: #262d38;
  --border-strong: #38414f;
  --text: #e6e9ee;
  --text-muted: #a0a9b6;
  --text-subtle: #8f99a7;

  --accent: #2dd4bf;
  --accent-hover: #5eead4;
  --accent-soft: rgb(45 212 191 / 0.13);
  --accent-soft-text: #7ce8d8;
  --on-accent: #04201e;
  --focus-ring: #5eead4;

  --success: #4ade80;
  --success-soft: rgb(74 222 128 / 0.11);
  --success-border: rgb(74 222 128 / 0.3);
  --warning: #fbbf24;
  --warning-soft: rgb(251 191 36 / 0.11);
  --warning-border: rgb(251 191 36 / 0.3);
  --danger: #f98080;
  --danger-soft: rgb(248 113 113 / 0.11);
  --danger-border: rgb(248 113 113 / 0.32);
  --on-danger: #1a0605;
  --info: #7cb4ff;
  --info-soft: rgb(124 180 255 / 0.11);
  --info-border: rgb(124 180 255 / 0.3);
  --neutral-soft: #232a35;
  --neutral-text: #b7c0cc;

  --code-bg: #10141a;
  --code-text: #dce2ea;
  --log-bg: #0a0d11;

  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-md: 0 1px 2px rgb(0 0 0 / 0.35), 0 4px 14px rgb(0 0 0 / 0.3);
  --shadow-lg: 0 16px 40px rgb(0 0 0 / 0.55);

  --side-bg: #0a0d11;
  --side-border: #1a2029;
}

/* ------------------------------------------------------------------------ */
/* 2. Base                                                                    */
/* ------------------------------------------------------------------------ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  color: var(--text);
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0 0 var(--space-3);
  text-wrap: balance;
}

h1 {
  font-size: var(--text-2xl);
  font-weight: 650;
  letter-spacing: -0.02em;
}

h2 {
  font-size: var(--text-lg);
  font-weight: 620;
}

h3 {
  font-size: var(--text-md);
  font-weight: 600;
}

p {
  margin: 0 0 var(--space-3);
}

a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}

a:hover {
  color: var(--accent-hover);
  text-decoration-color: currentColor;
}

code,
kbd,
pre,
samp {
  font-family: var(--font-mono);
  font-size: 0.875em;
}

:not(pre) > code {
  padding: 0.1em 0.4em;
  border-radius: 4px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--code-text);
}

img,
video {
  max-width: 100%;
}

strong {
  font-weight: 620;
}

::selection {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

[hidden] {
  display: none !important;
}

.icon {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-sm {
  width: 1rem;
  height: 1rem;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: -100px;
  z-index: 100;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  text-decoration: none;
}

.skip-link:focus {
  top: var(--space-4);
  color: var(--on-accent);
}

.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------------------ */
/* 3. Coquille                                                                */
/* ------------------------------------------------------------------------ */

.shell {
  min-height: 100vh;
}

.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 40;
  width: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  background: var(--side-bg);
  border-right: 1px solid var(--side-border);
  color: var(--side-text);
}

.sidebar :focus-visible,
.mobile-bar :focus-visible {
  outline-color: var(--side-focus);
}

.sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-3) var(--space-4) var(--space-5);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 40px;
  color: #ffffff;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.0625rem;
  letter-spacing: -0.01em;
}

.brand:hover {
  color: #ffffff;
}

.brand-mark {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: var(--side-indicator);
  color: #04201e;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18);
}

.brand-mark svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
  stroke: none;
}

.brand-name span {
  color: var(--side-indicator);
}

.sidebar .sidebar-close {
  display: none;
}

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-2) var(--space-3) var(--space-4);
  scrollbar-width: thin;
  scrollbar-color: #2a313c transparent;
}

.nav-group + .nav-group {
  margin-top: var(--space-5);
}

.nav-group-title {
  margin: 0 0 var(--space-1);
  padding: 0 var(--space-3);
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--side-muted);
}

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}

.nav-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--side-text);
  font-size: 0.90625rem;
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.nav-link .icon {
  color: var(--side-muted);
  transition: color var(--dur) var(--ease);
}

.nav-link:hover {
  background: var(--side-hover);
  color: #ffffff;
}

.nav-link:hover .icon {
  color: var(--side-text);
}

.nav-link[aria-current="page"] {
  background: var(--side-active-bg);
  color: #ffffff;
  font-weight: 600;
}

.nav-link[aria-current="page"] .icon {
  color: var(--side-indicator);
}

.nav-link[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -12px;
  top: 8px;
  bottom: 8px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--side-indicator);
}

.sidebar-foot {
  border-top: 1px solid var(--side-border);
  padding: var(--space-3);
  display: grid;
  gap: var(--space-2);
}

.user-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
}

.avatar {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 50%;
  background: #1f2732;
  color: #cdfaf3;
  font-weight: 650;
  font-size: 0.9375rem;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.08);
}

.user-meta {
  min-width: 0;
  display: grid;
  line-height: 1.3;
}

.user-name {
  color: #ffffff;
  font-weight: 600;
  font-size: 0.875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-role {
  color: var(--side-muted);
  font-size: var(--text-xs);
}

.sidebar-actions {
  display: flex;
  gap: var(--space-1);
}

.sidebar-actions form {
  display: contents;
}

.side-btn,
button.side-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 40px;
  min-width: 40px;
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  box-shadow: none;
  color: var(--side-text);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.side-btn:hover,
button.side-btn:hover {
  background: var(--side-hover);
  color: #ffffff;
}

.side-btn[aria-current="page"] {
  background: var(--side-active-bg);
  color: #ffffff;
}

.side-btn-grow {
  flex: 1;
  justify-content: flex-start;
}

/* Bascule de theme : affiche l'icone du theme vers lequel on bascule. */
.theme-toggle .icon-sun {
  display: none;
}

:root[data-theme="dark"] .theme-toggle .icon-sun {
  display: block;
}

:root[data-theme="dark"] .theme-toggle .icon-moon {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun {
    display: block;
  }

  :root:not([data-theme="light"]) .theme-toggle .icon-moon {
    display: none;
  }
}

.shell-main {
  margin-left: var(--sidebar-w);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.mobile-bar,
.sidebar-backdrop {
  display: none;
}

.page {
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-8) var(--space-10) var(--space-12);
}

/* Pages non connectees (connexion, premier lancement, erreurs). */
.auth-body {
  display: grid;
  place-items: center;
  padding: var(--space-8) var(--space-4);
  background:
    radial-gradient(900px 420px at 50% -8%, var(--accent-soft), transparent 70%),
    var(--bg);
}

.auth-wrap {
  width: 100%;
  max-width: 440px;
}

.auth-brand {
  display: flex;
  justify-content: center;
  margin-bottom: var(--space-6);
}

.auth-brand .brand {
  color: var(--text);
  font-size: 1.25rem;
}

.auth-brand .brand-name span {
  color: var(--accent);
}

.auth-brand .brand-mark {
  background: var(--accent);
  color: var(--on-accent);
}

.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  padding: var(--space-8);
}

.auth-card h1 {
  font-size: var(--text-xl);
  margin-bottom: var(--space-2);
}

.auth-card .form {
  max-width: none;
  margin-top: var(--space-5);
}

.auth-card .form > button[type="submit"] {
  align-self: stretch;
}

.auth-foot {
  display: flex;
  justify-content: center;
  margin-top: var(--space-4);
}

/* Audit visuel mobile (2026-10-01) : le bouton "Thème" des pages non connectees
   (connexion, premier lancement) porte `.btn-sm` (`min-height: 2.25rem` = 36 px),
   sous la cible tactile minimale de 40 px (`--control-h`) visee ailleurs dans
   l'application -- seul ce bouton isole en avait besoin, la hauteur reduite n'etait
   pas intentionnelle ici. */
.auth-foot .theme-toggle {
  min-height: var(--control-h);
}

/* ------------------------------------------------------------------------ */
/* 4. En-tete de page                                                         */
/* ------------------------------------------------------------------------ */

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
  margin-bottom: var(--space-6);
}

.page-header h1 {
  margin: 0;
}

.page-heading {
  min-width: 0;
  flex: 1 1 320px;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

a.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 32px;
  margin: 0 0 var(--space-1);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 560;
  text-decoration: none;
}

a.back-link:hover {
  color: var(--accent);
}

.page-lead {
  margin: var(--space-2) 0 0;
  max-width: 72ch;
  color: var(--text-muted);
}

/* Reprise IHM du 2026-09-30 (retour utilisateur : « les boutons ne sont pas bien
   placés » -- fiche vidéo en « Scénario prêt ») : empile désormais l'action
   principale (`.actions-primary`) et le groupe secondaire repliable (« Autres
   actions », voir plus bas) l'un sous l'autre, chacun occupant toute la largeur
   disponible -- `.page-actions` reste le conteneur (le test
   `test_l_action_principale_est_dans_la_barre_d_actions` cherche encore
   `<div class="page-actions">`). */
.page-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Vérification chrome-devtools du 2026-10-01 (PC 1920 px) : la mise en colonne
   étirée ci-dessous s'appliquait à TOUTES les `.page-actions` de l'application
   (« Télécharger le MP4 » de 1470 px de large, « Ouvrir cette version », etc.).
   Elle est réservée à la zone d'actions de l'en-tête vidéo, seule à contenir
   `.actions-primary`/« Autres actions ». */
.page-actions:has(> .actions-primary, > .page-actions-secondary) {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-3);
}

.page-actions form {
  display: inline-flex;
  margin: 0;
}

.header-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.actions-primary {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
}

/* Aligne le bas de chaque bouton/champ sur une même ligne de base -- un champ
   `.field` (ex. `.prompt-provider-field`/`.scenario-ai-field`, étiquette au-dessus de
   sa liste) se termine par sa liste, à la même hauteur que le bas d'un bouton voisin. */
.actions-primary .inline-form,
.actions-secondary-body .inline-form {
  align-items: flex-end;
  gap: var(--space-2) var(--space-3);
}

/* Groupe secondaire, visuellement en retrait (même motif que les autres
   `<details class="disclosure">` de cette page -- repli SANS JavaScript garanti). */
.page-actions-secondary {
  margin-top: 0;
}

.page-actions-secondary > summary {
  font-size: var(--text-sm);
  font-weight: 560;
  color: var(--text-muted);
}

.actions-secondary-body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
}

/* Listes « IA des prompts »/« IA de scénario » de la zone d'actions : largeur bornée
   (elles affichaient jusqu'ici l'intégralité de libellés longs, ex. « Grok via votre
   abonnement (xai-oauth) », et déformaient toute la ligne de boutons) -- le texte de
   l'option sélectionnée se tronque avec une ellipse plutôt que d'étirer le champ. */
.prompt-provider-field,
.actions-primary .scenario-ai-field,
.actions-secondary-body .scenario-ai-field {
  min-width: 12rem;
  max-width: 22rem;
}

.prompt-provider-field select,
.actions-primary .scenario-ai-field select,
.actions-secondary-body .scenario-ai-field select {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 640px) {
  .actions-primary,
  .actions-secondary-body {
    flex-direction: column;
    align-items: stretch;
  }

  .actions-primary .inline-form,
  .actions-secondary-body .inline-form {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
  }

  .prompt-provider-field,
  .actions-primary .scenario-ai-field,
  .actions-secondary-body .scenario-ai-field {
    max-width: none;
  }
}

/* ------------------------------------------------------------------------ */
/* 5. Composants                                                              */
/* ------------------------------------------------------------------------ */

/* Cartes et sections ------------------------------------------------------- */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
  min-width: 0;
}

.card + .card,
.card + .stack,
.stack + .card,
.grid-2 + .card,
.card + .grid-2 {
  margin-top: var(--space-5);
}

.card-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-5);
}

.card-header h2,
.card-header h3 {
  margin: 0;
}

.card-header p {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* Etape 11 point 5 (ADR-0008 §4/§8) : sous-section visible a l'interieur d'une carte
   (ex. "Prompts video" dans la carte "Storyboard") -- meme langage visuel que
   `.card-header`, mais avec une bordure haute pour marquer la separation SANS ouvrir
   une carte distincte (pas de duplication de la table des plans). */
.card-subsection-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin: var(--space-5) 0 var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.card-subsection-header h3 {
  margin: 0;
}

.card-subsection-header p {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.card-flush {
  padding: 0;
  overflow: hidden;
}

.card-flush > .card-header {
  padding: var(--space-5) var(--space-6) 0;
  margin-bottom: var(--space-4);
}

.card-danger {
  border-color: var(--danger-border);
}

.card-header h2 {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.card-flush > .card-body {
  padding: 0 var(--space-6) var(--space-5);
}

.card-foot {
  margin: 0;
  padding: var(--space-3) var(--space-6) var(--space-4);
  border-top: 1px solid var(--border);
}

.card-subtitle {
  margin: var(--space-6) 0 var(--space-3);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  font-size: var(--text-md);
}

.stack {
  display: grid;
  gap: var(--space-5);
}

.stack > .card + .card {
  margin-top: 0;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5);
  align-items: start;
}

.grid-2 > .card + .card {
  margin-top: 0;
}

.section-title {
  margin: var(--space-8) 0 var(--space-4);
}

/* Tuiles d'accueil --------------------------------------------------------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--space-4);
}

.tile {
  position: relative;
  display: grid;
  gap: var(--space-1);
  align-content: start;
  min-height: 148px;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.tile:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

.tile-icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-bottom: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.tile-title {
  font-family: var(--font-display);
  font-weight: 620;
  font-size: var(--text-md);
}

.tile-text {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.tile-arrow {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  color: var(--text-subtle);
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
}

.tile:hover .tile-arrow {
  color: var(--accent);
  transform: translateX(2px);
}

/* Boutons ------------------------------------------------------------------ */

button,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0 var(--space-4);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 560;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
    color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    transform 80ms var(--ease);
}

button:hover,
.btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

button:active,
.btn:active {
  transform: translateY(1px);
}

button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

.btn-primary,
button.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.btn-primary:hover,
button.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--on-accent);
}

.btn-danger,
button.btn-danger {
  border-color: var(--danger-border);
  color: var(--danger);
}

.btn-danger:hover,
button.btn-danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--on-danger);
}

.btn-ghost,
button.btn-ghost {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  color: var(--text-muted);
}

.btn-ghost:hover,
button.btn-ghost:hover {
  background: var(--surface-2);
  border-color: transparent;
  color: var(--text);
}

.btn-sm,
button.btn-sm {
  min-height: 2.25rem;
  padding: 0 var(--space-3);
  font-size: var(--text-sm);
}

.btn-icon,
button.btn-icon {
  padding: 0;
  width: var(--control-h);
}

.button-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: 0 var(--space-4);
  margin-top: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 600;
  text-decoration: none;
}

.button-link:hover {
  background: var(--accent-hover);
  color: var(--on-accent);
}

.button-link[hidden] {
  display: none;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.btn-row form {
  display: inline-flex;
  margin: 0;
}

/* Etape 13 (calendrier des publications) : bascule liste/semaine/mois -- liens
   simples, aucun JS. */
.tab-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.tab-link {
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-full, 999px);
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: var(--text-sm);
}

.tab-link:hover {
  background: var(--neutral-soft);
}

.tab-link.tab-active {
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.calendar-week {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-3);
}

.calendar-month {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-2);
}

.calendar-day,
.calendar-month-day {
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 10px);
  padding: var(--space-2);
  min-height: 90px;
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.calendar-month-day.calendar-outside-month {
  opacity: 0.45;
}

.calendar-day-title,
.calendar-month-day-title {
  font-size: var(--text-sm);
  font-weight: 650;
  margin: 0;
}

.calendar-entry {
  border-radius: var(--radius-sm, 6px);
  background: var(--neutral-soft);
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

@media (max-width: 720px) {
  .calendar-week,
  .calendar-month {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Formulaires -------------------------------------------------------------- */

.form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 560px;
}

.form > label:not(.checkbox-label) {
  margin-top: var(--space-2);
}

.form > label:first-of-type {
  margin-top: 0;
}

.form > button[type="submit"] {
  margin-top: var(--space-3);
  align-self: flex-start;
}

/* Vérification chrome-devtools du 2026-10-01 (PC 1920 px) : un bouton posé
   directement dans un conteneur en colonne (`.form`, `.field`) ou dans une cellule de
   `.form-grid` s'étirait sur toute la largeur (« Ajouter un gimmick »,
   « Ajouter une réplique », « Réinitialiser » : 1 150 px) ou toute la hauteur de la
   ligne (« Enregistrer les options » : 68 px). Il garde désormais sa taille naturelle.
   Le bouton pleine largeur de la page de connexion reste assuré par la règle plus
   précise `.auth-card .form > button[type="submit"]`. */
.form > button,
.form > .btn,
.field > button,
.field > .btn {
  align-self: flex-start;
}

.form-grid > button,
.form-grid > .btn {
  justify-self: start;
  align-self: end;
}

.form-wide {
  /* « Large » veut dire « plus large que les 560 px d'un `.form` », jamais « toute la
     largeur de l'écran » : depuis l'élargissement de `--content-max` à 1600 px
     (2026-09-20), un champ de saisie étiré sur 1600 px serait illisible. Ce plafond est
     EXACTEMENT l'ancienne largeur de contenu : ces formulaires gardent donc l'aspect
     qu'ils avaient, seules les cartes qui portent un TABLEAU profitent des 1600 px. */
  max-width: var(--reading-max);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4) var(--space-5);
  /* Plafond de lecture (2026-09-20, élargissement de `--content-max` à 1600 px) : un
     formulaire à 2 colonnes étalé sur toute la largeur devient pénible à parcourir --
     il garde donc la mesure qu'il avait avant l'élargissement. */
  max-width: var(--reading-max);
}

.form-grid-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.field-full {
  grid-column: 1 / -1;
}

.field-end {
  justify-content: flex-end;
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.form-section-title {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  font-size: var(--text-md);
}

.form-section-note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

label {
  font-size: var(--text-sm);
  font-weight: 560;
  color: var(--text);
}

.hint {
  margin: var(--space-1) 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="email"],
input[type="search"],
input[type="url"],
input[type="tel"],
/* Audit d'affichage du 2026-10-01 : les champs date/heure (« Date et heure » de la carte
   « Publier », calendrier, création multiple) gardaient le rendu NATIF du navigateur --
   20 px de haut, police à chasse fixe, aucune bordure du système -- au milieu de champs
   de 40 px. */
input[type="datetime-local"],
input[type="date"],
input[type="time"],
input:not([type]),
select,
textarea {
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.9375rem;
  line-height: 1.4;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

textarea {
  min-height: 6.5rem;
  resize: vertical;
}

input::placeholder,
textarea::placeholder {
  color: var(--text-subtle);
  opacity: 1;
}

input:hover,
select:hover,
textarea:hover {
  border-color: color-mix(in srgb, var(--text-muted) 55%, var(--border-strong));
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent);
}

input:disabled,
select:disabled,
textarea:disabled {
  background: var(--surface-2);
  color: var(--text-muted);
  cursor: not-allowed;
}

input[readonly],
textarea[readonly] {
  background: var(--surface-2);
  color: var(--text-muted);
}

select {
  appearance: none;
  padding-right: 2.25rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

select[multiple] {
  padding: var(--space-1);
  background-image: none;
  min-height: 7.5rem;
}

select[multiple] option {
  padding: var(--space-1) var(--space-2);
  border-radius: 4px;
}

input[type="checkbox"],
input[type="radio"] {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
  flex: none;
}

input[type="file"] {
  width: 100%;
  min-height: var(--control-h);
  padding: var(--space-2);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text-muted);
  font: inherit;
  font-size: var(--text-sm);
}

input[type="file"]::file-selector-button {
  margin-right: var(--space-3);
  min-height: 2rem;
  padding: 0 var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-weight: 560;
  cursor: pointer;
}

.checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  font-weight: 500;
  cursor: pointer;
}

/* Groupe de cases a cocher (etape 12c : classements de tendances, `.checkbox-group`
   dans `_admin_tab_tendances.html`) -- une `.checkbox-label` par choix, enveloppees
   pour qu'elles se replient proprement sur plusieurs lignes des le mobile, au lieu de
   deborder ou de s'ecraser les unes contre les autres. */
.checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
}

.inline-form {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* Regroupe plusieurs `.inline-form`/boutons (ex. « Enregistrer »/« Annuler » de
   l'apercu VPN WireGuard, `_admin_tab_connexions.html`) sur une meme ligne, avec un
   repli propre sur mobile -- sans elle, deux `.inline-form` adjacents ne sont separes
   que par l'espace blanc du HTML source. */
.inline-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Barre de filtres --------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
}

.filters .field {
  flex: 1 1 180px;
}

.filters-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* Tableaux ----------------------------------------------------------------- */

.table-wrap {
  width: 100%;
  /* `position: relative` (audit IHM du 2026-09-21) : sans lui, un element en
     `position: absolute` A L'INTERIEUR du tableau -- typiquement un libelle
     `.visually-hidden` de colonne d'actions -- se positionne par rapport au bloc
     englobant INITIAL (la page) et non par rapport a ce conteneur defilant. Sur un
     ecran etroit, il se retrouvait a 562 px alors que la fenetre en fait 390, et
     c'etait LA PAGE ENTIERE qui defilait horizontalement au lieu du seul tableau
     (constate sur /videos a 390 px de large ; /jobs et /publications, sans libelle
     masque dans leur tableau, n'avaient pas le defaut). */
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.card .table-wrap {
  box-shadow: none;
}

/* Audit visuel mobile/desktop (2026-10-01) : `.table-wrap.card-inset` (admin Système
   -- journal d'audit, admin Vidéo) combine `width: 100%` (ci-dessus) avec les marges
   horizontales de `.card-inset` (`margin: 0 var(--space-6) ...`) -- l'empreinte totale
   (largeur + marges) dépassait alors son propre conteneur de 2 × `--space-6` (48 px),
   provoquant un débordement horizontal MESURABLE sur `.card`/`.page`/le document
   entier (mobile ET desktop). `width: auto` laisse les marges se soustraire
   normalement de la largeur disponible, comme pour tout autre bloc `.card-inset`. */
.table-wrap.card-inset {
  width: auto;
}

.card-flush .table-wrap {
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
}

table,
.users-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

th,
td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--border);
}

thead th {
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

tbody tr:last-child td {
  border-bottom: 0;
}

tbody tr {
  transition: background-color var(--dur) var(--ease);
}

tbody tr:hover {
  background: color-mix(in srgb, var(--surface-2) 70%, transparent);
}

td a {
  font-weight: 560;
}

.card-flush th:first-child,
.card-flush td:first-child {
  padding-left: var(--space-6);
}

.card-flush th:last-child,
.card-flush td:last-child {
  padding-right: var(--space-6);
}

td.num,
th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.mono {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}

.muted {
  color: var(--text-muted);
}

h2.mono {
  font-size: var(--text-md);
  font-weight: 650;
}

h1 .mono {
  font-size: 0.6em;
  font-weight: 500;
  color: var(--text-muted);
  letter-spacing: 0;
  vertical-align: middle;
}

td input[type="text"],
td input[type="password"],
td select {
  min-width: 8.5rem;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

td.actions {
  display: table-cell;
}

td .inline-form input:not([type="checkbox"]):not([type="hidden"]),
td .inline-form select {
  width: auto;
  min-width: 8.5rem;
  max-width: 14rem;
}

td.actions .inline-form {
  margin: var(--space-1) var(--space-2) var(--space-1) 0;
  vertical-align: middle;
}

/* ----------------------------------------------------------------------
   Page « Paramètres » : sous-menu de section FIXE + disposition des blocs
   (retour utilisateur du 2026-09-20 : « La page est trop longue. Il faut mettre en
   place un sous-menu dans la page, qui est toujours fixe (comme le menu principal),
   pour permettre une navigation plus rapide. »)

   Le caractère fixe vient UNIQUEMENT du CSS (`position: sticky`) -- CSP stricte, aucun
   script inline, et la page reste navigable sans JavaScript (ce sont de simples ancres).
   `static/js/settings_nav.js` n'ajoute que la mise en évidence de la section en cours.
   ---------------------------------------------------------------------- */
.settings-layout {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

.settings-nav {
  position: sticky;
  /* Même dégagement que le padding haut de `.page` : le sommaire se cale exactement
     là où commence le contenu. */
  top: var(--space-8);
  align-self: start;
  min-width: 0;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  /* Sommaire plus haut que la fenêtre (petit portable) : il défile sur lui-même
     plutôt que de déborder. */
  max-height: calc(100vh - var(--space-12));
  overflow-y: auto;
}

.settings-nav-title {
  margin: 0 0 var(--space-2);
  padding: 0 var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.settings-nav-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.settings-nav-link {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 560;
  text-decoration: none;
}

.settings-nav-link:hover,
.settings-nav-link:focus-visible {
  background: var(--surface-2);
  color: var(--text);
}

/* Section en cours de lecture -- posé par `settings_nav.js` (IntersectionObserver).
   Sans JavaScript, aucun lien n'est mis en évidence : c'est un CONFORT, la navigation
   par ancres fonctionne de toute façon. */
.settings-nav-link[aria-current="true"] {
  background: var(--accent-soft);
  color: var(--accent);
}

.settings-sections {
  min-width: 0;
}

/* `scroll-margin-top` : sans lui, une ancre place le haut de la carte EXACTEMENT au
   bord de la fenêtre, et son titre passe sous le sommaire collant sur les écrans où
   celui-ci est horizontal. */
.settings-sections > section {
  scroll-margin-top: var(--space-8);
}

/* Message intercalé dans une carte `.card-flush` (qui n'a pas de padding propre). */
.card-inset {
  margin: 0 var(--space-6) var(--space-4);
}

/* ----------------------------------------------------------------------
   Barre d'onglets de l'administration par domaine (ADR-0009, étape 12b).
   Horizontale, défilable sur petit écran (`overflow-x: auto`) plutôt que la colonne
   sticky de `.settings-nav` (ancres d'UNE page) : ce sont ici de vraies URL distinctes.
---------------------------------------------------------------------- */
/* Correctif finitions admin (mission 2026-09-30, point 3) : à partir de 720 px, les
   neuf onglets doivent tenir SANS troncature -- libellés compacts (`tab.short_title`,
   `web/admin_tabs.py::TabDef`) + retour à la ligne propre (`flex-wrap`) plutôt qu'un
   défilement horizontal, qui masquerait silencieusement le dernier onglet sans le
   moindre indice visuel. En dessous de 720 px (mobile, écran trop étroit pour ne
   serait-ce qu'une ligne lisible de neuf onglets), on repasse au défilement horizontal
   -- avec un dégradé sur le bord droit (`.admin-tabs::after`) comme indicateur visible
   qu'il reste des onglets hors champ. */
.admin-tabs {
  position: relative;
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--border);
}

.admin-tabs-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.admin-tab-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3);
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 560;
  text-decoration: none;
  white-space: nowrap;
}

.admin-tab-link:hover,
.admin-tab-link:focus-visible {
  color: var(--text);
}

.admin-tab-link[aria-current="page"] {
  border-bottom-color: var(--accent);
  color: var(--accent);
}

@media (max-width: 720px) {
  /* Le defilement (et le `overflow-x`) vit sur le CONTENEUR (`.admin-tabs`), pas sur la
     liste elle-meme (`.admin-tabs-list`) : `min-width: max-content` force la liste a
     rester aussi large que necessaire pour ALIGNER les neuf onglets sur une seule ligne
     -- lui donner AUSSI `overflow-x: auto` serait contradictoire (une boite qui grandit
     pour epouser son contenu n'a plus rien a faire defiler ELLE-MEME, le debordement
     remonterait alors silencieusement jusqu'au `<body>`, scrollWidth du document entier
     compris -- bug constate a la capture d'ecran 390 px). */
  .admin-tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .admin-tabs-list {
    flex-wrap: nowrap;
    min-width: max-content;
    padding-right: var(--space-8);
  }

  /* Indicateur de défilement (mobile SEULEMENT, ADR-0009 finitions) : dégradé fixé sur
     le bord droit de la barre, jamais interactif (`pointer-events: none`) -- signale
     qu'il reste des onglets hors champ sans dépendre de JavaScript. */
  .admin-tabs::after {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    bottom: 1px;
    width: var(--space-8);
    pointer-events: none;
    background: linear-gradient(to right, transparent, var(--bg));
  }
}

/* ----------------------------------------------------------------------
   Refonte de l'administration du 2026-10-04 (ADR-0013).
---------------------------------------------------------------------- */
/* Sommaire d'ancres d'un onglet long (Génération vidéo) : simples liens, qui passent à
   la ligne -- jamais de défilement horizontal. */
.admin-anchors {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}

.admin-anchors a {
  display: inline-flex;
  align-items: center;
  /* Cible tactile d'au moins 40 px (audit d'affichage). */
  min-height: 40px;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.8125rem;
  font-weight: 560;
  text-decoration: none;
}

.admin-anchors a:hover,
.admin-anchors a:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

/* Cibles d'ancre hors `<section>` (Système : comptes, secrets ; Scénario : sous-blocs
   de « Nouvelles vidéos ») -- même dégagement que les sections. */
.admin-anchor-target,
.defaults-item,
.settings-sections [id^="section-"] {
  scroll-margin-top: var(--space-8);
}

/* Test d'un rôle d'IA : bouton et explication sur une ligne, résultat dessous. */
.role-test {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.role-test > .hint {
  flex: 1 1 18rem;
  margin: 0;
}

.role-test > [aria-live] {
  flex-basis: 100%;
}

/* Zones de test de connexion : bouton puis résultat juste dessous. */
.connection-test {
  display: grid;
  gap: var(--space-2);
  margin: 0 var(--space-6) var(--space-4);
}

.card-body .connection-test {
  margin: var(--space-3) 0 0;
}

/* Récapitulatif « Quelle IA fait quoi » : un nom de modèle très long passe à la ligne
   au lieu d'élargir le tableau. */
.ai-summary-value {
  overflow-wrap: anywhere;
}

.voice-catalog-provider .table-wrap {
  margin-top: var(--space-4);
}

/* ----------------------------------------------------------------------
   Liste des fournisseurs de `/admin/settings` (correctif du 2026-09-20).

   Cette section était un `<table>` dont chaque `<tr>` contenait un `<form>` -- du HTML
   invalide, que le navigateur « réparait » en sortant le formulaire du tableau : les
   champs n'y étaient plus rattachés et le bouton « Enregistrer » de la ligne n'envoyait
   rien du tout. Elle est devenue une LISTE de lignes, chacune portant un vrai
   formulaire (voir `_admin_settings_body.html`). Huit colonnes sur une page déjà large
   imposaient de toute façon un défilement horizontal permanent : chaque ligne se replie
   maintenant naturellement sur les écrans étroits.
   ---------------------------------------------------------------------- */
.provider-rows {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--border);
}

/* La bordure separatrice vit sur `.provider-row-wrap` (pas `.provider-row`) : chaque
   fournisseur avec catalogue de modeles porte DEUX `<form>` freres dans le meme wrap
   (`.provider-row` puis `.provider-refresh-form`, jamais imbriques -- HTML invalide
   sinon) -- la mettre sur `.provider-row` seul aurait dessine un trait juste avant le
   bouton "Rafraichir les modeles", au lieu de sous lui (correctif finitions admin,
   mission 2026-09-30, point 4). */
.provider-row-wrap {
  border-bottom: 1px solid var(--border);
}

.provider-row-wrap:last-child {
  border-bottom: 0;
}

.provider-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
}

.provider-row-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.provider-name {
  font-weight: 650;
}

/* Identifiant technique d'une ligne de fournisseur (mission finitions admin
   2026-09-30, point 7) : discret -- muet, plus petit que le libellé français qui le
   précède (`.mono` seul, `.provider-name` porte deja `font-weight: 650`, sinon
   herité). */
.provider-name-id {
  font-weight: 400;
  color: var(--text-muted);
}

/* Case « actif » à enregistrement immédiat : la roue `.form-busy` occupe sa place en
   permanence (voir `.form-busy` plus bas), le libellé ne bouge donc jamais quand
   l'enregistrement part. */
.provider-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: 0.875rem;
}

.provider-row-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-3);
  align-items: end;
}

.provider-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.provider-field > span,
.provider-field > .provider-field-label {
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* Correctif du 2026-10-03 : état de la liste des modèles d'une connexion (chargement
   sondé, erreur + « Réessayer », liste vide) sous le sélecteur de modèle. */
.catalog-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin: 0;
}

.catalog-loading {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.catalog-loading .spinner {
  display: inline-block;
  flex: none;
}

/* Parcours du 2026-10-02 : les aides placées sous un champ (URL de base, jeton d'API)
   héritaient des majuscules de l'étiquette. */
.provider-field > span.hint {
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.provider-field input,
.provider-field select {
  width: 100%;
  font-size: 0.875rem;
  color: var(--text);
}

/* Les options JSON sont la valeur la plus longue de la ligne : elle occupe toute la
   largeur restante dès que la grille tient sur deux colonnes ou plus. */
.provider-field-wide {
  grid-column: 1 / -1;
}

.provider-row-actions {
  display: flex;
  justify-content: flex-end;
}

/* Selecteur de modele + champ « Autre… » (etape 12c point 1, `js/admin_model_select.js`)
   -- le champ texte libre est masque en JS des qu'un modele du catalogue est choisi,
   mais reste TOUJOURS present dans le HTML (repli sans JavaScript garanti). Un espacement
   au-dessus evite qu'il colle au `<select>` quand les deux sont visibles a la fois (avant
   chargement du script, ou choix "Autre…"). */
.model-other-field {
  margin-top: var(--space-2);
}

/* « Rafraichir les modeles » d'une ligne de fournisseur (etape 12c, `_admin_provider_
   rows.html`) : bouton + lien "Recharger" sur la meme ligne, qui se replient proprement
   sur un ecran etroit plutot que de deborder du `.provider-row`. */
.provider-refresh-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-2);
  /* Correctif finitions admin (mission 2026-09-30, point 4) : cette ligne est un
     FRÈRE de `.provider-row` (voir `_admin_provider_rows.html`, DEUX `<form>` dans le
     même `.provider-row-wrap`, jamais imbriqués -- HTML invalide sinon) -- il lui faut
     donc le MÊME rembourrage horizontal que `.provider-row` pour rester DANS la carte,
     alignée sous sa liste de modèles, plutôt que de déborder à gauche contre le bord
     de la carte (`.card-flush` n'a lui-même aucun rembourrage propre). */
  padding: var(--space-2) var(--space-6) var(--space-4);
  border-top: 1px dashed var(--border);
  font-size: var(--text-sm);
}

.table-empty td {
  padding: var(--space-10) var(--space-4);
  text-align: center;
  color: var(--text-muted);
}

.table-empty:hover {
  background: transparent;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.tag {
  display: inline-flex;
  align-items: center;
  padding: 0.0625rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
  /* Audit d'affichage du 2026-10-01 : `nowrap` seul faisait sortir de la page une
     étiquette au texte long (rôle d'un personnage dans la distribution d'une série :
     +136 px à 1440 px, +516 px à 768 px). L'étiquette reste sur une ligne tant qu'elle
     tient, et ne passe à la ligne qu'en dernier recours. `break-word` et non
     `anywhere` : dans un tableau, `anywhere` ramène la largeur minimale de l'étiquette
     à un caractère et la colonne s'effondre (type de job sur une lettre par ligne). */
  max-width: 100%;
  white-space: normal;
  overflow-wrap: break-word;
}

/* Liste descriptive -------------------------------------------------------- */

.job-summary,
.details {
  display: grid;
  grid-template-columns: minmax(8rem, max-content) 1fr;
  margin: 0;
  font-size: 0.875rem;
}

.job-summary dt,
.job-summary dd {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
}

.job-summary dt {
  padding-right: var(--space-5);
  color: var(--text-muted);
  font-weight: 500;
}

.job-summary dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.job-summary dt:last-of-type,
.job-summary dd:last-of-type {
  border-bottom: 0;
}

/* Badges ------------------------------------------------------------------- */

.badge,
.job-status {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 1.5rem;
  padding: 0 0.625rem;
  border-radius: var(--radius-pill);
  background: var(--neutral-soft);
  color: var(--neutral-text);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
}

.badge::before,
.job-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.badge-plain::before {
  display: none;
}

.badge-accent {
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.badge-success {
  background: var(--success-soft);
  color: var(--success);
}

.badge-warning {
  background: var(--warning-soft);
  color: var(--warning);
}

.badge-danger,
/* Audit d'affichage du 2026-10-01 : badge « Erreur » du test Wan2GP, resté gris. */
.badge-error {
  background: var(--danger-soft);
  color: var(--danger);
}

.badge-info {
  background: var(--info-soft);
  color: var(--info);
}

.badge-neutral {
  background: var(--neutral-soft);
  color: var(--neutral-text);
}

/* Etats de jobs / etapes (data-status mis a jour en direct par
   static/js/jobs_sse.js) et de projets video. */
.badge[data-status="queued"],
.job-status[data-status="queued"],
.badge[data-status="scheduled"] {
  background: var(--info-soft);
  color: var(--info);
}

.badge[data-status="running"],
.job-status[data-status="running"],
.badge[data-status="generating"],
.badge[data-status="in_progress"],
.badge[data-status="rendering"],
.badge[data-status="scripting"],
.badge[data-status="storyboarding"],
.badge[data-status="publishing"],
.badge[data-status="uploaded"],
.badge[data-status="processing"] {
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.badge[data-status="retry_wait"],
.job-status[data-status="retry_wait"],
.badge[data-status="blocked"],
.job-status[data-status="blocked"],
.badge[data-status="cancel_requested"],
.job-status[data-status="cancel_requested"],
.badge[data-status="review"],
.badge[data-status="needs_review"] {
  background: var(--warning-soft);
  color: var(--warning);
}

.badge[data-status="succeeded"],
.job-status[data-status="succeeded"],
.badge[data-status="ready"],
.badge[data-status="published"],
.badge[data-status="draft_sent"],
.badge[data-status="done"],
.badge[data-status="approved"] {
  background: var(--success-soft);
  color: var(--success);
}

.badge[data-status="failed"],
.job-status[data-status="failed"],
.badge[data-status="error"] {
  background: var(--danger-soft);
  color: var(--danger);
}

/* Projets : etapes pretes a relire (scenario / storyboard). Brouillon et plan en
   attente restent neutres (explicite, meme rendu que le style par defaut). */
.badge[data-status="script_ready"],
.badge[data-status="storyboard_ready"] {
  background: var(--info-soft);
  color: var(--info);
}

.badge[data-status="draft"],
.badge[data-status="pending"],
.badge[data-status="cancelled"] {
  background: var(--neutral-soft);
  color: var(--neutral-text);
}

.badge[data-status="running"]::before,
.job-status[data-status="running"]::before,
.badge[data-status="generating"]::before,
.badge[data-status="rendering"]::before,
.badge[data-status="scripting"]::before,
.badge[data-status="storyboarding"]::before {
  animation: pulse 1.4s var(--ease) infinite;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.25;
  }
}

/* Messages ----------------------------------------------------------------- */
/* .message (succes / information neutre), .error, .warning-box : classes
   historiques conservees ; .alert-* : variantes explicites. */

.alert,
.message,
.error,
.warning-box {
  display: block;
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--info-border);
  border-left: 3px solid var(--info);
  border-radius: var(--radius-md);
  background: var(--info-soft);
  color: var(--text);
  font-size: 0.875rem;
  line-height: 1.55;
}

.message,
.alert-success {
  border-color: var(--success-border);
  border-left-color: var(--success);
  background: var(--success-soft);
}

.alert-info {
  border-color: var(--info-border);
  border-left-color: var(--info);
  background: var(--info-soft);
}

.warning-box,
.alert-warning {
  border-color: var(--warning-border);
  border-left-color: var(--warning);
  background: var(--warning-soft);
}

.warning-box {
  padding: var(--space-4) var(--space-5);
}

.error,
.alert-error,
/* Audit d'affichage du 2026-10-01 : `alert-danger` (erreur d'un plan, refus de quota,
   erreur de la fiche série) n'avait aucune règle et s'affichait en BLEU d'information. */
.alert-danger {
  border-color: var(--danger-border);
  border-left-color: var(--danger);
  background: var(--danger-soft);
}

.alert > :last-child,
.warning-box > :last-child {
  margin-bottom: 0;
}

.alert-title {
  display: block;
  margin-bottom: var(--space-1);
  font-weight: 650;
}

/* Refus d'une video (demande utilisateur du 2026-10-03 : « Quand je refuse une version,
   cela doit proposer de faire une nouvelle version »). Bandeau « Vidéo refusée le … » :
   formulaire compact « Créer la nouvelle version » sur une ligne quand la place le
   permet, empile sur mobile. */
.pending-rejection > p {
  margin: 0 0 var(--space-3);
}

.pending-rejection-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3) var(--space-4);
}

.pending-rejection-form .field {
  flex: 1 1 16rem;
  margin: 0;
}

.pending-rejection-form .checkbox-label {
  flex: 1 1 14rem;
}

.pending-rejection-form .form-actions {
  margin-top: 0;
}

/* Formulaire « Vidéo pas OK » : avec « Régénérer certains plans » coché, aucune version
   ne peut etre creee d'un coup -- le bouton principal et la case de bout en bout sont
   masques, l'explication affichee. Sans `:has()` (navigateur ancien) tout reste
   visible et la route explique (`reject_video`). */
.review-reject-form:has(input[name="restart"][value="some_shots"]:checked) .reject-new-version-only {
  display: none;
}

.review-reject-form:has(input[name="restart"][value="some_shots"]:not(:checked)) .reject-some-shots-only {
  display: none;
}

/* Note discrete liee a une connexion (ex. avertissement client OAuth tiers,
   ADR-0003) : texte petit et attenue, pas de fond colore ni bordure epaisse.
   Repliee par defaut via <details> quand l'info n'est pas critique au premier
   regard (onglet Connexions) ; en simple <p> -- toujours visible, sans etre
   repliee -- sur l'ecran de connexion, ou l'utilisateur doit la lire avant de
   cliquer sur « Se connecter ». */
.provider-note {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.5;
}

.provider-note summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-muted);
}

.provider-note summary::-webkit-details-marker {
  display: none;
}

.provider-note summary::before {
  /* L'espace qui suit un échappement CSS (`\2139 `) TERMINE l'échappement, il n'est pas
     affiché : l'icône touchait le texte (« ℹConnexion... », audit du 2026-10-01). */
  content: "\2139";
  margin-right: var(--space-1);
}

.provider-note p {
  margin: var(--space-1) 0 0;
}

/* Paragraphes imbriques dans un encadre ou dans la zone de connexion xAI : pas de
   double encadre. */
.warning-box .message,
.alert .message,
.connect-zone .message,
.connect-zone .error {
  margin: 0 0 var(--space-3);
  padding: 0;
  border: 0;
  background: none;
}

.connect-zone {
  display: grid;
  gap: var(--space-2);
  justify-items: start;
}

.connect-zone .error {
  color: var(--danger);
  font-weight: 560;
}

.user-code {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  letter-spacing: 0.08em;
}

.status-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.status-line p {
  margin: 0;
}

/* Barre de progression ----------------------------------------------------- */

.progress {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.progress-bar {
  flex: 1;
  width: 100%;
  height: 0.625rem;
  background: var(--surface-3);
  border-radius: var(--radius-pill);
  overflow: hidden;
}

.progress-bar-fill {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}

.progress-label {
  min-width: 3.5rem;
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  font-size: var(--text-md);
}

/* Etats vides -------------------------------------------------------------- */

.empty-state {
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  padding: var(--space-10) var(--space-6);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  text-align: center;
  color: var(--text-muted);
}

.empty-state .icon {
  width: 28px;
  height: 28px;
  color: var(--text-subtle);
}

.empty-state p {
  margin: 0;
}

.empty-state strong {
  color: var(--text);
}

/* Audio -------------------------------------------------------------------- */

audio {
  display: block;
  width: 100%;
  max-width: 420px;
  height: 40px;
}

.audio-cell {
  display: grid;
  gap: var(--space-2);
  min-width: 260px;
}

.audio-cell audio {
  max-width: 300px;
}

/* Grilles d'images --------------------------------------------------------- */

.ref-images {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-4);
}

.ref-images figure {
  margin: 0;
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}

.ref-images img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--surface-3);
}

.ref-images .inline-form,
.ref-images .inline-form button {
  width: 100%;
}

.upload-form {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}

/* Blocs de prompt + bouton copier ------------------------------------------ */

.prompt-block {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--code-bg);
  overflow: hidden;
}

.prompt-block + .prompt-block {
  margin-top: var(--space-4);
}

.prompt-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.prompt-head h3 {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 620;
}

.prompt-text {
  margin: 0;
  padding: var(--space-4);
  max-height: 22rem;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--code-text);
  font-size: 0.8125rem;
  line-height: 1.65;
}

.copy-btn[data-copied="true"] {
  border-color: var(--success-border);
  color: var(--success);
}

/* Journal de job (fond sombre dans les deux themes) ------------------------ */

.job-log {
  margin: 0;
  padding: var(--space-4);
  min-height: 8rem;
  max-height: 20rem;
  overflow-y: auto;
  border-radius: var(--radius-lg);
  background: var(--log-bg);
  color: var(--log-text);
  font-size: 0.8125rem;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.job-log:empty::before {
  content: "En attente d’évènements…";
  color: #8a94a3;
}

.result-block {
  margin: 0;
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--code-bg);
  color: var(--code-text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 0.8125rem;
}

/* Sections repliables (details / summary) ---------------------------------- */

.disclosure {
  margin-top: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.disclosure + .disclosure {
  margin-top: var(--space-3);
}

.disclosure > summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-lg);
  font-weight: 600;
  font-size: 0.875rem;
  cursor: pointer;
  list-style: none;
}

/* Vérification chrome-devtools du 2026-10-01 (brouillon, 390 px) : un `<summary>` qui
   porte aussi `.card-subtitle` (titre de section) héritait de sa marge haute et de
   son filet supérieur -- « Tout générer de bout en bout… » apparaissait dans une
   double boîte avec un vide au-dessus. Dans un repli, le résumé reste un bouton. */
.disclosure > summary.card-subtitle {
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border-top: 0;
}

.disclosure > summary::-webkit-details-marker {
  display: none;
}

.disclosure > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  margin-left: auto;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease);
}

.disclosure[open] > summary::after {
  transform: rotate(-135deg);
}

.disclosure > summary:hover {
  background: var(--surface-2);
}

.disclosure[open] > summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

.disclosure-body {
  padding: var(--space-5) var(--space-4) var(--space-4);
}

/* Groupes de cases a cocher ------------------------------------------------ */

.fieldset {
  margin: 0;
  padding: 0;
  border: 0;
}

.fieldset > legend {
  margin-bottom: var(--space-2);
  padding: 0;
  font-size: var(--text-sm);
  font-weight: 560;
}

.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: var(--space-2);
}

/* Gimmicks (DEMANDE 4) : liste de lecture, badge de type + description + badge de
   frequence. */
.gimmicks-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.gimmicks-summary li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Catalogue de voix (DEMANDE 2) : grille de cartes responsive. */
.voice-model-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-4);
}

.voice-model-card dl.job-summary {
  margin-top: var(--space-2);
}

.check-card {
  padding: 0 var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.check-card:hover {
  border-color: var(--border-strong);
}

.check-card:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Retour utilisateur du 2026-09-20 : le nom du personnage et celui de son univers sont
   desormais des LIENS vers leur fiche. La case a cocher ne peut donc plus englober le
   texte (un clic sur un lien basculerait la selection) : elle vit dans son propre
   `<label>`, aligne a gauche du texte. */
.check-card-linked {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3);
}

.check-card-linked .check-card-text {
  min-width: 0;
  padding: var(--space-2) 0;
}

/* Choix du point de reprise (« Nouvelle version » et refus de validation, retour
   utilisateur du 2026-09-21 : « Le systeme de nouvelle version n'est pas clair »). Une
   option PAR LIGNE, avec la phrase qui dit ce qu'elle conserve et ce qu'elle refait :
   un `<option>` de liste deroulante ne peut pas porter cette explication sans
   JavaScript (CSP stricte, `script-src 'self'`). Reutilise `.check-card` pour la
   bordure et l'etat selectionne. */
.choice-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.choice-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
}

.choice-card input[type="radio"] {
  margin-top: 0.2rem;
}

.choice-text {
  min-width: 0;
}

.choice-title {
  display: block;
  font-weight: 560;
}

.choice-card .hint {
  display: block;
  margin: 0;
}

/* Projets : scenario et storyboard ----------------------------------------- */

.prose {
  margin: 0;
  max-width: 80ch;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.text-danger {
  color: var(--danger);
}

.icon-xs {
  width: 12px;
  height: 12px;
}

.hook-callout {
  margin-bottom: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--accent-soft);
}

.hook-label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0 0 var(--space-1);
  color: var(--accent-soft-text);
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.hook-text {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 620;
  line-height: 1.4;
}

.scene-list {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.scene {
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}

.scene-head h3 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
}

.scene-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-5);
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
}

.scene-meta div {
  display: flex;
  gap: var(--space-1);
}

.scene-meta dt {
  color: var(--text-muted);
}

.scene-meta dt::after {
  content: " :";
}

.scene-meta dd {
  margin: 0;
}

.dialogue {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dialogue li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.dialogue .speaker {
  font-weight: 650;
}

.dialogue .line-text {
  flex-basis: 100%;
  overflow-wrap: anywhere;
}

/* Mise en scène et jeu d'une scène (demande utilisateur du 2026-10-03) : repli
   discret sous les répliques -- `_script_scene_acting.html`. */
.disclosure.scene-acting {
  margin-top: var(--space-3);
  background: var(--surface);
}

.scene-acting-body {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-4);
  font-size: var(--text-sm);
}

.scene-staging {
  margin: 0;
  overflow-wrap: anywhere;
}

.scene-acting-label {
  display: block;
  color: var(--text-muted);
  font-weight: 600;
}

.acting-list {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.acting-line {
  margin: 0 0 var(--space-1);
  overflow-wrap: anywhere;
}

.acting-line .speaker {
  font-weight: 650;
}

.acting-quote {
  color: var(--text-muted);
}

.acting-meta {
  display: grid;
  gap: var(--space-1);
  margin: 0;
}

.acting-meta div {
  display: grid;
  grid-template-columns: minmax(6.5rem, max-content) 1fr;
  gap: var(--space-2);
}

.acting-meta dt {
  color: var(--text-muted);
}

.acting-meta dd {
  margin: 0;
  overflow-wrap: anywhere;
}

@media (max-width: 480px) {
  .acting-meta div {
    grid-template-columns: 1fr;
    gap: 0;
  }
}

/* Consignes visées par une remarque (prompts vidéo / scénario, 2026-10-03). */
.remark-target .choice-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
}

.dialogue-compact {
  gap: var(--space-1);
  min-width: 14rem;
}

.dialogue-compact li {
  padding: var(--space-1) var(--space-2);
  background: transparent;
  font-size: var(--text-sm);
}

/* Plan long (demande du 2026-10-08) : moments regroupes dans un plan, a leurs instants. */
.moment-list {
  margin: var(--space-1) 0 0;
  padding-left: var(--space-5);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.moment-list li {
  margin: var(--space-1) 0;
}

.tag-speaking {
  gap: var(--space-1);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.duration-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.stat {
  display: grid;
  gap: var(--space-1);
  min-width: 8rem;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-2);
}

.stat-label {
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.stat-value {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

.stat-status {
  align-content: center;
  background: transparent;
  border-style: dashed;
}

.alert-list {
  margin: 0;
  padding-left: var(--space-5);
  /* Un libellé sans espace (légende, identifiant) ne doit jamais élargir la carte qui
     le contient ni faire apparaître une barre de défilement horizontale -- retour
     utilisateur du 2026-09-20 sur la mise en page du tableau de bord. */
  overflow-wrap: anywhere;
}

.shots-table td {
  vertical-align: top;
}

.shots-table .shot-row td {
  border-bottom: 0;
}

.shot-prompt-row td {
  padding-top: 0;
}

.shot-prompt-row:hover {
  background: transparent;
}

.shot-prompt-row .prompt-text {
  max-height: 12rem;
}

.shot-edits {
  padding-top: var(--space-2);
  padding-bottom: var(--space-6);
}

.shot-edits .card-subtitle {
  margin-top: var(--space-4);
}

/* Zone de danger ----------------------------------------------------------- */

.danger-zone {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.danger-zone p {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* ------------------------------------------------------------------------ */
/* 6. Utilitaires                                                             */
/* ------------------------------------------------------------------------ */

.small {
  font-size: var(--text-sm);
}

/* Étape 13 (publication TikTok) : guide pas à pas (`/admin/connections/tiktok`) et
   calendrier des publications -- liste numérotée/à puces avec l'espacement du système
   de conception plutôt que les valeurs par défaut du navigateur. */
.guide-steps {
  margin: 0;
  padding-left: var(--space-5, 1.25rem);
  display: grid;
  gap: var(--space-2, 0.5rem);
}

.guide-steps li {
  line-height: 1.5;
  /* Audit visuel mobile (2026-10-01) : certaines etapes contiennent une URL longue et
     ininterrompue (ex. guide TikTok, `admin_connections_tiktok.py::SETUP_GUIDE_STEPS`,
     "http://127.0.0.1:8000/admin/connections/tiktok/callback") -- sans point de
     cesure, elle debordait du conteneur en dessous de ~400 px (meme motif que les
     autres classes `overflow-wrap: anywhere` deja presentes dans cette feuille). */
  overflow-wrap: anywhere;
}

.text-muted {
  color: var(--text-muted);
}

.mt-4 {
  margin-top: var(--space-4);
}

/* Audit d'affichage du 2026-10-01 : utilisée (formulaire « Compléter avec l'IA » des
   fiches personnage et univers) mais jamais définie -- le bouton touchait l'étiquette
   « Nom » du formulaire suivant. */
.mb-4 {
  margin-bottom: var(--space-4);
}

.mb-0 {
  margin-bottom: 0;
}

.nowrap {
  white-space: nowrap;
}

/* ------------------------------------------------------------------------ */
/* 7. Responsive                                                              */
/* ------------------------------------------------------------------------ */

@media (max-width: 1100px) {
  .page {
    padding: var(--space-6) var(--space-6) var(--space-10);
  }

  .grid-2 {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Sous-menu de `/admin/settings` : en dessous de 1100 px, deux colonnes
     s'écraseraient (le sommaire mangerait la place des formulaires). Il passe donc en
     BANDEAU horizontal au-dessus du contenu, toujours collant -- à cette largeur la
     barre latérale principale est encore fixe, il n'y a pas de barre mobile au sommet
     avec laquelle se disputer la place. */
  .settings-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
  }

  .settings-nav {
    top: 0;
    z-index: 10;
    max-height: none;
    overflow-y: visible;
    padding: var(--space-3) var(--space-4);
  }

  .settings-nav-title {
    /* Le titre reste dans l'arbre d'accessibilité (`aria-labelledby` du `<nav>`), mais
       n'occupe plus de place dans le bandeau. */
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .settings-nav-list {
    flex-direction: row;
    gap: var(--space-1);
    overflow-x: auto;
  }

  .settings-nav-link {
    white-space: nowrap;
  }

  .settings-sections > section {
    scroll-margin-top: 4.5rem;
  }
}

@media (max-width: 900px) {
  .shell-main {
    margin-left: 0;
  }

  /* Sous-menu de `/admin/settings` : en dessous de 900 px, la barre mobile
     (`.mobile-bar`, `position: sticky; top: 0`) occupe déjà le haut de l'écran. Deux
     bandeaux collants superposés mangeraient la moitié d'un écran de téléphone : le
     sommaire redevient donc un simple bandeau NON collant, en tête de page. Les ancres
     continuent de fonctionner à l'identique. */
  .settings-nav {
    position: static;
  }

  .settings-sections > section {
    scroll-margin-top: 4rem;
  }

  .mobile-bar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    min-height: 56px;
    padding: var(--space-2) var(--space-3);
    background: var(--side-bg);
    color: var(--side-text);
    border-bottom: 1px solid var(--side-border);
  }

  .sidebar {
    width: min(86vw, 300px);
    transform: translateX(-100%);
    visibility: hidden;
    box-shadow: var(--shadow-lg);
    transition: transform 220ms var(--ease), visibility 0s linear 220ms;
  }

  .shell[data-sidebar-open="true"] .sidebar {
    transform: none;
    visibility: visible;
    transition: transform 220ms var(--ease), visibility 0s linear 0s;
  }

  .sidebar .sidebar-close {
    display: inline-flex;
  }

  .sidebar-backdrop {
    position: fixed;
    inset: 0;
    z-index: 35;
    display: block;
    background: rgb(5 8 12 / 0.55);
    opacity: 0;
    pointer-events: none;
    transition: opacity 220ms var(--ease);
  }

  .shell[data-sidebar-open="true"] .sidebar-backdrop {
    opacity: 1;
    pointer-events: auto;
  }

  .page {
    padding: var(--space-5) var(--space-4) var(--space-10);
  }

  .form-grid,
  .form-grid-3 {
    grid-template-columns: minmax(0, 1fr);
  }

  .card {
    padding: var(--space-5);
  }

  .card-flush {
    padding: 0;
  }

  .card-flush > .card-header {
    padding: var(--space-4) var(--space-5) 0;
  }

  .card-flush > .card-body {
    padding: 0 var(--space-5) var(--space-4);
  }

  .card-foot {
    padding: var(--space-3) var(--space-5) var(--space-4);
  }

  h1 {
    font-size: var(--text-xl);
  }
}

@media (max-width: 520px) {
  .auth-card {
    padding: var(--space-6) var(--space-5);
  }

  .job-summary {
    grid-template-columns: minmax(0, 1fr);
  }

  .job-summary dt {
    padding-bottom: 0;
    border-bottom: 0;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Renommage "Projet" -> "Video" + series (Step 9b) : quelques utilitaires reutilises
   par plusieurs gabarits (projects_list.html, project_detail.html, series_*.html). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.filter-bar {
  align-items: flex-end;
  margin-bottom: var(--space-4, 1rem);
}

.disclosure-block {
  display: block;
}

.btn-lg {
  padding: 0.75em 1.5em;
  font-size: var(--text-lg, 1.125rem);
}

.idea-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.idea-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border, #e2e2e2);
  border-radius: var(--radius-sm, 6px);
}

/* Lecteur de la vidéo finale (remplace un style inline bloqué par la CSP). `aspect-ratio`
   (même format 9:16 que `.step-generation-clip` ci-dessous) réserve la hauteur avant que
   les métadonnées de la vidéo soient chargées (`preload="none"`) -- sans elle, la carte
   s'effondre puis regrandit au premier rafraîchissement automatique de la fiche, ce qui
   fait sauter le défilement de la page (demande utilisateur du 2026-09-30). */
.final-video-player {
  max-width: 360px;
  width: 100%;
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-md, 8px);
  background: #000;
}

/* Aperçu vidéo d'un plan dans le détail du storyboard (`width="180"` posé en HTML) : même
   réservation de hauteur, même motif que `.final-video-player` ci-dessus. */
.clip-preview video {
  aspect-ratio: 9 / 16;
  height: auto;
  border-radius: var(--radius-md, 8px);
  background: #000;
}

/* ----------------------------------------------------------------------
   HTMX : suivi "tâche en cours" sans rechargement de page (retour utilisateur :
   "quand je fais tester un fournisseur, cela recharge la page ; je ne veux pas de
   rechargement de page"). `.htmx-indicator`/`.htmx-request` sont les classes
   STANDARD gérées par htmx.min.js lui-même (aucun JS supplémentaire nécessaire ici,
   CSP `script-src 'self'` respectée -- pas de script inline). Un élément
   `.htmx-indicator` reste masqué par défaut, affiché uniquement pendant qu'une
   requête déclenchée par un ancêtre `hx-indicator` est en vol.
   ---------------------------------------------------------------------- */
.htmx-indicator {
  display: none;
}

.htmx-request.htmx-indicator,
.htmx-request .htmx-indicator {
  display: inline-block;
}

/* Bouton qui déclenche une requête HTMX : désactivé visuellement (curseur, opacité)
   pendant que la requête est en vol -- htmx pose `.htmx-request` sur l'élément qui a
   déclenché la requête (ou sur `hx-indicator` s'il est différent). */
button.htmx-request,
.btn-primary.htmx-request,
.btn-sm.htmx-request {
  opacity: 0.7;
  cursor: progress;
}

/* Petit spinner CSS pur (aucune image/police externe -- CSP `img-src`/`font-src`
   déjà strictes) : cercle qui tourne, couleur héritée du texte du bouton. */
.spinner {
  width: 1em;
  height: 1em;
  border: 0.15em solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spinner-rotate 0.6s linear infinite;
  vertical-align: -0.15em;
}

@keyframes spinner-rotate {
  to {
    transform: rotate(360deg);
  }
}

/* Roue qui tourne du composant de soumission réutilisable (macro Jinja `submit_button`,
   `_form_async.html`) -- retour utilisateur du 2026-09-20 : « Il faut que leur
   validation se fasse en asynchrone avec une information (roue qui tourne) comme quoi
   cela est en cours. »

   Deux différences VOLONTAIRES avec `.htmx-indicator` ci-dessus, qui répondent à
   l'exigence « l'indicateur ne doit pas provoquer de clignotement sur une réponse
   immédiate » :

   1. pas de bascule `display: none` -> `display: inline-block` : le spinner occupe sa
      place dans le bouton en permanence, donc le libellé ne se décale jamais quand il
      apparaît (une bascule de `display` produit un saut de mise en page à chaque envoi,
      très visible sur un formulaire instantané) ;
   2. l'opacité ne monte qu'après un DÉLAI (250 ms) : une route qui répond tout de suite
      (enregistrement d'un paramètre, case à cocher) termine avant la fin du délai, htmx
      retire `.htmx-request` et la transition d'apparition n'est jamais jouée -- aucun
      clignotement. Un formulaire lent (assistants IA, lancement de job) dépasse
      largement 250 ms : la roue apparaît en fondu et reste visible jusqu'à la réponse.

   `visibility` (et non `opacity` seule) garde le spinner hors de l'arbre
   d'accessibilité tant qu'aucune requête n'est en vol. */
.form-busy {
  /* `display` explicite : `.spinner` fixe une largeur/hauteur, qu'un `<span>` INLINE
     ignorerait (l'ancien `.htmx-indicator` fournissait `inline-block` en même temps que
     sa bascule d'affichage -- ici il n'y a plus de bascule, il faut le poser). */
  display: inline-block;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 120ms linear,
    visibility 0s linear 120ms;
}

.htmx-request .form-busy,
.htmx-request.form-busy {
  opacity: 1;
  visibility: visible;
  transition:
    opacity 150ms linear 250ms,
    visibility 0s linear 250ms;
}

/* Bouton désactivé par htmx (`hx-disabled-elt`) LE TEMPS de l'envoi : empêche la DOUBLE
   soumission. Volontairement restreint aux descendants de `.htmx-request` (htmx pose
   cette classe sur le `<form>` qui a déclenché la requête, et la retire à la réponse) :
   un bouton désactivé pour une AUTRE raison -- quota vidéo dépassé, rôle « lecteur » --
   garde son rendu « action indisponible » (`button:disabled` plus haut, `cursor:
   not-allowed`), qui ne doit surtout pas devenir « patientez ». */
.htmx-request button[type="submit"]:disabled {
  opacity: 0.7;
  cursor: progress;
}

/* Zone de suivi générique d'un job en tâche de fond (macro Jinja `job_tracker`,
   `_job_tracker.html`) -- réutilisable pour n'importe quel apercu/génération suivi
   par polling HTMX (`hx-trigger="every Ns"`). */
.job-tracker {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius-sm, 6px);
  background: var(--surface-2, #f4f4f4);
}

.job-tracker[data-status="failed"] {
  background: var(--danger-bg, #fdecea);
}

.job-tracker[data-status="succeeded"] {
  background: var(--success-bg, #eaf7ee);
}

/* Tableau de bord d'accueil (etape 15) -------------------------------------- */

.thumb-cell {
  width: 2.75rem;
}

.dash-thumb {
  display: block;
  /* Audit d'affichage du 2026-10-01 : le `max-width: 100%` global des images, dans une
     cellule de tableau à largeur automatique, réduisait la vignette à un trait de 2 px
     (la colonne s'effondrait sur la contribution nulle de l'image). */
  max-width: none;
  width: 2.5rem;
  height: 4.4rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  object-fit: cover;
  background: var(--surface-2);
}

/* Petit graphique d'évolution des vues (SVG pur, `sparkline_points` --
   `services/dashboard.py`) : aucune dépendance JS/CDN, `stroke="currentColor"` suit la
   couleur de texte du thème clair/sombre courant. */
.sparkline {
  display: block;
  width: 100%;
  max-width: 320px;
  height: 3.5rem;
  margin: var(--space-3) 0;
  color: var(--accent);
}

.job-tracker-label {
  flex: 1 1 auto;
}

.job-tracker-progress {
  width: 6rem;
  height: 0.5rem;
}

.job-tracker-error {
  color: var(--danger, #a4262c);
  background: var(--danger-soft, #fdecea);
}

/* ----------------------------------------------------------------------
   Notifications globales ("toasts") en haut à droite (demande utilisateur, point 2 :
   "une notification/popup qui m'indique que le job est en cours et quand il est
   terminé"). Rempli/géré EXCLUSIVEMENT par `static/js/toasts.js` (CSP stricte, aucun
   style inline) -- toute la mise en forme vit ici.
   ---------------------------------------------------------------------- */
.toast-region {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(22rem, calc(100vw - 2rem));
  pointer-events: none;
}

/* Audit visuel mobile (2026-10-01) : en dessous de 900 px, `.mobile-bar` (collante,
   `min-height: 56px`, voir plus haut) occupe deja le haut de l'ecran -- sans ce
   correctif, `.toast-region` (`top: 1rem` ci-dessus) la recouvrait ENTIEREMENT
   (bouton menu, logo, bascule theme masques) des qu'un job etait en cours, un etat
   tres frequent (generation video, publication programmee...). Doit rester APRES la
   regle de base ci-dessus dans la feuille (meme specificite : `.mobile-bar` n'existe
   qu'en dessous de 900 px, voir sa propre regle plus haut, donc aucun besoin de
   dupliquer la media query pour le reste du responsive). */
@media (max-width: 900px) {
  .toast-region {
    top: calc(56px + var(--space-2));
    /* Audit visuel mobile (2026-10-01), suite : PLUSIEURS notifications empilees (ex.
       une generation vidéo + une publication programmée en cours en même temps, cas
       réel) pouvaient quand même s'étirer assez bas pour recouvrir le premier bouton
       d'action de la page sur un écran court. Bornée + défilable : les dernières
       notifications restent atteignables sans jamais masquer durablement le contenu
       en dessous. */
    max-height: 40vh;
    overflow-y: auto;
  }
}

/* Retour utilisateur/verification du 2026-10-01 (capture 360 px) : meme sous la barre
   mobile, deux notifications « job en cours » recouvraient le TITRE de la page et son
   action principale. Sur telephone, la pile passe EN BAS (usage habituel des
   notifications sur mobile), a gauche du bouton « Revenir en haut » (bas-droite,
   2.75rem) qu'elle ne recouvre jamais, bornee a 30 % de la hauteur et defilable. */
@media (max-width: 640px) {
  .toast-region {
    top: auto;
    bottom: max(1rem, env(safe-area-inset-bottom));
    left: max(0.75rem, env(safe-area-inset-left));
    right: calc(2.75rem + 1.75rem);
    max-width: none;
    max-height: 30vh;
  }
}

.toast {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  /* Marge droite elargie (2.25rem -> 2.75rem) pour laisser la place au bouton de
     fermeture agrandi (`.toast-close`, cible tactile 40 px, audit visuel mobile du
     2026-10-01) sans qu'il ne deborde du coin arrondi du toast. */
  padding: 0.75rem 2.75rem 0.75rem 1rem;
  border-radius: var(--radius-md, 8px);
  background: var(--surface, #fff);
  border: 1px solid var(--border, #e2e2e2);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  pointer-events: auto;
  animation: toast-in 0.18s ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(-0.5rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toast-progress {
  border-left: 3px solid var(--accent, #3f6fee);
}

.toast-success {
  border-left: 3px solid var(--success, #1e7b34);
}

.toast-error {
  border-left: 3px solid var(--danger, #a4262c);
  background: var(--danger-soft, #fdecea);
}

.toast-body {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}

.toast-title {
  margin: 0;
  font-weight: 600;
  font-size: 0.9rem;
}

.toast-message {
  margin: 0;
  font-size: 0.85rem;
  color: var(--text-muted, #555);
}

.toast-link {
  align-self: flex-start;
  font-size: 0.8rem;
}

.toast-close {
  position: absolute;
  top: 0.1rem;
  right: 0.1rem;
  border: none;
  background: transparent;
  cursor: pointer;
  line-height: 1;
  font-size: 1rem;
  border-radius: var(--radius-sm, 6px);
  color: inherit;
  /* Audit visuel mobile (2026-10-01) : `padding: 0.15rem 0.4rem` donnait une cible
     tactile d'environ 24 px, sous le minimum de 40 px (`--control-h`) vise ailleurs --
     agrandie via `min-width`/`min-height` (centrage flex) plutot que le padding, pour
     ne pas deplacer le glyphe "×" ni agrandir visuellement le bouton plus que
     necessaire (le fond au survol/focus, lui, reste a cette taille). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  min-height: 2.5rem;
}

.toast-close:hover,
.toast-close:focus-visible {
  background: var(--surface-2, #f4f4f4);
}

/* Cellule de titre d'un tableau (retour utilisateur du 2026-09-20 : dans une carte
   trop étroite, le titre d'une vidéo se coupait « un mot par ligne » et poussait les
   colonnes suivantes hors de l'écran). Une seule ligne, points de suspension, et
   l'attribut `title` du gabarit porte le texte complet. `.table-wrap` (défilement
   horizontal) reste le filet de sécurité en écran très étroit. */
.title-cell {
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Colonne « Source » de /jobs et ligne « Source » du détail d'un job
   (`web/job_sources.py`) : la famille d'entité en petit, puis le libellé cliquable. */
.source-cell {
  max-width: 24rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.source-kind {
  margin-right: var(--space-2);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Purge groupée des jobs terminés (en-tête de la carte « Historique »). */
.jobs-purge-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.jobs-purge-label {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* `input.` et non `.` seul : `input[type="number"]` (largeur 100 %) a la meme
   specificite qu'une classe -- sans le selecteur d'element, cette regle perdrait. */
input.jobs-purge-days {
  flex: 0 0 auto;
  width: 5rem;
}

/* Bandeau d'alertes du tableau de bord : titre + « Tout marquer comme vu », puis une
   ligne par alerte (texte, date courte, bouton « Vu »). */
.alert-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.alert-head .alert-title {
  margin-bottom: 0;
}

.alert-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.alert-row-text {
  flex: 1 1 18rem;
  min-width: 0;
}

.alert-row-source {
  color: var(--text-muted);
}

.alert-row-date {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.alert-row-ack {
  flex: 0 0 auto;
  margin: 0;
}

@media (max-width: 720px) {
  .title-cell,
  .source-cell {
    max-width: 12rem;
  }
}

/* =========================================================================
   AJOUT (2026-09-20) -- audit des formulaires : blocs de création repliés et
   agrandissement d'une vignette. Bloc AJOUTÉ EN FIN DE FICHIER, aucune règle
   existante modifiée ni réordonnée (un autre agent édite ce fichier en parallèle).

   1. `.create-panel` : les cartes « Créer … » des pages de liste sont des
      `<details>` repliés par défaut (demande utilisateur : « cela surcharge
      l'affichage »). Le `<summary>` est le déclencheur -- natif, atteignable au
      clavier, son état est annoncé par le navigateur, aucun JavaScript.
   2. `.zoomable-image` / `.image-lightbox` : une vignette est recadrée
      (`object-fit: cover`) ; l'utilisateur doit pouvoir voir l'image ENTIÈRE
      (`contain`) en grand. Le `<dialog>` est construit par
      `static/js/image_lightbox.js` ; sans JavaScript, la vignette reste un lien
      vers l'image réelle.
   ========================================================================= */

.create-panel {
  padding: 0;
}

/* `display: flex` retire le triangle natif dans les navigateurs Chromium : c'est
   assumé, l'icône « + » du libellé joue ce rôle et l'état ouvert/replié reste annoncé
   par le navigateur (le `summary` porte `aria-expanded` implicitement). */
.create-panel > .create-panel-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  cursor: pointer;
  border-radius: var(--radius-lg);
  font-weight: 600;
}

.create-panel > .create-panel-summary:hover {
  background: var(--surface-2);
}

.create-panel > .create-panel-summary:focus-visible {
  outline: 2px solid var(--focus, currentColor);
  outline-offset: 2px;
}

.create-panel-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--text-lg);
}

/* Le contenu du bloc respire comme le corps d'une carte ordinaire. */
.create-panel[open] > *:not(.create-panel-summary) {
  margin-left: var(--space-5);
  margin-right: var(--space-5);
}

.create-panel[open] > *:last-child {
  margin-bottom: var(--space-5);
}

/* --- Vignette agrandissable --------------------------------------------- */

.zoomable-image {
  display: block;
  border-radius: var(--radius-md);
}

.zoomable-image:focus-visible {
  outline: 2px solid var(--focus, currentColor);
  outline-offset: 2px;
}

.image-lightbox {
  max-width: min(95vw, 1200px);
  max-height: 95vh;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  /* `--surface-1` n'existe dans AUCUN theme (bogue constate le 2026-10-01, meme
     correction que `.end-to-end-dialog` plus bas) : la variable reelle du theme est
     `--surface`. */
  background: var(--surface);
  color: var(--text);
  padding: var(--space-4);
}

.image-lightbox::backdrop {
  background: rgb(0 0 0 / 0.72);
}

.image-lightbox-figure {
  margin: 0 0 var(--space-3);
  display: grid;
  gap: var(--space-2);
  justify-items: center;
}

.image-lightbox-image {
  display: block;
  max-width: 100%;
  max-height: 75vh;
  width: auto;
  height: auto;
  object-fit: contain;
}

.image-lightbox-caption {
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
}

.image-lightbox-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: flex-end;
}

/* Carte « Mise en route » de l'accueil (audit IHM du 2026-09-21) : le rappel de
   chaque étape passe SOUS son libellé plutôt qu'à sa suite, sinon la ligne devient
   illisible dès que le libellé est long. */
.setup-step-hint {
  display: block;
}

/* Pastille d'un job en attente (`blocked`/`retry_wait`) -- remplace le spinner
   anime, qui annoncait un travail en cours alors que rien n'avancait (retour
   utilisateur du 2026-09-22). */
.job-tracker-pause-dot {
  flex: none;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: var(--warning);
}

/* Onglets de versions d'une vidéo (demande utilisateur du 2026-09-22). Rendus par
   `_project_version_tabs.html` : une simple liste de liens, sans JavaScript. */
.version-tabs {
  margin: 0 0 var(--space-6);
  border-bottom: 1px solid var(--border);
}

.version-tab-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.version-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 560;
  text-decoration: none;
}

.version-tab:hover {
  background: var(--surface-2);
  color: var(--text);
}

.version-tab.is-active {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
  box-shadow: inset 0 2px 0 var(--accent);
}

.version-tab-badge {
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
}

.version-tab-badge-ok {
  background: var(--success-soft, var(--surface-2));
  color: var(--success, var(--text-muted));
}

/* Génération plan par plan (étape 5) : le plan à contrôler à gauche, les actions à droite. */
.step-generation-body {
  display: grid;
  grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.step-generation-clip {
  width: 100%;
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-md);
  background: #000;
}

.step-generation-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}

/* Génération des plans (étape 3 du plan `generation-separee-brouillon-upscale`) : une
   ligne par plan (nom, durée, état, « Générer ce plan »), qui passe à la ligne sur
   petit écran plutôt que de déborder. */
.shot-generation-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.shot-generation-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border);
}

.shot-generation-item:first-child {
  border-top: 0;
}

.shot-generation-name {
  flex: 1 1 8rem;
  min-width: 0;
}

.shot-generation-item .hint {
  margin: 0;
}

.shot-generation-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

@media (max-width: 720px) {
  .step-generation-body {
    grid-template-columns: minmax(0, 1fr);
  }

  .step-generation-clip {
    max-width: 240px;
  }
}

/* R6 (`docs/reference/e2e-2026-09-30.md`) remonte le lecteur de la vidéo finale sur
   petit écran : depuis la refonte en étapes repliables (étape 3b, section « Fiche
   vidéo : étapes et barre d'actions » en fin de feuille), les étapes terminées sont
   repliées et l'étape « Vidéo finale » est ouverte d'office une fois la vidéo montée --
   l'ordre numéroté des étapes n'est plus bousculé. */

/* Mention "Videos generees avec WanGP" (ADR-0008 point 12) -- pied de page discret,
   present sur toutes les pages (shell principal ET pages non connectees, base.html). */
.app-footer {
  margin-top: var(--space-4);
  padding: var(--space-2) 0;
  color: var(--text-muted);
  font-size: var(--text-xs);
  text-align: center;
}

/* IA du scénario à côté du bouton d'une reprise de version (2026-09-30). */
.scenario-ai-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3, 0.75rem);
}
.scenario-ai-actions .scenario-ai-field {
  margin: 0;
  min-width: 14rem;
}

/* Popup « Tout générer de bout en bout… » (demande utilisateur du 2026-09-30, refonte
   du 2026-10-01 -- retour utilisateur : le `<dialog>` était TRANSPARENT (laissait voir
   la page à travers, `--surface-1` n'existe dans AUCUN thème -- bogue identique à
   `.image-lightbox` ci-dessus), plus haut que l'écran sans défilement propre, boutons
   d'action inaccessibles sans faire défiler toute la page.

   Un `<details>` replié (repli sans JavaScript, même style que `.disclosure`),
   amélioré en `<dialog>` natif par `static/js/end_to_end_dialog.js`. Le `<dialog>`
   est désormais une colonne flex en TROIS bandes : en-tête (`.end-to-end-head`,
   titre + fermer) et pied (`.end-to-end-actions`, Lancer/Annuler) FIXES
   (`flex: 0 0 auto`), seul le corps (`.end-to-end-scroll`) défile
   (`flex: 1 1 auto; overflow-y: auto`) -- le titre et les boutons d'action restent
   donc TOUJOURS visibles, même quand le formulaire est plus grand que l'écran.
   `max-height: calc(100dvh - 2rem)` (`dvh` = hauteur visuelle réelle sur mobile,
   barre d'adresse comprise) plutôt qu'un pourcentage de `vh` fixe. */
.end-to-end-disclosure {
  margin-top: var(--space-3);
}

.end-to-end-title {
  margin: 0;
  font-size: var(--text-lg);
}

.end-to-end-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-3, 0.75rem);
  margin-top: var(--space-3);
}

.end-to-end-grid .scenario-ai-field,
.end-to-end-grid .field {
  margin: 0;
}

.end-to-end-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Le contenu (`[data-end-to-end-body]`) est déplacé TEL QUEL dans ce `<dialog>` par
   `end_to_end_dialog.js`. Fond OPAQUE (`--surface`, JAMAIS `--surface-1` qui n'existe
   pas), bordure et ombre du thème -- jamais le fond neutre/transparent du navigateur
   (retour utilisateur du 2026-10-01). */
.end-to-end-dialog {
  display: flex;
  flex-direction: column;
  width: min(92vw, 640px);
  max-width: min(92vw, 640px);
  max-height: calc(100dvh - 2rem);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  padding: 0;
}

.end-to-end-dialog::backdrop {
  background: rgb(0 0 0 / 0.72);
}

.end-to-end-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border);
}

.end-to-end-scroll {
  padding: var(--space-4) var(--space-5);
}

/* `[data-end-to-end-body]` porte déjà `.disclosure-body` (marges pour le repli sans
   JavaScript) -- superflu une fois déplacé dans `.end-to-end-dialog`, qui gère déjà ses
   propres marges par bande (en-tête/corps/pied) ; `display:flex` en colonne, SEULEMENT
   une fois dans le `<dialog>`, est ce qui fixe l'en-tête et le pied tandis que le corps
   défile seul. */
.end-to-end-dialog .end-to-end-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
  padding: 0;
}

.end-to-end-dialog .end-to-end-head {
  flex: 0 0 auto;
}

.end-to-end-dialog .end-to-end-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.end-to-end-dialog .end-to-end-actions {
  flex: 0 0 auto;
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--border);
  margin: 0;
}

/* Radios de reprise plus compactes à l'intérieur du popup (retour utilisateur du
   2026-10-01 : « les radios doivent être compactes ») -- même motif que `.choice-card`
   ailleurs sur le site, juste moins d'air autour de chaque ligne. */
.end-to-end-dialog .choice-list {
  gap: var(--space-1);
}

.end-to-end-dialog .choice-card {
  padding: var(--space-1) var(--space-2);
}

@media (max-width: 480px) {
  .end-to-end-grid {
    grid-template-columns: 1fr;
  }
}

/* Plein écran sous 640px (retour utilisateur du 2026-10-01) : sur un petit écran, le
   cadre/la marge autour du popup ne fait que réduire la place déjà comptée d'un
   formulaire chargé -- autant utiliser tout l'écran, comme `<dialog>` modal classique
   sur mobile. */
@media (max-width: 640px) {
  .end-to-end-dialog {
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    border-radius: 0;
  }
}

/* ----------------------------------------------------------------------
   Bouton "Revenir en haut de la page" (demande utilisateur du 2026-09-30), present
   sur TOUTES les pages (`templates/base.html`) -- lien natif `<a href="#top">`, sa
   cible est l'attribut `id="top"` du `<body>` : repli SANS JavaScript garanti, le
   lien est fonctionnel et VISIBLE en permanence tant que `static/js/back_to_top.js`
   n'a pas tourne. Une fois le script charge (`document.documentElement`
   `[data-js]`, pose par le script lui-meme), le bouton est masque jusqu'a ~400px de
   defilement (classe `.is-visible`, voir le docstring du script). Position fixe
   bas-droite, respecte les zones surees de l'ecran (`env(safe-area-inset-*)`, encoches/
   barres systeme) -- a l'oppose de la pile de notifications `.toast-region`
   (haut-droite, `z-index: 1000`) : aucun chevauchement possible.
   ---------------------------------------------------------------------- */
.back-to-top {
  position: fixed;
  right: max(1rem, env(safe-area-inset-right));
  bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 900;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-muted);
  box-shadow: var(--shadow-sm);
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
    background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.back-to-top:hover {
  background: var(--surface-2);
  color: var(--text);
}

.back-to-top:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Amelioration progressive : masque puis revele au defilement UNIQUEMENT si le
   script a pu s'executer (attribut pose par lui, jamais par le gabarit) -- sans lui,
   la regle ci-dessus (toujours visible) reste seule applicable. */
[data-js] .back-to-top {
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
}

[data-js] .back-to-top.is-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  .back-to-top {
    transition: none;
  }
}

/* Audit visuel du 2026-10-01 (mobile/PC) -- deux restes signales par l'audit :
   1. le bouton flottant « Revenir en haut » (fixe, bas-droite, 2.75rem) pouvait
      recouvrir le DERNIER bouton d'une page (ex. « Enregistrer » de /admin/montage en
      mobile) une fois tout en bas : la page reserve desormais sous son contenu la
      hauteur du bouton + sa marge, quel que soit le viewport (place apres les
      surcharges mobiles de `.page`, qui redefinissent `padding` en raccourci) ;
   2. la liste « IA des prompts » d'un formulaire hors `.actions-primary` (ex. carte
      « Valider le scenario et generer la video ») debordait sous 640 px : son libelle
      d'option le plus long imposait sa largeur intrinseque. */
.page {
  padding-bottom: calc(2.75rem + 2 * max(1rem, env(safe-area-inset-bottom)));
}

@media (max-width: 640px) {
  .prompt-provider-field {
    min-width: 0;
    width: 100%;
    max-width: 100%;
  }

  .prompt-provider-field select {
    width: 100%;
  }

  .inline-form:has(> .prompt-provider-field) {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
  }

  .prompt-provider-field select {
    min-width: 0;
  }

  /* Le grand bouton « Valider le scenario et generer la video » (texte `nowrap`)
     faisait 384 px dans une carte de 316 px : il passe a la ligne au lieu de deborder. */
  .btn-lg {
    max-width: 100%;
    white-space: normal;
    text-align: center;
  }
}

/* Vérification chrome-devtools du 2026-10-01 (marges, mobile 360-390 px) -- trois
   éléments entamaient la marge de page ou sortaient de leur carte :
   1. fiche personnage : boutons d'une image de référence (`nowrap`) qui élargissaient
      la colonne de 150 px de la grille (+13 px) ;
   2. fiche vidéo en brouillon : la liste « IA de scénario » prenait la largeur de son
      option la plus longue, et toute la zone d'actions de l'en-tête avec (+14 px) ;
   3. Connexions IA : « Utiliser le client public Hermes Agent » (`nowrap`) sortait de
      sa carte (+28 px).
   Sur petit écran, un bouton passe donc à la ligne plutôt que de déborder, et les
   conteneurs flex de ces zones acceptent de rétrécir sous leur contenu. */
.ref-images figure,
.ref-image-actions,
.ref-image-actions .inline-form {
  min-width: 0;
  max-width: 100%;
}

.ref-image-actions .inline-form {
  flex-wrap: wrap;
}

.ref-image-actions button,
.ref-image-actions .btn {
  max-width: 100%;
  white-space: normal;
  text-align: center;
}

@media (max-width: 640px) {
  button,
  .btn {
    max-width: 100%;
    white-space: normal;
    text-align: center;
  }

  .inline-form {
    max-width: 100%;
  }

  .page-header > .page-actions,
  .actions-primary,
  .actions-primary .inline-form,
  .actions-primary .field,
  .actions-primary select {
    min-width: 0;
    max-width: 100%;
  }

  .page-header > .page-actions {
    flex: 1 1 100%;
  }
}

/* Même vérification (Connexions IA, 360 px) : le texte qui suit un bouton dans un
   encadré d'avertissement lui était collé, et les cartes imbriquées (carte xAI >
   carte Statut > encadré) gardaient chacune leur grande marge intérieure, ce qui
   ne laissait que ~240 px de texte sur un écran de 360 px. */
.warning-box .inline-form {
  margin: var(--space-2) 0 var(--space-3);
}

@media (max-width: 640px) {
  .card .card:not(.card-flush) {
    padding: var(--space-4) var(--space-3);
  }
}

/* ------------------------------------------------------------------------ */
/* Audit d'affichage du 2026-10-01 (docs/reference/audit-affichage-2026-10-01.md,  */
/* `spikes/audit_affichage/`) : toutes les pages, 1440/1024/768/390/360 px.       */
/* ------------------------------------------------------------------------ */

/* Mots très longs (nom d'univers, de personnage, de série...) : un seul mot sans
   espace sortait de la page (+209 px à 390 px sur les fiches univers/personnage). Le
   texte courant se coupe en dernier recours ; les titres, souvent dans un conteneur
   flexible, se coupent aussi dans le calcul de leur largeur minimale. */
body {
  overflow-wrap: break-word;
}

h1,
h2,
h3,
h4 {
  overflow-wrap: anywhere;
}

/* Choix des personnages (création de vidéo, réglages d'une vidéo) : un nom très long
   sortait de sa carte (+155 px à 1024 px) et recouvrait la case de la carte voisine.
   La colonne ne dépasse plus jamais la largeur disponible, et la case garde une zone
   cliquable de 40 px de large (18 px auparavant). */
.check-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
}

.check-card-linked .check-card-text {
  overflow-wrap: anywhere;
}

.check-card-linked > .checkbox-label {
  align-self: stretch;
  justify-content: center;
  min-width: var(--control-h);
  margin-left: calc(-1 * var(--space-3));
}

/* `.card-inset` compense l'absence de marge intérieure d'une `.card-flush` ; dans une
   carte ORDINAIRE (déjà 24 px de marge), elle doublait le retrait -- onglet Connexions
   IA : « Tester la connexion », « Avancé -- paramètres OAuth », état du tunnel VPN,
   décalés de 24 px par rapport au reste de leur carte. */
.card:not(.card-flush) > .card-inset,
.card:not(.card-flush) > .card-body > .card-inset,
.card:not(.card-flush) > form.card-inset {
  margin-left: 0;
  margin-right: 0;
}

/* Cartes séparées par un `<script>` (onglet Connexions IA : xAI, Claude) : la règle
   `.card + .card` ne s'appliquait plus, les cartes se touchaient. */
.card + script + .card,
.card + script + script + .card {
  margin-top: var(--space-5);
}

/* Liste « Autres connexions » vide : le message touchait le filet de séparation. */
.provider-rows > .muted.card-inset {
  margin-top: var(--space-4);
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
}

/* Lignes de fournisseur (onglets Scénario, Vidéo, Voix...) : « Modèle » (suivi de sa
   date de rafraîchissement) n'était pas aligné avec « URL de base » et « Secret »,
   alignés sur le BAS de la ligne. */
.provider-row-fields {
  align-items: start;
}

/* Statut de connexion Claude : le bouton « Se connecter avec Claude » touchait
   l'encadré « Vous avez déjà généré un jeton » ; le texte « Aucun modèle récupéré »
   touchait la carte « Statut » au-dessus. */
.connect-zone {
  margin-bottom: var(--space-4);
}

.card > .card + .card-body,
.card > .card + .hint {
  margin-top: var(--space-4);
}

/* Carte « Storyboard » (`.card-flush`) : le repli « Détail des plans (avancé) » collait
   aux bords de la carte, contrairement aux autres replis de la fiche vidéo. */
.card-flush > .disclosure {
  margin: 0 var(--space-6) var(--space-5);
}

/* Panneau « Remarque sur la génération » (fiche vidéo, sous la vidéo finale et sous
   chaque plan) : aucune règle propre -- titre en retrait mais champ, case et bouton
   collés au bord, filet orphelin au-dessus du titre, boutons de la vidéo finale collés
   sous le panneau. */
.video-remarks-panel {
  margin: var(--space-4) 0;
  padding: var(--space-5) var(--space-6);
}

.video-remarks-panel.card-flush > .card-header {
  padding: 0;
}

.video-remarks-panel .card-header .card-subtitle {
  margin: 0;
  padding-top: 0;
  border-top: 0;
}

.video-remarks-panel .form-grid {
  gap: var(--space-3) var(--space-5);
}

.video-remarks-panel .form-actions {
  margin-top: 0;
}

.video-remarks-list {
  margin: var(--space-4) 0 0;
  padding: var(--space-4) 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
  display: grid;
  gap: var(--space-3);
}

.video-remark .remark-text {
  margin: 0 0 var(--space-1);
}

.video-remark .remark-detail {
  margin-top: var(--space-1);
}

/* Messages compacts (`alert-sm`, utilisée sans règle) et petits replis. */
.alert-sm {
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
}

/* Intertitres des onglets Système et Voix (`section-divider`, utilisée sans règle) :
   collés à la carte précédente. */
.section-divider {
  margin: var(--space-8) 0 var(--space-4);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
  font-size: var(--text-lg);
}

/* Historique des consignes de prompts vidéo (onglet Scénario). */
.history-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
}

/* Contrôle visuel du 2026-10-03 (390 px) : un élément de grille prend par défaut la
   largeur de son contenu le plus large -- le `<pre>` des consignes avant/après poussait
   toute l'entrée hors de la carte (+119 px). L'entrée se borne à la colonne, le texte
   des consignes passe à la ligne. */
.history-entry {
  min-width: 0;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--border);
  overflow-wrap: anywhere;
}

.history-diff {
  padding: var(--space-3) var(--space-4);
  overflow-x: auto;
}

.history-diff pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Calendrier (liste) : les actions d'une ligne (date + bouton, plusieurs formulaires)
   sortaient de leur cellule et du tableau (+93 px à 1440 px), en dehors du fond de
   l'en-tête. Elles passent à la ligne dans la cellule. */
td.nowrap:has(> .inline-form) {
  white-space: normal;
}

td.nowrap > .inline-form {
  flex-wrap: nowrap;
  margin: var(--space-1) var(--space-1) var(--space-1) 0;
}

td.nowrap > .inline-form input[type="datetime-local"] {
  width: 11rem;
  min-width: 0;
}

/* Même tableau : l'en-tête « Date programmée (Europe/Paris) », insécable, élargissait
   la première colonne à 265 px et faisait défiler tout le tableau à 1440 px. */
#publications-calendar-table thead th:first-child {
  white-space: normal;
}

/* Fiche vidéo, carte « Publier » : « Gérer dans le calendrier » touchait le tableau. */
.table-wrap + .message {
  margin-top: var(--space-4);
}

/* Cibles tactiles : sur petit écran ou pointeur tactile, toute cible interactive
   atteint les 40 px de `--control-h` (mesuré : boutons compacts `.btn-sm` à 36 px
   -- « Supprimer » de /jobs, actions du calendrier, « Enregistrer la remarque »... --,
   onglets à 28-39 px, liens d'en-tête de carte et « Recharger » à 20 px). Le bureau
   garde ses boutons compacts. */
@media (max-width: 640px), (pointer: coarse) {
  .btn-sm,
  button.btn-sm,
  .btn-icon,
  button.btn-icon {
    min-height: var(--control-h);
  }

  .tab-link,
  .version-tab,
  a.back-link,
  .card-header > a,
  .provider-refresh-form > a,
  .provider-toggle,
  .provider-note summary,
  .tag-list > a.tag {
    display: inline-flex;
    align-items: center;
    min-height: var(--control-h);
  }

  .calendar-entry > a {
    display: block;
    padding-block: var(--space-1);
  }
}

/* Bouton « Fermer » du dialogue « Tout générer de bout en bout » : rétréci à 30-36 px
   par la ligne flexible de l'en-tête. */
.end-to-end-head > .btn-icon {
  flex: none;
}

/* Bible de série (`<dl>`) : une étiquette au mot très long sortait encore de 40 px à
   360 px -- dans une liste d'étiquettes (et seulement là : dans un tableau, `anywhere`
   effondrerait la colonne), elle peut se couper n'importe où. */
dd > .tag-list > .tag {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Historique des jobs (/jobs) : à 1440 px (1094 px utiles à côté de la barre latérale),
   le tableau faisait 1400 px et défilait -- « Créé le » et « Supprimer » hors de vue,
   précisément ce que le retour utilisateur du 2026-09-20 demandait d'éviter. Cellules
   plus serrées, identifiant en petite police, « Source » plafonnée (titre complet dans
   l'infobulle) ; l'étiquette de type, qui passe sur plusieurs lignes, prend des coins
   arrondis plutôt qu'une pilule ovale. */
.jobs-table th,
.jobs-table td {
  padding-left: var(--space-3);
  padding-right: var(--space-3);
}

.jobs-table td.mono {
  font-size: var(--text-xs);
}

.jobs-table .source-cell {
  max-width: 14rem;
}

.jobs-table td > .tag {
  border-radius: var(--radius-sm);
}

/* Identifiant de job (ULID, 26 caractères) : sur deux lignes plutôt que tronqué -- son
   début (horodatage) est commun à tous les jobs récents, c'est la fin qui les distingue. */
.jobs-table td.mono {
  width: 8.5rem;
  word-break: break-all;
}

/* Accueil, « Dernières vidéos » : 16 px de trop à 1440 px (le tableau défilait). */
.thumb-cell {
  padding-right: 0;
}

#dash-videos-card .title-cell {
  max-width: 20rem;
}

/* Calendrier semaine/mois : le badge « Envoyée en brouillon » sortait de sa case
   (+15 à +19 px à 1440 px). Il passe à la ligne dans la case. */
.calendar-entry .badge {
  /* `inline-block` : en `inline-flex`, le texte (élément flexible anonyme) ne rétrécit
     pas sous la largeur de son mot le plus long et sortait des cases étroites (1024 px). */
  display: inline-block;
  align-self: flex-start;
  max-width: 100%;
  min-height: 0;
  padding: 0.2rem 0.4rem;
  line-height: 1.25;
  white-space: normal;
  overflow-wrap: anywhere;
}

.calendar-entry .badge::before {
  display: inline-block;
  margin-right: 0.25rem;
  vertical-align: middle;
}

/* Calendrier (liste) : la colonne « Vidéo » était réduite à un mot par ligne
   (« Caramel / reprend / son / trône ») par les colonnes voisines. */
#publications-calendar-table td:nth-child(2) {
  min-width: 7.5rem;
}

/* ----------------------------------------------------------------------
   Génération fine (ADR-0011 §4, étape 11) : panneau « Vérification des plans »
   (`templates/_fine_generation_panel.html`) -- bandeau, plans, défauts groupés par
   gravité, tentatives. Une colonne sous 640 px, aucun débordement (textes longs du
   vérificateur coupés), cibles tactiles portées à 40 px par la règle `.btn-sm` mobile.
   ---------------------------------------------------------------------- */
.badge[data-status="reviewing"],
.badge[data-status="revising"] {
  background: var(--info-soft);
  color: var(--info);
}

.badge[data-status="reviewing"]::before,
.badge[data-status="revising"]::before {
  animation: pulse 1.4s var(--ease) infinite;
}

.fine-gen-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.fine-gen-body > .alert,
.fine-gen-body > .hint {
  margin: 0;
}

.fine-banner-title {
  margin: 0 0 var(--space-2);
}

.fine-reviewer {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.fine-counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: 0 0 var(--space-2);
  padding: 0;
  list-style: none;
}

.fine-counts strong {
  font-size: 1rem;
}

.fine-counts [data-count="accepted"] strong {
  color: var(--success);
}

.fine-counts [data-count="to_verify"] strong {
  color: var(--warning);
}

.fine-counts [data-count="in_progress"] strong {
  color: var(--info);
}

.fine-shot-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.fine-shot-list > .disclosure,
.fine-shot-list > .disclosure + .disclosure {
  margin-top: 0;
}

.fine-shot[data-fine="to_verify"] {
  border-color: var(--warning-border);
}

.fine-shot > summary {
  flex-wrap: wrap;
  row-gap: var(--space-1);
}

.fine-shot-title {
  font-weight: 650;
}

.fine-progress,
.fine-score-chip {
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 500;
}

.fine-score-chip strong {
  color: var(--text);
}

/* Badges d'état d'un plan et verdicts d'une tentative. */
.fine-badge[data-fine="accepted"],
.fine-badge[data-fine="manual"],
.fine-badge[data-fine="ready"],
.fine-outcome[data-outcome="accepted"] {
  background: var(--success-soft);
  color: var(--success);
}

.fine-badge[data-fine="to_verify"],
.fine-badge[data-fine="waiting"],
.fine-badge[data-fine="stale"],
.fine-outcome[data-outcome="error"] {
  background: var(--warning-soft);
  color: var(--warning);
}

.fine-badge[data-fine="generating"],
.fine-badge[data-fine="reviewing"],
.fine-badge[data-fine="revising"],
.fine-outcome[data-outcome="reviewing"] {
  background: var(--info-soft);
  color: var(--info);
}

.fine-badge[data-fine="generating"]::before,
.fine-badge[data-fine="reviewing"]::before,
.fine-badge[data-fine="revising"]::before,
.fine-outcome[data-outcome="reviewing"]::before {
  animation: pulse 1.4s var(--ease) infinite;
}

.fine-badge[data-fine="failed"],
.fine-outcome[data-outcome="rejected"] {
  background: var(--danger-soft);
  color: var(--danger);
}

/* Clip retenu (ou dernier verdict) et son verdict, côte à côte au-dessus de 640 px. */
.fine-featured {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.fine-clip,
.fine-attempt-clip {
  max-width: 100%;
  height: auto;
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-md, 8px);
  background: #000;
}

.fine-clip {
  width: 180px;
}

.fine-attempt-clip {
  width: 120px;
}

.fine-subtitle {
  margin: 0 0 var(--space-2);
  font-size: 1rem;
}

.fine-score-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
}

.fine-score strong {
  font-size: 1.5rem;
  line-height: 1;
}

.fine-score[data-outcome="accepted"] strong {
  color: var(--success);
}

.fine-score[data-outcome="rejected"] strong {
  color: var(--danger);
}

.fine-summary {
  margin: 0 0 var(--space-2);
  overflow-wrap: anywhere;
}

/* Défauts relevés, groupés par gravité. */
.fine-issues {
  margin: var(--space-2) 0;
}

.fine-issue-group {
  margin: var(--space-3) 0 var(--space-2);
  padding-left: var(--space-2);
  border-left: 3px solid var(--border-strong);
  font-size: 0.875rem;
  font-weight: 650;
}

.fine-issues > .fine-issue-group:first-child {
  margin-top: 0;
}

.fine-issue-group[data-severity="bloquant"] {
  border-left-color: var(--danger);
  color: var(--danger);
}

.fine-issue-group[data-severity="majeur"] {
  border-left-color: var(--warning);
  color: var(--warning);
}

.fine-issue-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.fine-issue {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 8px);
  background: var(--surface-2);
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

.fine-issue-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
}

.fine-issue-time {
  font-weight: 600;
}

.fine-issue-suggestion {
  color: var(--text-muted);
  font-size: 0.8125rem;
}

.fine-issues-compact .fine-issue {
  padding: var(--space-1) var(--space-2);
}

.fine-meta {
  margin-top: var(--space-3);
}

.fine-meta dd {
  overflow-wrap: anywhere;
}

.fine-dialogue-missing {
  display: block;
  margin-top: var(--space-1);
}

.fine-transcript {
  margin-top: var(--space-2);
}

/* Consignes de révision transmises à l'IA des prompts. */
.fine-notes {
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid var(--info);
  border-radius: var(--radius-md, 8px);
  background: var(--info-soft);
}

.fine-notes-title {
  margin: 0 0 var(--space-1);
  font-size: 0.8125rem;
  font-weight: 650;
}

.fine-notes .prose {
  margin: 0;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.fine-notes-inline {
  margin: var(--space-1) 0;
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}

.fine-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.fine-actions .inline-form,
.fine-attempt-detail .inline-form {
  margin: 0;
}

.fine-attempts {
  margin-top: var(--space-4);
}

/* Liste des tentatives (boucle courante et archives), la plus récente d'abord. */
.fine-attempt-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  list-style: none;
}

.fine-attempt {
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md, 8px);
}

.fine-attempt.is-selected {
  border-color: var(--success-border);
  background: var(--success-soft);
}

.fine-attempt-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.fine-attempt-body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
}

.fine-attempt-detail > :first-child {
  margin-top: 0;
}

.fine-attempt-detail > :last-child {
  margin-bottom: 0;
}

.fine-attempt-noclip {
  width: 120px;
  margin: 0;
}

@media (max-width: 640px) {
  .fine-featured {
    grid-template-columns: minmax(0, 1fr);
  }

  .fine-clip {
    width: min(180px, 100%);
  }
}

@media (max-width: 400px) {
  .fine-attempt-body {
    grid-template-columns: minmax(0, 1fr);
  }

  .fine-attempt-noclip {
    width: auto;
  }
}

/* Demande du 2026-10-03 : aide « ? » (attribut HTML `popover`, sans script) a cote d'un
   libelle -- regles de l'IA de verification (administration, generation fine). */
.label-with-help {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.help-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.help-button:hover,
.help-button:focus-visible {
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.help-popover {
  width: min(46rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  overflow: auto;
  padding: 1rem 1.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}

.help-popover::backdrop {
  background: rgb(0 0 0 / 0.35);
}

.help-popover-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.5rem;
}

.help-popover-head h3 {
  margin: 0;
  font-size: 1.05rem;
}

.help-popover-pre {
  margin: 0.25rem 0 0.75rem;
  padding: 0.75rem;
  border-radius: var(--radius-md);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .help-button {
    width: 2.5rem;
    height: 2.5rem;
  }
}

/* ------------------------------------------------------------------------ */
/* Fiche vidéo : étapes et barre d'actions (étape 3b du plan                  */
/* `generation-separee-brouillon-upscale`, ADR-0012 §2)                       */
/* ------------------------------------------------------------------------ */
/* Demande utilisateur du 2026-10-03 : « Je me perds un peu dans la page. [...]
   Certains boutons sont placés en haut de page et je dois scroller pour pouvoir les
   atteindre. Voir si cela ne serait pas intéressant de les fixer soit en haut soit en
   bas. » Gabarits : `_project_detail_body.html`, `_project_steps.html`,
   `_project_live_status.html`.

   Barre d'actions (`.action-bar`) -- choix fait après essai aux 5 tailles
   (`docs/reference/fiche-video-refonte-2026-10-03.md`) :
   - PC et tablette (> 640 px) : COLLANTE EN HAUT, juste sous l'en-tête de page (sous la
     barre mobile de 56 px entre 641 et 900 px). Le regard y revient naturellement après
     le titre, elle ne masque jamais le bas d'un formulaire ni le bouton « Revenir en
     haut », et sa hauteur est stable (une ligne, `min-height`) : la progression qui
     change n'y décale rien ;
   - mobile (<= 640 px) : COLLANTE EN BAS, sous le pouce (la barre mobile de 56 px
     occupe déjà le haut). Elle est placée en fin de fiche par `order` (le document garde
     son ordre : barre, puis étapes) et la pile de notifications et le bouton « Revenir
     en haut », eux aussi en bas sur téléphone, remontent d'autant. */

.action-bar {
  position: sticky;
  top: 0;
  z-index: 25;
  display: grid;
  gap: var(--space-2);
  margin: 0 0 var(--space-5);
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.action-bar-message,
.action-bar-message > p {
  margin: 0;
}

/* « Tout générer » (2026-10-04) : formulaires en ligne dans la barre fixe. */
.action-bar-form {
  display: inline-flex;
  margin: 0;
}

.action-bar-armed {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}

.action-bar-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  min-height: 2.75rem;
}

.action-bar-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  flex: 1 1 16rem;
  min-width: 0;
}

.action-bar-state {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--text-sm);
}

.action-bar-status .job-tracker {
  flex: 0 1 auto;
  min-width: 0;
  padding: 0.25rem 0.5rem;
  font-size: var(--text-sm);
}

.action-bar-main {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
  margin-left: auto;
}

a.action-bar-step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h);
  min-width: 0;
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

a.action-bar-step:hover .action-bar-step-text {
  text-decoration: underline;
}

/* Étapes ------------------------------------------------------------------- */

.step {
  margin-top: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  /* Lien interne vers une étape : visible sous la barre collante (PC). */
  scroll-margin-top: 6rem;
}

.step-aside {
  margin-top: var(--space-6);
}

.project-detail-body [id] {
  scroll-margin-top: 6rem;
}

.step-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 3.5rem;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-lg);
  cursor: pointer;
  list-style: none;
}

.step-summary::-webkit-details-marker {
  display: none;
}

.step-summary::after {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  margin-left: auto;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease);
}

.step[open] > .step-summary::after {
  transform: rotate(-135deg);
}

.step-summary:hover {
  background: var(--surface-2);
}

.step-summary:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.step[open] > .step-summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

.step-number {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--neutral-soft);
  color: var(--neutral-text);
  font-size: var(--text-sm);
  font-weight: 700;
}

.step-number-sm {
  width: 1.75rem;
  height: 1.75rem;
  font-size: var(--text-xs);
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.step-head {
  display: grid;
  gap: 0.125rem;
  flex: 1 1 auto;
  min-width: 0;
}

.step-title-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
}

.step-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 650;
}

.step-brief {
  color: var(--text-muted);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}

.step-body {
  padding: var(--space-5);
  min-width: 0;
}

/* Couleurs par état : numéro et badge. */
.step[data-step-state="done"] .step-number,
.step-badge[data-step-state="done"] {
  background: var(--success-soft);
  color: var(--success);
}

.step[data-step-state="current"] .step-number,
.step-badge[data-step-state="current"],
.step-badge[data-step-state="todo"] {
  background: var(--accent-soft);
  color: var(--accent-soft-text);
}

.step[data-step-state="running"] .step-number,
.step[data-step-state="waiting"] .step-number,
.step-badge[data-step-state="running"],
.step-badge[data-step-state="waiting"] {
  background: var(--info-soft);
  color: var(--info);
}

.step[data-step-state="decision"] .step-number,
.step-badge[data-step-state="decision"] {
  background: var(--warning-soft);
  color: var(--warning);
}

.step[data-step-state="error"] .step-number,
.step-badge[data-step-state="error"] {
  background: var(--danger-soft);
  color: var(--danger);
}

/* Étape qui attend l'utilisateur : liseré coloré à gauche. */
.step[data-step-state="current"] {
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent), var(--shadow-sm);
}

.step[data-step-state="decision"] {
  border-color: var(--warning-border);
  box-shadow: inset 3px 0 0 var(--warning), var(--shadow-sm);
}

.step[data-step-state="error"] {
  border-color: var(--danger-border);
  box-shadow: inset 3px 0 0 var(--danger), var(--shadow-sm);
}

.step[data-step-state="running"],
.step[data-step-state="waiting"] {
  border-color: var(--info-border);
  box-shadow: inset 3px 0 0 var(--info), var(--shadow-sm);
}

.step[data-step-state="locked"] .step-title {
  color: var(--text-muted);
}

/* Contenu d'une étape : sous-sections séparées par un filet, cartes internes
   aplaties (la carte, c'est l'étape). */
.step-section + .step-section,
.step-section + .step-generation,
.step-generation + .step-section,
.step-body > .card + .step-section {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border);
}

.step-body > .card {
  box-shadow: none;
}

.step-body > * + .card {
  margin-top: var(--space-5);
}

.step-section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-2);
}

.step-section-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: var(--text-md);
}

.step-section-head .step-section-title {
  margin: 0;
}

.step-section-lead {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.step-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-3) 0;
}

.step-body > .step-actions:first-child {
  margin-top: 0;
}

.step-body > .empty-state .inline-form {
  margin-top: var(--space-2);
}

.step-more-actions {
  margin-top: var(--space-5);
}

/* Notifications : jamais par-dessus la barre collante. */
@media (min-width: 901px) {
  body:has(#project-action-bar) .toast-region {
    top: 5.5rem;
  }
}

@media (max-width: 900px) and (min-width: 641px) {
  .action-bar {
    top: 56px;
  }

  body:has(#project-action-bar) .toast-region {
    top: calc(56px + 5rem);
  }
}

@media (max-width: 640px) {
  .project-detail-body {
    display: flex;
    flex-direction: column;
  }

  .project-detail-body > .action-bar {
    order: 50;
    position: sticky;
    top: auto;
    bottom: 0;
    margin: var(--space-4) calc(-1 * var(--space-4)) 0;
    padding: var(--space-2) var(--space-3) max(var(--space-2), env(safe-area-inset-bottom));
    border-width: 1px 0 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: 0 -6px 18px rgb(16 24 40 / 0.12);
  }

  .action-bar-row {
    gap: var(--space-2);
  }

  .action-bar-status {
    flex-basis: 100%;
  }

  .action-bar-main {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    margin-left: 0;
  }

  .action-bar-main .action-bar-primary {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* « Tout générer » / « Nouvelle version » (2026-10-04) : à la ligne plutôt que de
     déborder à 390 px. */
  .action-bar-main:has(.action-bar-secondary) {
    flex-wrap: wrap;
  }

  .action-bar-main .action-bar-form:has(.action-bar-primary) {
    flex: 1 1 auto;
    display: flex;
  }

  /* Avec une action principale, le lien d'étape se réduit à son numéro (le texte
     reste lu par les lecteurs d'écran). */
  .action-bar-main:has(.action-bar-primary) .action-bar-step-text {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .action-bar-main:has(.action-bar-primary) a.action-bar-step {
    flex: none;
    justify-content: center;
    min-width: var(--control-h);
  }

  .step-summary {
    padding: var(--space-3) var(--space-4);
  }

  .step-body {
    padding: var(--space-4) var(--space-3);
  }

  .step,
  .project-detail-body [id] {
    scroll-margin-top: 4.5rem;
  }

  body:has(#project-action-bar) .toast-region,
  body:has(#project-action-bar) .back-to-top {
    bottom: 6.5rem;
  }
}

/* Zone de danger : séparée de la dernière étape. */
.project-detail-body > .step + .card {
  margin-top: var(--space-6);
}

/* ------------------------------------------------------------------------ */
/* Options de génération -- macro unique `_generation_options.html` (étape 4 */
/* du plan `generation-separee-brouillon-upscale`, ADR-0012 §6) : quatre      */
/* groupes repliables dans l'ordre des étapes, résumé lisible replié.         */
/* ------------------------------------------------------------------------ */
.gen-options {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.gen-options-lead {
  margin: 0;
}

.gen-options > .gen-options-group,
.gen-options > .gen-options-group + .gen-options-group {
  margin-top: 0;
}

.gen-options-group > summary.gen-options-summary {
  flex-wrap: wrap;
  row-gap: 0;
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}

.gen-options-step {
  flex: 0 0 auto;
}

/* Résumé des valeurs courantes : visible replié, coupé proprement s'il est long. */
.gen-options-values {
  flex: 1 1 12rem;
  min-width: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-weight: 400;
  font-size: 0.8125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gen-options-body {
  padding: var(--space-4);
}

.gen-options-body > .field,
.gen-options-body > .fieldset {
  min-width: 0;
}

.gen-options-loras {
  margin-top: var(--space-2);
}

.gen-options-loras .check-card input[type="number"] {
  width: 5.5rem;
  min-height: var(--control-h);
}

/* Parcours réel du 2026-10-03 (étape 9) : les vraies LoRA de Wan2GP sont des noms de
   fichiers longs et sans espace (« minimax_h3_lightx2v_ref2v_turbo_4step_alpha8_v0.1_
   bf16.safetensors ») ; ils sortaient de leur carte (options de la vidéo : jusqu'à
   178 px de débordement horizontal de la page) ou laissaient la case seule sur sa ligne
   (onglet Vidéo). Le nom passe à la ligne n'importe où ; la case garde sa taille. */
.gen-options-loras .check-card {
  min-width: 0;
}

.gen-options-loras .checkbox-label,
.lora-name-label {
  display: flex;
  min-width: 0;
}

.gen-options-loras .checkbox-label > input[type="checkbox"],
.lora-name-label > input[type="checkbox"] {
  flex-shrink: 0;
}

.gen-options-loras .checkbox-label > span,
.lora-name-label > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Dans le popup bout en bout : la macro occupe toute la largeur du corps. */
.end-to-end-scroll .gen-options {
  margin-top: var(--space-3);
}

/* ------------------------------------------------------------------------ */
/* Amélioration (upscale) des plans -- `_upscale_controls.html`, section     */
/* `#shots-upscale` du bloc « 5. Génération des plans » (étapes 7 et 8).      */
/* ------------------------------------------------------------------------ */
.upscale-section {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.upscale-panel-embedded > .hint {
  margin-top: 0;
}

.upscale-status {
  margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  font-size: 0.875rem;
}

.upscale-all-form {
  margin: var(--space-3) 0 var(--space-4);
}

.upscale-options {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  grid-column: 1 / -1;
}

.upscale-options > legend {
  margin-bottom: var(--space-2);
  padding: 0;
  font-weight: 600;
  font-size: 0.875rem;
}

.upscale-options > .checkbox-label {
  min-height: var(--control-h);
}

.upscale-options > .hint {
  margin: 0 0 var(--space-2);
  padding-left: 1.75rem;
}

.upscale-shots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
  gap: var(--space-3);
}

.upscale-shot {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.upscale-shot-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin: 0;
  font-size: 0.875rem;
}

/* Lecteurs avant / après côte à côte (empilés sur un écran étroit). */
.upscale-compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  gap: var(--space-2);
  min-width: 0;
}

.upscale-compare figure {
  margin: 0;
  min-width: 0;
}

.upscale-compare figcaption {
  margin-bottom: var(--space-1);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.upscale-compare video {
  display: block;
  width: 100%;
  max-height: 22rem;
  aspect-ratio: 9 / 16;
  border-radius: var(--radius-md);
  background: #000;
  object-fit: contain;
}

.upscale-action-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.upscale-action-form select {
  min-height: var(--control-h);
  max-width: 100%;
}

.upscale-action-form .checkbox-label,
.upscale-noscript-confirm {
  min-height: var(--control-h);
}

/* Contrôles d'amélioration désactivés pendant une tâche, sans changer la mise en page
   (aucun saut de défilement après un clic) : le fieldset n'ajoute aucune boîte. */
.upscale-fieldset {
  display: contents;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

/* Essais de voix (fiche personnage, 2026-10-03) : une carte par essai -- clip vertical
   (9:16, entier, jamais recadré), lecteur audio de la voix extraite, actions. La grille
   passe à une colonne sur mobile (`min(100%, 15rem)` : aucun débordement à 390 px). */
.voice-trials-progress {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
}

.voice-trials-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-4);
  margin-bottom: 0;
  padding: 0;
  list-style: none;
}

.voice-trial {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
}

.voice-trial-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.voice-trial-video {
  display: block;
  width: 100%;
  max-height: 22rem;
  aspect-ratio: 9 / 16;
  object-fit: contain;
  border-radius: var(--radius-md);
  background: var(--surface-3);
}

.voice-trial-body {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  min-width: 0;
}

.voice-trial-body p {
  margin: 0;
  overflow-wrap: anywhere;
}

.voice-trial-body audio {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

/* Actions empilées, pleine largeur de la carte : le libellé long (« Choisir comme
   empreinte vocale ») passe à la ligne au lieu de déborder de la carte. */
.voice-trial-actions {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.voice-trial-actions .inline-form,
.voice-trial-actions button {
  width: 100%;
  min-width: 0;
}

.voice-trial-actions button {
  justify-content: center;
  white-space: normal;
  text-align: center;
}

/* Personnages de l'univers (personnages coherents, etape 3) : cartes compactes avec
   vignette (image de reference principale, sinon initiale), presence et roles dans les
   series. Grille fluide : une colonne sur telephone, plusieurs sur PC. */
.cast-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
}

.cast-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface);
}

.cast-thumb {
  flex: none;
  max-width: none;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  object-fit: cover;
  background: var(--surface-2);
}

.cast-thumb-empty {
  display: grid;
  place-items: center;
  font-weight: 650;
  font-size: 1.25rem;
  color: var(--text-muted);
}

.cast-card-text {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

.cast-card-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}

.cast-card-text .muted {
  overflow-wrap: anywhere;
}

/* Relecture d'une distribution proposee (personnages coherents, etape 5) : personnages
   existants a reutiliser (case, vignette, role propose, justification) puis titre du
   groupe des nouveaux personnages. */
.cast-reuse-check {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 19px 0 0;
}

.cast-reuse-card.is-muted {
  background: var(--surface-2);
}

.cast-reuse-card .hint {
  margin: 0;
}

.cast-reuse-role {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.cast-reuse-role select {
  max-width: 100%;
}

.cast-group-heading h2 {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
}

.cast-group-heading .hint {
  margin: var(--space-1) 0 0;
}

/* Fiche univers : filtres de la carte « Personnages de l'univers » (recherche par
   nom, presence) ; une ligne sur PC, empiles sur telephone. */
.cast-filters {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) auto;
  gap: var(--space-2);
  align-items: end;
  margin: 0 0 var(--space-3);
}

.cast-filters .field {
  margin: 0;
  min-width: 0;
}

@media (max-width: 640px) {
  .cast-filters {
    grid-template-columns: minmax(0, 1fr);
  }
}

.cast-card-series a {
  color: inherit;
}

/* Fiche serie : « Ajouter à la distribution » sous chaque personnage disponible
   (role + bouton), qui passe a la ligne plutot que de deborder de la carte. */
.cast-add-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.cast-add-form select {
  width: auto;
  max-width: 100%;
  min-width: 0;
}

.cast-add-form button {
  white-space: normal;
}

/* Case « Générer les personnages manquants » et ligne « Actualiser la liste des
   personnages » du formulaire de creation de video. */
.cast-propose-option {
  margin-top: var(--space-3);
}

.cast-propose-option .hint {
  margin: var(--space-1) 0 0;
}

.cast-refresh-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
