/* _core/_css/robot.css — Robot Guide (mascotte).
   Styles scopés .wq-robot-* : aucun sélecteur global, rien qui puisse déteindre
   sur l'éditeur ou sur les autres widgets. */

.wq-robot {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;          /* la zone du widget gère le clic, pas l'image */
  user-select: none;
  /* Sur la règle DE BASE, pas seulement sur .is-gone : une transition posée
     uniquement sur .is-gone n'anime que l'ALLER (la déclaration disparaît en
     même temps que la classe au retour, donc réapparaître serait instantané,
     sans le fondu). Ici les deux sens s'animent pareil. */
  transition: opacity .26s ease, transform .26s ease;
}

.wq-robot-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Toutes les poses partagent le même cadre : une pose qui charge remplace
     la précédente exactement au même endroit, sans saut ni recadrage. */
  display: block;
}

/* Flottement : la mascotte plane, elle ne se pose jamais (elle n'a pas de
   jambes). Amplitude volontairement faible — au-delà, ça distrait de la
   ressource qu'elle est censée présenter. */
.wq-robot.is-floating .wq-robot-img {
  animation: wq-rb-float 3.2s ease-in-out infinite;
}
@keyframes wq-rb-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4%); }
}

/* Regarder vers la gauche : utile quand elle présente une ressource située à
   sa gauche. Miroir horizontal, combiné au flottement sans l'annuler. */
.wq-robot.is-flipped .wq-robot-img { scale: -1 1; }

/* Reflet (Phase 38.5) — décoratif : une 2ᵉ image (src synchronisée en JS,
   voir setPose()), retournée verticalement, sous la mascotte, estompée par
   un dégradé de masque. Masquée par défaut (display:none), posée par
   ctrl.setMirror(true). pointer-events:none — ce n'est qu'un décor, jamais
   une 2ᵉ mascotte cliquable. */
.wq-robot-reflection {
  display: none;
  position: absolute;
  top: 96%; left: 0;
  width: 100%; height: 70%;
  object-fit: contain;
  object-position: top;
  scale: 1 -1;
  opacity: .35;
  filter: blur(1px);
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .55), transparent 85%);
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .55), transparent 85%);
  pointer-events: none;
}
.wq-robot.is-mirrored .wq-robot-reflection { display: block; }
/* Le miroir horizontal (is-flipped, ci-dessus) doit aussi retourner le
   reflet — sinon le personnage regarderait un sens et son reflet l'autre. */
.wq-robot.is-flipped .wq-robot-reflection { scale: -1 -1; }

/* Bouton « fermer » (✕) — demandé, optionnel (case dans la section
   Mascotte). Posé par robot-engine.js en position:fixed (comme la pastille
   d'émoticône ci-dessous) et repositionné au scroll/resize à partir du
   RENDU RÉEL du personnage (node.getBoundingClientRect()) — signalé : ancré
   en CSS sur le cadre du widget (host), il atterrissait n'importe où dès que
   ce cadre débordait du personnage réellement dessiné (object-fit:contain).
   Toujours accessible (pas attaché à la bulle, qui elle est temporaire). */
.wq-robot-close {
  position: fixed;
  width: 26px; height: 26px;
  border: 2px solid #fff;
  border-radius: 50%;
  background: #dc2626;
  color: #fff;
  font-size: .8rem;
  line-height: 1;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  box-shadow: 0 3px 8px rgba(0, 0, 0, .3);
  /* Au-dessus de la bulle de parole (z-index 970) : la bulle apparaît
     au-dessus de la tête de la mascotte et peut couvrir ce coin — sans ce
     z-index plus haut, le bouton restait cliquable mais VISUELLEMENT caché
     sous elle (signalé). */
  z-index: 971;
}
.wq-robot-close:hover { background: #b91c1c; }

/* Manifeste illisible : cadre discret plutôt qu'une image cassée. */
.wq-robot.is-missing {
  border: 1px dashed var(--line, #d8cbb0);
  border-radius: 10px;
  opacity: .5;
}

/* Disparaître / réapparaître. Posé sur .wq-robot (le CADRE), jamais sur
   .wq-robot-img : le flottement anime déjà l'image (translateY en boucle) —
   ajouter un 2e scale au même endroit ferait sauter l'une des deux
   animations. Ici, chacune sur son propre niveau, aucun conflit. */
.wq-robot.is-gone {
  opacity: 0;
  transform: scale(.7);
}

/* Phase 38 — rotation en boucle : anime la propriété INDIVIDUELLE `rotate`
   (pas `transform`, déjà pris par .is-gone et la transition de base — voir
   la note en tête de robot-engine.js § setLook/rotate/teleport) : compose
   sans conflit avec le flottement (transform sur l'image) et le miroir
   (scale sur l'image). Durée pilotée par --wq-rb-spin-duration (JS). */
.wq-robot.wq-robot-spin { animation: wq-rb-spin var(--wq-rb-spin-duration, 2s) linear infinite; }
@keyframes wq-rb-spin {
  from { rotate: 0deg; }
  to   { rotate: 360deg; }
}

/* Téléportation : fondu bref pendant que la position % change sous le
   capot — réutilise la transition opacity déjà posée sur la règle de base
   (.wq-robot), aucune nouvelle transition à déclarer. */
.wq-robot.is-teleporting { opacity: 0; }

/* Respect du réglage système : pas d'animation pour qui n'en veut pas. */
@media (prefers-reduced-motion: reduce) {
  .wq-robot.is-floating .wq-robot-img { animation: none; }
  .wq-robot.wq-robot-spin { animation: none; }
}

/* --- bulle de parole (Phase 2) ------------------------------------------
   Même mécanique que .nr-popup (narration-engine.js) : position:fixed,
   recalculée au scroll/resize par robot-engine.js. Classe distincte car
   ancrée au-dessus de la mascotte, pas d'une cible narrative. */
.wq-robot-bubble {
  --wq-rb-bubble-bg: #ffffff;
  --wq-rb-bubble-fg: #1e293b;
  position: fixed;
  transform: translate(-50%, -100%) scale(.92);
  transform-origin: bottom center;
  z-index: 970;
  max-width: min(280px, 78vw);
  /* Filet de sécurité : un texte anormalement long ne doit jamais
     « éclipser » le reste de la page. Troncature à 6 lignes avec « … »
     plutôt qu'un ascenseur (une barre de défilement dans une bulle de
     parole, signalé comme moche à raison) — propre, sans scrollbar. */
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: break-word;
  cursor: pointer;
  background: var(--wq-rb-bubble-bg);
  color: var(--wq-rb-bubble-fg);
  font-weight: 600;
  line-height: 1.45;
  box-shadow: 0 12px 28px rgba(15, 23, 42, .28);
  opacity: 0;
  transition: opacity .2s ease, transform .2s ease;
}
.wq-robot-bubble.show { opacity: 1; transform: translate(-50%, -110%) scale(1); }
.wq-robot-bubble::after {
  content: '';
  position: absolute;
  left: 50%; bottom: -7px;
  width: 14px; height: 14px;
  background: var(--wq-rb-bubble-bg);
  transform: translateX(-50%) rotate(45deg);
  border-radius: 0 0 3px;
}

/* Taille : agit sur le texte, le rembourrage suit en em pour rester proportionné. */
.wq-rb-size-s { font-size: .74rem; padding: 7px 11px; }
.wq-rb-size-m { font-size: .86rem; padding: 10px 14px; }
.wq-rb-size-l { font-size: 1.02rem; padding: 13px 18px; }

/* Forme : mêmes coins que les autres widgets (arrondi / carré / pilule). */
.wq-rb-shape-rounded { border-radius: 14px; }
.wq-rb-shape-square { border-radius: 3px; }
.wq-rb-shape-pill { border-radius: 999px; }

/* --- rayon laser (Phase 6) ----------------------------------------------
   Calque SVG posé DANS le canevas : il subit le même `transform: scale` que
   la page, donc le rayon suit le zoom sans le moindre recalcul (robot-beam.js
   explique le repère). Ne capte jamais le clic : l'enfant doit pouvoir
   toucher la cible que le rayon désigne. */
.wq-rb-beams {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  /* SOUS les widgets normaux — mais par ORDRE D'AFFICHAGE (posé en premier
     enfant du canevas, voir getBeamLayer()/robot-beam.js), pas par
     z-index. Un z-index négatif a d'abord fait passer le rayon sous le
     fond du canevas lui-même (invisible partout), puis un second bug est
     apparu au zoom (le calque disparaissait un instant à chaque
     changement, le `transform: scale()` du canevas créant/détruisant son
     propre contexte d'empilement). L'ordre DOM ne dépend d'aucun contexte
     d'empilement : aucun z-index ici, volontairement. */
  overflow: visible;
}

/* Halo large + cœur fin : c'est CE seul empilement qui fait lire « laser »
   plutôt que « trait ». Le halo est flou et translucide, le cœur presque
   blanc et net. */
/* Épaisseur : un seul multiplicateur (posé par robot-beam.js, comme la
   couleur --wq-rb-beam) plutôt qu'une valeur par trait — « fin/moyen/épais »
   grossit le rayon ENTIER d'un coup, halo et cœur restent proportionnés
   entre eux. Défaut 1 = les épaisseurs d'origine, inchangées. */
.wq-rb-beams { --wq-rb-beam-w: 1; }

.wq-rb-beam-glow {
  fill: none;
  stroke: var(--wq-rb-beam, #ff5a3c);
  stroke-width: calc(9px * var(--wq-rb-beam-w, 1));
  stroke-linecap: round;
  opacity: 0;
  /* Statique : animer un filtre effondre le framerate sur tablette. */
  filter: blur(4px);
}
.wq-rb-beam-core {
  fill: none;
  stroke: #fff;
  stroke-width: calc(2.5px * var(--wq-rb-beam-w, 1));
  stroke-linecap: round;
  opacity: 0;
}
/* Segment brillant qui parcourt le trajet en boucle : dit en continu
   « de moi → vers là ». */
.wq-rb-beam-pulse {
  fill: none;
  stroke: #fff;
  stroke-width: calc(5px * var(--wq-rb-beam-w, 1));
  stroke-linecap: round;
  opacity: 0;
}
@keyframes wq-rb-travel {
  from { stroke-dashoffset: var(--wq-rb-len, 100); }
  to   { stroke-dashoffset: 0; }
}

/* Anneau de verrouillage sur la cible. Le rayon s'arrête AVANT elle et c'est
   l'anneau qui l'entoure — on ne recouvre jamais ce qu'on demande de
   regarder. */
.wq-rb-beam-ring {
  fill: none;
  /* Couleur du CONTOUR — --wq-rb-shape, distincte de --wq-rb-beam (le trait) :
     posée séparément par robot-beam.js, avec repli sur la couleur du trait
     si l'auteur n'a rien choisi de spécifique. Rouge cercle bleu, pourquoi
     pas — l'un ne force plus l'autre. */
  stroke: var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c));
  stroke-width: calc(3px * var(--wq-rb-beam-w, 1));
  opacity: 0;
  transition: opacity .2s ease;
  animation: wq-rb-lock 1.4s ease-in-out infinite;
}
/* Variante « forme du widget » : même trait, mais un cadre qui épouse le
   rectangle réel de la cible (et l'arrondi de ses coins). Sur un bouton large
   et bas, un cercle englobant déborde énormément — le cadre, lui, colle. */
.wq-rb-beam-box { fill: none; }
@keyframes wq-rb-lock {
  0%, 100% { stroke-width: calc(3px   * var(--wq-rb-beam-w, 1)); stroke-opacity: .95; }
  50%      { stroke-width: calc(4.5px * var(--wq-rb-beam-w, 1)); stroke-opacity: .55; }
}

/* --- marqueurs niveau 1 (Phase 9) ---------------------------------------
   Ellipse, étoile et crochets REPRENNENT .wq-rb-beam-ring (même trait, même
   pulsation de verrouillage) : ce sont juste d'autres tracés dans le même
   calque, pas un nouveau système visuel. Flèche et soulignement ont leur
   propre classe : la flèche est pleine (un repère, pas un contour), le
   soulignement est un trait qui se dessine comme le rayon lui-même. */
.wq-rb-beam-arrow {
  fill: var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c));
  stroke: none;
  opacity: 0;
}
.wq-rb-beam-underline {
  fill: none;
  stroke: var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c));
  stroke-width: calc(4px * var(--wq-rb-beam-w, 1));
  stroke-linecap: round;
  opacity: 0;
}

/* Pulsation d'armement à la main, juste avant le tir. */
.wq-rb-beam-charge {
  fill: #fff;
  stroke: var(--wq-rb-beam, #ff5a3c);
  stroke-width: 2;
  opacity: 0;
}
@keyframes wq-rb-charge {
  from { transform: scale(.2); opacity: .2; }
  60%  { opacity: 1; }
  to   { transform: scale(1.5); opacity: 0; }
}
.wq-rb-beam-charge { transform-box: fill-box; transform-origin: center; }

@media (prefers-reduced-motion: reduce) {
  .wq-rb-beam-ring  { animation: none; }
  .wq-rb-beam-pulse { display: none; }
}

/* --- marqueurs niveau 2 (Phase 12) --------------------------------------
   Cercle à main levée : robot-beam.js pose déjà un `d` bruité sur un <path>
   qui porte .wq-rb-beam-ring — aucune règle propre nécessaire ici, le trait,
   la couleur et la pulsation de verrouillage sont hérités tels quels. */

/* Lueur : classe posée/retirée par robot-beam.js sur le contour actif. Deux
   halos superposés (rapproché + large) plutôt qu'un seul gros flou — un seul
   flou épais mange le trait net en dessous, deux tailles gardent le contour
   lisible tout en élargissant la lueur. */
.wq-rb-glow {
  filter:
    drop-shadow(0 0 3px var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c)))
    drop-shadow(0 0 9px var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c)));
}

/* Badge de séquence ①②③ : posé au coin haut-droit du marqueur (ou du cercle
   quand il n'y a pas de rectangle). Couleur pleine de la forme, texte blanc —
   lisible sur n'importe quelle couleur de contour, contrairement à un simple
   contour de la même couleur qui se fondrait dans le trait. */
.wq-rb-badge {
  opacity: 0;
  transition: opacity .18s ease;
}
.wq-rb-badge-circle {
  fill: var(--wq-rb-shape, var(--wq-rb-beam, #ff5a3c));
  stroke: #fff;
  stroke-width: 1.5;
}
.wq-rb-badge-text {
  fill: #fff;
  font-weight: 800;
  font-family: inherit;
}

/* Étiquette texte en machine à écrire : <div> HTML posé DANS le canevas,
   comme le calque SVG — même conteneur transformé par le zoom, donc `left`/
   `top` en % suivent sans recalcul (voir robot-beam.js, showLabel()).
   Révélation lettre par lettre pilotée en JS (textContent), pas par une
   animation CSS de largeur — un texte multi-lignes n'a pas de « largeur
   cible » unique à animer, contrairement à Phase 12 (une seule ligne). */
.wq-rb-label {
  position: absolute;
  /* Sous les widgets, comme .wq-rb-beams — par ordre DOM (posée juste après
     le calque du rayon, voir getBeamLayer()), plus de z-index ici non plus. */
  pointer-events: none;
  padding: calc(3px * var(--wq-rb-label-scale, 1)) calc(9px * var(--wq-rb-label-scale, 1));
  border-radius: 7px;
  max-width: min(240px, 46%);
  background: var(--wq-rb-label-bg, #ff5a3c);
  box-shadow: 0 4px 10px rgba(15, 23, 42, .22);
  opacity: 0;
  transition: opacity .16s ease;
}
.wq-rb-label.show { opacity: 1; }
/* Position relative au marqueur — l'ancre (posée par robot-beam.js en
   left/top) est le même point dans les 4 cas, seule la façon dont la bulle
   grandit autour de ce point change. */
.wq-rb-label-below { transform: translate(-50%, 0); }
.wq-rb-label-above { transform: translate(-50%, -100%); }
.wq-rb-label-left  { transform: translate(-100%, -50%); }
.wq-rb-label-right { transform: translate(0, -50%); }
.wq-rb-label-text {
  display: block;
  white-space: pre-wrap;      /* garde les retours à la ligne de l'auteur */
  overflow-wrap: break-word;
  color: var(--wq-rb-label-fg, #fff);
  font-weight: 700;
  font-size: calc(.78rem * var(--wq-rb-label-scale, 1));
  line-height: 1.4;
}
/* Curseur clignotant façon machine à écrire — reste après la dernière
   lettre, comme un vrai curseur qui attend, plutôt que de disparaître
   brusquement à la fin de la révélation. */
.wq-rb-label-text::after {
  content: '';
  display: inline-block;
  width: .1em;
  height: 1em;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: rgba(255, 255, 255, .85);
  animation: wq-rb-caret .8s steps(1) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .wq-rb-label-text::after { animation: none; }
}
@keyframes wq-rb-caret {
  50% { opacity: 0; }
}

/* CTA « Cliquez ici » : présentation à part de l'étiquette normale — rouge,
   pulsante, pensée pour le tout premier contact avec l'interactivité chez un
   jeune enfant. Rendue cliquable UNIQUEMENT dans ce cas (voir showLabel()
   dans robot-beam.js, qui pose pointer-events:auto + un onclick) — le clic
   rejoue exactement le même clic que sur la mascotte elle-même, donc peu
   importe où ce badge s'affiche à l'écran, il reste toujours réactif. Pas de
   z-index : l'ordre DOM (étiquette posée après le calque du rayon, badge
   après l'étiquette — voir getBeamLayer()) suffit déjà à l'afficher au bon
   niveau, sans reproduire le bug d'empilement au zoom déjà rencontré ici. */
.wq-rb-label-cta {
  background: #e0263f;
  box-shadow: 0 6px 18px rgba(224, 38, 63, .45);
  animation: wq-rb-cta-pulse 1.1s ease-in-out infinite;
}
.wq-rb-label-cta .wq-rb-label-text { color: #fff; font-weight: 800; }
@keyframes wq-rb-cta-pulse {
  0%, 100% { box-shadow: 0 6px 18px rgba(224, 38, 63, .45), 0 0 0 0 rgba(224, 38, 63, .55); }
  50% { box-shadow: 0 6px 18px rgba(224, 38, 63, .45), 0 0 0 10px rgba(224, 38, 63, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .wq-rb-label-cta { animation: none; }
}
/* Effet « fondu » (alternative à la révélation lettre par lettre). */
@keyframes wq-rb-label-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Rail de stations (Phase 28) : tracé + stations numérotées reliant les
   points de déplacement d'une visite. Calque posé sous les widgets, comme
   celui du rayon (voir robot-rail.js) — ni z-index ni transform ici non
   plus, même leçon retenue (§6.16/6.18). Pas de pointer-events par défaut
   sur le calque entier : seules les stations autorisées à revenir en
   arrière en reçoivent (posé au cas par cas par robot-rail.js). */
.wq-rail-layer { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
/* Un vrai chemin, pas un fil (signalé) : deux traits superposés sur le
   MÊME tracé — un large en dessous (la bordure), un plus étroit au-dessus
   (le centre) — plutôt qu'une simple ligne fine. Épaisseur pilotée par
   --wq-rail-w (posée par robot-rail.js, défaut 1 — .6/1/1.7 fin/moyen/
   épais, même échelle que --wq-rb-beam-w pour le rayon) : un seul
   multiplicateur, les largeurs de base restent par habillage ci-dessous. */
.wq-rail-path-border { stroke-linecap: round; }
.wq-rail-path { stroke-linecap: round; }
.wq-rail-station-circle { stroke: #fff; stroke-width: 1.5; }
.wq-rail-station-text { fill: #fff; font-weight: 800; font-size: 13px; font-family: inherit; }
/* FX « wow » (optionnelle, voir <animateMotion> dans robot-rail.js) — un
   éclat qui voyage le long du tracé en boucle, coloré selon l'habillage. */
.wq-rail-fx-dot { stroke: #fff; stroke-width: 1.5; opacity: .95; }
.wq-rail-skin-treasure.wq-rail-fx-dot { fill: #ef9f27; }
.wq-rail-skin-train.wq-rail-fx-dot { fill: #378add; }
.wq-rail-skin-ribbon.wq-rail-fx-dot { fill: #d4537e; }

/* Habillage « Carte au trésor » : chemin de terre battue (bordure sable
   pleine), centre plus foncé — le style de trait (plein/tirets/pointillé)
   est un réglage séparé désormais (voir opts.dashStyle), plus câblé en dur
   ici. */
.wq-rail-skin-treasure.wq-rail-path-border { stroke: #dccdae; stroke-width: calc(18px * var(--wq-rail-w, 1)); opacity: .9; }
.wq-rail-skin-treasure.wq-rail-path { stroke: #b4924f; stroke-width: calc(9px * var(--wq-rail-w, 1)); }
.wq-rail-skin-treasure .wq-rail-station-circle { fill: #ef9f27; }

/* Habillage « Voie ferrée » : ballast bleu foncé (bordure), rail bleu clair
   au centre. */
.wq-rail-skin-train.wq-rail-path-border { stroke: #0c447c; stroke-width: calc(16px * var(--wq-rail-w, 1)); }
.wq-rail-skin-train.wq-rail-path { stroke: #85b7eb; stroke-width: calc(7px * var(--wq-rail-w, 1)); }
.wq-rail-skin-train .wq-rail-station-circle { fill: #378add; }

/* Habillage « Ruban pastel » : le plus « playful » des trois, pensé pour
   les tout premiers livres — large bande rose foncé, centre rose clair. */
.wq-rail-skin-ribbon.wq-rail-path-border { stroke: #993556; stroke-width: calc(22px * var(--wq-rail-w, 1)); opacity: .9; }
.wq-rail-skin-ribbon.wq-rail-path { stroke: #ed93b1; stroke-width: calc(13px * var(--wq-rail-w, 1)); }
.wq-rail-skin-ribbon .wq-rail-station-circle { fill: #d4537e; }

/* États d'une station, communs aux 3 habillages. */
.wq-rail-station { transition: opacity .15s ease; }
.wq-rail-station.is-current .wq-rail-station-circle {
  animation: wq-rail-pulse 1.4s ease-in-out infinite;
}
@keyframes wq-rail-pulse {
  0%, 100% { r: 15; }
  50% { r: 17; }
}
@media (prefers-reduced-motion: reduce) {
  .wq-rail-station.is-current .wq-rail-station-circle { animation: none; }
}
/* Grisée : même logique visuelle que .wq-el.is-disabled (core.css) — limite
   de retours atteinte, la station reste visible mais inerte. */
.wq-rail-station.is-disabled .wq-rail-station-circle { fill: #b4b2a9; }
.wq-rail-station.is-disabled { opacity: .55; }

/* --- outil de placement visuel (point X/Y libre, éditeur uniquement) -----
   Posé DANS #q-canvas comme le calque du rayon : il subit le même
   `transform: scale` (zoom), donc les px fixes ci-dessous restent cohérents
   visuellement à tous les niveaux de zoom, sans recalcul (shared-actions.js). */
.wq-rb-place-tool {
  position: absolute;
  z-index: 60;
  transform: translate(-50%, -50%);
  pointer-events: none;   /* seuls le point et la poignée captent le clic */
}
.wq-rb-place-dot, .wq-rb-place-handle {
  position: absolute;
  left: 0; top: 0;
  width: 22px; height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  cursor: grab;
  pointer-events: auto;
  touch-action: none;
  box-shadow: 0 2px 8px rgba(15, 23, 42, .35);
}
.wq-rb-place-dot {
  background: #ff5a3c;
  border: 2px solid #fff;
}
.wq-rb-place-handle {
  width: 18px; height: 18px;
  margin: -9px 0 0 -9px;
  background: #fff;
  border: 3px solid #ff5a3c;
}
.wq-rb-place-dot:active, .wq-rb-place-handle:active { cursor: grabbing; }
.wq-rb-place-done {
  position: absolute;
  /* En diagonale haut-droite du point, PAS en dessous : dessous est
     l'endroit où l'étiquette (position par défaut) et le badge (coins bas)
     se dessinent au fil du tir en direct — le bouton s'y superposait
     (signalé, capture à l'appui). Décalage fixe plutôt que dépendant du
     rayon de la poignée (qui varie avec la taille) : reste net quelle que
     soit la taille du marqueur. */
  left: 26px; top: -34px;
  white-space: nowrap;
  padding: 5px 12px;
  border: none;
  border-radius: 999px;
  font-size: .74rem;
  font-weight: 700;
  cursor: pointer;
  pointer-events: auto;
  background: rgba(15, 23, 42, .85);
  color: #fff;
}
.wq-rb-place-btn {
  display: block;
  width: 100%;
  margin: .3rem 0 .5rem;
  padding: 7px 10px;
  border: 1px dashed var(--accent, #d69a3c);
  border-radius: 8px;
  background: var(--paper-2, #fbf7ef);
  font-size: .78rem;
  font-weight: 700;
  cursor: pointer;
}
.wq-rb-place-btn:hover { background: var(--card, #fff); }

/* --- bouton « passer » de la visite ------------------------------------
   Posé sur <body> en position fixe par robot-tour.js, JAMAIS sur le cadre de
   la mascotte : accroché à elle, il se déplaçait pendant la visite, et une
   cible qui bouge est intenable pour un enfant. Fixe, il échappe aussi au
   `transform: scale` du canevas et garde donc sa taille réelle au zoom.
   `--nr-dock-space` est publié par le lecteur de narration collé en bas :
   on se pose au-dessus de lui au lieu d'être enterré dessous. */
.wq-rb-skip {
  position: fixed;
  right: 16px;
  bottom: calc(16px + var(--nr-dock-space, 0px));
  z-index: 965;
  display: flex; align-items: center; gap: 7px;
  /* ≥44px de haut : doigts d'enfant, écrans tactiles. */
  min-height: 44px; padding: 8px 16px;
  font-family: inherit; font-size: .8rem; font-weight: 700;
  border: none; border-radius: 999px; cursor: pointer;
  background: rgba(15, 23, 42, .82); color: #fff;
  box-shadow: 0 6px 18px rgba(15, 23, 42, .3);
}
.wq-rb-skip:hover { background: rgba(15, 23, 42, .94); }

/* Phase 39 — placement dans les 4 coins (défaut : bas-droite, ci-dessus).
   `--nr-dock-space` (lecteur de narration collé en bas) ne concerne QUE les
   coins du bas, un coin haut n'a jamais besoin de s'en décaler. */
.wq-rb-skip-tr, .wq-rb-skip-tl { bottom: auto; top: 16px; }
.wq-rb-skip-bl, .wq-rb-skip-tl { right: auto; left: 16px; }

/* Suit la mascotte (Phase 39) : position pilotée en JS (left/top, voir
   startFollow() dans robot-tour.js) — les offsets fixes des coins ci-dessus
   n'ont pas de sens ici, et le bouton se centre sur son point (translateX). */
.wq-rb-skip-follow { right: auto; bottom: auto; transform: translateX(-50%); }

/* Tailles — le plancher ≥44px (doigts d'enfant) n'est JAMAIS descendu, même
   en « Petit » : seuls le padding/la police rétrécissent. */
.wq-rb-skip-sz-s { padding: 6px 12px; font-size: .72rem; }
.wq-rb-skip-sz-l { padding: 10px 20px; font-size: .9rem; }

/* Carrée plutôt que pilule — même fond/texte, coins arrondis modestes. */
.wq-rb-skip-sq { border-radius: 10px; }

/* Icône seule : rond compact, pas de texte/compteur (posés par JS, voir
   updateSkipBtn() dans robot-tour.js — cette classe ne fait QUE la forme). */
.wq-rb-skip-icon {
  min-width: 44px; padding: 0; justify-content: center;
  border-radius: 999px; font-size: 1.1rem;
}
/* 3ᵉ cran de la relance (§4.1) : le bouton se met à pulser pour devenir
   évident — même technique que le CTA du rayon (wq-rb-cta-pulse) ci-dessus. */
.wq-rb-skip.is-pulsing { animation: wq-rb-skip-pulse 1.1s ease-in-out infinite; }
@keyframes wq-rb-skip-pulse {
  0%, 100% { box-shadow: 0 6px 18px rgba(15, 23, 42, .3), 0 0 0 0 rgba(224, 38, 63, .55); }
  50% { box-shadow: 0 6px 18px rgba(15, 23, 42, .3), 0 0 0 10px rgba(224, 38, 63, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .wq-rb-skip.is-pulsing { animation: none; }
}

/* --- pastille d'émoticône (Phase 7) -------------------------------------
   Canal séparé de la bulle : elle porte l'ÉTAT (attend/relance/réussi),
   lisible sans savoir lire. Posée en position:fixed comme la bulle, au
   coin haut-droit de la mascotte. */
.wq-robot-emoji {
  position: fixed;
  z-index: 964;
  transform: translate(-50%, -50%) scale(.4);
  opacity: 0;
  font-size: 1.6rem;
  line-height: 1;
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .25));
}
.wq-robot-emoji.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
@media (prefers-reduced-motion: reduce) {
  .wq-robot-emoji { transition: opacity .18s ease; }
  .wq-robot-emoji.show { transform: translate(-50%, -50%) scale(1); }
}
/* Le compteur « 2/4 » : dire combien il reste rassure et donne bien moins
   envie de sauter que l'inconnu. */
.wq-rb-skip-count {
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, .2);
  padding: 2px 8px; border-radius: 999px; font-size: .72rem;
}

/* --- sections repliables du panneau (éditeur) --------------------------- */
/* La Visite est ouverte d'entrée ; le reste se replie. Sans ça, la grille des
   poses (qui capte la molette, voir plus bas) formait un mur entre l'auteur et
   le cœur de l'outil. */
.wq-rb-fold {
  border: 1px solid var(--line, #d8cbb0);
  border-radius: 10px;
  margin-bottom: .5rem;
  background: var(--card, #fff);
}
.wq-rb-fold > summary {
  cursor: pointer;
  padding: .55rem .7rem;
  font-size: .78rem;
  font-weight: 800;
  list-style: none;
  border-radius: 10px;
  background: var(--paper-2, #fbf7ef);
  user-select: none;
}
.wq-rb-fold > summary::-webkit-details-marker { display: none; }
.wq-rb-fold > summary::before { content: '▸ '; opacity: .6; }
.wq-rb-fold[open] > summary::before { content: '▾ '; }
.wq-rb-fold[open] > summary { border-radius: 10px 10px 0 0; border-bottom: 1px solid var(--line, #d8cbb0); }
.wq-rb-fold > *:not(summary) { padding: .6rem .7rem; }

/* --- sections colorées du système laser (bouton ET Robot) --------------
   Même mécanique que .wq-rb-fold ci-dessus (repliable, natif <details>),
   mais une teinte par catégorie — trait/forme, badge, étiquette, couleurs,
   timing & son — pour s'y retrouver d'un coup d'œil plutôt qu'une longue
   liste plate de champs. Couleurs reprises de BUBBLE_PALETTE
   (quest-robot.js), déjà utilisée ailleurs dans ce système : cohérence
   visuelle, pas une palette inventée pour l'occasion. */
.wq-rb-fold-group {
  border-radius: 10px;
  margin-bottom: .5rem;
  background: var(--card, #fff);
  overflow: hidden;
}
.wq-rb-fold-group > summary {
  cursor: pointer;
  padding: .5rem .65rem;
  font-size: .76rem;
  font-weight: 800;
  list-style: none;
  user-select: none;
}
.wq-rb-fold-group > summary::-webkit-details-marker { display: none; }
.wq-rb-fold-group > summary::before { content: '▸ '; opacity: .6; }
.wq-rb-fold-group[open] > summary::before { content: '▾ '; }
.wq-rb-fold-group > *:not(summary) { padding: .6rem .7rem; }
.wq-rb-fold-trait  > summary { background: #dde9ef; }
.wq-rb-fold-badge  > summary { background: #fef3c7; }
.wq-rb-fold-label  > summary { background: #dcecdf; }
.wq-rb-fold-color  > summary { background: #f6dcdc; }
.wq-rb-fold-timing > summary { background: #e7e1f5; }
/* Phase 7 — porte (« attendre que… ») et garde (« ne jouer que si… »). */
.wq-rb-fold-gate   > summary { background: #dde9ef; }
.wq-rb-fold-guard  > summary { background: #f3e2bd; }

/* --- sélecteur de pose (éditeur) --------------------------------------- */

/* 36 vignettes réparties en 17 intentions : à plat, la liste poussait le reste
   du panneau hors de l'écran. Elle défile dans sa propre boîte, comme la liste
   d'éléments (.q-el-list), plutôt que d'allonger le panneau.
   Plafond volontairement bas (220 px) : cette boîte retient la molette
   (`overscroll-behavior: contain`), donc plus elle est haute, plus elle piège
   l'auteur qui essaie simplement de faire défiler le panneau. */
.wq-rb-picker {
  display: flex; flex-direction: column; gap: .5rem;
  max-height: 220px; overflow-y: auto; overscroll-behavior: contain;
  padding: .35rem; border: 1px solid var(--line, #d8cbb0); border-radius: 10px;
  background: var(--paper-2, #fbf7ef);
}
/* Les en-têtes d'intention restent lisibles pendant le défilement. */
.wq-rb-picker .wq-rb-role {
  position: sticky; top: 0; z-index: 1;
  background: var(--paper-2, #fbf7ef); padding: .15rem 0;
}
.wq-rb-role { font-size: .7rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted, #7b6a52); margin-top: .25rem; }
.wq-rb-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: .3rem; }
.wq-rb-cell {
  aspect-ratio: 1; padding: 0; cursor: pointer; overflow: hidden;
  background: #fff; border: 1px solid var(--line, #d8cbb0); border-radius: 8px;
}
.wq-rb-cell img { width: 100%; height: 100%; object-fit: contain; display: block; }
.wq-rb-cell:hover { border-color: var(--accent, #d69a3c); }
.wq-rb-cell.is-active { border-color: var(--accent, #d69a3c); box-shadow: 0 0 0 2px var(--accent, #d69a3c) inset; }
