/* ============================================================
   Échange — le dialogue réparé. Static, vanilla, no build.
   ============================================================ */

:root {
  --font: "Rajdhani", "Avenir Next", system-ui, sans-serif;
  --bg: #07060f;
  --fg: #f6f4ff;
  --muted: #9aa0c8;

  --a: #fb7185;      /* actor A — warm (celui qui exprime) */
  --b: #3b82f6;      /* actor B — cool (celui qui répond) */
  --b-2: #60a5fa;

  --bad: #f43f5e;    /* bulle violente */
  --good: #34d399;   /* bulle réparée */

  --glass: rgba(20, 18, 40, 0.55);
  --border: rgba(255, 255, 255, 0.10);
  --radius: 18px;
  --radius-pill: 999px;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { color-scheme: dark; }
[hidden] { display: none !important; }

body {
  min-height: 100dvh;
  font-family: var(--font);
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  color: var(--fg);
  display: flex;
  justify-content: center;
  padding: 32px 18px;
  overflow-x: hidden;
  background:
    radial-gradient(42vw 42vw at 12% 8%, rgba(251, 113, 133, 0.10), transparent 62%),
    radial-gradient(42vw 42vw at 90% 12%, rgba(59, 130, 246, 0.10), transparent 62%),
    radial-gradient(50vw 50vw at 50% 100%, rgba(129, 140, 248, 0.07), transparent 64%),
    var(--bg);
}

/* La scène (théâtre à deux personnages) profite d'une largeur généreuse pour
   déployer ses corridors extérieurs ; l'accueil, lui, reste une colonne de
   lecture étroite et centrée (voir .home). */
.app { width: 100%; max-width: 1320px; }

/* ─── Screens ─── */
.screen { display: none; }
.screen--active { display: block; }
/* La scène est une « diapo » : on la centre verticalement dans la fenêtre pour
   qu'elle ne reste pas collée en haut avec un grand vide en dessous. */
#screen-stage.screen--active {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(100dvh - 64px);
}

/* ─── Boutons ─── */
.btn {
  font-family: inherit;
  font-weight: 700;
  letter-spacing: 0.02em;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-pill);
  transition: transform 0.14s ease, opacity 0.15s, box-shadow 0.2s, background 0.2s;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-lg { padding: 14px 34px; font-size: 17px; }
.btn-primary {
  color: #07060f;
  background: linear-gradient(120deg, var(--b), var(--b-2));
  box-shadow: 0 0 26px rgba(59, 130, 246, 0.28);
}
.btn-primary:hover:not(:disabled) { opacity: 0.92; box-shadow: 0 0 34px rgba(59, 130, 246, 0.42); }
.btn-secondary {
  padding: 12px 26px; font-size: 15px;
  color: var(--fg);
  background: var(--glass);
  border: 1.5px solid var(--border);
}
.btn-secondary:hover { border-color: rgba(255, 255, 255, 0.28); }
.btn-ghost {
  padding: 10px 20px; font-size: 14px;
  color: var(--muted);
  background: transparent;
  border: 1.5px solid var(--border);
}
.btn-ghost:hover { color: var(--fg); border-color: rgba(255, 255, 255, 0.28); }
.btn-ghost[aria-pressed="true"] { color: #07060f; background: var(--fg); border-color: var(--fg); }
.btn-icon {
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 20px; line-height: 1;
  color: var(--fg);
  background: var(--glass);
  border: 1.5px solid var(--border);
  border-radius: 50%;
  cursor: pointer;
}
.btn-icon:hover { border-color: rgba(255, 255, 255, 0.28); }
.btn-icon:disabled { opacity: 0.35; cursor: not-allowed; }

/* ─── Accueil ─── */
.kicker {
  text-transform: uppercase;
  letter-spacing: 0.32em;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  text-align: center;
}
.home { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; padding-top: 12px; width: 100%; max-width: 640px; margin-inline: auto; }
.home__title {
  font-size: clamp(40px, 11vw, 68px);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  background: linear-gradient(110deg, #fff 4%, var(--a) 40%, var(--b) 92%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.home__sub { max-width: 46ch; color: var(--muted); font-size: 16px; line-height: 1.6; }
.home__sub strong { color: var(--fg); font-weight: 600; }

.picker { width: 100%; margin-top: 8px; border: none; }
.picker legend {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); margin-bottom: 14px; text-align: center; width: 100%;
}
.picker__loading { color: var(--muted); text-align: center; }
.picker__groups { display: flex; flex-direction: column; gap: 20px; }
.picker__group-label {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted); margin-bottom: 8px;
}
.picker__options { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.chip-choice {
  font-family: inherit; font-size: 14px; font-weight: 500;
  padding: 9px 16px; border-radius: var(--radius-pill);
  color: var(--fg); background: var(--glass);
  border: 1.5px solid var(--border); cursor: pointer;
  transition: border-color 0.16s, background 0.16s, transform 0.12s;
}
.chip-choice:hover { border-color: rgba(255, 255, 255, 0.28); }
.chip-choice[aria-pressed="true"] {
  background: linear-gradient(120deg, rgba(251, 113, 133, 0.22), rgba(59, 130, 246, 0.22));
  border-color: rgba(255, 255, 255, 0.4);
}

/* Menu déroulant de sous-catégorie, révélé après le choix du parent */
.picker__subcat {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  margin-top: 18px;
  animation: subcat-in 0.3s ease both;
}
.picker__subcat-label {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted);
}
.picker__select {
  font-family: inherit; font-size: 15px; font-weight: 500;
  color: var(--fg); background-color: var(--glass);
  border: 1.5px solid var(--border); border-radius: var(--radius-pill);
  padding: 10px 42px 10px 18px; cursor: pointer;
  max-width: 100%;
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239aa0c8' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat; background-position: right 16px center;
  transition: border-color 0.16s;
}
.picker__select:hover { border-color: rgba(255, 255, 255, 0.28); }
.picker__select:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.4); outline-offset: 2px; }
.picker__select option { background: #14122a; color: var(--fg); }
@keyframes subcat-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* ─── Scène ─── */
.stage-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 8px;
}
.stage-head__context { flex: 1; font-size: 14px; color: var(--muted); line-height: 1.4; }
.stage-head__count { font-size: 13px; font-weight: 600; color: var(--muted); white-space: nowrap; }

/* Annonce de contexte, façon carton de film, seule à l'écran */
.context-card {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center;
  min-height: 320px;
  justify-content: center;
  animation: context-in 0.5s ease both;
}
.context-card__kicker {
  text-transform: uppercase; letter-spacing: 0.18em;
  font-size: 12px; font-weight: 700; color: var(--muted);
  max-width: 32ch;
}
.context-card__text {
  font-size: clamp(19px, 4.6vw, 26px);
  font-weight: 600; line-height: 1.45;
  max-width: 40ch;
  color: var(--fg);
}
@keyframes context-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: none; } }

.scene {
  display: grid;
  /* Cinq pistes : un corridor extérieur pour les étiquettes de chaque côté,
     un couloir dédié à chaque avatar, la bulle au centre. Les corridors
     (minmax 1fr) absorbent toute la largeur disponible au-delà de la scène :
     c'est là que passe la marge extérieure, désormais exploitée au lieu de
     rester vide. Les couloirs d'avatar sont fixes (124px) : l'avatar en gros
     plan (92px × 1.45 ≈ 133px) déborde d'à peine ~4px de chaque côté dans le
     gap de 20px, sans jamais mordre sur la bulle centrale.
     Plancher des corridors à 290px : assez large pour lire confortablement
     une carte figée dépliée (voir .settled-card__tab) sans la compresser
     sous une largeur lisible. */
  grid-template-columns:
    minmax(290px, 1fr)   /* corridor étiquettes A */
    120px                /* avatar A */
    minmax(340px, 1.4fr) /* bulle centrale */
    120px                /* avatar B */
    minmax(290px, 1fr);  /* corridor étiquettes B */
  grid-template-areas: "tags-a actor-a beat actor-b tags-b";
  gap: 20px;
  align-items: center;
  /* Hauteur plancher constante : les avatars restent centrés à la même
     hauteur d'un beat à l'autre tant que le contenu tient dans cette bande,
     donc leur position ne « saute » plus quand une pile d'étiquettes apparaît. */
  min-height: 360px;
}
.scene > .actor-tags--a { grid-area: tags-a; }
.scene > .actor--a { grid-area: actor-a; }
.scene > .beat-column { grid-area: beat; }
.scene > .actor--b { grid-area: actor-b; }
.scene > .actor-tags--b { grid-area: tags-b; }

/* Personnages — grossis pour que l'animation reste le centre d'attention */
.actor {
  display: flex; flex-direction: column; align-items: center; gap: 10px; padding-top: 10px;
  transition: transform 0.4s ease, opacity 0.4s ease;
}
.avatar {
  width: 92px; height: 92px; border-radius: 50% 50% 46% 46%;
  position: relative;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  background: var(--glass);
  border: 2px solid var(--border);
  box-shadow: 0 8px 22px rgba(0,0,0,0.4);
  /* Réservées en permanence (pas juste sur .actor--focus), en haut ET en
     bas : quand un seul des deux avatars grossit (gros plan), ses marges
     doivent exister côté avatar au repos aussi, sinon les deux colonnes
     de personnage n'ont plus la même hauteur totale et `align-items:
     center` du `.scene` décale les deux avatars — et leurs étiquettes de
     nom — l'un par rapport à l'autre. Le haut compte tout autant que le
     bas : l'avatar en gros plan (scale 1.45) peint environ 20px de plus
     de chaque côté que sa boîte, et sans marge haute réservée, il mord
     sur les tags violation/émotion/besoin empilés juste au-dessus. */
  margin-top: 24px;
  margin-bottom: 24px;
  animation: bob 3.4s ease-in-out infinite;
  transition: transform 0.4s cubic-bezier(.34,1.2,.4,1), box-shadow 0.4s ease, border-color 0.4s ease, background 0.4s ease;
}
/* décalage vers le centre de la scène quand ce perso prend le focus (gros plan) */
.actor--a .avatar { border-color: var(--a); box-shadow: 0 8px 22px rgba(0,0,0,0.4); --focus-shift: 10px; --halo-rgb: 251, 113, 133; }
.actor--b .avatar { border-color: var(--b); box-shadow: 0 8px 22px rgba(0,0,0,0.4); --focus-shift: -10px; --halo-rgb: 59, 130, 246; }
/* Halo permanent, propre à chaque personnage : un calque indépendant du
   box-shadow "vrai" de l'avatar (ombre portée, focus, colère…) pour que le
   halo respire dans toutes les vues sans jamais entrer en conflit avec les
   autres états visuels. */
.avatar::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 16px 1px rgba(var(--halo-rgb, 154, 160, 200), 0.25);
  animation: halo-pulse 2.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes halo-pulse {
  0%, 100% { box-shadow: 0 0 16px 1px rgba(var(--halo-rgb, 154, 160, 200), 0.22); }
  50% { box-shadow: 0 0 24px 3px rgba(var(--halo-rgb, 154, 160, 200), 0.4); }
}
/* Ni --halo-rgb ni border-color ne se posent plus ici : la couleur de chaque
   personnage suit le plan de coloration du beat en cours, posé directement
   en inline par setTint() (voir game.js, applyBeatTint) — ces classes ne
   pilotent plus que le mouvement (secousse, respiration, glow…). */
.avatar .eye {
  width: 11px; height: 11px; border-radius: 50%;
  background: transparent;
  border: 2px solid var(--fg);
  transition: transform 0.2s, border-color 0.2s;
}
.actor__role {
  font-size: 13px; color: var(--muted); text-align: center;
  line-height: 1.3; max-width: 110px;
  transition: color 0.3s ease;
}
@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }

/* Étiquettes de violation / émotions / besoins — elles vivent dans le corridor
   extérieur de leur personnage (colonne de grille dédiée), centrées
   verticalement au niveau de l'avatar et collées contre lui (bord intérieur du
   corridor) : l'espace restant vers le bord de l'écran est une marge qui aère,
   jamais un vide où les étiquettes flotteraient détachées du personnage.
   Comme chaque corridor est une piste de grille indépendante, la hauteur de la
   pile n'influe plus sur la colonne de l'avatar : les deux avatars gardent leur
   position, quel que soit le nombre d'étiquettes — plus besoin du hack `:has()`
   qui réservait 320px des deux côtés. */
/* Chaque carte figée s'empile en colonne, pleine largeur du corridor : repliée
   par défaut, sur toute cette largeur, elle laisse la place à ses voisines
   sans jamais forcer un côte-à-côte qui compresserait le texte sous une
   largeur lisible (voir .settled-card__tab plus bas, et toggleSettledCard en
   JS pour la règle « une seule carte dépliée à la fois »). */
.actor-tags {
  display: flex; flex-direction: column; flex-wrap: nowrap; justify-content: flex-start;
  align-items: center; gap: 8px;
  min-height: 0;
}
.actor-tags .settled-card { flex: 0 0 auto; width: 100%; }
.actor-tags .beat-tag {
  max-width: 100%;
  white-space: normal;
  text-align: center;
  font-size: 10px;
}
.actor-tags .chip-stack {
  flex-direction: column;
  width: 100%;
  justify-content: flex-start;
}
.actor-tags--a .chip-stack,
.actor-tags--b .chip-stack { align-items: center; }
.actor-tags .v-chip,
.actor-tags .feel-chip {
  justify-content: center;
  max-width: 100%;
  white-space: normal;
  text-align: center;
  line-height: 1.3;
}
.actor-tags .v-chip {
  font-size: 12px; font-weight: 700;
  padding: 7px 14px;
}
.actor-tags .feel-chip {
  font-size: 11px;
  padding: 6px 12px;
}

/* Historique figé : au beat suivant, la carte active (bulle + étiquettes de
   violation) du speaker qui vient de parler ne disparaît pas — elle se pose,
   réduite, tout en haut du corridor de son propre personnage. La bordure du
   côté de son avatar (gauche pour A, droite pour B) rappelle à qui elle
   appartient pendant tout le reste de l'échange (voir settleActive en JS). */
.settled-card {
  width: 100%;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(20, 18, 40, 0.4);
  opacity: 0;
  transform: translateY(16px) scale(0.94);
  transition: transform 0.5s cubic-bezier(.22, 1, .36, 1), opacity 0.45s ease;
}
.settled-card--in { opacity: 0.85; transform: none; }
.settled-card--who-a { border-left: 3px solid; }
.settled-card--who-b { border-right: 3px solid; }
/* Le dégradé suit le contenu de la carte plutôt qu'une couleur unique :
   violation (bulle brute + reproche) rouge→jaune, sous les mots (émotion/besoin)
   jaune→bleu, OEBD bleu→vert, bulle brute vs. version allégée rouge→vert — mêmes
   teintes que .v-chip / .feel-chip--emotion / .feel-chip--besoin / .bubble--oebd. */
.settled-card--violation { border-image: linear-gradient(180deg, #f43f5e, #f59e0b) 1; }
.settled-card--feelings { border-image: linear-gradient(180deg, #f59e0b, #3b82f6) 1; }
.settled-card--oebd { border-image: linear-gradient(180deg, #3b82f6, #34d399) 1; }
.settled-card--natural { border-image: linear-gradient(180deg, #34d399, #34d399) 1; }
/* Carte repliée par défaut : seuls le tab (bordure dégradée + étiquette)
   restent visibles, jamais son contenu. Un clic déplie la carte ; au plus une
   carte par corridor reste dépliée à la fois — ouvrir une carte referme les
   autres (voir toggleSettledCard en JS) — pour ne jamais reproduire le mur de
   texte compressé que provoquait le côte-à-côte pleine hauteur. */
.settled-card__tab {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; gap: 8px;
  background: none; border: none; padding: 0;
  font: inherit; color: inherit; cursor: pointer;
}
.settled-card__tab-label {
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted);
}
.settled-card__tab-chevron {
  font-size: 9px; flex: 0 0 auto;
  transition: transform 0.25s ease;
}
.settled-card--expanded .settled-card__tab-chevron { transform: rotate(180deg); }
/* Carte verrouillée (voir settleActive/renderHandoff en JS) : toujours
   dépliée, sans bascule possible — le chevron n'a donc plus de sens ici. */
.settled-card--locked .settled-card__tab { cursor: default; }
.settled-card--locked .settled-card__tab-chevron { display: none; }
.settled-card__body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}
.settled-card__body > .settled-card__inner { overflow: hidden; }
.settled-card--expanded .settled-card__body { grid-template-rows: 1fr; margin-top: 8px; }
.settled-card__inner { display: flex; flex-direction: column; gap: 8px; }
.settled-card .bubble {
  font-size: 12.5px;
  padding: 10px 12px;
  max-width: 100%;
  animation: none; /* déjà jouée : ne pas la rejouer en la déplaçant */
}
.settled-card .bubble::before { display: none; } /* guillemet décoratif, superflu à cette taille */
.settled-card .beat-tag { font-size: 9px; }
.settled-card .chip-stack { gap: 6px; flex-direction: row; flex-wrap: wrap; }

/* le personnage qui parle prend le devant de la scène, en gros plan, au centre */
.actor--focus {
  position: relative;
  z-index: 3;
}
.actor--focus .avatar {
  transform: scale(1.45) translateX(var(--focus-shift));
  box-shadow: inset 0 -6px 12px rgba(0,0,0,0.25), 0 10px 34px rgba(0,0,0,0.55);
}
.actor--focus .actor__role { color: var(--fg); font-weight: 600; }
.actor--a.actor--focus .avatar { box-shadow: inset 0 -6px 12px rgba(0,0,0,0.25), 0 10px 34px rgba(0,0,0,0.55), 0 0 16px rgba(251, 113, 133, 0.25); }
.actor--b.actor--focus .avatar { box-shadow: inset 0 -6px 12px rgba(0,0,0,0.25), 0 10px 34px rgba(0,0,0,0.55), 0 0 16px rgba(59, 130, 246, 0.25); }

/* celui qui écoute s'efface un peu, en retrait */
.actor--dim { opacity: 0.32; }
.actor--dim .avatar { transform: scale(0.74); }

/* état "parle" */
.actor--talking .avatar { animation: talk 0.5s ease-in-out infinite; }
.actor--talking .eye { transform: scaleY(0.5); }
@keyframes talk {
  0%, 100% { transform: scale(1.45) translateX(var(--focus-shift)) translateY(0); }
  50% { transform: scale(1.5) translateX(var(--focus-shift)) translateY(-4px); }
}

/* impact quand on encaisse une réponse */
.actor--hit .avatar { animation: hit 0.42s ease; }
@keyframes hit {
  0% { transform: scale(1.45) translateX(var(--focus-shift)); }
  25% { transform: scale(1.45) translateX(calc(var(--focus-shift) + 6px)) rotate(2deg); }
  50% { transform: scale(1.45) translateX(calc(var(--focus-shift) - 5px)) rotate(-2deg); }
  75% { transform: scale(1.45) translateX(calc(var(--focus-shift) + 3px)); }
  100% { transform: scale(1.45) translateX(var(--focus-shift)); }
}

/* colère : quel que soit le personnage, il grossit et rougit — puis redevient
   sa couleur naturelle (bleu/vert ou rose) dès que le besoin est exprimé */
/* La couleur (bordure + halo) ne vient plus d'ici : elle suit le plan de
   coloration du beat en cours, posé directement en inline par setTint() en
   JS (voir game.js) — cette classe ne pilote plus que le mouvement. */
.actor--angry .avatar {
  animation: rage-grow 0.5s cubic-bezier(.34, 1.56, .64, 1) both,
             rage-shake 0.7s ease-in-out 0.5s infinite;
  box-shadow: 0 0 32px rgba(var(--halo-rgb), 0.42);
}
.actor--angry .actor__role { color: #fecaca; font-weight: 700; }
/* les yeux s'aplatissent en cercles plats et diagonaux, comme des sourcils
   froncés : chaque œil bascule vers son centre (le coin intérieur descend,
   le coin extérieur monte) — les deux inclinaisons sont opposées (--eye-tilt)
   pour que les deux yeux convergent en pointe vers le milieu du visage,
   comme une paire de sourcils froncés. Le glare l'emporte sur le clignement
   de `.actor--talking .eye` car une animation qui touche `transform` prime
   toujours sur une valeur statique. */
.actor--angry .eye { animation: eye-glare 0.6s ease-in-out infinite; border-color: #fca5a5; }
.actor--angry .eye:first-child { transform-origin: 60% 50%; --eye-tilt: 24deg; }
.actor--angry .eye:last-child { transform-origin: 40% 50%; --eye-tilt: -24deg; }
@keyframes rage-grow {
  from { transform: scale(1.45) translateX(var(--focus-shift)); }
  to { transform: scale(1.7) translateX(var(--focus-shift)); }
}
@keyframes rage-shake {
  0%, 100% { transform: scale(1.7) translateX(var(--focus-shift)) rotate(0deg); }
  25% { transform: scale(1.7) translateX(calc(var(--focus-shift) - 3px)) rotate(-2deg); }
  75% { transform: scale(1.7) translateX(calc(var(--focus-shift) + 3px)) rotate(2deg); }
}
@keyframes eye-glare {
  0%, 100% { transform: scaleY(0.32) rotate(var(--eye-tilt)); }
  50% { transform: scaleY(0.22) rotate(var(--eye-tilt)); }
}

/* réflexion / respiration : le personnage se tait, respire lentement et
   regarde en lui — pendant que son émotion et son besoin cachés se révèlent
   (beat « sous les mots »). Contraste volontaire avec la turbulence de la
   colère : un mouvement ample mais lent, des yeux mi-clos. La teinte (posée
   par setTint, voir game.js) bascule vers le bleu à ce moment-là du plan de
   coloration : le basculement porte à lui seul le message « on sort de la
   colère ». */
.actor--reflect .avatar {
  animation: breathe 4.2s ease-in-out infinite;
}
.actor--reflect .actor__role { color: var(--muted); }
.actor--reflect .eye { animation: eye-soften 4.2s ease-in-out infinite; border-color: #c7d2fe; }
@keyframes breathe {
  0%, 100% { transform: scale(1.45) translateX(var(--focus-shift)); }
  50% { transform: scale(1.5) translateX(var(--focus-shift)) translateY(-2px); }
}
@keyframes eye-soften {
  0%, 100% { transform: scaleY(0.75); opacity: 0.85; }
  50% { transform: scaleY(0.5); opacity: 1; }
}

/* expression claire / ouverture à l'autre : au moment de la reformulation
   CNV, puis dans l'échange réparé — le personnage se pose, ne bouge plus,
   le regard grand ouvert. Seul un halo chaleureux respire encore, ancré dans
   la même teinte ambre que les chips d'émotion (`.feel-chip--emotion`). */
.actor--open .avatar {
  animation: open-glow 2.8s ease-in-out infinite;
}
.actor--open .actor__role { color: #fde68a; font-weight: 600; }
.actor--open .eye {
  transform: scale(1.15);
  border-color: #fde68a;
  box-shadow: 0 0 6px rgba(253, 230, 138, 0.65);
}
@keyframes open-glow {
  0%, 100% { box-shadow: 0 0 24px rgba(var(--halo-rgb), 0.3); }
  50% { box-shadow: 0 0 40px rgba(var(--halo-rgb), 0.55); }
}

/* Le relais : celui qui vient de reformuler reste fragile (fissure discrète) ;
   sa teinte (posée par setTint) reste celle de sa réplique brute. Celui qui
   prend le relais n'a pas encore choisi : jaune/rouge en variateur, tant
   qu'il n'a pas répondu (voir applyBeatTint en JS). */
.actor--cracked .avatar {
  animation: crack-waver 2.2s ease-in-out infinite;
}
.actor--cracked .avatar::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background-image:
    linear-gradient(115deg, transparent 44%, rgba(255, 255, 255, 0.7) 45%, rgba(255, 255, 255, 0.7) 46%, transparent 47%),
    linear-gradient(25deg, transparent 58%, rgba(255, 255, 255, 0.45) 59%, transparent 60%),
    linear-gradient(150deg, transparent 68%, rgba(255, 255, 255, 0.35) 69%, transparent 70%);
  opacity: 0.4;
  animation: crack-flicker 2.6s ease-in-out infinite;
  pointer-events: none;
}
.actor--cracked .actor__role { color: var(--muted); }
@keyframes crack-waver {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  25% { transform: translateY(-3px) rotate(-1deg); }
  50% { transform: translateY(-5px) rotate(0.5deg); }
  75% { transform: translateY(-2px) rotate(-0.5deg); }
}
@keyframes crack-flicker {
  0%, 100% { opacity: 0.28; }
  50% { opacity: 0.62; }
}

.actor--composed .avatar {
  animation: bob 3.4s ease-in-out infinite, composed-glow 2.4s ease-in-out infinite;
}
.actor--composed .actor__role { color: #86efac; font-weight: 600; }
@keyframes composed-glow {
  0%, 100% { box-shadow: 0 0 28px rgba(var(--halo-rgb), 0.35); }
  50% { box-shadow: 0 0 46px rgba(var(--halo-rgb), 0.6); }
}
.actor--composed:not(.actor--open) .avatar {
  animation: bob 3.4s ease-in-out infinite, composed-glow-relay 2.4s ease-in-out infinite;
}
.actor--composed:not(.actor--open) .actor__role { color: #fbbf24; }
@keyframes composed-glow-relay {
  0%, 100% { box-shadow: 0 0 28px rgba(var(--halo-rgb), 0.35); }
  50% { box-shadow: 0 0 46px rgba(var(--halo-rgb), 0.6); }
}

/* Colonne centrale : un beat à la fois, façon carrousel */
.beat-column { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; }
.beat-label {
  text-align: center; font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); min-height: 1.2em;
}
.oebd-frame {
  text-align: center; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); padding: 10px 14px;
  border: 2px dotted var(--border);
  border-radius: var(--radius);
  max-width: 360px;
  animation: beat-in 0.4s ease both;
}
.beat-stage { width: 100%; display: flex; align-items: center; justify-content: center; min-height: 130px; }
.beat-card {
  width: 100%; display: flex; flex-direction: column; align-items: center; gap: 10px;
  animation: beat-in 0.4s ease both;
}
@keyframes beat-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* Sépare visuellement gauche/droite selon qui parle : le speaker A colle
   à gauche (côté de son avatar), le speaker B colle à droite.
   L'alignement est posé sur `.beat-card__inner` (le vrai conteneur flex
   direct de la bulle/étiquette), pas sur `.beat-card` : un wrapper sans
   largeur fixe s'étire pour tenir le texte et neutraliserait l'alignement
   du parent. */
.beat-card__inner {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: 100%; min-width: 0;
}
/* padding côté avatar : l'avatar en gros plan peint ~20px au-delà de sa boîte
   (scale 1.45) puis se décale encore de --focus-shift vers le centre — sans
   cette marge, son contour mord directement sur la bulle qui, elle, colle au
   bord intérieur de la colonne centrale. */
.beat-card__inner--who-a { align-items: flex-start; padding-left: 24px; }
.beat-card__inner--who-b { align-items: flex-end; padding-right: 24px; }

/* Align feeling chips based on speaker */
.beat-card__inner--who-a .chip-stack { justify-content: flex-start; }
.beat-card__inner--who-b .chip-stack { justify-content: flex-end; }

/* Vue « sous les mots » : la révélation (émotion + besoin cachés) occupe le
   centre de la scène, là où la phrase violente vient de disparaître. Les chips
   restent en rangées horizontales centrées (le comportement de base de
   .chip-stack, puisqu'on n'est plus dans un corridor .actor-tags), un peu plus
   grandes pour porter ce moment clé. */
.feelings-reveal { gap: 14px; }
.feelings-reveal .beat-tag { color: var(--muted); border-color: var(--border); }
.feelings-reveal .chip-stack { justify-content: center; max-width: 100%; }
.feelings-reveal .feel-chip { font-size: 14px; padding: 8px 17px; }

/* Le relais : un trait dégradé rose → jaune qui relie les deux personnages au
   centre, une flèche discrète pointant vers celui qui prend le relais — le
   jaune (pas vert) rappelle que B n'a pas encore renoué, juste une chance de
   l'éviter (voir .actor--composed:not(.actor--open) ci-dessus).
   La bulle de A rappelée juste au-dessus n'a plus besoin du couloir
   d'étiquettes pleine largeur (elle est déjà figée dans .actor-tags--a) :
   on resserre la colonne centrale pour que le trait — et les deux avatars
   qu'il relie — se rapprochent visiblement, au lieu de rester à distance
   pensée pour loger bulle + étiquettes des autres beats. */
.scene--relay {
  grid-template-columns:
    minmax(290px, 1fr)
    120px
    minmax(200px, 0.6fr)
    120px
    minmax(290px, 1fr);
}
.beat-stage .relay { flex-direction: row; align-items: center; width: 100%; }
.relay__line {
  position: relative; flex: 1; height: 2px; border-radius: 2px; opacity: 0.75;
  background: linear-gradient(90deg,
    rgba(251, 113, 133, 0.12), var(--a) 16%, #f59e0b 84%, rgba(245, 158, 11, 0.12));
}
.relay__line::after {
  content: ""; position: absolute; right: 0; top: 50%;
  width: 9px; height: 9px; transform: translateY(-50%) rotate(45deg);
  border-top: 2px solid #f59e0b; border-right: 2px solid #f59e0b;
}

/* Simple légende de section (« Derrière les mots », « X se sent »…) : un
   repère textuel discret, pas un bouton — la pastille/bordure alourdissait
   l'écran pour une information qui n'est ni cliquable ni le propos du beat. */
.beat-tag {
  display: block;
  max-width: 100%;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); text-align: center;
  opacity: 0.8;
}
/* Laisse respirer la pointe de la bulle, ou la pile de chips, qui précède
   une nouvelle étiquette (vues reformulation et sentiments/besoins) */
.bubble + .beat-tag, .chip-stack + .beat-tag { margin-top: 10px; }

/* « Plutôt que de répondre : » précède toujours la réplique brute que B
   retient (seul speaker à qui ce choix se pose) — rouge pour signaler la
   tentation de la bulle violente qui suit, même teinte que .bubble--bad. */
.beat-tag--pending { color: var(--bad); }

/* Pile d'étiquettes de violation */
.chip-stack { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; min-height: 0; }
.v-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.02em;
  padding: 5px 12px 5px 10px; border-radius: var(--radius-pill);
  color: #ffc7cf;
  background: rgba(244, 63, 94, 0.12);
  border: 1px solid rgba(244, 63, 94, 0.32);
  opacity: 0; transform: translateY(12px) scale(0.85);
  transition: transform 0.45s cubic-bezier(.34,1.56,.64,1), opacity 0.4s ease;
}
.v-chip::before {
  display: none;
}
.chip-stack--in .v-chip { opacity: 1; transform: none; }

/* Chips émotion / besoin (vue « sous les mots ») — un mot ou une courte
   phrase par chip, coloré par nature, ancré du côté du personnage. */
.feel-chip {
  display: inline-flex; align-items: center;
  font-size: 13px; font-weight: 600; letter-spacing: 0.01em;
  padding: 7px 15px; border-radius: var(--radius-pill);
  opacity: 0; transform: translateY(12px) scale(0.85);
  transition: transform 0.45s cubic-bezier(.34,1.56,.64,1), opacity 0.4s ease;
}
.chip-stack--in .feel-chip { opacity: 1; transform: none; }
.feel-chip--emotion {
  color: #fde9b8;
  background: rgba(245, 158, 11, 0.12);
  border: 1px solid rgba(245, 158, 11, 0.3);
}
.feel-chip--besoin {
  color: #cfe1fd;
  background: rgba(59, 130, 246, 0.12);
  border: 1px solid rgba(59, 130, 246, 0.3);
}

/* Bulles */
.bubble {
  position: relative;
  max-width: min(46ch, 84%);
  padding: 20px 26px;
  border-radius: 26px;
  font-size: 17px; line-height: 1.55;
  text-align: center;
  color: var(--fg);
  background: var(--glass);
  border: 1.5px solid var(--border);
  box-shadow: 0 12px 24px -16px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  animation: bubble-in 0.4s ease both;
}
/* guillemet décoratif discret dans le coin, façon citation */
.bubble::before {
  content: "“";
  position: absolute; top: -8px;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 52px; line-height: 1;
  color: currentColor; opacity: 0.16;
  pointer-events: none;
}
.bubble--who-a::before { left: 16px; }
.bubble--who-b::before { right: 16px; }
/* le coin anguleux du côté de celui qui parle suffit à indiquer la direction */
.bubble--who-a { border-radius: 28px 28px 28px 8px; }
.bubble--who-b { border-radius: 28px 28px 8px 28px; }
.bubble--bad {
  color: var(--fg);
  background: var(--glass);
  border: 1.5px solid rgba(244, 63, 94, 0.5);
}
.bubble--risk {
  color: var(--fg);
  border-style: dashed;
  opacity: 0.9;
  background: var(--glass);
  border: 1.5px dashed rgba(244, 63, 94, 0.4);
}
.bubble--good {
  color: var(--fg);
  background: var(--glass);
  border: 1.5px solid rgba(52, 211, 153, 0.45);
}
/* Formulation OEBD : bordure pointillée pour marquer que ce n'est qu'une
   étape de la structure (observation/émotion/besoin/demande), pas encore la
   version allégée finale — celle-ci garde la bordure pleine de .bubble--good
   (beat « natural », puis écran réparé). */
.bubble--oebd {
  color: var(--fg);
  background: var(--glass);
  border: 1.5px dotted rgba(52, 211, 153, 0.5);
}
.bubble--natural {
  font-size: 15px;
  color: var(--fg);
  background: var(--glass);
  border: 2px solid rgba(52, 211, 153, 0.7);
  box-shadow: 0 0 28px rgba(52, 211, 153, 0.35), 0 12px 24px -16px rgba(0, 0, 0, 0.5);
}
@keyframes bubble-in { from { opacity: 0; transform: translateY(8px) scale(0.96); } to { opacity: 1; transform: none; } }

/* Pourquoi c'est une violation : posée à côté des étiquettes de violation, en
   fond plein (pas de flou ni de guillemet de citation) pour bien la
   distinguer d'une réplique — c'est le narrateur qui commente, pas le
   personnage. */
.explication {
  max-width: min(46ch, 84%);
  padding: 14px 18px;
  border-radius: 14px;
  font-size: 14px;
  font-style: italic;
  line-height: 1.55;
  text-align: center;
  color: #adb4c8;
  background: transparent;
  border: 1px solid #fbbf24;
  animation: beat-in 0.4s ease both;
}

/* Étiquettes de violation + explication : les deux se lisent en même temps,
   côte à côte, au moment où la phrase violente est décortiquée. */
.violation-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  width: 100%;
}
.violation-row .chip-stack { flex: 1 1 auto; justify-content: center; }
.violation-row .explication { flex: 1 1 240px; max-width: 320px; margin: 0; }

/* Une fois figée dans le corridor (voir settleActive), la rangée repasse en
   colonne : le corridor est trop étroit pour un côte-à-côte lisible, et
   l'explication rejoint le traitement compact du reste de l'historique.
   Le `flex: 1 1 240px` ci-dessus vise la largeur en mode ligne : sans reset,
   ce même flex-basis de 240px s'applique à la hauteur une fois la rangée
   passée en colonne, et gonfle la boîte bien au-delà du texte qu'elle
   contient. */
.settled-card .violation-row { flex-direction: column; align-items: stretch; gap: 8px; }
.settled-card .explication { font-size: 10px; padding: 8px 10px; max-width: 100%; flex: 0 1 auto; }

/* Échange réparé : les deux reformulations se font face, empilées
   verticalement, chacune du côté de son avatar (A à gauche, B à droite) */
.repaired-pair { display: flex; flex-direction: column; gap: 16px; width: 100%; }
.repaired-pair .bubble--who-a { align-self: flex-start; }
.repaired-pair .bubble--who-b { align-self: flex-end; }

.beat-caption {
  text-align: center;
  font-size: clamp(17px, 4.4vw, 21px);
  font-weight: 600; line-height: 1.5;
  color: var(--fg);
  min-height: 3.2em;
  margin: 0 auto 18px;
  max-width: 46ch;
}
.beat-caption strong { font-weight: 700; }

/* Fait vraiment « jouer » chaque réplique du narrateur : elle rentre en
   scène à chaque beat, au lieu de simplement changer de texte. */
.narrate-in { animation: narrate-in 0.42s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes narrate-in {
  from { opacity: 0; transform: translateY(10px) scale(0.96); }
  to { opacity: 1; transform: none; }
}

.stage-actions {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  margin-top: 4px;
}
.stage-actions__advance { display: flex; align-items: center; gap: 12px; }
.end-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

/* ─── Responsive ─── */
/* En dessous du seuil où les corridors extérieurs tiennent (≈1300px de scène —
   relevé depuis 1000px avec le plancher des corridors, voir .scene plus haut),
   on abandonne les cinq colonnes : la bulle passe en haut sur toute la largeur,
   les deux avatars se rangent côte à côte dessous, et chaque pile d'étiquettes
   se glisse juste sous son personnage. Le rôle A/gauche, B/droite est conservé,
   et la scène reste symétrique. Le repli en onglets des cartes figées (voir
   .settled-card__tab plus haut) s'applique lui à toutes les largeurs. */
@media (max-width: 1300px) {
  .scene {
    /* minmax(0, …) et pas 1fr : un `1fr` a un minimum implicite de min-content,
       si bien qu'une chip large (« espace partagé équitable ») élargirait la
       piste et ferait déborder la scène sous ~450px. minmax(0, …) autorise la
       piste à rétrécir, la chip enveloppe alors le contenu au lieu de pousser. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-areas:
      "beat    beat"
      "actor-a actor-b"
      "tags-a  tags-b";
    gap: 18px 12px;
    min-height: 0;
  }
}

@media (max-width: 560px) {
  body { padding: 22px 12px; }
  .scene { gap: 14px 10px; }
  .avatar { width: 72px; height: 72px; }
  /* Colonnes en demi-largeur ici : le gros plan a de la place, un léger décalage
     (±4px) rapproche juste les deux avatars du centre sans risque de morsure. */
  .actor--a .avatar { --focus-shift: 4px; }
  .actor--b .avatar { --focus-shift: -4px; }
  .actor--focus .avatar { transform: scale(1.3) translateX(var(--focus-shift)); }
  .actor--talking .avatar { animation-name: talk-sm; }
  .actor--hit .avatar { animation-name: hit-sm; }
  .actor--angry .avatar { animation-name: rage-grow-sm, rage-shake-sm; }
  .actor--reflect .avatar { animation-name: breathe-sm; }
  .actor__role { font-size: 11px; max-width: 88px; }
  .actor-tags .beat-tag { font-size: 9px; padding: 4px 8px; }
  .actor-tags .v-chip { font-size: 10px; padding: 6px 10px; }
  .actor-tags .feel-chip { font-size: 10px; padding: 5px 10px; }
  .feelings-reveal .feel-chip { font-size: 12px; padding: 6px 12px; max-width: 100%; }
  .feelings-reveal { gap: 10px; }
  .bubble { font-size: 15px; padding: 18px 20px; }
  .explication { font-size: 13px; padding: 12px 16px; }
  .violation-row { flex-direction: column; align-items: stretch; }
  /* Même reset qu'en `.settled-card` : la rangée passe en colonne ici aussi,
     donc le flex-basis de 240px hérité du mode ligne doit être neutralisé
     pour que la hauteur suive le texte au lieu d'un flex-basis pensé pour
     une largeur. */
  .violation-row .explication { max-width: 100%; flex: 0 1 auto; }
}
@keyframes talk-sm {
  0%, 100% { transform: scale(1.3) translateX(var(--focus-shift)) translateY(0); }
  50% { transform: scale(1.34) translateX(var(--focus-shift)) translateY(-3px); }
}
@keyframes hit-sm {
  0%, 100% { transform: scale(1.3) translateX(var(--focus-shift)); }
  25% { transform: scale(1.3) translateX(calc(var(--focus-shift) + 5px)) rotate(2deg); }
  50% { transform: scale(1.3) translateX(calc(var(--focus-shift) - 4px)) rotate(-2deg); }
  75% { transform: scale(1.3) translateX(calc(var(--focus-shift) + 2px)); }
}
@keyframes rage-grow-sm { from { transform: scale(1.3) translateX(var(--focus-shift)); } to { transform: scale(1.5) translateX(var(--focus-shift)); } }
@keyframes rage-shake-sm {
  0%, 100% { transform: scale(1.5) translateX(var(--focus-shift)) rotate(0deg); }
  25% { transform: scale(1.5) translateX(calc(var(--focus-shift) - 3px)) rotate(-2deg); }
  75% { transform: scale(1.5) translateX(calc(var(--focus-shift) + 3px)) rotate(2deg); }
}
@keyframes breathe-sm {
  0%, 100% { transform: scale(1.3) translateX(var(--focus-shift)); }
  50% { transform: scale(1.34) translateX(var(--focus-shift)) translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .avatar, .avatar::before, .actor--talking .avatar, .actor--hit .avatar, .actor--angry .avatar,
  .actor--angry .eye, .actor--cracked .avatar, .actor--cracked .avatar::after, .actor--composed .avatar,
  .actor--reflect .avatar, .actor--reflect .eye, .actor--open .avatar,
  .context-card, .beat-card, .narrate-in { animation: none; }
  .actor--cracked .avatar::after { opacity: 0.45; }
  .bubble { animation-duration: 0.01ms; }
  .v-chip, .feel-chip { transition-duration: 0.01ms; }
}
