/* _core/_css/core.css
   Shared structural styles for both editor/player modes. Mode-agnostic:
   no skin colours live here — only geometry that Sheet and Quest both need.
   The look of each mode comes from theme-sheet.css / theme-quest.css. */

.wq-canvas,
.wq-page { position: relative; width: 100%; }

/* Every placed element is absolutely positioned by x/y/width/height % */
.wq-el {
  position: absolute;
  box-sizing: border-box;
}

/* Bouton désactivé (grisé) — état de séquençage : « pas encore accessible »,
   pas caché (l'enfant voit qu'il existe, juste pas encore). Mode-agnostic
   comme le reste de ce fichier — utile en Sheet comme en Quest. */
.wq-el.is-disabled {
  opacity: .45;
  filter: grayscale(.7);
  cursor: not-allowed;
}

/* Verrou d'accessibilité (access-lock.js) — une feuille posée PAR-DESSUS
   TOUT le widget (pas seulement un bouton) tant qu'il est grisé : capte le
   clic en phase de capture avant qu'il n'atteigne quoi que ce soit à
   l'intérieur (bouton, zone de glisser-déposer, réponse de quiz…). Un
   z-index local, scopé au cadre DÉJÀ positionné du widget lui-même (pas le
   canevas entier) — sans rapport avec le zoom du canevas qui a déjà cassé
   un z-index ailleurs sur ce projet (voir ROADMAP §6.16/6.18) : ce cas-ci
   n'a pas cette dépendance. */
.wq-access-lock-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  cursor: not-allowed;
  background: transparent;
}

/* Resize grip shared by editable zones */
.wq-resize-handle {
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 12px;
  height: 12px;
  border-radius: 4px;
  cursor: nwse-resize;
  z-index: 6;
  touch-action: none;
}

/* State flags the engine toggles, styled per-theme */
.wq-el[data-locked="1"] { cursor: not-allowed; }
.wq-el.wq-hidden { opacity: .3; }

/* ---------- Narration (narration-engine.js) ----------
   Classes an audio timeline toggles on the elements it talks about. Kept in
   core.css so both chromes (Sheet player + Quest player) get them. */
.nr-hidden { opacity: 0 !important; pointer-events: none !important; transition: opacity .35s ease; }
.nr-reveal { opacity: 1 !important; animation: nrReveal .5s ease both; }
@keyframes nrReveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.nr-highlight { animation: nrPulse 1.1s ease-in-out 2; box-shadow: 0 0 0 4px rgba(245, 158, 11, .45) !important; border-radius: 6px; }
@keyframes nrPulse { 0%, 100% { box-shadow: 0 0 0 4px rgba(245,158,11,.45); } 50% { box-shadow: 0 0 0 10px rgba(245,158,11,.12); } }

/* Caption bar — under the player, or docked at the bottom of the viewport. */
.nr-caption { font-weight: 700; text-align: center; line-height: 1.35;
  opacity: 0; transform: translateY(4px); transition: opacity .3s ease, transform .3s ease; }
.nr-caption.show { opacity: 1; transform: none; }
.nr-caption-page { position: fixed; left: 50%; transform: translateX(-50%) translateY(6px);
  bottom: 84px; z-index: 900; max-width: min(760px, 92vw);
  background: rgba(15, 23, 42, .88); color: #fff; padding: 10px 18px; border-radius: 12px;
  box-shadow: 0 10px 30px rgba(15, 23, 42, .35); pointer-events: none; }
.nr-caption-page.show { transform: translateX(-50%) translateY(0); }

/* Sticky player docks. The element leaves the page flow entirely.
   Every offset is !important and the node is centred with margin:auto rather
   than left:50% + translateX: applyRect() writes left/top/width INLINE, and an
   inline value beats a plain stylesheet rule — the dock was landing at the
   element's authored left% and then being pulled half its width further left,
   half off-screen. No transform either, so widget rotation can't fight it. */
.wq-audio.nr-dock-bottom, .wq-audio.nr-dock-top {
  position: fixed !important;
  left: 0 !important; right: 0 !important;
  margin-left: auto !important; margin-right: auto !important;
  width: min(680px, 94vw) !important; height: auto !important;
  /* The player is page furniture: it must sit UNDER the chrome (nav bar, menu
     sheet, modals) and clear the header/footer rather than cover them. Each
     chrome declares its own scale — Quest counts in tens, the Sheet player in
     thousands — so the value comes from the host, never hardcoded here. */
  z-index: var(--nr-dock-z, 45) !important;
}
.wq-audio.nr-dock-bottom {
  bottom: calc(14px + var(--nr-dock-gap-bottom, 0px)) !important; top: auto !important;
}
.wq-audio.nr-dock-top {
  top: calc(14px + var(--nr-dock-gap-top, 0px)) !important; bottom: auto !important;
}
/* The lift belongs to the visible card, not to the (invisible, wider) node —
   on the node it read as a grey haze floating around the player. */
.wq-audio.nr-dock-bottom .wq-audio-box,
.wq-audio.nr-dock-top .wq-audio-box { box-shadow: 0 12px 34px rgba(15, 23, 42, .28); }

/* Cue markers — bulles flottantes au-dessus de la barre (pas des traits SUR
   la barre, signalé indissociables dès que deux repères sont proches — voir
   sheet-audio.js). Icône colorée par CATÉGORIE d'action (mêmes 11 groupes
   que le menu d'action, §6.24), reliée à son instant exact par une tige. */
.wq-audio-progress { position: relative; }
.nr-cue-chips { position: absolute; inset: 0; pointer-events: none; }
.nr-cue-chip {
  position: absolute; bottom: 100%; left: 0;
  transform: translateX(-50%);
  /* --nr-chip-scale (posée sur .nr-cue-chips, hérite ici) : un seul
     multiplicateur pour toute la bulle (taille, tige, marge) — même
     principe que --wq-rail-w pour le rail de stations du Robot Guide. */
  width: calc(20px * var(--nr-chip-scale, 1));
  height: calc(20px * var(--nr-chip-scale, 1));
  /* The gap MUST equal the stem's own length (below) or the stem stops
     short of / overshoots the bar — so this uses the same fallback chain
     as .nr-cue-stem's height, not --nr-chip-scale alone. */
  margin-bottom: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1)));
  border-radius: 50%;
  background: var(--nr-chip-c, #6366f1);
  border: 1.5px solid #fff;
  box-shadow: 0 2px 6px rgba(15, 23, 42, .25);
  display: flex; align-items: center; justify-content: center;
  font-size: calc(11px * var(--nr-chip-scale, 1)); line-height: 1; padding: 0;
  cursor: pointer; pointer-events: auto;
  transition: transform .12s ease;
}
/* Une bulle sur deux, quand deux repères seraient trop proches pour
   coexister sans se chevaucher : rangée plus haute, tige plus longue — le
   calcul de collision vit dans sheet-audio.js (paintTicks), cette classe ne
   fait qu'en dessiner le résultat. */
.nr-cue-chip.row-1 { margin-bottom: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
.nr-cue-chip:hover, .nr-cue-chip:focus-visible { transform: translateX(-50%) scale(1.15); z-index: 2; }
.nr-cue-stem {
  position: absolute; left: 50%; top: 100%;
  /* --nr-chip-stem-scale (posée sur .nr-cue-chips) : longueur de la tige,
     indépendante de --nr-chip-scale (taille de la bulle) — retombe sur
     --nr-chip-scale tant que l'auteur n'a rien réglé (sheet-audio.js). */
  width: 2px; height: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1)));
  background: var(--nr-chip-c, #6366f1); opacity: .6;
  transform: translateX(-50%);
}
.nr-cue-chip.row-1 .nr-cue-stem { height: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
/* Bulle de texte au survol — stylée, pas l'infobulle grise du navigateur
   (`title`, abandonné pour ce composant). */
.nr-cue-chip::before {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translateX(-50%);
  background: #1e293b; color: #fff; font-size: 11px; padding: 4px 8px;
  border-radius: 6px; white-space: nowrap; max-width: 220px;
  overflow: hidden; text-overflow: ellipsis;
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.nr-cue-chip:hover::before, .nr-cue-chip:focus-visible::before { opacity: 1; }

/* Orientation « en dessous » (demandé) : un lecteur collant en HAUT de
   l'écran ferait déborder des bulles AU-DESSUS de la barre sur ce qu'il y a
   plus haut (un en-tête). Bascule chip + tige + bulle de texte — toujours
   modifiable à la main (buildNarrationPanel), suggéré automatiquement pour
   un lecteur collant-haut tant que l'auteur n'a rien réglé. */
.nr-cue-chips.orient-below .nr-cue-chip {
  bottom: auto; top: 100%;
  margin-bottom: 0; margin-top: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1)));
}
.nr-cue-chips.orient-below .nr-cue-chip.row-1 { margin-top: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
.nr-cue-chips.orient-below .nr-cue-stem { top: auto; bottom: 100%; }
.nr-cue-chips.orient-below .nr-cue-chip::before { bottom: auto; top: calc(100% + 8px); }

/* Piste verticale (demandé) : les bulles se positionnent par le bas
   (`bottom: pct%`, posé en JS) et débordent sur le CÔTÉ de la piste — à
   droite par défaut, à gauche en `orient-left` — au lieu d'au-dessus/en
   dessous. Même logique de tige + bulle de texte, axe changé. */
.nr-cue-chips.nr-vert .nr-cue-chip {
  left: 100%; bottom: 0; top: auto;
  margin-bottom: 0; margin-left: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1)));
  transform: translateY(50%);
}
.nr-cue-chips.nr-vert .nr-cue-chip.row-1 { margin-left: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
.nr-cue-chips.nr-vert .nr-cue-chip:hover,
.nr-cue-chips.nr-vert .nr-cue-chip:focus-visible { transform: translateY(50%) scale(1.15); }
.nr-cue-chips.nr-vert .nr-cue-stem {
  left: auto; right: 100%; top: 50%; bottom: auto;
  width: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); height: 2px;
  transform: translateY(-50%);
}
.nr-cue-chips.nr-vert .nr-cue-chip.row-1 .nr-cue-stem { width: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
.nr-cue-chips.nr-vert .nr-cue-chip::before {
  bottom: auto; top: 50%; left: calc(100% + 8px);
  transform: translateY(-50%);
}
.nr-cue-chips.nr-vert.orient-left .nr-cue-chip {
  left: auto; right: 100%;
  margin-left: 0; margin-right: calc(10px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1)));
}
.nr-cue-chips.nr-vert.orient-left .nr-cue-chip.row-1 { margin-right: calc(24px * var(--nr-chip-stem-scale, var(--nr-chip-scale, 1))); }
.nr-cue-chips.nr-vert.orient-left .nr-cue-stem { right: auto; left: 100%; }
.nr-cue-chips.nr-vert.orient-left .nr-cue-chip::before { left: auto; right: calc(100% + 8px); }

/* Bulle non-cliquable (demandé) : grisée pour signaler « ceci ne réagit pas
   au clic » — purement visuel, sans lien avec le système de verrou/message
   du bouton grisé (access-lock.js). */
.nr-cue-chip.is-inert {
  filter: grayscale(.8); opacity: .55; cursor: default;
}
.nr-cue-chip.is-inert:hover, .nr-cue-chip.is-inert:focus-visible { transform: translateX(-50%); }

/* Pastille rouge « dernière bulle cliquée » (demandé) — un pointeur, pas un
   historique : une seule à la fois, se déplace au clic suivant (sheet-audio.js
   gère le déplacement, ceci n'est que le rendu). */
.nr-cue-chip.is-last-clicked::after {
  content: ''; position: absolute; top: -3px; right: -3px;
  width: 9px; height: 9px; border-radius: 50%;
  background: #ef4444; box-shadow: 0 0 0 2px #fff;
}

/* Infobulle coupée globalement (demandé) : utile quand le lecteur est
   piloté à 100% par des boutons externes en mode discret — personne ne
   survole vraiment ces bulles, l'infobulle ne fait qu'apparaître par
   accident. */
.nr-cue-chips.no-tip .nr-cue-chip::before { display: none; }

/* Mode discret (demandé) : cache ▶️/barre/temps, garde seulement les
   repères — la piste (.wq-audio-track) reste dans le DOM et sa largeur
   réelle (les bulles s'y ancrent en %), juste rendue invisible. Le <audio>
   lui-même n'est jamais caché : ça n'empêcherait pas le son, juste inutile
   de le cacher visuellement puisqu'il n'a pas de rendu propre. */
.wq-audio.nr-discreet .wq-audio-play,
.wq-audio.nr-discreet .wq-audio-time,
.wq-audio.nr-discreet .wq-audio-fill { display: none; }
.wq-audio.nr-discreet .wq-audio-track { background: transparent; cursor: default; }
/* Fil discret (option, mode discret uniquement) — un simple trait fin à la
   place de la barre complète, réglages séparés (style/épaisseur/couleur). */
.wq-audio.nr-discreet .wq-audio-track.nr-discreet-line {
  border-bottom: var(--nr-line-w, 2px) var(--nr-line-style, solid) var(--nr-line-color, #94a3b8);
}
/* Piste verticale + fil discret : le trait passe du bas (border-bottom) au
   côté droit (border-right) de la piste debout. */
.wq-audio.nr-vertical.nr-discreet .wq-audio-track.nr-discreet-line {
  border-bottom: none;
  border-right: var(--nr-line-w, 2px) var(--nr-line-style, solid) var(--nr-line-color, #94a3b8);
}
/* Petits décors aux deux bouts du fil discret (demandé) — purement
   ornemental, couleur toujours héritée de --nr-line-color (posée par le
   fil lui-même juste au-dessus), taille pilotée par bulle via
   --nr-deco-size. */
.nr-line-deco {
  position: absolute; bottom: -1px;
  width: var(--nr-deco-size, 8px); height: var(--nr-deco-size, 8px);
  background: var(--nr-line-color, #94a3b8);
  transform: translateY(50%);
  pointer-events: none;
}
.nr-line-deco-start { left: 0; transform: translate(-50%, 50%); }
.nr-line-deco-end { right: 0; transform: translate(50%, 50%); }
.nr-line-deco-diamond { transform: translate(-50%, 50%) rotate(45deg); }
.nr-line-deco-end.nr-line-deco-diamond { transform: translate(50%, 50%) rotate(45deg); }
.nr-line-deco-square { border-radius: 1px; }
.nr-line-deco-flower {
  width: calc(var(--nr-deco-size, 8px) * 0.55); height: calc(var(--nr-deco-size, 8px) * 0.55);
  border-radius: 50%;
  box-shadow:
    0 calc(var(--nr-deco-size, 8px) * -0.42) var(--nr-line-color, #94a3b8),
    0 calc(var(--nr-deco-size, 8px) * 0.42) var(--nr-line-color, #94a3b8),
    calc(var(--nr-deco-size, 8px) * -0.42) 0 var(--nr-line-color, #94a3b8),
    calc(var(--nr-deco-size, 8px) * 0.42) 0 var(--nr-line-color, #94a3b8);
}
.nr-line-deco-arrow {
  background: none; width: 0; height: 0;
  border-top: calc(var(--nr-deco-size, 8px) * 0.5) solid transparent;
  border-bottom: calc(var(--nr-deco-size, 8px) * 0.5) solid transparent;
  border-left: var(--nr-deco-size, 8px) solid var(--nr-line-color, #94a3b8);
}
.nr-line-deco-start.nr-line-deco-arrow {
  border-left: none;
  border-right: var(--nr-deco-size, 8px) solid var(--nr-line-color, #94a3b8);
}
/* Piste verticale + fil discret : les décors passent des deux bouts gauche/
   droite d'un trait horizontal aux deux bouts bas/haut d'un trait vertical
   (le fil lui-même est en border-right, voir plus haut). */
.wq-audio.nr-vertical .nr-line-deco {
  bottom: auto; right: -1px;
  transform: translateX(50%);
}
.wq-audio.nr-vertical .nr-line-deco-start { top: auto; bottom: 0; left: auto; transform: translate(50%, 50%); }
.wq-audio.nr-vertical .nr-line-deco-end { top: 0; right: -1px; left: auto; transform: translate(50%, -50%); }
.wq-audio.nr-vertical .nr-line-deco-diamond { transform: translate(50%, 50%) rotate(45deg); }
.wq-audio.nr-vertical .nr-line-deco-end.nr-line-deco-diamond { transform: translate(50%, -50%) rotate(45deg); }
/* .is-current (repère du segment en cours, pour le mode « un seul visible »
   — sheet-audio.js) reste une classe purement logique, SANS animation :
   précisé après retour — la pulsation ne doit apparaître QUE là où la
   lecture est VRAIMENT arrêtée (.is-stopped ci-dessous), pas dès qu'on
   approche d'un repère. */

/* Pulsation à l'arrêt (option, demandée) — la lecture est VRAIMENT
   arrêtée sur ce repère précis. Grossit la bulle en plus de l'anneau. */
.nr-cue-chip.is-stopped {
  animation: nr-chip-pulse-stop 1s ease-in-out infinite;
}
@keyframes nr-chip-pulse-stop {
  0%, 100% { transform: translateX(-50%) scale(1); box-shadow: 0 2px 6px rgba(15, 23, 42, .25), 0 0 0 0 var(--nr-chip-c, #6366f1); }
  50% { transform: translateX(-50%) scale(1.25); box-shadow: 0 2px 6px rgba(15, 23, 42, .25), 0 0 0 9px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .nr-cue-chip.is-stopped { animation: none; }
}

/* ---------- action_badge (quest-action-badge.js) ---------- */
/* Freely placed badge-on-a-stem — same visual family as the cue chips above
   (colored circle, icon, thin stem), but a standalone element positioned by
   its own x_pos/y_pos rather than anchored along an audio track. */
.wq-action-badge { position: absolute; }
.wq-ab-anchor {
  position: absolute; left: 50%; top: 50%; width: 6px; height: 6px;
  margin: -3px 0 0 -3px; border-radius: 50%;
  background: var(--ab-c, #6366f1); opacity: .35; pointer-events: none;
}
.wq-ab-stem {
  position: absolute; left: 50%; top: 50%; width: 2px;
  background: var(--ab-c, #6366f1); opacity: .6; pointer-events: none;
}
.wq-ab-btn {
  position: absolute; left: 50%; top: 50%; padding: 0; cursor: pointer;
  width: var(--ab-size, 26px); height: var(--ab-size, 26px);
  border-radius: 50%; background: var(--ab-c, #6366f1); border: 1.5px solid #fff;
  box-shadow: 0 2px 6px rgba(15, 23, 42, .25);
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--ab-size, 26px) * .5); line-height: 1;
  transition: transform .12s ease;
}
.wq-ab-btn:hover, .wq-ab-btn:focus-visible { z-index: 2; }
.wq-action-badge.is-disabled .wq-ab-btn { filter: grayscale(.8); opacity: .55; cursor: not-allowed; }
/* Bulle de texte au survol (player only — data-tip is only set there) — même
   habillage que .nr-cue-chip::before (cue chips de la piste audio), pour que
   les deux badges de la même famille visuelle se comportent pareil. */
.wq-ab-btn[data-tip]::before {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: 50%;
  transform: translateX(-50%);
  background: #1e293b; color: #fff; font-size: 11px; padding: 4px 8px;
  border-radius: 6px; white-space: nowrap; max-width: 220px;
  overflow: hidden; text-overflow: ellipsis;
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.wq-ab-btn[data-tip]:hover::before, .wq-ab-btn[data-tip]:focus-visible::before { opacity: 1; }
/* Editor-only stand-ins for what only renders live in the player (see file
   header note on why the connecting line isn't drawn in the editor). */
.wq-ab-linktag, .wq-ab-actiontag {
  position: absolute; left: 50%; top: 100%; transform: translateX(-50%);
  font-size: 9px; font-weight: 700; white-space: nowrap; color: #64748b;
  background: #fff; border: 1px solid #e2e8f0; border-radius: 4px; padding: 1px 4px;
  pointer-events: none;
}
.wq-ab-linktag { margin-top: 2px; }
.wq-ab-actiontag { margin-top: 16px; opacity: .8; }

/* ---------- Outil de placement visuel du spotlight (sheet-audio.js) ----------
   Même famille visuelle que .wq-rb-place-* (robot.css, point+rayon du Robot
   Guide), adaptée pour un RECTANGLE : posé DANS #q-canvas, donc soumis au
   même transform:scale (zoom) que le reste — les % restent cohérents à
   tout niveau de zoom sans recalcul. */
.wq-spot-place-box {
  position: absolute; z-index: 60;
  border: 2px dashed #ff5a3c; border-radius: 8px;
  background: rgba(255, 90, 60, .08);
}
.wq-spot-place-move {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 28px; height: 28px; border-radius: 50%; background: #ff5a3c; color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 1rem;
  cursor: grab; touch-action: none; box-shadow: 0 2px 8px rgba(15, 23, 42, .35);
}
.wq-spot-place-move:active { cursor: grabbing; }
.wq-spot-place-resize {
  position: absolute; right: -9px; bottom: -9px;
  width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 3px solid #ff5a3c;
  cursor: nwse-resize; touch-action: none; box-shadow: 0 2px 8px rgba(15, 23, 42, .35);
}
.wq-spot-place-done {
  position: absolute; left: 0; top: -34px; white-space: nowrap;
  padding: 5px 12px; border: none; border-radius: 999px; font-size: .74rem; font-weight: 700;
  cursor: pointer; background: rgba(15, 23, 42, .85); color: #fff;
}

/* ---------- Narration Tier 2 overlays ---------- */
/* Bubble on an element (translation, definition…). Fixed-positioned from the
   target's viewport rect, so a page zoom transform can't drag it off. */
.nr-popup {
  position: fixed; transform: translate(-50%, -115%) scale(.9); transform-origin: bottom center;
  z-index: 960; max-width: min(320px, 80vw); pointer-events: none;
  background: #1e293b; color: #fff; font-weight: 700; font-size: .82rem; line-height: 1.4;
  padding: 8px 13px; border-radius: 10px; box-shadow: 0 10px 26px rgba(15,23,42,.35);
  opacity: 0; transition: opacity .22s ease, transform .22s ease;
}
.nr-popup.show { opacity: 1; transform: translate(-50%, -125%) scale(1); }
.nr-popup::after {
  content: ''; position: absolute; top: 100%; left: 50%; margin-left: -7px;
  border: 7px solid transparent; border-top-color: var(--nr-popup-arrow, #1e293b);
}

/* Push in on an element. */
.nr-zoom {
  transform: scale(var(--nr-zoom, 1.35)) !important;
  transition: transform .5s cubic-bezier(.2,.8,.2,1) !important;
  z-index: 120 !important; position: relative;
}

/* Marker on a picture: a pin (dot), a hand-drawn circle around a detail, or a
   rectangle around a whole area — plus an optional caption and an optional
   zoom-reveal magnifier. Positioned inside the target's offset parent, so it
   scrolls and zooms WITH the page it annotates. --nr-hs-color is set from JS
   (per-cue, so every marker can be a different colour). */
.nr-hotspot { position: absolute; z-index: 130; pointer-events: none; opacity: 0; }

/* dot/circle are point-anchored (left/top = the point, centred on it);
   frame's left/top/width/height already describe the exact box, so it must
   NOT also be re-centred by a translate. */
.nr-hotspot-dot, .nr-hotspot-circle {
  display: flex; align-items: center; gap: 8px;
  transform: translate(-50%, -50%) scale(.6);
  transition: opacity .25s ease, transform .25s ease;
}
.nr-hotspot-dot.show, .nr-hotspot-circle.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }

.nr-hotspot-frame {
  box-sizing: border-box; transform: scale(.9);
  transition: opacity .25s ease, transform .25s ease;
}
.nr-hotspot-frame.show { opacity: 1; transform: scale(1); }

/* --- dot: the original pin --- */
.nr-hotspot-marker-dot {
  width: var(--nr-hs-size, 18px); height: var(--nr-hs-size, 18px);
  flex: 0 0 var(--nr-hs-size, 18px);
  border-radius: 50%;
  background: var(--nr-hs-color, #f59e0b); border: 3px solid #fff;
  box-shadow: 0 0 0 4px rgba(0, 0, 0, .15), 0 2px 8px rgba(0, 0, 0, .3);
  animation: nrPinPulse 1.6s ease-in-out infinite;
}
@keyframes nrPinPulse {
  0%, 100% { box-shadow: 0 0 0 4px rgba(0, 0, 0, .15), 0 2px 8px rgba(0, 0, 0, .3); }
  50%      { box-shadow: 0 0 0 11px rgba(0, 0, 0, 0), 0 2px 8px rgba(0, 0, 0, .3); }
}

/* --- circle: the "I circled it in red pen" effect — transparent centre so
   the image detail stays visible, a wobbly hand-drawn outline around it --- */
.nr-hotspot-marker-circle {
  /* Keeps the original 62×54 (≈.87) proportion at any size the author picks. */
  width: var(--nr-hs-size, 62px); height: calc(var(--nr-hs-size, 62px) * .87);
  flex: 0 0 var(--nr-hs-size, 62px); box-sizing: border-box;
  border: 3.5px solid var(--nr-hs-color, #ef4444); background: transparent;
  animation: nrHsWobble 1.8s ease-in-out infinite;
}
@keyframes nrHsWobble {
  0%, 100% { border-radius: 46% 54% 60% 40% / 55% 45% 60% 40%; transform: rotate(-3deg); }
  50%      { border-radius: 54% 46% 40% 60% / 45% 55% 40% 60%; transform: rotate(3deg); }
}

/* --- frame: a targeted AREA, not a point. No rotation (would misalign with
   the image beneath it) — a dashed "selection" border with a soft pulse
   reads as "look inside this box" without distorting anything. --- */
.nr-hotspot-frame {
  border: 3px dashed var(--nr-hs-color, #ef4444); border-radius: 8px;
  animation: nrHsFramePulse 1.8s ease-in-out infinite;
}
@keyframes nrHsFramePulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(0, 0, 0, .12); }
  50%      { box-shadow: 0 0 0 9px rgba(0, 0, 0, 0); }
}

.nr-hotspot-label {
  background: #fff; color: #1e293b; font-weight: 700; font-size: .76rem; white-space: nowrap;
  padding: 4px 10px; border-radius: 999px; box-shadow: 0 4px 12px rgba(15,23,42,.25);
}
/* A frame's label has no flex row to sit in — anchor it under the box. */
.nr-hotspot-frame .nr-hotspot-label {
  position: absolute; top: 100%; left: 0; margin-top: 6px;
}

/* Zoom-reveal: a magnifier window on the SAME image, cropped/scaled via
   background-position math — never a second image request. position:absolute
   takes it out of the marker's flex row automatically, on every shape. */
.nr-hotspot-zoom {
  position: absolute; left: 50%; bottom: 100%; transform: translateX(-50%);
  margin-bottom: 10px; border-radius: 50%; border: 4px solid #fff;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .4); background-repeat: no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  .nr-hotspot-marker-dot, .nr-hotspot-marker-circle, .nr-hotspot-frame { animation: none; }
}

/* ---------- Narration Tier 2/3 effects ---------- */
/* Slow pan + push, the "Ken Burns" of the reference pages. */
.nr-kenburns { animation: nrKen 9s ease-in-out forwards; transform-origin: 50% 45%; }
@keyframes nrKen { from { transform: scale(1); } to { transform: scale(1.14) translate(-1.5%, -1.5%); } }
/* Black-and-white → colour. */
.nr-colorize { animation: nrColorize 2.2s ease forwards; }
@keyframes nrColorize { from { filter: grayscale(1) contrast(.95); } to { filter: none; } }

/* Spotlight: a scrim with a hole. The huge spread box-shadow IS the scrim, so
   there is no clip-path or canvas involved. Revu (demandé) : couleur/
   opacité réglables (--nr-spot-shadow, calculé en JS — voir hexToRgba dans
   narration-engine.js), forme cercle en option, et une transition sur la
   position/taille/arrondi — pas seulement l'opacité — pour qu'un RE-
   spotlight sur une nouvelle cible GLISSE jusqu'à elle au lieu de sauter. */
.nr-spot {
  --nr-spot-shadow: rgba(15, 23, 42, .72);
  position: fixed; z-index: 940; pointer-events: none; border-radius: 14px;
  box-shadow: 0 0 0 100vmax var(--nr-spot-shadow);
  opacity: 0;
  transition: opacity .3s ease, left .5s cubic-bezier(.4,0,.2,1), top .5s cubic-bezier(.4,0,.2,1),
              width .5s cubic-bezier(.4,0,.2,1), height .5s cubic-bezier(.4,0,.2,1), border-radius .35s ease;
}
.nr-spot.show { opacity: 1; }
.nr-spot.circle { border-radius: 50%; }
/* Bouton ✕ (option, demandé) — pointer-events réactivé juste pour lui, le
   reste du cadre reste pointer-events:none (il ne doit jamais bloquer le
   clic sur ce qu'il isole). Coin haut-droit, hors du cercle en mode
   cercle (sinon un cadre carré-arrondi le couperait à moitié). */
.nr-spot-close {
  position: absolute; top: -10px; right: -10px; z-index: 1;
  width: 26px; height: 26px; border-radius: 50%; border: 2px solid #fff;
  background: #1e293b; color: #fff; font-size: .8rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; pointer-events: auto; box-shadow: 0 2px 8px rgba(15,23,42,.4);
}
.nr-spot-close:hover { background: #334155; }
/* Anneau qui respire (option) — un halo doré qui pulse en dehors du cadre,
   purement décoratif (le trou lui-même ne bouge pas). */
.nr-spot.pulse.show::after {
  content: ''; position: absolute; inset: -6px; border-radius: inherit;
  box-shadow: 0 0 0 0 rgba(253, 224, 71, .55);
  animation: nrSpotPulse 1.8s ease-out infinite;
}
@keyframes nrSpotPulse {
  0% { box-shadow: 0 0 0 0 rgba(253, 224, 71, .55); }
  70% { box-shadow: 0 0 0 14px rgba(253, 224, 71, 0); }
  100% { box-shadow: 0 0 0 0 rgba(253, 224, 71, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .nr-spot.pulse.show::after { animation: none; }
  .nr-spot { transition: opacity .3s ease; }
}

/* Karaoke sweep: each word lights as the voice reaches it. */
.nr-kw { transition: background-color .18s ease, color .18s ease; border-radius: 4px; padding: 0 2px; }
.nr-kw.on { background-color: #fde68a; color: #7c2d12; }

/* Branching: the narration stops and asks. */
.nr-choice {
  position: fixed; inset: 0; z-index: 970; display: flex; align-items: center; justify-content: center;
  background: rgba(15, 23, 42, .5); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .2s ease; padding: 1.2rem;
}
.nr-choice.show { opacity: 1; }
.nr-choice-card {
  background: #fff; border-radius: 16px; padding: 1.3rem 1.4rem; width: min(420px, 100%);
  box-shadow: 0 24px 60px rgba(15, 23, 42, .35); display: flex; flex-direction: column; gap: 9px;
}
.nr-choice-q { margin: 0 0 4px; font-weight: 800; font-size: 1rem; color: #1e293b; text-align: center; }
.nr-choice-btn {
  border: none; border-radius: 999px; padding: 11px 18px; cursor: pointer;
  font-family: inherit; font-weight: 800; font-size: .9rem;
  background: #6366f1; color: #fff; transition: filter .15s;
}
.nr-choice-btn:hover { filter: brightness(1.08); }
@media (prefers-reduced-motion: reduce) {
  .nr-kenburns { animation: none; }
}

/* playVideo action: a lightbox opened from a click or a narration cue —
   works in BOTH Sheet and Quest since narration-engine.js is shared, so it
   lives here (core.css) rather than in a mode-specific theme file. */
.nr-video-pop {
  position: fixed; inset: 0; z-index: 975;
  background: rgba(4, 8, 20, .86);
  display: flex; align-items: center; justify-content: center;
  padding: 4vh 4vw; opacity: 0; transition: opacity .15s ease;
}
.nr-video-pop.show { opacity: 1; }
.nr-video-pop-inner {
  position: relative; width: min(92vw, 1100px); aspect-ratio: 16 / 9;
  background: var(--nr-video-bg, #0f172a); border-radius: var(--nr-video-radius, 14px);
  overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.nr-video-pop-inner iframe, .nr-video-pop-inner video {
  position: absolute; inset: 0; width: 100%; height: 100%; border: none; display: block;
}
.nr-video-pop-close {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 36px; height: 36px; border-radius: 50%; border: none;
  background: rgba(15, 23, 42, .7); color: #fff; font-size: 1.2rem; line-height: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.nr-video-pop-close:hover { background: var(--nr-video-accent, #6366f1); }

/* playVideo custom skins (playful / elegant) — LOCAL video only; mirrors
   quest-video.js's own skin CSS but under the nr- prefix, since this action
   runs from the shared engine (Quest AND Sheet), not the Quest-only widget. */
.nr-video-pop-inner--split { display: flex; flex-direction: column; }
.nr-video-pop-inner--split video { position: static; flex: 1; min-height: 0; width: 100%; height: 100%; }

.nr-video-ctrl { position: relative; flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; }
.nr-vc-progress { padding: 0 2px; }
.nr-vc-track { position: relative; cursor: pointer; }
.nr-vc-fill { position: absolute; top: 0; bottom: 0; left: 0; width: 0%; }
.nr-vc-handle { position: absolute; top: 50%; left: 0%; pointer-events: none; }
.nr-vc-row { display: flex; align-items: center; gap: 6px; }
.nr-vc-btn { border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; }
.nr-vc-spacer { flex: 1; }
.nr-vc-time { white-space: nowrap; }

.nr-video-ctrl--playful { background: #2f6fb0; padding: 10px 12px 12px; }
.nr-video-ctrl--playful .nr-vc-track { height: 8px; border-radius: 999px; background: rgba(255, 255, 255, .32); }
.nr-video-ctrl--playful .nr-vc-fill { border-radius: 999px; background: #e0483e; }
.nr-video-ctrl--playful .nr-vc-handle {
  width: 18px; height: 18px; border-radius: 50%; background: #fff; border: 3px solid #e0483e;
  transform: translate(-50%, -50%); box-shadow: 0 2px 4px rgba(0, 0, 0, .3);
}
.nr-video-ctrl--playful .nr-vc-row { flex-wrap: wrap; }
.nr-video-ctrl--playful .nr-vc-btn {
  width: 40px; height: 36px; border-radius: 11px; color: #fff;
  box-shadow: 0 3px 0 rgba(0, 0, 0, .22); transition: transform .08s ease, box-shadow .08s ease;
}
.nr-video-ctrl--playful .nr-vc-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0, 0, 0, .22); }
.nr-video-ctrl--playful .nr-vc-play { background: #3a7dc4; width: 46px; }
.nr-video-ctrl--playful .nr-vc-rw { background: #f0a23a; }
.nr-video-ctrl--playful .nr-vc-fw { background: #8b6fd6; }
.nr-video-ctrl--playful .nr-vc-vol { background: #5cb85c; }
.nr-video-ctrl--playful .nr-vc-speed {
  background: #f0a23a; color: #fff; font-weight: 800; font-size: .72rem; border-radius: 11px;
  width: auto; padding: 0 10px; height: 36px; box-shadow: 0 3px 0 rgba(0, 0, 0, .22);
}
.nr-video-ctrl--playful .nr-vc-fs { background: #e6597a; }
.nr-video-ctrl--playful .nr-vc-time {
  background: #16283c; color: #fff; font-weight: 700; font-size: .74rem; padding: 0 12px; height: 36px;
  border-radius: 10px; display: flex; align-items: center; font-variant-numeric: tabular-nums;
}

.nr-video-ctrl--elegant {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  background: linear-gradient(to top, rgba(0, 0, 0, .75), rgba(0, 0, 0, 0) 90%);
  padding: 26px 12px 8px; opacity: 0; transform: translateY(6px); pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.nr-video-ctrl--elegant.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
.nr-video-ctrl--elegant .nr-vc-track { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, .28); }
.nr-video-ctrl--elegant .nr-vc-fill { border-radius: 999px; background: var(--nr-video-accent, #6366f1); }
.nr-video-ctrl--elegant .nr-vc-handle {
  width: 12px; height: 12px; border-radius: 50%; background: var(--nr-video-accent, #6366f1);
  transform: translate(-50%, -50%) scale(0); box-shadow: 0 0 0 4px rgba(255, 255, 255, .15);
  transition: transform .15s ease;
}
.nr-video-ctrl--elegant .nr-vc-track:hover .nr-vc-handle { transform: translate(-50%, -50%) scale(1); }
.nr-video-ctrl--elegant .nr-vc-btn {
  width: 30px; height: 30px; border-radius: 8px; background: transparent; color: #fff; opacity: .92;
  transition: background .15s ease, opacity .15s ease;
}
.nr-video-ctrl--elegant .nr-vc-btn:hover { background: rgba(255, 255, 255, .14); opacity: 1; }
.nr-video-ctrl--elegant .nr-vc-speed { font-size: .68rem; font-weight: 700; width: auto; padding: 0 8px; color: #fff; background: transparent; }
.nr-video-ctrl--elegant .nr-vc-speed:hover { background: rgba(255, 255, 255, .14); }
.nr-video-ctrl--elegant .nr-vc-time { color: rgba(255, 255, 255, .85); font-size: .7rem; font-variant-numeric: tabular-nums; padding: 0 4px; }

/* Carnet: the platform's own kraft-paper palette, hardcoded here (not the
   --wq-* tokens) since this action runs on Sheet pages too, which don't
   define those Quest-scoped variables. */
.nr-video-ctrl--carnet { background: #f6ecd8; padding: 9px 10px 3px; }
.nr-video-ctrl--carnet .nr-vc-track { height: 5px; border-radius: 3px; background: #ecdcbb; }
.nr-video-ctrl--carnet .nr-vc-fill { border-radius: 3px; background: #3d6e8c; }
.nr-video-ctrl--carnet .nr-vc-handle {
  width: 9px; height: 9px; border-radius: 50%; background: #d69a3c;
  border: 2px solid #f6ecd8; box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
}
.nr-video-ctrl--carnet .nr-vc-btn { width: 32px; height: 32px; border-radius: 50%; background: #ecdcbb; color: #3b2f22; transition: background .12s ease; }
.nr-video-ctrl--carnet .nr-vc-btn:hover { background: #d8c69f; }
.nr-video-ctrl--carnet .nr-vc-play { background: #3d6e8c; color: #fdfaf0; }
.nr-video-ctrl--carnet .nr-vc-speed {
  background: transparent; border: 1px solid #d8c69f; color: #6b5a44;
  font-weight: 700; font-size: .68rem; width: auto; padding: 0 10px; height: 32px; border-radius: 999px;
}
.nr-video-ctrl--carnet .nr-vc-speed:hover { background: #ecdcbb; }
.nr-video-ctrl--carnet .nr-vc-time { color: #6b5a44; font-size: .72rem; font-variant-numeric: tabular-nums; }

/* playWordwall action popup — same "self-contained, shared engine" spirit as
   the playVideo popup above; works on Sheet too. */
.nr-ww-pop {
  position: fixed; inset: 0; z-index: 99999;
  background: rgba(4, 8, 20, var(--nr-ww-backdrop-opacity, .86));
  display: flex; align-items: center; justify-content: center; padding: 4vh 4vw;
}
/* Signalé : "pas optimisé sur écran moyen/grand, juste correct en petit" —
   la largeur était plafonnée à 720px QUOI QU'IL ARRIVE, donc sur un grand
   écran le quiz restait minuscule au milieu d'un immense fond sombre. Les
   deux dimensions montent maintenant avec la fenêtre (min(vw/vh, plafond))
   au lieu d'un plafond fixe pensé pour un petit écran seulement. */
.nr-ww-pop-inner {
  position: relative; width: min(92vw, 1100px); height: min(86vh, 760px); max-height: 90vh;
  background: var(--nr-ww-bg, #0f172a); border-radius: var(--nr-ww-radius, 14px);
  overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
  display: flex; flex-direction: column;
}
.nr-ww-pop-header {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; background: var(--nr-ww-header-bg, var(--nr-ww-accent, #6366f1)); color: #fff;
  font-weight: 800; font-size: .85rem; flex: 0 0 auto;
}
.nr-ww-pop-title {
  flex: 1; min-width: 0; color: var(--nr-ww-title-color, #fff);
  text-align: var(--nr-ww-title-align, left);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Rouge franc, alignée sur l'esthétique du bouton "réessayer" plus bas —
   une croix de fermeture qui se voit du premier coup d'œil, jamais confondue
   avec un autre contrôle du header. */
.nr-ww-pop-close {
  border: none; background: #dc2626; color: #fff;
  width: 28px; height: 28px; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 1rem; flex: 0 0 auto;
  box-shadow: 0 3px 8px rgba(220, 38, 38, .45);
  transition: transform .1s ease, box-shadow .1s ease;
}
.nr-ww-pop-close:hover { background: #ef4444; transform: scale(1.08); box-shadow: 0 4px 10px rgba(220, 38, 38, .55); }
.nr-ww-pop-close:active { transform: scale(.94); }
.nr-ww-pop-body { position: relative; flex: 1; min-height: 380px; }
.nr-ww-pop-body iframe {
  position: absolute; inset: 0; width: 100%; height: 100%; border: none; display: block;
  transition: opacity .2s ease;
}
.nr-ww-spinner { position: absolute; top: 50%; left: 50%; width: 1px; height: 1px; z-index: 2; display: none; }
.nr-ww-spinner.show { display: block; }
.nr-ww-spinner span {
  position: absolute; top: 0; left: 0; width: 6px; height: 6px; margin: -3px 0 0 -3px;
  border-radius: 50%; background: var(--nr-ww-accent, #6366f1);
  transform: rotate(calc(var(--i) * 45deg)) translateY(-18px);
  animation: wq-ww-spin 1.1s linear infinite;
  animation-delay: calc(var(--i) * -0.1375s);
  opacity: .25;
}
@keyframes wq-ww-spin { 0% { opacity: 1; } 100% { opacity: .25; } }

/* Retry state — same reasoning as quest-wordwall.js's own version: a cross-
   origin iframe never reliably reports failure, so a timeout + navigator.
   onLine check stands in for it, with a big obvious way to try again. */
.nr-ww-error {
  position: absolute; inset: 0; z-index: 3; display: none;
  flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  background: var(--nr-ww-bg, #0f172a); text-align: center; padding: 20px;
}
.nr-ww-error.show { display: flex; }
.nr-ww-error-msg { color: #e2e8f0; font-weight: 700; font-size: .85rem; max-width: 280px; margin: 0; }
.nr-ww-retry {
  width: 64px; height: 64px; border-radius: 50%; border: none; cursor: pointer;
  background: #dc2626; color: #fff; font-size: 1.8rem;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 8px 20px rgba(220, 38, 38, .4);
  transition: transform .1s ease, box-shadow .1s ease;
}
.nr-ww-retry:hover { transform: scale(1.06); box-shadow: 0 10px 24px rgba(220, 38, 38, .5); }
.nr-ww-retry:active { transform: scale(.96); }

/* Player leaving the stage at the end of its track (hideOnEnd). */
.wq-audio.nr-fade-out { opacity: 0 !important; transform: translateY(8px); transition: opacity .4s ease, transform .4s ease; pointer-events: none; }
.wq-audio.nr-dock-bottom.nr-fade-out, .wq-audio.nr-dock-top.nr-fade-out { margin-left: auto !important; margin-right: auto !important; }

/* ---------- Clickable elements (clickable-kit.js) ----------
   Opt-in per element: reuses NarrationEngine's action catalogue as a click
   trigger instead of a timestamp. Hover/press are generic feedback; .styled
   additionally dresses a plain element (text, image…) as a real button. */
.wq-clickable { cursor: pointer; transition: transform .12s ease, box-shadow .12s ease, filter .12s ease; }
.wq-clickable:hover { transform: translateY(-2px); filter: brightness(1.04); }
.wq-clickable:active { transform: translateY(1px) scale(.97); filter: brightness(.97); transition-duration: .05s; }
.wq-clickable:focus-visible { outline: 3px solid #6366f1; outline-offset: 2px; }

.wq-clickable-styled {
  background: #fff; border: 2px solid #6366f1; border-radius: 12px;
  box-shadow: 0 4px 10px rgba(15, 23, 42, .12);
  display: flex; align-items: center; justify-content: center;
}
.wq-clickable-styled:hover { box-shadow: 0 8px 20px rgba(15, 23, 42, .18); }
.wq-clickable-styled:active { box-shadow: 0 2px 6px rgba(15, 23, 42, .12); }
@media (prefers-reduced-motion: reduce) {
  .wq-clickable, .wq-clickable:hover, .wq-clickable:active { transition: none; transform: none; }
}

/* ---------- Slideshow (sheet-slideshow.js) ---------- */
.wq-ss-preview {
  position: relative; overflow: hidden; display: flex; align-items: flex-end;
  font-family: inherit; color: #fff;
}
.wq-ss-badge {
  background: rgba(15, 23, 42, .7); font-size: .72rem; font-weight: 700;
  padding: 5px 10px; margin: 8px; border-radius: 999px;
}
.wq-ss-hint {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  background: #e2e8f0; color: #64748b; font-weight: 700; font-size: .85rem; border-radius: inherit;
}

.wq-slideshow {
  position: relative; overflow: hidden; background: var(--ss-bg, #0f172a);
  border-radius: var(--ss-radius, 16px); box-sizing: border-box;
}
.wq-ss-header {
  position: absolute; top: 0; left: 0; right: 0; z-index: 20;
  background: rgba(15, 23, 42, .55); color: #fff; font-weight: 800; font-size: 1rem;
  padding: 10px 16px; text-align: center;
}
.wq-ss-stage { position: absolute; inset: 0; overflow: hidden; }
.wq-ss-slide {
  position: absolute; inset: 0; background-position: center; background-repeat: no-repeat;
  background-size: cover; /* overridden below when the author picks "contain" */
  opacity: 0; z-index: 1; transition: opacity var(--ss-trans-ms, 500ms) ease;
}
/* "Image entière": the picture is never cropped, so letterboxing shows the
   configured background instead of the slideshow's own dark backdrop —
   otherwise a light photo would sit on an unexplained black bar. */
.wq-slideshow[data-fit="contain"] .wq-ss-stage,
.wq-slideshow[data-fit="contain"] .wq-ss-slide { background-color: var(--ss-image-bg, #0f172a); }
.wq-slideshow[data-fit="contain"] .wq-ss-slide { background-size: contain; }
.wq-ss-slide.is-active { opacity: 1; z-index: 2; }
/* Ken Burns: replay the slow drift each time a slide becomes active. */
.wq-ss-slide.has-kb.is-active { animation: wqSsKen 7s ease-in-out forwards; }
@keyframes wqSsKen {
  from { transform: scale(1) translate(0, 0); }
  to   { transform: scale(var(--ss-kb-scale, 1.12)) translate(-1.5%, -1.5%); }
}

.wq-ss-caption {
  position: absolute; left: 50%; transform: translateX(-50%); z-index: 25;
  max-width: 88%; background: var(--ss-cap-bg, rgba(15,23,42,.65)); color: var(--ss-cap-color, #fff);
  font-family: var(--ss-cap-font, inherit); font-weight: 700; font-size: var(--ss-cap-size, .85rem);
  padding: 7px 14px; border-radius: var(--ss-cap-radius, 10px);
  text-align: center;
}
.wq-ss-caption-top { top: 14px; }
/* Bottom stack: one anchor for whatever sits at the bottom of the frame
   (caption and/or dots), so they queue up instead of overlapping — see the
   JS comment in sheet-slideshow.js for why. Children opt out of their own
   absolute positioning via .in-stack and just flow inside the column. */
.wq-ss-bottom-stack {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 10px; z-index: 30;
  max-width: 92%; display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.wq-ss-caption.in-stack { position: static; left: auto; transform: none; }
.wq-ss-dots.in-stack { position: static; left: auto; transform: none; bottom: auto; }

.wq-ss-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 30;
  width: var(--ss-arrow-size, 34px); height: var(--ss-arrow-size, 34px);
  border-radius: 50%; border: none; cursor: pointer;
  background: var(--ss-arrow-bg, rgba(15,23,42,.45)); color: var(--ss-arrow-color, #fff);
  font-size: calc(var(--ss-arrow-size, 34px) * .27);
  display: flex; align-items: center; justify-content: center; transition: filter .15s;
}
.wq-ss-arrow:hover { filter: brightness(1.15); }
.wq-ss-arrow.prev { left: 10px; }
.wq-ss-arrow.next { right: 10px; }

.wq-ss-dots {
  position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); z-index: 30;
  display: flex; align-items: center; gap: 6px;
}
.wq-ss-dot {
  width: var(--ss-dot-size, 8px); height: var(--ss-dot-size, 8px); border-radius: 50%;
  border: none; cursor: pointer; background: var(--ss-dot-color, rgba(255,255,255,.4));
  padding: 0; transition: background-color .15s, transform .15s;
}
.wq-ss-dot.is-active {
  background: var(--ss-dot-color-active, #6366f1);
  transform: scale(1.3);
}

/* "slide" transition: the active slide comes from the right, the outgoing one
   exits left — an extra class so it can coexist with the default fade CSS. */
.wq-slideshow[data-transition="slide"] .wq-ss-slide {
  transition: transform var(--ss-trans-ms, 500ms) ease, opacity 0s; opacity: 0; transform: translateX(100%);
}
.wq-slideshow[data-transition="slide"] .wq-ss-slide.is-active { opacity: 1; transform: translateX(0); }

.wq-slideshow[data-transition="zoom"] .wq-ss-slide {
  transition: opacity var(--ss-trans-ms, 500ms) ease, transform var(--ss-trans-ms, 500ms) ease;
  transform: scale(1.15);
}
.wq-slideshow[data-transition="zoom"] .wq-ss-slide.is-active { transform: scale(1); }

.wq-slideshow[data-transition="none"] .wq-ss-slide { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .wq-ss-slide.has-kb.is-active { animation: none; }
  .wq-ss-slide { transition: none !important; }
}

/* Kid/ludique — particules à la dernière diapo (voir burstSparkle() dans
   sheet-slideshow.js). z-index 5 : au-dessus des diapos/légende/puces (2-4),
   sous rien d'autre puisque ce widget n'a pas de barre flottante. */
.wq-ss-sparkle { position: absolute; inset: 0; z-index: 5; pointer-events: none; overflow: hidden; }
.wq-ss-sparkle span {
  position: absolute; top: -10%; width: 7px; height: 12px; opacity: 0;
  animation: wqSsSparkleFall 1s ease-in forwards;
}
@keyframes wqSsSparkleFall {
  0% { opacity: 1; transform: translateY(0) rotate(0deg); }
  100% { opacity: 0; transform: translateY(220%) rotate(340deg); }
}
@media (prefers-reduced-motion: reduce) { .wq-ss-sparkle { display: none; } }
