/* La Table — theme layer (v3)
   Chargé APRÈS style.css : surcouche sans toucher au fichier d'origine.
   Identité « table de JDR universelle, moderne » :
   - cadre de jeu sans coins en équerre (retirés) : jetons, HUD de vitalité, d20
   - glassmorphism raffiné + palette acier / ambre / violet arcanique
   - typographie Science Gothic (variable, une seule famille pour display / narratif / UI)
   - motion vivante : micro-interactions, récompense visuelle des actions
   Ne casse aucun binding Vue ni appel API. */


/* Tokens : voir play/tokens.css (source unique, spec §5).
   Aucun :root ici — ces fichiers consomment les tokens, ils ne les definissent plus. */

/* ================= base ================= */
html { color-scheme: dark; }

::selection { background: rgba(232, 163, 61, 0.32); color: #fff; }

/* scrollbars thématisées */
* { scrollbar-width: thin; scrollbar-color: rgba(150, 166, 205, 0.28) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(150, 166, 205, 0.24);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(232, 163, 61, 0.45); background-clip: padding-box; }

/* focus clavier visible */
:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ================= transitions d'écran ================= */
/* Carte t_2936a2e5 — les écrans s'affichent dans leur position finale : plus de
   translation/scale d'entrée (`screen-in` supprimée, elle déplaçait du texte). */

/* FIX : écran setup scrollable */
.setup-screen { height: 100vh; overflow-y: auto; align-items: flex-start; }
.setup-panel  { margin: auto; }

/* ================= surfaces / glass ================= */
.glass {
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.035) 0%, rgba(255, 255, 255, 0) 38%),
    radial-gradient(120% 90% at 50% 0%, rgba(165, 141, 255, 0.05), transparent 55%),
    var(--surface);
  backdrop-filter: blur(16px) saturate(1.15);
  -webkit-backdrop-filter: blur(16px) saturate(1.15);
  box-shadow: var(--shadow-lg), inset 0 0 44px rgba(150, 166, 205, 0.05);
}

/* ---------- conteneurs fixes : position (les barres HUD y ancrent leur ::after) ----------
   Les « coins en équerre » (cadre de jeu, --frame) ont été RETIRÉS : demande design
   de l'utilisateur — plus aucun coin carré sur les panneaux. `position: relative`
   reste : les ::after des barres segmentées (PV/FP) s'ancrent dessus. */
.setup-panel, .topbar, .vitals, .actionbar { position: relative; }

/* ---------- en-têtes des panneaux scrollables : filet + coins ---------- */
.sheet-head, .creator-head, .drawer-head {
  position: relative;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.sheet-head::after, .creator-head::after, .drawer-head::after {
  content: "";
  position: absolute;
  left: 0; bottom: -1px;
  width: 52px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--ember), transparent);
  box-shadow: 0 0 10px var(--ember-glow);
}
.char-sheet-live { border-top: 2px solid var(--border-strong); }

/* ================= marque / titre ================= */
.brand { position: relative; padding-bottom: 14px; margin-bottom: 6px; }
.brand::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 64px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--ember), transparent);
  box-shadow: 0 0 12px var(--ember-glow);
}
.brand-mark { display: inline-flex; align-items: center; justify-content: center; }
.brand-mark svg { width: 34px; height: 34px; color: var(--gold); filter: drop-shadow(0 0 10px var(--ember-glow)); }

.title {
  font-family: var(--display);
  font-size: 46px;
  font-weight: 700;
  letter-spacing: 2px;
  text-shadow: 0 0 26px var(--ember-glow), 0 2px 14px rgba(0, 0, 0, 0.6);
}
.subtitle { font-style: italic; }

/* titre de campagne (topbar) — lisible au premier coup d'œil */
.campaign {
  font-family: var(--display);
  font-size: 23px;
  font-weight: 600;
  letter-spacing: 0.4px;
  color: var(--gold);
  text-shadow: 0 0 18px rgba(232, 163, 61, 0.35);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.campaign::before {
  content: "";
  width: 20px; height: 20px;
  background: var(--d20) center / contain no-repeat;
  flex: none;
  opacity: 0.9;
  filter: drop-shadow(0 0 6px rgba(232, 163, 61, 0.5));
}

/* noms de personnages — clairs et hiérarchisés */
.vital .name { font-size: 14px; font-weight: 600; letter-spacing: 0.02em; }
.msg-name { font-size: 15px; font-weight: 600; letter-spacing: 0.02em; }

/* ================= contrôles ================= */
input, select {
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.32);
  transition: border-color var(--ease), box-shadow var(--ease), background var(--ease);
}
input:hover:not(:disabled), select:hover:not(:disabled) { border-color: rgba(150, 166, 205, 0.42); }
input:focus, select:focus {
  border-color: var(--amber);
  box-shadow: var(--ring);
  background: rgba(0, 0, 0, 0.42);
}

/* select : chevron thématisé */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23e8a33d' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 14px;
  padding-right: 32px;
}

/* ================= boutons ================= */
.btn {
  border-radius: 10px;
  font-weight: 500;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04) inset;
  transition: transform var(--ease), box-shadow var(--ease), background var(--ease),
    border-color var(--ease), color var(--ease), filter var(--ease);
}
.btn:hover {
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), 0 0 0 1px var(--border-strong) inset;
}
.btn:active { transform: translateY(0) scale(0.96); }
.btn:disabled { opacity: 0.4; filter: saturate(0.6); }

.btn.primary {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, #f0b04a, #ff7a3d);
  color: #231303;
  font-weight: 600;
  box-shadow: 0 4px 14px rgba(255, 122, 61, 0.25), 0 1px 0 rgba(255, 255, 255, 0.18) inset;
}
/* récompense visuelle : reflet qui balaye au survol */
.btn.primary::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, 0.4) 50%, transparent 70%);
  transform: translateX(-130%);
  transition: transform 500ms ease;
  pointer-events: none;
}
.btn.primary:hover {
  box-shadow: 0 8px 24px var(--ember-glow), 0 1px 0 rgba(255, 255, 255, 0.22) inset;
  filter: brightness(1.05);
}
.btn.primary:hover::before { transform: translateX(130%); }
.btn.primary:active { filter: brightness(0.96); }
.btn.icon.quit:hover { box-shadow: none; }
/* « Initiative, images, journal » : venu de la barre du bas à l'extrémité
   droite de l'en-tête (carte t_8011ed56). Même facture que ses voisins de
   barre (fond et bordure transparents, icône seule) : l'en-tête reste une
   rangée d'ICÔNES, le titre seul porte du texte. */
.btn.icon.journal-btn { color: var(--amber); border-color: transparent; background: transparent; font-size: 18px; }
.btn.icon.journal-btn:hover { color: var(--gold); background: var(--amber-soft); box-shadow: none; }

/* ================= vitals — jetons de joueur + HUD de vitalité ================= */
.vital {
  position: relative;
  border-radius: 12px;
  border: 1px solid var(--border);
  transition: background var(--ease), box-shadow var(--ease), transform var(--ease), opacity var(--ease), border-color var(--ease);
}
.vital:hover { background: rgba(150, 166, 205, 0.08); box-shadow: 0 0 0 1px rgba(232, 163, 61, 0.35); }
.vital:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.vital.active {
  background: linear-gradient(135deg, rgba(232, 163, 61, 0.14), rgba(165, 141, 255, 0.10));
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber), 0 6px 18px rgba(0, 0, 0, 0.4), 0 0 20px rgba(232, 163, 61, 0.22);
}
/* marqueur « en action » sur le jeton actif */
.vital.active::after {
  content: "▸";
  position: absolute;
  left: -9px; top: 50%;
  transform: translateY(-50%);
  color: var(--gold);
  font-size: 15px;
  text-shadow: 0 0 10px var(--ember-glow);
}
/* le badge « En train de jouer » reste lisible même au survol d'un jeton actif */
.vital.active:hover .playing-badge { filter: brightness(1.08); }

/* nom = bouton fiche (D2) */
.vital-name {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  line-height: 1.3;
  cursor: pointer;
}
.vital-name:hover { color: var(--amber); text-decoration: underline; text-underline-offset: 2px; }
.vital-name:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; border-radius: 4px; }
.vital.down .vital-name { color: var(--muted); text-decoration: line-through; }

/* Le bouton 👁 dédié à la fiche est retiré (carte t_d350abfe) : le NOM du
   héros est le seul point d'entrée vers la fiche, souris comme tactile. */

/* badge « En train de jouer » — discret : c'est un repère de table, pas un titre.
   (Retour utilisateur 2026-09-10 : la version 11 px écrasait le jeton.) */
.playing-badge {
  position: absolute;
  /* P2 (t_c3c5ec15) : calé à 8 px du bord droit du jeton — il dépassait de
     ~4 px et se lisait posé sur la limite des deux jetons (mesuré 1280 px) —
     et tenu en étiquette flottante du coin haut-droit. Une position ENTIÈREMENT
     dans le jeton est impossible sans recouvrir la rangée du nom (mesuré : le
     badge fait 14,6 px de haut pour 11,4 px de place au-dessus du nom : en
     `top: 8px` il écrase le nom et le badge « vous » du cas multi). */
  top: -8px; right: 8px;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--amber);
  color: #20160a;
  font-size: 9px; font-weight: 600; line-height: 1.4;
  letter-spacing: 0.02em;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  white-space: nowrap;
  z-index: 3;
}
.playing-badge .pulse-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: #20160a;
  animation: badge-dot-pulse 1.8s ease-in-out infinite;
}
@keyframes badge-dot-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.45; transform: scale(0.75); }
}

/* barres HUD segmentées */
.bar::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  pointer-events: none;
  background: repeating-linear-gradient(90deg, transparent 0, transparent calc(10% - 1px), rgba(0, 0, 0, 0.45) calc(10% - 1px), rgba(0, 0, 0, 0.45) 10%);
}

/* ================= ligne des personnages — bouton « + » ================= */
/* La création de personnage a déménagé ici depuis la page de sélection (carte
   t_bd5bec25) : un bouton calé à DROITE de la ligne (margin-left: auto), avec
   une cible tactile de 44 px comme le reste des gestes de table. */
.vital-add {
  margin-left: auto;
  align-self: center;
  flex: none;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  border: 1px dashed var(--border-strong);
  background: rgba(232, 163, 61, 0.08);
  color: var(--amber);
  font-family: var(--sans);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--ease), border-color var(--ease);
}
.vital-add:hover {
  background: rgba(232, 163, 61, 0.18);
  border-color: var(--amber);
}
.vital-add:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

/* ================= avatars / MJ ================= */
.gm-avatar {
  background: radial-gradient(circle at 35% 30%, #3a3166, #171029);
  color: var(--violet);
  box-shadow: 0 0 14px rgba(165, 141, 255, 0.25), 0 0 0 2px rgba(0, 0, 0, 0.4);
}

/* ================= chat ================= */
/* Carte t_2936a2e5 — plus d'animation d'entrée sur le fil : un message
   apparaît là où il reste (`rise` supprimée, elle déplaçait du texte). */
.msg-body { border-radius: 4px 16px 16px 16px; }
.msg.player .msg-body {
  border-radius: 16px 4px 16px 16px;
  background: rgba(150, 166, 205, 0.08);
  border-color: rgba(150, 166, 205, 0.28);
}
.msg-name { color: var(--violet); }
/* --- « Lire à haute voix » : contrôle EN LIGNE, sur la même ligne que le
       libellé d'auteur (carte t_d350abfe). Sans fond ni bordure : c'est une
       action discrète, pas un bouton de barre ; libellé et action de clic
       inchangés. Le `::after` invisible porte la cible tactile ≥ 44 px (même
       patron que `.turn-badge`) sans grossir l'icône. --- */
.msg-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.msg-speak {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* P3 (t_c3c5ec15) : boîte de 24 px au lieu de 22 — de l'air pour le glyphe
     emoji (encre mesurée 18×13 px, donc au-dessus de toute police de repli). */
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  transition: color var(--ease), filter var(--ease);
}
.msg-speak:hover { color: var(--amber); }
.msg-speak.playing { color: var(--amber); filter: drop-shadow(0 0 3px var(--amber)); }
.msg-speak:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.msg-speak:disabled { color: var(--muted); opacity: 0.45; cursor: default; }
.msg-speak::after { content: ""; position: absolute; inset: -10px; border-radius: 50%; } /* cible 44 px tenue (24 + 2×10) */
/* les bulles de joueur gardent leur nom aligné à droite */
.msg.player .msg-meta { justify-content: flex-end; }
.msg.player .msg-name { color: var(--ember); }
.scene-img { border-radius: 14px; box-shadow: 0 14px 34px rgba(0, 0, 0, 0.55); }
.msg-body:has(.scene-img) { max-width: min(760px, 86%); }

/* ================= barre d'action (HUD) ================= */
.actionbar:has(input:focus) {
  border-color: var(--border-strong);
  box-shadow: 0 0 0 1px var(--border-strong), 0 10px 34px rgba(0, 0, 0, 0.45), 0 0 26px rgba(165, 141, 255, 0.12);
}
/* « Envoyer » : le verbe pour les écrans larges, l'icône seule en mobile (le
   libellé reste dans le DOM, `aria-label` porte le nom accessible — voir le
   bloc `max-width: 720px`). Carte t_39088cb6. */
.send-icon { display: none; }

/* ================= fiche — panneau de personnage ================= */
.sheet {
  background:
    radial-gradient(130% 70% at 50% 0%, rgba(232, 163, 61, 0.06), transparent 55%),
    var(--surface-2);
}

/* portrait : d20 en filigrane derrière l'avatar */
.sheet-portrait { position: relative; }
.sheet-portrait::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--d20) center 70% / 150px no-repeat;
  opacity: 0.09;
  pointer-events: none;
}

/* attributs en tuiles tactiles */
.attr {
  position: relative;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.06) 0%, rgba(0, 0, 0, 0.30) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06), 0 4px 10px rgba(0, 0, 0, 0.35);
  transition: transform var(--ease-spring), box-shadow var(--ease), border-color var(--ease), background var(--ease);
}
.attr:hover {
  border-color: var(--amber);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.09) 0%, rgba(0, 0, 0, 0.26) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.09), 0 10px 22px rgba(0, 0, 0, 0.45), 0 0 18px rgba(232, 163, 61, 0.2);
}
.attr:active { transform: translateY(0) scale(0.96); }
.attr label { letter-spacing: 0.14em; }
.attr span {
  font-family: var(--display);
  font-size: 28px;
  font-weight: 600;
  color: var(--gold);
  text-shadow: 0 0 14px var(--ember-glow);
}

/* stats dérivées en tuiles secondaires */
.stat {
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.28);
  transition: border-color var(--ease), background var(--ease), transform var(--ease);
}
.stat:hover { border-color: var(--border-strong); }
.stat label { color: var(--text-dim); text-transform: uppercase; font-size: 10px; letter-spacing: 0.06em; }

/* ================= chips / jetons de traits ================= */
.chip {
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--amber-soft);
  transition: transform var(--ease), box-shadow var(--ease), border-color var(--ease);
}
.chip:hover { border-color: var(--border-strong); }
.chip b { color: var(--gold); }
.chip.adv   { border-color: rgba(127, 176, 105, 0.5); color: #a8d19a; background: rgba(127, 176, 105, 0.08); }
.chip.disav { border-color: rgba(193, 102, 107, 0.5); color: #e0a0a0; background: rgba(193, 102, 107, 0.08); }

/* ================= barre de points (création) ================= */
.points-bar .fill {
  background: linear-gradient(90deg, #7fb069, #a7c957);
  box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.35), 0 0 10px rgba(127, 176, 105, 0.4);
}
.points.over .fill {
  background: linear-gradient(90deg, #c1666b, #e07a5f);
  box-shadow: inset 0 -2px 4px rgba(0, 0, 0, 0.35), 0 0 10px rgba(224, 122, 95, 0.45);
}

/* ================= drawer — ordre d'initiative en jetons ================= */
.order { list-style: none; padding-left: 0; margin: 0; }
.order li {
  position: relative;
  padding: 7px 10px 7px 30px;
  margin: 4px 0;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.25);
  font-size: 14px;
  transition: border-color var(--ease), background var(--ease), transform var(--ease);
}
.order li::before {
  content: "";
  position: absolute;
  left: 10px; top: 50%;
  transform: translateY(-50%);
  width: 11px; height: 11px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.35), transparent 60%), rgba(150, 166, 205, 0.30);
  border: 1px solid var(--border-strong);
}
.order li:hover { border-color: var(--border-strong); }
.order li.current {
  color: var(--gold);
  font-weight: 600;
  border-color: var(--amber);
  background: linear-gradient(90deg, var(--amber-soft), rgba(0, 0, 0, 0.15));
  animation: token-pulse 1.8s ease-in-out infinite;
}
.order li.current::before {
  background: radial-gradient(circle at 35% 30%, #fff, var(--gold) 60%);
  border-color: var(--amber);
  box-shadow: 0 0 8px var(--ember-glow);
}
@keyframes token-pulse {
  50% { box-shadow: 0 0 0 1px var(--amber), 0 0 20px var(--ember-glow); }
}

/* ================= responsive fin ================= */
@media (max-width: 720px) {
  /* ── Barre d'action : UNE SEULE LIGNE (carte t_39088cb6) ─────────────────
     Elle se repliait sur QUATRE lignes à 375 px : le `select` « qui joue ? »
     — retiré depuis, carte t_82adfe4f — prenait la première (`width: 100%`),
     puis le champ de saisie tombait seul sur la troisième (son `min-width`
     automatique — la largeur intrinsèque d'un `<input>`, ~177 px — dépassait la
     place restante) et « Envoyer » sur la quatrième. Mesuré au harnais
     (375 px) : 4 lignes, barre de 214 px.
     Ici : rien ne se replie (`nowrap`), chaque contrôle cède dans l'ordre et
     la barre reste dans la largeur de l'écran.
     Deux verrous indispensables, chacun mesuré :
     1. `min-width: 0` sur la barre elle-même. `.game-screen` est une grille
        dont la colonne `auto` prend la largeur min-content de ses items : sans
        ça la somme des minima de la barre (633 px) ÉLARGIT la page entière
        (mesuré : `log` et `topbar` à 633 px, débordement horizontal) ;
     2. `min-width: 0` sur le champ : un flex item ne descend JAMAIS sous sa
        taille intrinsèque sans lui, donc le champ ne « compresse » pas.
     Cibles tactiles : 44 px de haut (même plancher que `.die-throw`, carte
     t_ed1a772d) et ≥ 44 px de large pour les boutons-icônes. */
  .actionbar {
    flex-wrap: nowrap;
    gap: 6px;
    padding: 8px;
    min-width: 0;
  }
  .actionbar .btn.icon { min-width: 44px; min-height: 44px; }
  .actionbar .btn.primary { min-width: 44px; min-height: 44px; padding: 9px 10px; }
  .actionbar input { flex: 1 1 auto; min-width: 0; }
  /* « Envoyer » passe à l'icône : le verbe coûtait 60 px. */
  .actionbar .send-label { display: none; }
  .actionbar .send-icon { display: inline; }
  /* États multi (table occupée, « c'est à X de lancer ») : ils cèdent au lieu
     de pousser la ligne hors de l'écran. `overflow: hidden` est nécessaire :
     `min-width: 0` laisse la boîte descendre sous son contenu, et le texte de
     `.turn-busy` (`white-space: nowrap`, `display: inline-flex`) déborderait
     alors AU-DESSUS des voisins (mesuré : 73 px de débordement à 375 px). */
  .actionbar .turn-busy,
  .actionbar .roll-notice { min-width: 0; }
  .actionbar .turn-busy { flex: 0 1 auto; overflow: hidden; }
  /* Barre du haut : la même règle de grille qu'en bas (voir ci-dessus) + un
     peu d'air rendu au titre (l'écart de 14 px coûtait 56 px au titre).
     Depuis la carte t_8011ed56 l'en-tête porte un contrôle de PLUS (le ⚔️ du
     journal, venu de la barre du bas) : l'écart descend à 6 px — la valeur de
     la barre du bas — et les icônes se resserrent de 3 px de chaque côté
     (leur hauteur de cible tactile, 44 px, ne bouge pas). C'est le TITRE qui
     cède le reste : il tronque, il ne se replie jamais. Coût mesuré de la
     carte : 60 px de titre ; ces deux réglages en rendent 54 (titre mesuré :
     76 px à 320 px, 129 px à 375 px — 81/136 px avant la carte). */
  .topbar { flex-wrap: nowrap; min-width: 0; gap: 6px; padding: 8px 10px; }
  .topbar .btn.icon { padding: 9px 9px; }
  .msg-text { font-size: 18px; line-height: 1.5; }
  .sheet-head { flex-direction: column; align-items: flex-start; gap: 10px; }
  .sheet-head-actions { width: 100%; justify-content: flex-start; }
  .setup-panel { padding: 24px 20px; }
  .title { font-size: 38px; }
}

/* 320–355 px : « La Table » à 38 px ne tient plus dans la colonne de la marque
   (flex item, la marque d20 + l'écart coûtent 46 px) et passait sur DEUX lignes
   — mesuré au harnais : le mot demande 219 px à 38 px pour 183 px disponibles à
   320 px (1 ligne dès 356 px de large). Il demande 181 px à 31 px : un cran de
   moins suffit, sans jamais déborder la boîte. Carte t_39088cb6. */
@media (max-width: 360px) {
  .title { font-size: 31px; }
}

@media (prefers-reduced-motion: reduce) {
  .btn.primary::before { display: none; }
  .order li.current { animation: none; }
  .playing-badge .pulse-dot { animation: none; }
  .vital { transition: none; }
  /* Cartes de partie (§7) : ni translation ni zoom, shimmer coupé (teinte
     plate), les transitions de couleur sont conservées. */
  .game-card,
  .game-card-wrap:hover .game-card { transform: none; }
  .game-card-bg,
  .game-card-wrap:hover .game-card-bg { transform: none; transition: none; }
  .game-card.skeleton { animation: none; background: var(--surface-2); }
}

/* ================= accueil : grille de cartes de partie =================
   Spec : docs/design-selection-creation-partie.md §2 — valeurs contractuelles
   (px + tokens play/tokens.css). Le panneau s'élarge pour la grille de cartes. */
.setup-panel { width: min(1040px, 100% - 32px); }
.head-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.head-row .setup-heading { margin: 0; }
.setup-heading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- carte de partie (§2.2) ---- */
.game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--sp-4);
  margin-top: 4px;
}
.game-card-wrap { position: relative; }
.game-card {
  width: 100%;
  min-height: 172px;
  aspect-ratio: 16 / 10;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  overflow: hidden;
  position: relative;
  display: block;
  text-align: left;
  cursor: pointer;
  background: var(--base-2);
  padding: 0;
  transition:
    transform var(--dur-2) var(--ease-out),
    border-color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
}
.game-card-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(1.05);
  transition: transform var(--dur-3) var(--ease-out);
}
.game-card-veil {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 6, 3, 0.15) 0%, rgba(10, 6, 3, 0.55) 42%, rgba(10, 6, 3, 0.92) 100%);
}
.game-card-body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.game-card-name {
  font-family: var(--display);
  font-variation-settings: var(--fv-display);
  font-size: 18px;
  color: var(--text);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.game-card-summary {
  font-family: var(--prose);
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(3 * 13px * 1.45); /* hauteur stable, résumé vide compris */
}
.game-card-summary.placeholder {
  font-size: 12px;
  font-style: italic;
  color: var(--text-dim);
}
.game-card-heroes { display: flex; gap: 6px; flex-wrap: wrap; }
.hero-chip {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: rgba(244, 238, 228, 0.10);
  border: 1px solid var(--border);
  color: var(--text);
  white-space: nowrap;
  /* Un nom de héros interminable s'ellipsoïde : la pastille ne déborde jamais
     de la carte (le nom complet reste dans l'aria-label, §2.2). */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hero-chip.more { color: var(--muted); }
.game-card-play {
  position: absolute;
  right: var(--sp-4);
  bottom: var(--sp-4);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.75;
  transition: opacity var(--dur-1);
}
.game-card-delete {
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  width: 28px;
  height: 28px;
  border-radius: var(--r-pill);
  background: rgba(10, 6, 3, 0.55);
  border: 1px solid var(--border);
  color: var(--muted);
  opacity: 0;
  cursor: pointer;
  transition: opacity var(--dur-1);
  font-size: 12px;
}

/* ---- états de la carte (§2.5) : hover / focus / lancement ---- */
.game-card-wrap:hover .game-card {
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-1);
}
.game-card-wrap:hover .game-card-bg { transform: scale(1.04); }
.game-card-wrap:hover .game-card-play { opacity: 1; }
.game-card-wrap:hover .game-card-delete { opacity: 1; }
.game-card:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}
.game-card-delete:focus-visible {
  opacity: 1;
  outline: none;
  box-shadow: var(--ring);
}
.game-card.state-active {
  border-color: var(--border-strong);
  box-shadow: var(--glow-amber);
}
.game-card-loading {
  position: absolute;
  inset: 0;
  background: rgba(10, 6, 3, 0.72);
  color: var(--gold);
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.game-grid.has-launching .game-card-wrap:not(.is-launching) {
  pointer-events: none;
  opacity: 0.6;
}

/* ---- repli décoratif sans image (§2.3) : dégradé + mark d20 + grain ---- */
.game-card-fallback {
  position: absolute;
  inset: 0;
  background: linear-gradient(150deg, #241a12, #14100b 60%);
}
.game-card-fallback::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--d20);
  background-size: 120px;
  background-position: center;
  background-repeat: no-repeat;
  opacity: 0.10;
}
.game-card-fallback::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--film-grain);
  opacity: var(--grain-opacity);
}

/* ---- dalle « Nouvelle partie » (§2.1) : dernière cellule de la grille ---- */
.new-card {
  min-height: 172px;
  aspect-ratio: 16 / 10;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: all var(--dur-2) var(--ease-out);
}
.new-card:hover {
  background: var(--amber-soft);
  color: var(--text);
  border-color: var(--amber);
}
.new-card .plus { font-size: 26px; color: var(--amber); }
.new-card .lbl {
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* ---- états de la liste (§2.4) : squelettes, vide, erreurs ---- */
.game-card.skeleton {
  border: 1px solid var(--border);
  background: linear-gradient(100deg, var(--surface-2) 40%, rgba(232, 163, 61, 0.10) 50%, var(--surface-2) 60%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
}
.empty-state {
  border: 1px dashed var(--border);
  border-radius: var(--r-md);
  padding: 44px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}
.empty-state .mark {
  width: 48px;
  height: 48px;
  background: var(--d20);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  opacity: 0.85;
}
.empty-state .t { font-size: 16px; color: var(--text); }
.empty-state .s {
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 8px;
}
.list-error {
  border: 1px solid rgba(224, 87, 74, 0.45);
  background: rgba(224, 87, 74, 0.10);
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 14px;
  font-size: 13.5px;
}

/* ---- mobile (§6) : panneau pleine largeur, 1 carte, CTA sous le titre ---- */
@media (max-width: 680px) {
  .setup-panel { width: 100%; padding: 20px 16px; }
  .head-row { flex-direction: column; align-items: stretch; }
  .game-grid { grid-template-columns: 1fr; }
}

.btn.ghost {
  background: transparent;
  border: 1px dashed var(--border-strong);
  color: var(--muted);
  font-weight: 500;
}
.btn.ghost:hover {
  border-color: var(--amber);
  color: var(--text);
  background: var(--amber-soft);
}

/* ================= fiche — archétype / passé / parcours ================= */
/* textarea (mêmes tokens que input/select, mais serif + multiligne) */
textarea {
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 10px 11px;
  font-size: 16px;
  font-family: var(--serif);
  line-height: 1.5;
  resize: vertical;
  min-height: 7em;
  transition: border-color var(--ease), box-shadow var(--ease), background var(--ease);
}
textarea::placeholder { color: var(--muted); }
textarea:hover:not(:disabled) { border-color: rgba(150, 166, 205, 0.42); }
textarea:focus {
  outline: none;
  border-color: var(--amber);
  box-shadow: var(--ring);
  background: rgba(0, 0, 0, 0.42);
}
textarea:disabled { opacity: 0.5; cursor: default; }

/* badge archétype — violet « concept/identité » */
.chip.arch {
  border-color: rgba(165, 141, 255, 0.5);
  color: #cbbaff;
  background: rgba(165, 141, 255, 0.10);
}

/* affordance archétype vide (setup uniquement) */
.arch-empty {
  display: inline-block;
  padding: 3px 10px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  transition: border-color var(--ease), color var(--ease);
}
.arch-empty:hover { border-color: var(--amber); color: var(--text); }

/* édition inline de l'archétype (sous la race) */
.arch-edit {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
}
.arch-edit-label {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.arch-edit select,
.arch-edit input { width: 100%; }
.counter {
  font-size: 12px;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.counter.over { color: #e07a5f; font-weight: 600; }

/* bloc narratif pleine largeur (passé / parcours) */
.sheet-story {
  border-top: 1px solid var(--border);
  padding-top: 16px;
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.story-block { display: flex; flex-direction: column; gap: 6px; }
.story-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.story-block h3 {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 12px;
  letter-spacing: 0.08em;
  margin: 0;
  font-family: var(--sans);
}
.story-text {
  margin: 0;
  font-family: var(--serif);
  font-size: 17px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}
.story-text.clamped {
  max-height: 7.5em;
  overflow: hidden;
  position: relative;
}
.story-text.clamped::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2.2em;
  background: linear-gradient(to bottom, rgba(13, 17, 32, 0), rgba(13, 17, 32, 0.96));
  pointer-events: none;
}
.story-toggle { flex: none; }

/* pied d'édition (Annuler / Enregistrer) */
.sheet-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}
.story-empty {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.sheet-story .story-textarea { width: 100%; }

@media (max-width: 720px) {
  .story-head { flex-wrap: wrap; }
  .story-empty { flex-direction: column; align-items: flex-start; }
}

/* ================= Carnet secret du MJ (overlay partagé setup + game) ================= */
.secrets-overlay {
  position: fixed;
  inset: 0;
  z-index: 6;
  background: rgba(5, 6, 12, 0.62);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  animation: fadeIn 180ms ease-out;
}
.secrets-confirm { z-index: 7; }

.secrets-panel {
  display: flex;
  flex-direction: column;
  width: min(1040px, 94vw);
  height: min(720px, 88vh);
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(165, 141, 255, 0.07), transparent 55%),
    var(--surface-2);
  border-radius: var(--radius-lg);
  padding: 0;
  overflow: hidden;
}

.secrets-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 18px 22px 14px;
  border-bottom: 1px solid var(--border);
  position: relative;
}
.secrets-head::after {
  content: "";
  position: absolute;
  left: 22px; bottom: -1px;
  width: 52px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--violet), transparent);
  box-shadow: 0 0 10px var(--violet-glow);
}
.secrets-head h2 {
  font-family: var(--display);
  color: var(--amber);
  margin: 0;
  font-size: 24px;
}
.secrets-head-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
.secrets-back { display: none; }

.secrets-banner {
  flex: none;
  margin: 12px 22px 0;
  padding: 8px 12px;
  border: 1px solid rgba(224, 85, 74, 0.4);
  background: rgba(224, 85, 74, 0.10);
  color: #f0b0aa;
  border-radius: 10px;
  font-size: 13px;
}

.secrets-load-error {
  flex: none;
  margin: 12px 22px 0;
  padding: 8px 12px;
  border: 1px solid var(--ember);
  background: rgba(255, 122, 61, 0.08);
  border-radius: 10px;
  color: #ffc9b0;
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.secrets-toolbar {
  flex: none;
  display: flex;
  gap: 14px;
  align-items: flex-end;
  flex-wrap: wrap;
  padding: 12px 22px 0;
}
.secrets-filter { display: flex; flex-direction: column; gap: 4px; }
.secrets-filter span { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
.secrets-filter select { min-width: 150px; }
.secrets-toggle {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  padding-bottom: 10px;
  user-select: none;
}
.secrets-toggle input { accent-color: var(--amber); }

.secrets-body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 16px;
  padding: 16px 22px 22px;
}

.secrets-list {
  overflow-y: auto;
  border-right: 1px solid var(--border);
  padding-right: 14px;
  min-height: 0;
}
.secrets-rows { display: flex; flex-direction: column; gap: 8px; }
.secret-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
  text-align: left;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.22);
  color: var(--text);
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  transition: border-color var(--ease), background var(--ease), transform var(--ease);
}
.secret-row:hover { border-color: var(--border-strong); }
.secret-row.active {
  border-color: var(--amber);
  background: var(--amber-soft);
  box-shadow: 0 0 0 1px var(--amber), 0 0 18px rgba(232, 163, 61, 0.16);
}
.secret-row.archived { opacity: 0.6; }
.secret-row.archived .secret-row-title { color: var(--muted); font-style: italic; }
.secret-row-title { font-family: var(--serif); font-weight: 600; font-size: 15px; line-height: 1.3; }
.secret-row-meta { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); }

/* chips type (distincts des .chip adv/disav/arch) */
.secret-chip {
  align-self: flex-start;
  display: inline-block;
  padding: 2px 8px;
  border-radius: 9px;
  font-size: 11px;
  border: 1px solid var(--border);
}
.secret-chip.type-thread { color: var(--amber); background: var(--amber-soft); border-color: rgba(232, 163, 61, 0.4); }
.secret-chip.type-twist  { color: var(--ember); background: rgba(255, 122, 61, 0.12); border-color: var(--ember-glow); }
.secret-chip.type-side   { color: #cbbaff; background: var(--violet-soft); border-color: rgba(165, 141, 255, 0.4); }
.secret-chip.type-rev    { color: var(--gold); background: rgba(246, 201, 106, 0.10); border-color: rgba(246, 201, 106, 0.35); }
.secret-chip.type-fore   { color: var(--muted); background: rgba(152, 162, 186, 0.10); border-color: rgba(152, 162, 186, 0.3); }

/* chips status */
.status-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 9px;
  font-size: 11px;
  border: 1px solid var(--border);
}
.status-chip.st-pending   { color: var(--muted); background: rgba(152, 162, 186, 0.10); }
.status-chip.st-active    { color: var(--amber); background: var(--amber-soft); border-color: rgba(232, 163, 61, 0.4); }
.status-chip.st-revealed  { color: #cbbaff; background: var(--violet-soft); border-color: rgba(165, 141, 255, 0.4); }
.status-chip.st-fulfilled { color: #a8e6bd; background: rgba(79, 191, 127, 0.12); border-color: rgba(79, 191, 127, 0.4); }
.status-chip.st-discarded { color: var(--text-dim); background: rgba(152, 162, 186, 0.08); text-decoration: line-through; }

.secrets-detail { overflow-y: auto; min-height: 0; padding-right: 4px; }

/* vue lecture */
.secret-view { display: flex; flex-direction: column; gap: 14px; }
.secret-view-head { display: flex; flex-direction: column; gap: 8px; }
.secret-view-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.secret-view-badges { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.secret-view-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.secret-view-head h3 { font-family: var(--serif); color: var(--amber); margin: 0; font-size: 22px; line-height: 1.25; }
.secret-view-meta { color: var(--muted); font-size: 13px; margin: 0; }
.secret-view-sections { display: flex; flex-direction: column; gap: 16px; }
.secret-section h4 {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.08em;
  margin: 0 0 6px;
  font-family: var(--sans);
}
.secret-text {
  margin: 0;
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}
.secret-secret {
  border-left: 3px solid var(--hp);
  padding-left: 12px;
}
.secret-secret h4 { color: #f0b0aa; }
.secret-coherence-block { margin-top: 12px; }

/* formulaire édition / création */
.secret-form { display: flex; flex-direction: column; gap: 16px; }
.secret-form-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.secret-form-head h3 { font-family: var(--serif); color: var(--amber); margin: 0; font-size: 20px; }
.secret-form-actions { display: flex; gap: 8px; }
.secret-fields { display: flex; flex-direction: column; gap: 14px; }
.secret-field { display: flex; flex-direction: column; gap: 6px; }
.secret-field label {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}
.secret-field input, .secret-field select, .secret-field textarea { width: 100%; }
.secret-field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.secret-field-note { margin: 0; font-size: 11px; }
.secret-summary { min-height: 5em; }
.secret-secret-input { min-height: 10em; }
.secret-trigger { min-height: 3em; }
.secret-coherence { min-height: 5em; }
.secret-secret-label { color: #f0b0aa !important; }
.secret-machine-toggle { align-self: flex-start; margin-top: 2px; }
.secret-machine {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  padding: 10px;
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
}
.secret-priority-value { color: var(--amber); font-variant-numeric: tabular-nums; }
.secret-field input[type="range"] { width: 100%; accent-color: var(--amber); padding: 0; }

/* stepper de cycle de vie */
.status-stepper-wrap {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.status-stepper { display: flex; gap: 4px; }
.status-step {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 4px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: rgba(0, 0, 0, 0.18);
  opacity: 0.5;
  text-align: center;
}
.status-step .status-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--text-dim); }
.status-step .status-label { font-size: 11px; color: var(--muted); line-height: 1.15; }
.status-step.done { opacity: 0.8; border-color: var(--border-strong); }
.status-step.done .status-dot { background: var(--muted); }
.status-step.current {
  opacity: 1;
  border-color: var(--amber);
  background: var(--amber-soft);
  box-shadow: 0 0 0 1px var(--amber), 0 0 14px rgba(232, 163, 61, 0.15);
}
.status-step.current .status-label { color: var(--text); font-weight: 600; }
.status-step.current .status-dot { background: var(--amber); box-shadow: 0 0 8px var(--ember-glow); }
.status-step.current.st-revealed .status-dot { background: var(--violet); }
.status-step.current.st-fulfilled .status-dot { background: var(--ok); }
.status-step.current.st-discarded .status-dot { background: var(--text-dim); }
.status-step.current.st-pending .status-dot { background: var(--muted); }
.status-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.secret-saved { color: var(--ok); font-size: 12px; animation: fadeIn 160ms ease-out; }

/* états vides */
.secret-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  padding: 30px 18px;
  color: var(--muted);
}
.secret-empty-icon { font-size: 40px; }
.secret-empty-detail { height: 100%; min-height: 260px; }
.secret-empty p { margin: 0; }
.secret-empty .muted { max-width: 440px; }

/* bouton topbar (la section de gestion du setup a suivi son bloc) */
.secrets-btn { position: relative; color: var(--violet); border-color: transparent; background: transparent; font-size: 18px; }
.secrets-btn:hover { color: var(--gold); background: var(--violet-soft); box-shadow: none; }
.secrets-badge {
  position: absolute;
  top: -6px; right: -8px;
  min-width: 18px; height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--violet);
  color: #16102a;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  pointer-events: none;
}

/* drill-in mobile */
@media (max-width: 720px) {
  .secrets-body { grid-template-columns: 1fr; }
  .secrets-list { border-right: none; padding-right: 0; }
  .secrets-panel.detail-open .secrets-back { display: inline-flex; }
  .secrets-panel.detail-open .secrets-list { display: none; }
  .secrets-panel:not(.detail-open) .secrets-detail { display: none; }
  .secrets-head h2 { font-size: 20px; }
  .secret-field-row { grid-template-columns: 1fr; }
  .secret-machine { grid-template-columns: 1fr; }
  .status-stepper { flex-wrap: wrap; }
  .status-step { min-width: 64px; }
}

/* ================= palette narrative — Firewatch (volet 1) ================= */
/* Le décor de jeu s'assombrit / rougit selon le dernier message du MJ.
   `body[data-mood]` est piloté par app.js (mots-clés nuit/danger), pas le scroll. */

/* --film-grain / --grain-tex : deplaces dans play/tokens.css. */

/* Voile plein écran posé au-dessus du fond (CSS + WebGL), sous le contenu.
   Couleurs et grain viennent des tokens d'ambiance (tokens.css §5.1) : un seul
   accent à la fois, fondu de 400 ms, jamais un cut. */
.mood-veil {
  position: absolute;
  inset: 0;
  z-index: var(--z-veil);
  pointer-events: none;
  opacity: 1;
  transition: background var(--dur-3) var(--ease-out);
  background:
    radial-gradient(125% 95% at 50% 0%, transparent 50%, var(--bg-2) 100%),
    linear-gradient(180deg, var(--bg-1) 0%, var(--bg-2) 100%);
}

/* Grain global (décision 2026-09-10) : calque dédié pour que l'intensité suive
   --grain-opacity (0.10 jour → 0.16 danger) au lieu d'être figée dans le SVG.
   Jamais au-dessus du texte : il vit dans le voile, sous le contenu. */
.mood-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grain-tex);
  opacity: var(--grain-opacity);
  transition: opacity var(--dur-3) var(--ease-out);
}

/* ================= voile + grain sur les visuels IA (volet 2) ================= */
/* Un <img> ne porte pas de ::after : on enveloppe l'illustration de scène
   dans .scene-frame. Vignette assombrie + teinte braise + grain film dédié. */

.scene-frame {
  position: relative;
  width: fit-content;
  max-width: 100%;
  margin-top: 10px;
  border-radius: 14px;
}
.scene-frame .scene-img { margin-top: 0; }
.msg.player .scene-frame { margin-left: auto; }

.scene-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: 14px;
  background:
    var(--film-grain),
    radial-gradient(120% 110% at 50% 42%, transparent 48%, rgba(8, 5, 3, 0.5) 100%),
    linear-gradient(160deg, rgba(255, 150, 70, 0.10) 0%, rgba(255, 122, 61, 0.04) 45%, rgba(90, 30, 15, 0.10) 100%);
}

/* ================= transition de scène (spec §8) ================= */
/* Fondu au noir à l'entrée en jeu : la bascule d'écran et l'application de
   l'ambiance se font pendant le noir. `prefers-reduced-motion` → coupe nette. */
.scene-curtain {
  position: fixed;
  inset: 0;
  z-index: var(--z-curtain);
  pointer-events: none;
  opacity: 0;
  background: var(--bg-0);
  transition: opacity var(--dur-4) var(--ease-out);
}
/* Entrée en jeu : le noir est IMMÉDIAT (pas de fondu vers le noir, sinon on
   attendrait 1,8 s avant de voir la scène) ; seule la sortie est fondue. */
.scene-curtain.on {
  opacity: 1;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .scene-curtain { display: none; }
}

/* ================= indicateur d'ambiance (topbar, icône seule) ================= */
/* Décision 2026-09-10 : icône seule ; le sens passe par `title`/`aria-label`
   (renseignés par app.js depuis MOOD_LABELS), jamais par un mot à l'écran. */

.ambience {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  color: var(--gold);
  background: rgba(232, 163, 61, 0.08);
  border: 1px solid rgba(232, 163, 61, 0.22);
  transition: color var(--ease), background var(--ease), border-color var(--ease),
    box-shadow var(--ease);
}
.ambience svg { width: 15px; height: 15px; display: block; }

.ambience.night {
  color: #a9bde4;
  background: rgba(120, 150, 220, 0.1);
  border-color: rgba(140, 170, 230, 0.24);
  box-shadow: inset 0 0 10px rgba(90, 130, 220, 0.16);
}

.ambience.danger {
  color: var(--ember);
  background: rgba(255, 122, 61, 0.1);
  border-color: rgba(255, 122, 61, 0.3);
  box-shadow: 0 0 12px rgba(255, 122, 61, 0.18);
}

/* ================= hiérarchie typographique (lot 2, spec §5.3) ================= */
/* Une seule famille (Science Gothic variable) : les rôles se distinguent par les
   AXES, jamais par une police supplémentaire. `font-variation-settings` n'est
   volontairement PAS posé sur `body` : il écraserait tous les `font-weight`
   existants (un `wght` d'axe prime sur `font-weight`). Chaque rôle est donc
   ciblé explicitement, du plus générique (UI) au plus spécifique (prose). */

/* --- UI : boutons, champs, chrome de table --- */
.btn,
button,
input,
select,
textarea,
.msg-actions,
.status-step,
.secret-row,
.vital-name {
  font-variation-settings: var(--fv-ui);
}

/* --- Display : titres et nom de campagne --- */
.title,
.campaign,
.setup-heading,
.secret-form-head h3,
.secret-view-head h3,
.creator h3 {
  font-variation-settings: var(--fv-display);
}

/* --- Prose : la voix du MJ (narration) et les récits de la fiche. C'est le seul
       rôle en SERIF (Literata) : la famille porte la différence, la graisse reste
       normale (400) car un serif maigre devient fragile sur fond sombre. --- */
.msg-text,
.story-text {
  font-family: var(--prose);
  font-optical-sizing: auto;
  font-variation-settings: var(--fv-prose);
  font-size: 19px;
  line-height: 1.62;
  max-width: var(--measure-prose);
  letter-spacing: normal;
}

/* --- Données GURPS : chiffres alignés (PV/FP, seuils, jets, points) --- */
.chip,
.stat span,
.stat b,
.attr span,
.points,
.vital-body .flag {
  font-variation-settings: var(--fv-data);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

/* --- Micro-labels : capitales serrées, réservées aux états --- */
.playing-badge,
.stat label,
.secret-row-meta,
.secret-chip,
.status-chip,
.status-step {
  font-variation-settings: var(--fv-label);
  letter-spacing: 0.06em;
}

/* Plancher de lisibilité de la prose (décision 2026-09-10) : jamais moins de
   16 px. Le `wght 320` devient trop fin en dessous. */
@media (max-width: 640px) {
  .msg-text { font-size: 16px; line-height: 1.6; }
}

/* ================= HUD de table (lot 3, spec §7.3) ================= */
/* Le personnage devient un JETON : portrait rond, anneau de PV à l'extérieur,
   anneau de FP à l'intérieur, chiffres en données GURPS. Le personnage actif est
   désigné par la LUMIÈRE (halo chaud), pas par une bordure de plus. */

.vitals { gap: var(--sp-3); }

/* Un jeton de table se cale sur son contenu : il ne s'étire pas pour remplir la
   rangée (sinon trois héros occupent toute la largeur). */
.vital {
  flex: 0 1 auto;
  min-width: 0;
}

.vital,
.vital:hover,
.vital.active {
  border-radius: var(--r-pill);
}

.token {
  position: relative;
  flex: 0 0 auto;
  display: block;
  width: 48px;
  height: 48px;
}
.token .rings { position: absolute; inset: 0; transform: rotate(-90deg); }
.ring-track { fill: none; stroke: rgba(12, 9, 6, 0.8); stroke-width: 3; }
.ring-fill {
  fill: none;
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke-dashoffset var(--dur-3) var(--ease-out);
}
.ring-fill.hp { stroke: var(--hp); filter: drop-shadow(0 0 3px rgba(224, 87, 74, 0.45)); }
.ring-fill.fp { stroke: var(--fp); }

.token-img,
.token-initial {
  position: absolute;
  inset: 5px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(232, 163, 61, 0.28);
}
.token-img { object-fit: cover; cursor: zoom-in; }
.token-initial {
  display: grid;
  place-items: center;
  background: var(--surface-2);
  color: var(--gold);
  font-size: 16px;
  font-variation-settings: var(--fv-display);
}

/* Ordre de tour : visible seulement pendant un combat. */
.turn-badge {
  position: absolute;
  top: -2px; left: -2px;
  min-width: 16px; height: 16px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--gold);
  color: #24170a;
  font-size: 10px;
  font-variation-settings: var(--fv-data);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* Personnage ACTIF : désigné par la lumière. */
.vital.active {
  background:
    radial-gradient(120% 150% at 6% 50%, rgba(232, 163, 61, 0.2), transparent 62%),
    var(--surface-2);
  border-color: transparent;
  box-shadow: 0 0 0 1px rgba(232, 163, 61, 0.45), 0 0 28px rgba(232, 163, 61, 0.16);
}
.vital.active .token::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(closest-side, var(--accent-glow), transparent 72%);
}
/* Le marqueur « ▸ » d'origine fait doublon avec le halo et le badge. */
.vital.active::after { content: none; }

/* Tour en cours : les anneaux s'allument. */
.vital.turn .rings { filter: drop-shadow(0 0 5px var(--accent-glow)); }

/* Jauges chiffrées : données GURPS (tnum), pas de décimales bancales. */
.gauges { display: flex; gap: 10px; margin-top: 1px; }
.gauge { display: inline-flex; align-items: baseline; gap: 2px; }
.gauge b {
  font-size: 14px;
  font-variation-settings: var(--fv-data);
  font-feature-settings: "tnum";
}
.gauge i {
  font-style: normal;
  font-size: 11px;
  color: var(--text-dim);
  font-variation-settings: var(--fv-data);
}
.gauge small {
  font-size: 9px;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  font-variation-settings: var(--fv-label);
}
.gauge.hp b { color: var(--hp); }
.gauge.fp b { color: var(--fp); }

/* ================= scène en fond de fil (lot 3, spec §7.3) ================= */
/* La dernière illustration s'installe DERRIÈRE la prose, très effacée (≈ 0.18) :
   on lit toujours le texte en premier. `background-attachment: scroll` sur un
   conteneur qui défile garde l'image fixe pendant que la prose monte — c'est
   exactement l'effet recherché, sans calque ni JS supplémentaire. */
.log.has-scene {
  background-image:
    linear-gradient(180deg, rgba(18, 14, 10, 0.84) 0%, rgba(12, 9, 6, 0.9) 100%),
    var(--scene);
  background-size: cover, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}

/* ================= gestes de table (lot 5, spec §8) ================= */
/* Un seul effet « fort » par action. Aucun geste ne déplace le texte pendant la
   lecture. `prefers-reduced-motion` neutralise tout ce qui bouge (les durées
   tombent à 0.01 ms via tokens.css, et le bloc final coupe les animations). */

/* --- Lueur bougie : le survol ÉCLAIRE (ombre chaude), jamais un gris neutre --- */
.btn:hover,
.chip:hover,
.secret-row:hover,
.story-toggle:hover,
.char-card:hover,
.vital:hover {
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35), 0 0 18px var(--accent-glow);
}

/* --- Poids au clic : réponse élastique courte, pas un sursaut --- */
.btn:active,
.chip:active,
.secret-row:active,
.story-toggle:active,
.char-card:active,
.vital:active {
  transform: scale(0.985);
  transition: transform var(--dur-1) var(--ease-spring);
}

/* --- Prose : plus de révélation animée (carte t_2936a2e5). Le message du MJ
       s'affiche d'emblée dans sa position finale : `prose-in` supprimée, elle
       déplaçait chaque nouveau texte de 6 px vers le haut. --- */

/* --- Dégâts : le portrait flashe braise (lumière seule, rien ne recule).
       `damage-recoil` supprimée (carte t_2936a2e5) : le jeton ne bouge plus. --- */
.vital.damaged .token { animation: damage-flash var(--dur-3) var(--ease-out); }
@keyframes damage-flash {
  0%   { filter: none; }
  35%  { filter: brightness(1.25) saturate(1.35) drop-shadow(0 0 10px var(--ember-glow)); }
  100% { filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vital.damaged .token { animation: none; }
  .btn:active,
  .chip:active,
  .secret-row:active,
  .story-toggle:active,
  .char-card:active,
  .vital:active { transform: none; }
}

/* ================= finition (lot 7, spec §9 + §7.7) ================= */

/* --- Cibles tactiles : la ZONE cliquable fait ≥ 44 px sans grossir le bouton
       (un badge de 44 px écraserait le jeton de table). --- */
.turn-badge::after {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 50%;
}
.turn-badge::after { inset: -14px; }
.ambience { position: relative; }
.ambience::after {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
}

/* --- Focus visible sur TOUT l'interactif, y compris jetons et cartes --- */
.vital:focus-visible,
.char-card:focus-visible,
.secret-row:focus-visible,
.token-img:focus-visible,
.ambience:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- Chargement : le MJ respire (braise) au lieu d'un gris statique --- */
.msg-text.typing {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--accent);
  font-style: normal;
}
.msg-text.typing::before {
  content: "";
  flex: none;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--accent), transparent 74%);
  animation: ember-breathe 2.4s ease-in-out infinite;
}
/* l'ancien « … » figé laisse la place aux trois points animés */
.msg-text.typing::after { content: none; }
.typing-dots { display: inline-flex; gap: 3px; }
.typing-dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  animation: dot-wave 1.4s ease-in-out infinite;
}
.typing-dots i:nth-child(2) { animation-delay: 0.16s; }
.typing-dots i:nth-child(3) { animation-delay: 0.32s; }

@keyframes ember-breathe {
  0%, 100% { opacity: 0.35; transform: scale(0.8); }
  50%      { opacity: 1;    transform: scale(1.2); }
}
@keyframes dot-wave {
  0%, 100% { opacity: 0.25; transform: translateY(0); }
  50%      { opacity: 1;    transform: translateY(-2px); }
}

/* --- Fil vide : la table est dressée, la scène n'a pas commencé --- */
.log-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin: 16vh auto 0;
  max-width: 34ch;
  text-align: center;
  color: var(--muted);
}
.log-empty::before {
  content: "";
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: radial-gradient(closest-side, var(--accent), transparent 74%);
  animation: ember-breathe 3.2s ease-in-out infinite;
}

/* --- Bandeau d'erreur : ambre + reprise, jamais un alert() --- */
.banner {
  background: linear-gradient(180deg, rgba(232, 163, 61, 0.18), rgba(255, 122, 61, 0.1));
  border-color: var(--border-strong);
  color: var(--text);
}
.banner::before { content: "⚠"; color: var(--accent); flex: none; }

/* --- Région d'annonce (lecteurs d'écran) --- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Champs et listes : indicateur de focus explicite, aligné sur l'accent
       (les `<select>` des panneaux n'avaient qu'un anneau implicite). --- */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--border-strong);
}

@media (prefers-reduced-motion: reduce) {
  .msg-text.typing::before,
  .log-empty::before,
  .typing-dots i { animation: none; }
}

/* --- Mobile (spec §7.7) : rien ne déborde, la table reste lisible --- */
@media (max-width: 760px) {
  /* Le titre de campagne tient sur UNE SEULE ligne, tronqué par une ellipse
     (carte t_39088cb6). Il en occupait TROIS à 375 px, poussant le reste de la
     barre du haut. Le commentaire précédent disait qu'un « nowrap + ellipse sur
     un inline-flex étire la barre au lieu de tronquer » : c'était vrai *de
     l'inline-flex*, pas de l'ellipse — un flex item refuse de descendre sous sa
     taille intrinsèque sans `min-width: 0`, donc `flex: 1 1 auto` + `min-width:
     0` + `display: block` (l'ellipse s'applique au bloc, pas au flex container).
     Le d20 redevient un `inline-block` (dans un bloc, un pseudo n'est plus un
     flex item : sans ça, ses 20 px de largeur seraient ignorés).
     Mesuré à 375 px : 1 ligne, 108 px de large, plus aucun repli de la barre. */
  .campaign {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    font-size: 17px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .campaign::before {
    display: inline-block;
    width: 16px; height: 16px;
    margin-right: 8px;
    vertical-align: -3px;
  }
  /* Les jetons s'empilent EN COLONNE (un par ligne) : sur un écran étroit, la
     rangée horizontale défilante écrasait chaque jeton (`min-width: 0`) et les
     jauges PV/FP d'un joueur débordaient sur le jeton voisin — chevauchement
     signalé le 11/09. Un jeton par ligne rend sa largeur au contenu, plus de
     débordement horizontal (scrollWidth == clientWidth). */
  .vitals {
    flex-wrap: wrap;
    min-width: 0;
  }
  .vital {
    flex: 1 1 100%;
  }
  /* Le badge « En train de jouer » occupe seul le coin supérieur droit —
     il est remonté pour ne pas être rogné par le bord du jeton (l'ancien
     bouton 👁 qui le chevauchait est retiré, carte t_d350abfe). */
  .playing-badge {
    top: -11px;
  }
  /* Le tiroir prend toute la largeur : pas de panneau qui flotte à moitié. */
  .drawer { width: 100%; }
}

/* 320–360 px (P1, carte t_c3c5ec15) : le titre de campagne était réduit à un
   moignon — mesuré à 320 px : 63,2 px de boîte dont 24 px de d20 → 39 px de
   texte, « Véri… ». Sous 360 px, le DÉCOR cède la place au TEXTE : le d20 du
   titre et l'indicateur d'ambiance (deux ornements `role="img"`) sont masqués,
   les icônes de l'en-tête se resserrent de 2 px de padding par côté
   (35,5 → 27,5 px de large, 44,7 → 36,7 pour le ⚔️ ; leur hauteur de cible
   tactile, 45-47 px, ne bouge pas) et le titre garde 110 px de texte lisible
   (`min-width`). Mesuré après, à 320 px : 39 → 118,2 px utiles (« Vérif du… »,
   9 caractères au lieu de 1), une seule ligne, 0 collision, bord droit de la
   barre respecté.
   Le resserrage des icônes est INDISPENSABLE, pas une option : la barre n'a que
   270 px de contenu à 320 px (mesuré) et les quatre boutons + le retour + un
   titre de 110 px n'y rentrent pas sans lui (293,7 px → le ⚔️ dépassait du
   bord droit de la barre de 22,8 px, mesuré). Au-delà de 360 px rien ne bouge :
   le d20, l'ambiance et le padding des icônes restent ceux des blocs 720/760.
   Le d20 ne disparaît QUE sous ce seuil : la règle du bloc 760 (garantie par
   `test_the_d20_mark_stays_visible_in_the_title`) reste intacte. Ce bloc est
   placé APRÈS le bloc 760 pour l'emporter en cascade sur ses règles. */
@media (max-width: 360px) {
  .campaign::before { display: none; }
  .topbar .ambience { display: none; }
  .topbar .btn.icon { padding: 9px 5px; }
  .campaign { min-width: 110px; }
}

/* 761 px et plus : la barre de bureau n'a plus la place de laisser le titre se
   REPLIER (l'en-tête porte un contrôle de plus depuis la carte t_8011ed56 : le
   ⚔️ du journal). Mesuré à 768 px : le titre passait sur DEUX lignes et la
   barre grandissait de 66 à 84 px, poussant la table vers le bas. Il tronque
   donc comme sur mobile, mais garde sa facture de bureau (23 px, d20 de 20 px,
   recentré au `vertical-align` — un pseudo n'est plus un flex item dans un
   bloc). Le bloc `max-width: 760px` ci-dessus le fait déjà à sa taille. */
@media (min-width: 761px) {
  /* Le verrou de la grille (même cause qu'en dessous de 761 px, cf. le bloc
     `max-width: 720px`) : sans lui la colonne `auto` de `.game-screen` prend
     la largeur min-content de la barre — un titre `nowrap` ne peut plus se
     replier, et la page entière s'élargit (mesuré : débordement de 15 px à
     768 px, la barre à 769 px pour un écran de 768). */
  .topbar { min-width: 0; }
  .campaign {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .campaign::before {
    display: inline-block;
    width: 20px; height: 20px;
    margin-right: 10px;
    vertical-align: -4px;
  }
}

/* ================= mouvement : le fond seul bouge (carte t_2936a2e5) ======= */
/* Règle de la carte « background image movement — make it more subtle » :
   aucun texte et aucune image hors fond ne bouge — ni à l'affichage, ni au
   survol, ni au défilement, ni au redimensionnement. Les gestes d'entrée qui
   déplaçaient du texte ou une image sont supprimés, pas neutralisés :
   - `secret-flip`    : retournement 3D de la carte secret (420 ms) ;
   - `reveal-clip`    : révélation par volets des portraits / de la lightbox ;
   - parallaxe du cadre : l'illustration du MJ suivait la souris (`--par-x`).
   Chaque élément s'affiche donc dans sa position finale. Seule exception : les
   changements de lumière qui ne déplacent rien (lueur de survol, flash braise
   des dégâts, pastilles d'état).

   --- Parallaxe : réservée au calque d'image de fond ---
   Un seul consommateur : `.log.has-scene`, l'illustration de scène posée
   derrière la prose. Amplitude ±2 px (PARALLAX_RANGE = 4 dans app.js) et suivi
   amorti par lerp lent (PARALLAX_EASE) : le fond respire à peine. */
.log.has-scene {
  background-position: center, calc(50% + var(--par-x, 0px)) calc(50% + var(--par-y, 0px));
}

@media (prefers-reduced-motion: reduce) {
  .log.has-scene { background-position: center, center; }
}

/* ================= le dé (lot 4, spec §8 + ADR-008) ================= */
/* Le dé du MJ occupe la place du champ de saisie tant qu'un jet attend (le
   moteur refuse le tour pendant ce temps : narrer la suite serait mentir).
   Il ne porte JAMAIS l'information seul — le libellé, le seuil et le verdict
   sont du texte (le graphique est `aria-hidden`), et le chiffre affiché est
   celui du moteur : le navigateur ne tire rien lui-même. */

.die {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
}

/* La forme : la SVG d20 existante (token --d20), avec sa face au centre.
   Les arêtes internes convergent au centre : sans le disque sombre, le chiffre
   se perdrait dedans (vérifié en navigateur, captures du 11/09). */
.die-shape {
  position: relative;
  flex: none;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  background: var(--d20) center / contain no-repeat;
}
.die-shape::before {
  content: "";
  position: absolute;
  inset: 21%;
  border-radius: 50%;
  background: var(--bg-0);
  transition: opacity var(--dur-1) var(--ease-out);
}
/* Le dé n'a pas encore de valeur : on montre la forme, pas un disque vide. */
.die.waiting .die-shape::before { opacity: 0; }

/* --- Le dé 3D : trois d6 en three.js (carte t_afc611ba, spec de rendu §2) ---
   En mode 3D la boîte s'élargit : trois d6 de 20 px (4 px d'écart) côte à côte
   dans un canvas de 68 x 20, puis 6 px, puis la SOMME (`.die-value`) À CÔTÉ —
   elle n'est plus posée sur les dés (règlement de R5 par construction). La
   hauteur reste 46 px : aucun reflow entre les deux modes, zéro CLS.
   En mode SVG, AUCUNE de ces règles ne s'applique : la boîte reste le carré
   46 x 46 d'aujourd'hui, jeton `--d20` compris (c'est le repli). */
.die-canvas {
  flex: none;
  width: 68px;
  height: 20px;
  display: block;
  pointer-events: none;
}
.die-shape[data-die-mode="3d"] {
  width: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  background-image: none;
}
/* La somme n'est plus posée SUR les dés : le disque sombre n'a plus d'objet. */
.die-shape[data-die-mode="3d"]::before { display: none; }

.die-value {
  position: relative;
  font-family: var(--display);
  font-size: 15px;
  font-variation-settings: var(--fv-data);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  text-shadow: 0 0 10px var(--accent-glow);
}

.die-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.die-label {
  font-size: 14px;
  font-variation-settings: var(--fv-label);
  color: var(--text);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.die-reason,
.die-status {
  font-size: 12px;
  color: var(--text-dim);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.die-throw { flex: none; }
.die-throw.rolling { opacity: 0.72; }

/* --- Le lancer : le dé quitte la main et retombe (700 ms, ressort) --- */
.die.rolling .die-shape { animation: die-throw 700ms var(--spring) both; }
@keyframes die-throw {
  0%   { transform: translateY(-16px) rotate(-32deg) scale(0.82); opacity: 0.45; }
  38%  { transform: translateY(3px) rotate(18deg) scale(1.1); }
  68%  { transform: translateY(-5px) rotate(-7deg) scale(0.97); }
  100% { transform: none; opacity: 1; }
}

/* --- Le moteur tranche : le dé reste retourné et respire en attendant la
       conséquence narrée (l'appel du MJ peut être long) --- */
.die.settling .die-shape { animation: die-tumble 1.2s ease-in-out infinite; }
@keyframes die-tumble {
  0%, 100% { transform: rotate(-7deg); }
  50%      { transform: rotate(7deg); }
}

/* --- La chute : la valeur RÉELLE se pose, et ne bouge plus ensuite --- */
.die.landed .die-shape { animation: die-land 360ms var(--spring) both; }
@keyframes die-land {
  0%   { transform: scale(1.22); }
  55%  { transform: scale(0.96); }
  100% { transform: none; }
}
.die.landed.success .die-shape { filter: drop-shadow(0 0 12px var(--accent-glow)); }
.die.landed.success .die-status { color: var(--accent); }
/* Un échec ne s'alarme pas : il s'éteint (la braise reste pour le danger). */
.die.landed.failure .die-shape { opacity: 0.82; }

/* --- Le dé rangé dans le fil : le jet qui a produit la prose --- */
.msg-roll {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 2px;
  color: var(--text-dim);
  font-size: 13px;
}
.msg-roll-text b { color: var(--text); font-variation-settings: var(--fv-label); }
.msg-roll.success .die-value { color: var(--accent); }
.msg-roll.failure .die-value { color: var(--text-dim); text-shadow: none; }
.msg-roll.failure .die-shape { opacity: 0.82; }
/* Variante réduite de la forme (le dé du fil, pas celui de l'actionbar). */
.die-shape.small { width: 30px; height: 30px; }
.die-shape.small .die-value,
.die-value.small { font-size: 11px; }

@media (prefers-reduced-motion: reduce) {
  /* La valeur s'affiche sans animation (le bloc global de style.css coupe déjà
     tout ; celui-ci protège le dé si cette règle vient à changer). */
  .die.rolling .die-shape,
  .die.settling .die-shape,
  .die.landed .die-shape { animation: none; }
  /* Wizard (§8) : le changement d'étape ne fait pas de fondu animé. */
  .wizard-body { animation: none; }
  .wizard-progress-fill { transition: none; }
}

/* Mobile : le dé garde le libellé à côté de la face, le bouton passe dessous,
   pleine largeur et 44 px de haut — jamais tronqué. Le bouton en fait 42
   naturellement (15 px de texte + 18 px de padding + 2 px de bordure) : c'est
   `min-height` qui le porte à 44 px (cible tactile), pas un padding propre à
   ce bouton-là. Mesuré au harnais à 375 px ; l'assertion du lot 4 exige ≥ 32. */
@media (max-width: 760px) {
  .die { flex-wrap: wrap; gap: var(--sp-2); }
  .die-copy { flex: 1 1 auto; }
  .die-throw { flex: 1 0 100%; min-height: 44px; }
}

/* Échantillon d'audition du Voice design (écran « fiche terminée » du créateur
   de personnage) : bouton pleine largeur, dans le même ton que le portrait. */
.creator-done .voice-sample {
  width: 100%;
  margin: var(--sp-2) 0;
}

/* ==========================================================================
   Wizard « Nouvelle partie » — spec docs/design-selection-creation-partie.md §3.
   Onboarding 3 étapes (campagne → aventuriers → lancement). Valeurs
   contractuelles (§3.1-§3.2) en tokens play/tokens.css uniquement.
   ========================================================================== */
.wizard-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: rgba(10, 6, 3, 0.62);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
}

.wizard {
  width: min(1060px, 100% - 32px);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  border-radius: var(--r-lg);
  padding: 0;
  display: flex;
  flex-direction: column;
}

.wizard-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.wizard-head h2 {
  font-family: var(--display);
  font-variation-settings: var(--fv-display);
  font-size: 20px;
  margin: 0;
}

/* Stepper : pastilles 28px reliées par une ligne 2px. */
.wizard-steps {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  padding: var(--sp-4) var(--sp-5) var(--sp-2);
}
.wizard-step { display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0; }
.wizard-step:last-child { flex: 0 0 auto; }
.wizard-step:not(:last-child)::after {
  content: "";
  flex: 1;
  height: 2px;
  background: var(--border);
  border-radius: var(--r-pill);
}
.wizard-step.done:not(:last-child)::after { background: var(--amber); }
.wizard-dot {
  width: 28px;
  height: 28px;
  flex: 0 0 28px;
  border-radius: var(--r-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  padding: 0;
  background: transparent;
  cursor: default;
}
.wizard-step.done .wizard-dot {
  background: var(--amber);
  color: var(--bg-0);
  border: none;
  cursor: pointer;
}
.wizard-step.current .wizard-dot { border: 2px solid var(--amber); color: var(--gold); }
.wizard-step.todo .wizard-dot { border: 1px solid var(--border); color: var(--text-dim); }
.wizard-step-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text);
  white-space: nowrap;
}
.wizard-step.todo .wizard-step-label { color: var(--text-dim); }
button.wizard-dot:focus-visible { outline: none; box-shadow: var(--ring); }

/* Barre de progression (scaleX 0.33 / 0.66 / 1). */
.wizard-progress {
  height: 3px;
  margin: 0 var(--sp-5) var(--sp-3);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  overflow: hidden;
}
.wizard-progress-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--amber);
  transform-origin: left;
  transition: transform var(--dur-3) var(--ease-out);
}

.wizard-body {
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-height: 0;
  animation: wiz-fade var(--dur-2) both;
}
@keyframes wiz-fade { from { opacity: 0; } to { opacity: 1; } }

.wizard-floor {
  margin: 0;
  padding: 0 var(--sp-5);
  font-family: var(--prose);
  font-size: 12px;
  color: var(--muted);
}

.wizard-foot {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* États IA (§3.6) : attente longue + bandeau de tour échoué (§2.4 partagé). */
.wiz-slow {
  margin: var(--sp-2) 0 0;
  font-family: var(--prose);
  font-size: 12px;
  color: var(--text-dim);
}
/* Le champ de saisie des créations doit pouvoir rétrécir sous sa largeur
   intrinsèque (~177 px) : sans `min-width: 0`, la barre (micro + champ +
   Envoyer) déborde à 375 px et le bouton d'envoi sort de l'écran (mesuré au
   harnais mobile — piège documenté du skill rpg-engine). */
.creator-bar input { min-width: 0; }
.list-error {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  border: 1px solid rgba(224, 87, 74, 0.45);
  background: rgba(224, 87, 74, 0.10);
  border-radius: var(--r-sm);
  padding: var(--sp-3) var(--sp-4);
  font-size: 13px;
  color: var(--text);
}

/* Rangée des aventuriers déjà créés (§3.4) + carte 88px (aspect 3:4). */
.wiz-hero-row { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
.wiz-hero {
  width: 88px;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--base-2);
  overflow: hidden;
  position: relative;
  padding: 0;
  cursor: pointer;
  color: var(--text);
  transition: border-color var(--dur-1) var(--ease-out);
}
.wiz-hero:hover { border-color: var(--border-strong); }
.wiz-hero:focus-visible { outline: none; box-shadow: var(--ring); }
.wiz-hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.wiz-hero-initial {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--display);
  font-size: 24px;
  color: var(--muted);
  background: linear-gradient(150deg, #241a12, #14100b 60%);
}
.wiz-hero small {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 4px 6px;
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: rgba(10, 6, 3, 0.72);
}
.wiz-hero-add {
  border-style: dashed;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: transparent;
  color: var(--muted);
}
.wiz-hero-plus { font-size: 20px; color: var(--amber); transition: color var(--dur-1); }
.wiz-hero-add:hover { border-color: var(--border-strong); color: var(--text); background: var(--amber-soft); }

/* Récapitulatif étape 3 (§3.5) : centré, 560px max. */
.wiz-recap-card {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  text-align: center;
}
.wiz-recap-cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
}
.wiz-recap-cover.fallback {
  background: linear-gradient(150deg, #241a12, #14100b 60%);
  position: relative;
}
.wiz-recap-cover.fallback::after {
  /* Repli décoratif §2.3 : mark d20 en filigrane + grain. */
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--d20), var(--film-grain);
  background-repeat: no-repeat, repeat;
  background-position: center, center;
  background-size: 120px, 160px;
  opacity: 0.10;
}
.wiz-recap-name {
  font-family: var(--display);
  font-variation-settings: var(--fv-display);
  font-size: 18px;
  margin: 0;
}
.wiz-recap-summary {
  font-family: var(--prose);
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.wiz-incident { margin: 0; font-size: 12px; color: var(--text-dim); }

/* Responsive (§6) : sous 680px, libellés masqués, panneau pleine largeur,
   split de l'étape 2 empilé verticalement. */
@media (max-width: 680px) {
  .wizard-overlay { padding: 12px; }
  .wizard { width: 100%; max-height: calc(100dvh - 24px); }
  .wizard-steps { justify-content: center; }
  .wizard-step-label { display: none; }
  .wizard-step:not(:last-child)::after { min-width: 24px; }
  .wizard-body { padding: var(--sp-4); }
  .wizard-head,
  .wizard-foot { padding: var(--sp-3) var(--sp-4); }
  .char-creator-grid { grid-template-columns: 1fr; }
}
