/* ─────────────────────────────────────────
   Mascotte et fenêtre d'accueil
   Chargé sur toutes les pages. Mobile first.
   Ne redéclare aucun token, tout vient de
   assets/css/main.css.

   Deux emplacements pour la mascotte :
   - .ml-am-mascotte-zone seule (pas de parent .ml-am-mascotte-hero) :
     position fixe, bas droite, sur toutes les pages sauf l'accueil, suit
     le défilement.
   - .ml-am-mascotte-hero .ml-am-mascotte-zone : posée dans le hero de
     l'accueil, en grand, à côté du titre. Ne suit pas le défilement, elle
     disparaît avec le hero (voir ml_am_mascotte_accueil() dans
     inc/assistant-modale.php et son appel dans template-home.php).
───────────────────────────────────────── */

/* ── Zone de la mascotte, fixe en bas à droite par défaut ──
   Bornée en largeur avec calc() et le token d'espacement : la bulle des
   drapeaux ne doit jamais forcer un défilement horizontal, même sur un
   très petit écran. */
.ml-am-mascotte-zone {
  position: fixed;
  inset-block-end: var(--ml-space-sm);
  inset-inline-end: var(--ml-space-sm);
  inset-inline-start: var(--ml-space-sm);
  z-index: 200;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  pointer-events: none;
}

.ml-am-mascotte-zone > * {
  pointer-events: auto;
}

/* Le SVG du globe. Sur l'accueil, il est purement décoratif : la bulle de
   question est déjà ouverte à côté, le drapeau et le nom de chaque langue
   portent l'action. Sur les autres pages (.ml-am-mascotte-lien), il devient
   lui-même l'élément actif qui déplie la bulle repliée.

   Couleur du SVG : violet par défaut (mascotte fixe, sur fond clair). Le
   hero sombre de l'accueil la repasse en blanc, voir .ml-am-mascotte-hero
   plus bas. Le repli (un simple disque, voir ml_am_visuel_mascotte() dans
   inc/assistant-modale.php) est lui aussi en currentColor : il suit la même
   recoloration, jamais de trou ni de couleur figée. */
.ml-am-mascotte-image {
  position: relative;
  display: block;
  inline-size: clamp(64px, 14vw, 108px);
  margin-block-start: var(--ml-space-xs);
  color: var(--ml-accent);
  /* Le passage au blanc (voir .is-sur-fond-sombre plus bas) se fait en
     douceur, jamais d'un coup. */
  transition: color 200ms var(--ml-ease);
}
.ml-am-mascotte-image img,
.ml-am-mascotte-image svg {
  inline-size: 100%;
  block-size: auto;
  filter: drop-shadow(var(--ml-shadow-md));
}

/* ── Fond derrière la mascotte, hors accueil : blanc ou violet ──
   Demande de Marino : la mascotte fixe (voir .ml-am-mascotte-zone plus haut)
   suit le défilement sur toutes les pages sauf l'accueil, et peut donc se
   retrouver aussi bien sur le fond clair du site que sur une section sombre
   (.ml-section-dark) ou sur la photo du hero des fiches pays et ville
   (body.ml-has-dark-header). Le script détermine ce qu'il y a réellement
   derrière elle (voir initFondMascotte() dans assets/js/assistant-modale.js)
   et pose cette classe sur la ZONE, jamais sur l'accueil : là, le blanc est
   déjà forcé en dur par la maquette, voir .ml-am-mascotte-hero
   .ml-am-mascotte-image plus bas dans ce fichier.

   Le violet par défaut n'a rien à écrire ici, c'est déjà la couleur de base
   de .ml-am-mascotte-image ci dessus : cette règle ne fait que la remplacer
   quand le script a une preuve, jamais une supposition, que le fond est
   sombre. */
.ml-am-mascotte-zone.is-sur-fond-sombre .ml-am-mascotte-image {
  color: #fff;
}

/* L'ombre portée existante (--ml-shadow-md, ci dessus) est réglée pour un
   tracé violet sur un fond clair. Sur un fond sombre, un tracé blanc a besoin
   d'une assise un peu plus marquée pour ne pas se fondre dans une photo
   dense : une seule nappe suffit, la mascotte fixe est petite. */
.ml-am-mascotte-zone.is-sur-fond-sombre .ml-am-mascotte-image img,
.ml-am-mascotte-zone.is-sur-fond-sombre .ml-am-mascotte-image svg {
  filter: drop-shadow(0 2px 10px rgba(6, 14, 26, 0.45));
}

/* ── Le globe et le bras qui salue ──
   Demande de la cliente (juillet 2026) : le personnage (corps, jambes, yeux)
   a disparu, remplacé par le globe. assets/img/mascotte.svg ne compte plus
   que deux tracés :
   - .ml-mascotte-globe : le monde, immobile (voir le flottement discret
     plus bas, qui porte sur le SVG entier, globe et bras ensemble) ;
   - .ml-mascotte-bras  : un bras et une main stylisés, seul tracé animé,
     qui salue de temps en temps.

   Plus de clignement des yeux : il n'y en a plus. Les anciennes règles
   .ml-mascotte-oeil-* et le rythme du clignement (demarrerClignement() dans
   assets/js/assistant-modale.js) ont été retirés avec le personnage, pas
   gardés « au cas où ».

   Le pivot du bras est son épaule, dans les unités du dessin (viewBox
   « -94 15 469 430 ») : x = 58, y = 270, à l'endroit où le bras rejoint le
   globe, sur son flanc gauche, bas. Le bras est celui de l'ancienne mascotte, remis à
   la demande de Marino et reposé sur le globe (juillet 2026). Le tourner
   autour du centre de sa propre boîte le ferait décrire un arc qui
   décollerait la main du globe au lieu de la faire pivoter sur place. */
.ml-mascotte-bras {
  transform-box: view-box;
  transform-origin: 58px 270px;
  /* La main repose un peu plus bas qu'au dessin (demande de Marino) : six
     degrés vers le bas autour de l'épaule. Le salut ci dessous part et revient
     à cette valeur, il ne remet jamais le bras à zéro, sans quoi la main
     remonterait d'un cran à la fin de chaque salut. */
  rotate: 6deg;
}

.ml-am-mascotte-image.is-salut .ml-mascotte-bras {
  animation: ml-am-salut 1000ms var(--ml-ease);
}

/* Toutes les valeurs sont décalées de six degrés avec le repos : l'amplitude
   du salut, treize degrés, n'a pas bougé. */
@keyframes ml-am-salut {
  0%   { rotate: 6deg; }
  18%  { rotate: -7deg; }
  38%  { rotate: 4deg; }
  58%  { rotate: -7deg; }
  78%  { rotate: 3deg; }
  100% { rotate: 6deg; }
}

/* Flottement discret : trois pixels, dix secondes de cycle. Un globe qui
   flotte légèrement, plutôt que l'ancien personnage qui respirait : sobre et
   lent, pensé pour un public d'étudiants majeurs et d'institutions, jamais un
   mouvement enfantin. Porte sur le SVG et non sur son conteneur. */
.ml-am-mascotte-image img,
.ml-am-mascotte-image svg {
  animation: ml-am-respire 10s ease-in-out infinite;
}

@keyframes ml-am-respire {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -3px; }
}

/* Sur le haut de page, le globe ne flotte pas : il est POSÉ (voir
   .ml-am-mascotte-hero .ml-am-mascotte-image plus bas). Cette règle éteint
   donc explicitement le flottement hérité de la règle générale ci-dessus
   (.ml-am-mascotte-image img/svg), qui s'appliquerait sinon aussi ici, sa
   spécificité étant plus faible que ce sélecteur composé.

   Le halo derrière lui continue de pulser, et lui seul : voir
   .ml-am-mascotte-hero .ml-am-mascotte-image::before plus bas, dans ses
   pseudo-éléments, jamais sur le SVG lui-même. */
.ml-am-mascotte-hero .ml-am-mascotte-image img,
.ml-am-mascotte-hero .ml-am-mascotte-image svg {
  animation: none;
}

/* Réglage système « moins de mouvement » : le globe ne bouge plus du tout.
   Il reste immobile, le bras au repos, exactement comme sur le dessin
   d'origine. Le script ne pose de toute façon jamais ces classes dans ce
   cas ; les règles sont là pour que le style ne dépende pas de lui.

   La mascotte du hero n'a plus besoin d'être nommée ici : elle est déjà
   posée, sans le moindre flottement, reduced motion ou non (voir la règle
   juste au-dessus). Seul le flottement de la mascotte fixe des autres pages
   doit encore être coupé ici. */
@media (prefers-reduced-motion: reduce) {
  .ml-am-mascotte-image img,
  .ml-am-mascotte-image svg {
    animation: none;
  }
  .ml-am-mascotte-image.is-salut .ml-mascotte-bras {
    animation: none;
  }
  /* Le passage blanc/violet reste, seule la transition qui l'adoucit
     disparaît : le changement de couleur est instantané, pas interdit. */
  .ml-am-mascotte-image {
    transition: none;
  }
}

.ml-am-mascotte-lien {
  cursor: pointer;
  border-radius: var(--ml-radius-lg);
}
.ml-am-mascotte-lien:focus-visible {
  outline: 2px solid var(--ml-accent);
  outline-offset: 4px;
}

@media (min-width: 700px) {
  .ml-am-mascotte-zone {
    inset-block-end: var(--ml-space-lg);
    inset-inline-end: var(--ml-space-lg);
    inset-inline-start: auto;
  }
  .ml-am-mascotte-image {
    inline-size: clamp(84px, 9vw, 132px);
  }
}

/* ── Variante hero de l'accueil : Linguine et sa carte de messagerie ──
   Maquette « page de chapitre » (juillet 2026, demande de Marine et de
   Marino) : la mascotte et sa carte ne tiennent plus une colonne à côté du
   texte, elles sont CENTRÉES en dessous de lui, dans la même colonne unique
   que le titre, le paragraphe et les deux actions (voir .ml-home-hero-inner,
   assets/css/main.css). Posées dans le flux normal, jamais en position fixe :
   elles disparaissent avec le haut de page comme n'importe quel autre
   contenu de la section.

   Le globe et la carte restent posés côte à côte (le globe à gauche de la
   paire, la carte à droite), centrés comme un seul bloc dans la colonne du
   hero : elle ne le recouvre jamais, et il ne la mange jamais.

   L'ordre dans le HTML reste carte puis mascotte (le même balisage sert aux deux
   emplacements, voir ml_am_zone_mascotte()). C'est « order » qui réordonne
   visuellement, sans toucher à l'ordre de lecture ni de tabulation : au clavier,
   on atteint donc les quatre langues avant le globe, ce qui est le bon ordre
   puisque ce sont elles qui portent l'action.

   La répartition de la largeur est laissée à flexbox, et non calculée à la main :
   la carte réclame un plancher (deux pastilles côte à côte, jamais moins), le
   globe réclame le sien (sous 84px il n'est plus qu'une tache), et c'est
   l'arbitrage naturel du flex qui donne le reste à l'un ou à l'autre. Un calc()
   qui soustrait une largeur à l'autre se périme au premier changement de contenu. */
.ml-am-mascotte-hero {
  display: flex;
  justify-content: center;
  inline-size: 100%;
}
.ml-am-mascotte-hero .ml-am-mascotte-zone {
  position: relative;
  inset: auto;
  z-index: 1;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: var(--ml-space-xs);
  inline-size: 100%;
  pointer-events: auto;
}

/* Le globe. Sa hauteur vaut environ 1,284 fois sa largeur (le viewBox du
   dessin fait 335 sur 430). Le second terme du min() est un budget de
   hauteur, jamais une valeur choisie au jugé, qui l'empêche de pousser le bas
   du hero (min-block-size: 88svh, voir .ml-home-hero, main.css) trop loin
   sous la ligne de flottaison ; le plancher de 96px le retient sur un écran
   très court, où ce budget tombe à rien : mieux vaut un globe à l'étroit
   qu'un globe évaporé. Il ne rétrécit pas (flex: none) : sous cette taille il
   n'est plus qu'une tache, c'est la carte qui cède la place, elle a de la
   marge. */
.ml-am-mascotte-hero .ml-am-mascotte-image {
  order: 1;
  flex: none;
  /* Agrandie une fois de plus, d'environ moitié (demande de Marino, juillet
     2026 : « genre 1,5x »). Toutes les bornes ont été multipliées ensemble,
     plancher compris, pour que la proportion tienne à toutes les largeurs :
     84 devient 126, 24vw devient 36vw, le plafond 221 devient 332.

     La borne en svh, elle, monte moins vite (30 devient 42, pas 45) et c'est
     volontaire : c'est le garde-fou de hauteur. Le haut de page fait 88svh et
     doit loger le titre, deux boutons, le globe ET la carte de Linguine. Sur
     un écran bas, cette borne reprend la main avant que le globe ne pousse la
     carte hors de l'écran. Sur un écran d'ordinateur ordinaire, c'est le
     plafond de 332px qui gagne, donc la taille demandée. */
  inline-size: max(126px, min(clamp(126px, 36vw, 332px), calc(42svh - 26px)));
  margin-block-start: 0;
  /* Fond sombre (vidéo) : la mascotte doit être blanche pour rester lisible
     et cohérente avec le texte du haut de page. */
  color: #fff;
}

/* Le halo et l'ombre au sol, tous deux décoratifs, tous deux dans des
   pseudo-éléments : rien à décrire pour une synthèse vocale, rien à charger.

   Le halo est NEUTRE et sombre, jamais violet : il assied le globe sur la
   vidéo, et il aide au passage sa lisibilité sur une image claire, là où un halo
   clair l'aurait effacé. Il n'a rien à voir avec l'ancien voile violet du haut de
   page, qui teintait toute l'image : celui-ci ne couvre que les quelques centaines
   de pixels autour du globe.

   L'ombre au sol, elle, ne bouge jamais : elle reste au sol, c'est tout son sens.
   Elle vit dans un pseudo-élément du CONTENEUR, jamais du SVG (voir plus haut,
   .ml-am-mascotte-hero .ml-am-mascotte-image img/svg, désormais sans aucune
   animation ici) : la mascotte du hero est posée, elle ne flotte plus, mais
   l'ombre garde son ancrage propre au cas où un mouvement reviendrait un jour
   sur le globe seul.

   z-index: -1 les glisse derrière le globe sans les faire disparaître : la
   zone porte un z-index positif (1, voir plus haut) et forme donc un contexte
   d'empilement, dont ils ne peuvent pas sortir. Sans cette valeur négative, un
   pseudo-élément positionné se peint APRÈS le contenu en flux, et le halo
   passerait devant le globe.

   Le halo est une ELLIPSE calée sur la silhouette entière, pas un cercle : le
   globe est plus haut que large (bras compris), un cercle en laissait le
   sommet et la base hors du halo, sur le blanc nu. Il se dissout en douceur
   sur ses bords :
   c'est un halo, pas une pastille.

   La mascotte est un tracé blanc DÉCORATIF (aria-hidden côté PHP) : elle n'est
   pas soumise au seuil de contraste, à la différence du texte du haut de page.
   Le halo n'est donc pas là pour atteindre un chiffre, il est là pour que le
   globe ne disparaisse pas sur une photo très claire, et pour l'asseoir
   sur la scène, comme demandé. Il reste léger : au coeur il assombrit assez pour
   détacher le trait, sur une photo ordinaire il ne fait que poser une ombre
   douce sous le globe. */
.ml-am-mascotte-hero .ml-am-mascotte-image::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: -12% -18% -6%;
  /* Le halo est passé du sombre au lumineux avec la maquette « Hero
     Mascotte » : une lueur blanche qui respire derrière le globe,
     plutôt qu'une assise d'ombre. Sa pulsation vit ici, en CSS : elle est
     continue et douce, pas un évènement, le métronome ne s'entend pas. Le
     globe garde son ombre portée (--ml-hero-ombre-trait, main.css)
     pour tenir sur une photo claire. */
  background: radial-gradient(
    58% 60% at 50% 45%,
    rgba(255, 255, 255, 0.28) 0%,
    rgba(255, 255, 255, 0.12) 55%,
    rgba(255, 255, 255, 0) 70%
  );
  animation: ml-am-halo 6s ease-in-out infinite;
}
@keyframes ml-am-halo {
  0%, 100% { opacity: 0.5; scale: 1; }
  50%      { opacity: 0.9; scale: 1.07; }
}
@media (prefers-reduced-motion: reduce) {
  .ml-am-mascotte-hero .ml-am-mascotte-image::before {
    animation: none;
  }
}
.ml-am-mascotte-hero .ml-am-mascotte-image::after {
  content: '';
  position: absolute;
  z-index: -1;
  inset-inline: 12%;
  inset-block-end: 0%;
  block-size: 7%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(6, 14, 26, 0.5),
    rgba(6, 14, 26, 0)
  );
}

/* ── La carte de messagerie ──
   Même balisage que la bulle des autres pages, autre visage : fond blanc plein,
   coins de 24px, une ombre portée qui la décolle de la vidéo. Ni trait de craie,
   ni pointe de phylactère : ce n'est plus une bulle, c'est une conversation
   ouverte à côté du globe.

   Le fond blanc est plein, jamais translucide : sur une vidéo, une surface
   translucide fait varier le contraste de son texte avec chaque image qui passe.
   Le blanc le fige, une fois pour toutes.

   Elle prend ce que le globe lui laisse, sans jamais descendre sous le
   plancher qui garde ses deux pastilles côte à côte. */
.ml-am-mascotte-hero .ml-am-bulle-question {
  order: 2;
  flex: 1 1 250px;
  min-inline-size: 196px;
  inline-size: auto;
  max-inline-size: 360px;
  margin: 0;
  /* La carte descend un peu par rapport au sommet du globe (demande de
     Marino) : la zone est alignée flex-start, seule cette marge la décale. */
  margin-block-start: clamp(16px, 4vh, 44px);
  padding: var(--ml-space-xs) var(--ml-space-xs) 0.75rem;
  position: relative; /* l'ancre de la pointe ci-dessous */
  background: #fff;
  border: 0;
  border-radius: 24px;
  box-shadow: var(--ml-shadow-lg);
  z-index: 1;
}

/* La petite pointe vers la mascotte (demande de Marino) : un angle discret
   qui rattache la carte au globe posé à sa gauche, pas un phylactère
   entier. Un carré blanc pivoté, glissé sous le bord gauche à hauteur du
   message ; z-index négatif : il passe sous la carte (la carte forme son
   propre contexte d'empilement avec z-index 1) et seul le coin dépasse.
   L'::after de la bulle commune reste éteint. */
.ml-am-mascotte-hero .ml-am-bulle-question::before {
  content: '';
  position: absolute;
  inset-inline-start: -7px;
  /* Ancrée en pourcentage DEPUIS LE BAS de la carte, jamais en pixels depuis
     le haut (demande de Marino) : la carte grandit quand les pastilles de
     langue apparaissent en fondu, et une distance fixe depuis le haut faisait
     dépasser la pointe sous la carte encore courte. En pourcentage, elle
     reste dans la carte à toutes ses tailles et glisse d'elle-même vers le
     haut du globe une fois la carte déployée. La règle commune pose
     inset-block-start: 100% (pointe sous la bulle craie) : neutralisée ici. */
  inset-block-start: auto;
  inset-block-end: 38%;
  inline-size: 16px;
  block-size: 16px;
  background: #fff;
  /* La bulle « craie » commune (plus bas dans ce fichier) pose sur ce même
     pseudo-élément un trait violet (border-inline, border-block-start) pour
     dessiner le phylactère : cette variante redéfinit le fond et la rotation,
     mais jamais ce trait, qui restait donc actif et laissait voir une moitié
     de triangle violette. Il est ici explicitement éteint : la pointe est un
     carré blanc pur, sans trait ni ombre, comme la carte qu'elle prolonge. */
  border: 0;
  rotate: 45deg;
  border-radius: 2px;
  z-index: -1;
}
.ml-am-mascotte-hero .ml-am-bulle-question::after {
  content: none;
}

/* Écran très étroit (moins de 380px) : la carte peut redescendre bien plus bas
   en largeur. Seuil monté de 360 à 380 (audit mobile) : entre 360 et 376px, le
   plancher de 224px de la carte faisait déborder la rangée mascotte + carte de
   la boîte de contenu, un saut visible pile au seuil.

   La mise en colonne unique des quatre pastilles, pensée pour ce seuil, n'a
   plus lieu d'être ici : sous 600px (voir le bloc MOBILE plus bas), les
   pastilles cèdent déjà la place à l'invitation courte de la carte, quelle
   que soit la largeur en dessous. */
@media (max-width: 379px) {
  .ml-am-mascotte-hero .ml-am-bulle-question {
    min-inline-size: 140px;
  }
}

/* Les deux paliers qui agrandissaient encore le globe à partir de 900px puis
   1200px ont disparu avec la maquette « page de chapitre » (juillet 2026) :
   ils supposaient une colonne dédiée, large, à côté du texte (jusqu'à 650px,
   voire plus, sur grand écran). Le hero n'a plus qu'une colonne centrée, la
   même à toutes les largeurs (voir .ml-home-hero-inner, main.css), et le
   globe garde donc partout le même plafond (240px, voir
   .ml-am-mascotte-hero .ml-am-mascotte-image plus haut) : un globe plus
   grand y concurrencerait le titre plutôt que de l'accompagner. */

/* ── Bulle de question : élément d'interface permanent ──
   Contrairement aux bulles de salutation, elle n'est jamais masquée ni
   animée à l'apparition : elle est là dès le chargement de la page, sans
   dépendre du script. Elle garde un fond clair même dans le hero sombre de
   l'accueil : c'est ce qui la rend lisible sur la vidéo.

   Demande de la cliente : « est-ce que la bulle qui pose la question pourrait
   avoir le même design que les bulles Hola et Hallo ? ». Elle rejoint donc la
   famille des dessins à la craie : un trait violet franc, des coins très
   arrondis, une ombre douce.

   Ce n'est pas une imitation au pixel près, et ce n'est pas le but. Les
   salutations sont des PNG dessinés à la main : leur trait est irrégulier,
   granuleux, il n'existe aucun vecteur dont on pourrait tirer une bordure CSS.
   Reproduire ce grain demanderait une image de bordure par taille de bulle, et
   la bulle change de taille avec la colonne. Un trait net de la même couleur,
   posé sur des coins de la même générosité, suffit à faire reconnaître la
   famille : c'est la couleur et la rondeur qui portent la parenté, pas le
   grain.

   Le fond reste blanc plein. Le verre dépoli a été retiré : sur une vidéo, une
   surface translucide fait varier le contraste du texte avec chaque image qui
   passe. Le blanc plein le fige, une fois pour toutes. Contraste calculé :
   16,5:1 pour le titre de la bulle (#2e123f sur #ffffff) et 15,43:1 pour le
   nom des langues (#221d29 sur #faf7f5). Le trait violet lui même est à
   10,13:1 sur le blanc. Très au dessus des 4,5:1 exigés. */
.ml-am-bulle-question {
  /* Fond, trait et épaisseur du trait passent par des variables locales : la
     pointe du phylactère (::before et ::after, plus bas) les relit, elle suit
     donc la bulle sans qu'on ait à répéter la valeur à deux endroits. */
  --ml-bulle-fond: var(--ml-bg);
  --ml-bulle-trait: var(--ml-accent);
  --ml-bulle-trait-w: 3px;

  position: relative;
  inline-size: min(100%, 280px);
  max-inline-size: calc(100vw - 2 * var(--ml-space-sm));
  background: var(--ml-bulle-fond);
  border: var(--ml-bulle-trait-w) solid var(--ml-bulle-trait);
  /* Les bulles à la craie sont presque des ovales. Un rayon largement
     supérieur au rayon le plus généreux du site (28px) rapproche la boîte de
     cette forme, sans jamais la déformer : au delà de la moitié de sa hauteur,
     le navigateur ramène le rayon de lui même. */
  border-radius: 40px;
  box-shadow: var(--ml-shadow-md);
  padding: var(--ml-space-sm) var(--ml-space-md);
  margin-block-end: var(--ml-space-md);
}

/* Hors accueil, la bulle de question est repliable (attribut hidden posé
   et retiré par le script). Si elle restait dans le flux flex column de la
   zone, son apparition pousserait la mascotte plus bas et déplacerait avec
   elle les bulles de salutation, pourtant ancrées sur la mascotte : c'est
   le déplacement que Marino veut supprimer. En la sortant du flux (position
   absolute, ancrée en bas de la zone, juste au dessus de la mascotte), son
   dépliage ne change plus jamais la position de la mascotte ni des bulles
   de salutation qui l'entourent. */
[data-ml-am-accueil="0"] .ml-am-bulle-question {
  position: absolute;
  inset-block-end: 100%;
  inset-inline-end: 0;
  margin-block-end: var(--ml-space-sm);
}

/* La pointe de la bulle, comme un vrai phylactère dirigé vers la mascotte,
   posée juste en dessous. Un triangle en bordures (deux bords transparents,
   un bord de la couleur du trait de la bulle) plutôt qu'un clip-path : la
   bordure du triangle peut ainsi suivre celle de la bulle (::before, plus
   grand), pour un raccord propre entre la pointe et le corps. Un second
   triangle (::after), de la couleur du fond et rentré de la valeur exacte du
   trait, vient recouvrir l'intérieur du premier : ce qui dépasse dessine le
   trait tout autour de la pointe, à la même épaisseur que le contour de la
   bulle. Décalée vers la droite : c'est de ce côté que se trouve la mascotte,
   juste en dessous.

   La pointe a grossi avec le trait : depuis que la bulle porte le trait violet
   des salutations, une pointe fine ferait maigrichonne à côté d'un contour de
   trois pixels. Les couleurs et l'épaisseur passent par les variables locales
   déclarées plus haut, la pointe suit donc la bulle sans qu'on ait à répéter
   quoi que ce soit. */
.ml-am-bulle-question::after,
.ml-am-bulle-question::before {
  content: '';
  position: absolute;
  inset-block-start: 100%;
  inset-inline-end: var(--ml-space-lg);
  inline-size: 0;
  block-size: 0;
  border-inline: 13px solid transparent;
}
.ml-am-bulle-question::before {
  border-block-start: 16px solid var(--ml-bulle-trait);
}
.ml-am-bulle-question::after {
  inset-block-start: calc(100% - var(--ml-bulle-trait-w));
  inset-inline-end: calc(var(--ml-space-lg) + 3px);
  border-inline-width: 10px;
  border-block-start: 13px solid var(--ml-bulle-fond);
}

.ml-am-bulle-question-titre {
  font-family: var(--ml-font-heading);
  font-size: var(--ml-fs-sm);
  font-weight: 600;
  color: var(--ml-primary);
  margin: 0 0 var(--ml-space-xs);
}

.ml-am-bulle-langues {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4em;
}

.ml-am-bulle-langue-lien {
  display: flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.5em 0.6em;
  /* Cible tactile de 44px sans gonfler le padding : c'est un choix de langue,
     une action de premier plan (audit mobile). */
  min-block-size: 44px;
  background: var(--ml-bg-alt);
  border: 1px solid var(--ml-border);
  border-radius: var(--ml-radius);
  color: var(--ml-text);
  font-size: var(--ml-fs-sm);
  font-weight: 600;
  text-align: start;
}

.ml-am-bulle-langue-lien:hover {
  border-color: var(--ml-accent);
}

.ml-am-bulle-langue-lien:focus-visible {
  outline: 2px solid var(--ml-accent);
  outline-offset: 2px;
}

/* Le petit mot de salutation (« Hi », « Hallo »...), en pastille violette
   devant le nom de la langue. Il a remplacé les drapeaux emojis (maquette
   « Hero Mascotte ») : un mot s'affiche pareil partout, là où Windows ne
   dessinait d'un drapeau que les deux lettres du pays. Décoratif (aria-hidden
   côté PHP), le nom écrit à côté porte le sens. Contraste du violet
   --ml-accent sur le lilas --ml-accent-soft : 8,7:1. */
.ml-am-bulle-salut-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  padding: 0.1em 0.55em;
  border-radius: 7px;
  background: var(--ml-accent-soft);
  color: var(--ml-accent);
  font-family: var(--ml-font-heading);
  font-size: 0.72em;
  font-weight: 800;
  line-height: 1.5;
}

.ml-am-bulle-langue-nom {
  flex: 1 1 auto;
}

@media (min-width: 700px) {
  .ml-am-bulle-question {
    inline-size: 300px;
  }
}

/* ── L'intérieur de la carte de Linguine (haut de page seulement) ──
   Trois étages, dans l'ordre de lecture d'une conversation : qui parle, ce qu'il
   dit, ce qu'on peut lui répondre.

   Contrastes, tous sur le fond blanc plein de la carte :
   - le nom de l'assistant, --ml-primary sur blanc : 16,5:1
   - « En ligne », --ml-text-mid sur blanc : 7,3:1
   - le message, --ml-text sur le lilas --ml-accent-soft : 14,4:1
   - le nom des langues, --ml-text sur blanc : 15,4:1
   Tous très au dessus des 4,5:1 exigés. */

/* 1. L'en-tête : la vignette, le nom, l'état. */
.ml-am-carte-entete {
  display: flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.25rem 0.35rem 0.6rem;
  border-block-end: 1px solid var(--ml-border);
  margin-block-end: 0.6rem;
}

/* La vignette : un simple disque, dans la couleur de l'accent (voir
 * ml_am_zone_mascotte() dans inc/assistant-modale.php). Depuis que le
 * personnage a laissé place au globe (juillet 2026), reprendre le SVG du
 * globe en entier pour une vignette de trente-six pixels coûterait à nouveau
 * le même poids que sur l'ancienne photo, pour un détail que cette taille
 * n'affiche de toute façon pas (le tracé des continents devient illisible
 * sous une quarantaine de pixels) : un disque uni rend le même effet visuel
 * pour un coût nul. */
/* LA VIGNETTE DE LA CARTE : un disque violet plein, le globe en blanc dedans.
   C'est l'avatar de l'interlocuteur dans une carte de messagerie, et un rond
   violet nu n'en tenait pas le role (demande de Marino).

   Le conteneur est vide dans le HTML, il ne porte que le disque : c'est son
   ::before qui dessine le globe, juste en dessous. flex + center le placent au
   milieu sans avoir a le positionner. */
.ml-am-carte-vignette {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  inline-size: 36px;
  block-size: 36px;
  overflow: hidden;
  border-radius: 50%;
  background: var(--ml-accent);
  color: #fff;
}

/* LE GLOBE SEUL, EN MASQUE. C'est le meme fichier et la meme technique que le
   filigrane de section (.ml-watermark dans main.css) : le masque decoupe un
   aplat blanc a la forme du globe. Le bras de la mascotte n'y est pas, il fait
   trop de detail a trente-six pixels (demande de Marino).

   Le globe ne touche pas le bord du disque : colle au cercle, il se lirait
   comme une tache. */
.ml-am-carte-vignette::before {
  content: '';
  inline-size: 66%;
  block-size: 66%;
  background-color: #fff;
  -webkit-mask: url('../img/globe.svg') no-repeat center / contain;
          mask: url('../img/globe.svg') no-repeat center / contain;
}

/* Repli pour un navigateur sans masque CSS : un disque blanc plein vaut mieux
   qu'une pastille violette vide. */
@supports not ((-webkit-mask: url('../img/globe.svg')) or (mask: url('../img/globe.svg'))) {
  .ml-am-carte-vignette::before {
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.85);
  }
}

.ml-am-carte-nom {
  flex: 1 1 auto;
  min-inline-size: 0;
  font-family: var(--ml-font-heading);
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.2;
  /* L'aubergine nuit de la marque (--ml-accent-nuit), désormais aussi celle du
     chrome : la carte vit dans le monde violet du haut de page. 16,5:1 sur le
     blanc de la carte. */
  color: var(--ml-accent-nuit);
}

/* L'état. Le point vert ne dit rien à lui seul : le mot est écrit à côté, et
   c'est le mot qui informe. Une pastille de couleur seule serait invisible pour
   qui ne distingue pas le vert du rouge, et muette pour une synthèse vocale. */
.ml-am-carte-etat {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  flex: none;
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.2;
  /* Violet comme la maquette, plus un gris de plus : 10,1:1 sur le blanc de
     la carte. Le point vert reste décoratif, le mot informe. */
  color: var(--ml-accent);
}
.ml-am-carte-point {
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: var(--ml-en-ligne);
}

/* 2. Le message entrant, dans sa bulle lilas. Le coin bas gauche est ramené à
   presque rien : c'est le coin qui touche l'interlocuteur, et c'est ce petit
   angle, dans toutes les messageries, qui distingue un message reçu d'un message
   envoyé. */
.ml-am-mascotte-hero .ml-am-bulle-question-titre {
  background: var(--ml-accent-soft);
  border-radius: 14px 14px 14px 4px;
  padding: 0.45rem 0.7rem;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ml-text);
  margin-block: 0 0.6rem;
  margin-inline: 0.1rem 0.6rem;
}

/* 3. Les quatre réponses possibles, en pastilles, deux par deux. Le nom de la
   langue ne disparaît jamais, à aucune largeur : le mot de salutation qui le
   précède est décoratif, c'est le nom qui rend la pastille compréhensible
   partout (voir ml_am_salut_pour_langue() dans inc/assistant-modale.php).
   Sous 360px, elles repassent sur une colonne plutôt que de le couper (voir
   la requête de média plus haut). */
.ml-am-mascotte-hero .ml-am-bulle-langues {
  grid-template-columns: repeat(2, 1fr);
  gap: 0.3rem;
  padding-inline: 0.1rem;
}
.ml-am-mascotte-hero .ml-am-bulle-langue-lien {
  justify-content: center;
  gap: 0.35em;
  padding: 0.42em 0.5em;
  background: var(--ml-bg);
  border: 1px solid var(--ml-border-strong);
  border-radius: var(--ml-radius-sm);
  font-size: 0.8125rem;
  text-align: center;
  transition: border-color 180ms var(--ml-ease), background-color 180ms var(--ml-ease);
}
.ml-am-mascotte-hero .ml-am-bulle-langue-lien:hover {
  border-color: var(--ml-accent);
  background: var(--ml-accent-soft);
}
.ml-am-mascotte-hero .ml-am-bulle-langue-nom {
  flex: 0 1 auto;
}
@media (prefers-reduced-motion: reduce) {
  .ml-am-mascotte-hero .ml-am-bulle-langue-lien {
    transition: none;
  }
}

/* 4. L'invitation courte, réservée au téléphone (demande de Marino) : elle
   remplace la question tapée ET les quatre pastilles sous 600px, voir le
   bloc MOBILE plus bas, qui bascule l'affichage. Par défaut (dès 600px),
   elle reste hors champ : la carte garde sa forme habituelle, question
   entière et pastilles visibles. Même famille visuelle que le message
   entrant ci dessus (fond lilas, coin bas gauche resserré), mais c'est un
   vrai lien (voir ml_am_zone_mascotte() dans inc/assistant-modale.php) :
   cible tactile de 44px, état de focus visible. Contraste identique à la
   bulle de message : --ml-text sur --ml-accent-soft, 14,4:1. */
.ml-am-carte-invite-mobile {
  display: none;
}
.ml-am-mascotte-hero .ml-am-carte-invite-mobile {
  align-items: center;
  min-block-size: 44px;
  background: var(--ml-accent-soft);
  border: 1px solid transparent;
  border-radius: 14px 14px 14px 4px;
  padding: 0.6rem 0.85rem;
  font-family: var(--ml-font-heading);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ml-text);
  text-decoration: none;
  transition: border-color 180ms var(--ml-ease);
}
.ml-am-mascotte-hero .ml-am-carte-invite-mobile:hover {
  border-color: var(--ml-accent);
}
.ml-am-mascotte-hero .ml-am-carte-invite-mobile:focus-visible {
  outline: 2px solid var(--ml-accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .ml-am-mascotte-hero .ml-am-carte-invite-mobile {
    transition: none;
  }
}

/* ── La mascotte parle : sa question s'écrit, puis les langues arrivent ──
   Demande de la cliente : sur le haut de page, la bulle apparaît vide, la
   question s'y écrit lettre par lettre, puis la bulle grandit et les quatre
   langues se posent l'une après l'autre.

   RIEN N'EST MASQUÉ EN STYLE STATIQUE. Tout ce bloc est enfermé derrière
   « .is-frappe », une classe que seul le script pose, et seulement une fois
   qu'il est sûr de jouer l'effet (voir demarrerFrappe() dans
   assets/js/assistant-modale.js). Sans script, ou si le script échoue avant
   d'avoir posé la classe, la bulle est complète : question entière, quatre
   langues visibles et cliquables. C'est la règle du projet, elle ne se
   négocie pas : un opacity: 0 statique rendrait ces quatre choix invisibles
   pour toujours le jour où le script ne se charge pas.

   Le texte, lui, ne bouge jamais du DOM. Le script découpe la phrase en
   lettres AVANT la première image, une seule fois, et n'y touche plus : ce
   ne sont que des opacités qui changent ensuite. Le paragraphe garde la
   phrase entière en aria-label, le découpage est marqué aria-hidden : une
   synthèse vocale lit la question d'un seul tenant, elle ne voit pas les
   lettres. Pourquoi pas la méthode classique de la largeur en « ch » : elle
   ne tient que sur une police à chasse fixe et sur une seule ligne, or Baloo
   2 est proportionnelle et la question passe sur deux lignes dans une bulle
   de 176 à 260px. */

/* Le mot est une boîte, pour que le retour à la ligne ne puisse jamais couper
   un mot entre deux de ses lettres, maintenant que chaque lettre en est une.
   L'espace entre deux mots reste dehors : c'est lui, et lui seul, qui autorise
   la coupure. */
.ml-am-mot {
  display: inline-block;
}

.ml-am-frappe .ml-am-lettre {
  /* La lettre garde sa place dans le flux dès la première image : elle est
     seulement éteinte. Sans cela, la ligne se réorganiserait à chaque lettre
     allumée, et le texte danserait au lieu de s'écrire. Aucune transition :
     une frappe est franche, une lettre apparaît ou n'apparaît pas. */
  opacity: 0;
}
.ml-am-frappe .ml-am-lettre.is-ecrite {
  opacity: 1;
}

/* Le curseur, un simple trait qui clignote là où le globe en est de sa
   phrase. Il SUIT la frappe : le script déplace la classe « is-curseur » de
   lettre en lettre, et le curseur est le pseudo-élément de la lettre qui la
   porte. Avant la première lettre, il se pose au tout début du texte
   (« is-avant », sur l'enveloppe).

   Ce n'est pas un raffinement. Posé une fois pour toutes à la fin du texte, il
   se retrouvait à droite d'une phrase encore invisible mais qui occupe déjà
   toute sa place (c'est le principe même de l'effet) : on voyait donc un
   curseur flotter en plein milieu d'une bulle vide, à trente caractères de là
   où le texte allait s'écrire. Vu à la capture d'écran, corrigé.

   Il s'éteint avec « is-dit », quand la phrase est finie. Il est décoratif au
   sens strict : il ne porte aucune information, et il vit dans un
   pseudo-élément, donc hors du texte accessible.

   Les marges négatives annulent exactement sa largeur (2px de trait, 1px de
   marge) : le curseur ne pèse alors plus rien dans la largeur du texte, et il
   ne peut pas décaler d'un cheveu la lettre qui le suit. Sans elles, il
   élargissait la bulle de trois pixels tant qu'il clignotait, et la bulle
   rétrécissait d'autant en s'éteignant : sous 900px, où la rangée est centrée,
   cela déplaçait le globe d'un pixel et demi au moment précis où les
   langues apparaissaient. Mesuré à 768 x 1024, et parfaitement visible. */
.is-frappe .ml-am-frappe.is-avant::before,
.is-frappe .ml-am-lettre.is-curseur::after {
  content: '';
  display: inline-block;
  inline-size: 2px;
  block-size: 1em;
  margin-inline: 1px -3px;
  vertical-align: text-bottom;
  background: var(--ml-accent);
  animation: ml-am-curseur 800ms steps(1, end) infinite;
}
/* La toute première position du curseur n'a pas de lettre à sa gauche : sa
   marge de tête n'a plus lieu d'être, elle le décollerait du bord du texte. */
.is-frappe .ml-am-frappe.is-avant::before {
  margin-inline: 0 -2px;
}
.is-frappe.is-dit .ml-am-lettre.is-curseur::after {
  display: none;
}

@keyframes ml-am-curseur {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* La bulle grandit. La hauteur cible est MESURÉE par le script sur la liste
   réelle, puis posée en --ml-am-langues-h : une hauteur en « auto » ne s'anime
   pas, et un plafond deviné donnerait une courbe qui traîne à la fin. Le script
   rend la hauteur au navigateur (il retire « is-frappe ») dès la croissance
   finie : une bulle rétrécie plus tard par un changement de largeur ne peut
   donc jamais rester coincée sur une hauteur périmée.

   overflow: hidden pendant la croissance seulement : sinon les quatre langues
   dépasseraient de la bulle repliée avant même d'apparaître.

   Même règle que pour les langues ci dessous : la transition ne vit que sur
   l'état ouvert. Portée par l'état replié, elle animerait le REPLI, et la bulle
   commencerait par se dégonfler sous les yeux du visiteur au chargement de la
   page. Elle doit être basse d'emblée, pas se rétracter. */
.is-frappe .ml-am-bulle-langues {
  block-size: 0;
  overflow: hidden;
}
.is-frappe.is-dit .ml-am-bulle-langues {
  block-size: var(--ml-am-langues-h, auto);
  transition: block-size 320ms var(--ml-ease);
}

/* ── LA BULLE QUI GRANDIT NE POUSSE RIEN ──
   La place que la bulle prendra une fois ouverte est RÉSERVÉE dès le départ,
   sur la zone qui la contient, jamais sur la bulle elle même : la bulle reste
   basse, comme la cliente la veut, mais la zone garde déjà sa taille finale.
   Le globe est calé sur le haut de cette zone (align-items: flex-start,
   plus haut) : il ne bouge donc pas d'un pixel quand la bulle se déploie sous
   lui, dans une place qui l'attendait.

   Ce n'est pas une précaution de principe, c'est la correction d'un défaut
   mesuré au navigateur. Sans cette réserve, sous 900px (là où le globe et
   sa bulle passent SOUS le titre, dans une rangée que la grille du haut de page
   centre verticalement), la bulle devenait l'élément le plus haut de sa rangée
   en s'ouvrant : la rangée grandissait, et tout le haut de page remontait.
   Mesuré à 360 x 740 : le globe montait de 17,5px et le titre de 8,8px,
   sous les yeux du visiteur, au beau milieu de l'animation. C'est exactement le
   piège que ce projet a déjà connu.

   Mesures après correction, de 360 à 1920px de large : globe, titre et
   texte du haut de page immobiles au pixel près, du premier octet à la dernière
   image.

   La hauteur est posée par le script (--ml-am-zone-h), qui seul peut la
   mesurer. Sans script, la classe « is-frappe » n'existe pas, cette règle ne
   s'applique jamais, et la zone prend sa hauteur naturelle : celle d'une bulle
   déjà pleine. Rien à réserver, il n'y a rien à animer. */
.ml-am-mascotte-zone:has(.ml-am-bulle-question.is-frappe) {
  min-block-size: var(--ml-am-zone-h, auto);
}

/* La LARGEUR de la bulle, elle, n'a rien à réserver, et c'est vérifié : la liste
   des langues, même repliée à zéro de haut, garde sa largeur intrinsèque et
   continue donc de dimensionner la bulle. Mesuré de 360 à 1920px : la bulle a
   exactement la même largeur avant, pendant et après la scène. Le seul élément
   qui l'élargissait était le curseur, et sa marge négative l'a réglé (voir plus
   haut). */

/* Chaque langue arrive en fondu, en montant d'un cheveu : le script pose
   « is-visible » l'une après l'autre. Le mouvement est minuscule (6px) et sans
   rebond : le globe propose, il ne fait pas un numéro.

   La transition ne vit QUE sur l'état visible, jamais sur l'état caché. C'est
   la correction d'un défaut mesuré au navigateur : la classe « is-frappe » est
   posée par le script alors que les langues sont déjà peintes et opaques (elles
   le sont dès le premier octet, c'est la règle). Une transition portée par
   l'état caché animait donc leur DISPARITION : on voyait les quatre langues
   s'effacer avant de revenir, ce qui trahit le procédé. Sans transition sur cet
   état, elles s'éteignent net, le temps d'une image, et il ne reste que leur
   apparition à l'oeil. Mesuré à 390 x 844 : quatre langues encore visibles à
   150 ms avant correction, aucune après. */
.is-frappe .ml-am-bulle-langues > li {
  opacity: 0;
  translate: 0 6px;
}
.is-frappe .ml-am-bulle-langues > li.is-visible {
  opacity: 1;
  translate: 0 0;
  transition: opacity 260ms var(--ml-ease), translate 260ms var(--ml-ease);
}

/* Réglage système « moins de mouvement » : la mascotte ne joue pas la comédie.
   Le script sort avant même de poser « is-frappe » dans ce cas, la bulle est
   donc déjà complète et immobile. Ces règles sont le filet : le style ne dépend
   pas du script pour se tenir correctement, exactement comme pour le salut
   plus haut. */
@media (prefers-reduced-motion: reduce) {
  .ml-am-frappe .ml-am-lettre,
  .is-frappe .ml-am-bulle-langues > li {
    opacity: 1;
    translate: 0;
    transition: none;
  }
  .is-frappe .ml-am-bulle-langues {
    block-size: auto;
    overflow: visible;
    transition: none;
  }
  .is-frappe .ml-am-bulle-question-titre .ml-am-frappe::after {
    display: none;
  }
}

/* ── Bulles de salutation, autour de la mascotte FIXE (hors accueil) ──
   Demande de Marino : à côté de la mascotte fixe (jamais sur l'accueil, où
   le globe vit dans le hero et porte déjà sa carte de messagerie), les
   quatre mots de salutation des langues (« Hi », « Hallo », « Hola »,
   « Ciao », voir ml_am_salut_pour_langue() dans inc/assistant-modale.php)
   apparaissent chacun leur tour, en boucle, animés par
   demarrerBullesSalut() dans assets/js/assistant-modale.js.

   RIEN N'EST MASQUÉ EN STYLE STATIQUE EN ATTENDANT LE SCRIPT : chaque bulle
   porte l'attribut HTML « hidden » dès le rendu PHP
   (ml_am_zone_mascotte()), et seul le script en retire une à la fois. Sans
   JavaScript, ou sous « moins de mouvement » (voir plus bas), aucune bulle
   n'existe jamais visuellement : pas de bulle orpheline figée à l'écran.

   Chaque bulle est un enfant du lien de la mascotte (.ml-am-mascotte-image,
   déjà en position: relative, plus haut dans ce fichier) : 100 % ou 0
   valent donc les bords réels de l'icône elle même, quelle que soit la
   largeur réservée à la zone autour d'elle (bien plus large que l'icône sur
   mobile). pointer-events: none : une bulle ne doit jamais devenir une
   cible de clic ni élargir la zone active de la mascotte. */
.ml-am-bulle-salut {
  position: absolute;
  z-index: 1;
  display: inline-block;
  max-inline-size: min(42vw, 120px);
  padding: 0.3em 0.75em;
  background: var(--ml-bg);
  border: 2px solid var(--ml-accent);
  border-radius: 999px;
  box-shadow: var(--ml-shadow-sm);
  color: var(--ml-accent);
  font-family: var(--ml-font-heading);
  font-size: var(--ml-fs-sm);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
}
/* Le hidden natif suffit à la masquer, mais le sélecteur de classe ci
   dessus (même spécificité que l'attribut) l'emporterait sur la feuille de
   style du navigateur : cette règle est nécessaire, pas un double emploi.
   Même geste que .ml-am-fenetre[hidden] et .ml-am-groupe-pays[hidden] plus
   loin dans ce fichier. */
.ml-am-bulle-salut[hidden] {
  display: none;
}

/* Une bulle sur deux à gauche de l'icône (pointe vers la droite, vers la
   mascotte), verticalement centrée sur elle : c'est le côté qui a le plus
   de champ libre, la mascotte étant elle même collée au bord droit de
   l'écran. */
.ml-am-bulle-salut:nth-of-type(odd) {
  inset-inline-end: calc(100% + var(--ml-space-xs));
  inset-block-start: 50%;
  translate: 0 -50%;
}
.ml-am-bulle-salut:nth-of-type(odd)::after {
  content: '';
  position: absolute;
  inset-inline-start: 100%;
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 0;
  block-size: 0;
  border-block: 6px solid transparent;
  border-inline-start: 8px solid var(--ml-accent);
}

/* L'autre moitié au dessus de l'icône (pointe vers le bas, vers la
   mascotte), alignée sur son bord droit plutôt que centrée : centrer sur
   l'icône aurait débordé de l'écran à droite sur un petit mobile, la
   mascotte étant elle même collée à ce bord (même raison que la bulle de
   question repliée, voir [data-ml-am-accueil="0"] .ml-am-bulle-question plus
   haut, qui suit le même bord pour la même raison). */
.ml-am-bulle-salut:nth-of-type(even) {
  inset-block-end: calc(100% + var(--ml-space-xs));
  inset-inline-end: 0;
}
.ml-am-bulle-salut:nth-of-type(even)::after {
  content: '';
  position: absolute;
  inset-block-start: 100%;
  inset-inline-end: var(--ml-space-sm);
  inline-size: 0;
  block-size: 0;
  border-inline: 6px solid transparent;
  border-block-start: 8px solid var(--ml-accent);
}

/* Le chat de la mascotte (bulle de question dépliée) passe devant : une
   bulle de salutation ne doit jamais se superposer à un vrai bouton. */
.ml-am-mascotte-zone:has(.ml-am-bulle-question:not([hidden])) .ml-am-bulles-salut {
  display: none;
}

/* Filet de sécurité : demarrerBullesSalut() ne retire déjà aucun « hidden »
   sous « moins de mouvement » (elle sort avant de rien faire), cette règle
   ne fait que confirmer ce que le script fait déjà, comme pour le salut du
   bras plus haut dans ce fichier. */
@media (prefers-reduced-motion: reduce) {
  .ml-am-bulles-salut {
    display: none;
  }
}

/* ── Fenêtre : fond et boîte ──
   Parcours en deux étapes : indicateur de progression, flèche de retour
   unique, transitions de contenu avec hauteur animée. L'étape 2 (résultats)
   élargit la boîte, pour laisser respirer les cartes de destination. */
.ml-am-fenetre[hidden] {
  display: none;
}

.ml-am-fond {
  position: fixed;
  inset: 0;
  background: rgba(30, 10, 42, 0.6);
  z-index: 300;
}

.ml-am-boite {
  position: fixed;
  inset: 0;
  z-index: 301;
  background: var(--ml-bg);
  overflow-y: auto;
  padding: var(--ml-space-lg) var(--ml-space-sm) var(--ml-space-xl);
}

@media (min-width: 700px) {
  .ml-am-boite {
    inset: auto;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: min(560px, calc(100vw - 2 * var(--ml-space-md)));
    max-block-size: min(680px, calc(100svh - 2 * var(--ml-space-md)));
    border-radius: var(--ml-radius-lg);
    box-shadow: var(--ml-shadow-lg);
    padding: var(--ml-space-xl) var(--ml-space-lg);
    /* La largeur de la boîte elle même s'anime : c'est ce qui fait
       « s'élargir » la fenêtre à l'étape des résultats, en plus de la
       hauteur animée par le script sur .ml-am-etapes-viewport. */
    transition: inline-size 380ms var(--ml-ease), max-block-size 380ms var(--ml-ease);
  }
  /* L'étape des résultats a besoin de place pour les cartes de destination
     groupées par pays : la boîte s'élargit nettement, sans jamais dépasser la
     largeur de la fenêtre. */
  .ml-am-boite:has(#ml-am-etape-resultats:not([hidden])) {
    inline-size: min(880px, calc(100vw - 2 * var(--ml-space-md)));
    max-block-size: min(760px, calc(100svh - 2 * var(--ml-space-md)));
  }
  @media (prefers-reduced-motion: reduce) {
    .ml-am-boite {
      transition: none;
    }
  }
}

.ml-am-fermer {
  position: absolute;
  inset-block-start: var(--ml-space-sm);
  inset-inline-end: var(--ml-space-sm);
  /* 44px : cible tactile minimale confortable (audit mobile). */
  inline-size: 44px;
  block-size: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ml-bg-alt);
  border: 1px solid var(--ml-border);
  border-radius: 50%;
  color: var(--ml-text);
  cursor: pointer;
}

.ml-am-fermer:hover {
  background: var(--ml-border);
}

/* ── En-tête : flèche de retour + indicateur d'étapes ──────────────────── */
.ml-am-entete {
  display: flex;
  align-items: center;
  gap: var(--ml-space-sm);
  margin-inline-end: 3rem; /* laisse la place au bouton de fermeture */
  margin-block-end: var(--ml-space-md);
  min-block-size: 40px;
}

/* La flèche de retour, unique, en haut à gauche. Absente à l'étape 1
   (attribut hidden posé par le script). */
.ml-am-entete .ml-am-precedent {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  /* 44px : cible tactile minimale confortable (audit mobile). */
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  background: var(--ml-bg-alt);
  border: 1px solid var(--ml-border);
  border-radius: 50%;
  color: var(--ml-primary);
  text-decoration: none;
  cursor: pointer;
}
.ml-am-entete .ml-am-precedent:hover {
  border-color: var(--ml-accent);
  color: var(--ml-accent);
}
.ml-am-entete .ml-am-precedent:focus-visible {
  outline: 2px solid var(--ml-accent);
  outline-offset: 2px;
}
.ml-am-entete .ml-am-precedent[hidden] {
  display: none;
}

/* L'indicateur est décoratif (aria-hidden posé côté PHP) : la progression
   réelle est portée par la zone aria-live juste en dessous. Deux pastilles
   rondes reliées par un trait qui se remplit à mesure. */
.ml-am-etapes-indicateur {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  max-inline-size: 180px;
}

.ml-am-etapes-pastille {
  flex: none;
}

.ml-am-etapes-rond {
  display: block;
  inline-size: 22px;
  block-size: 22px;
  border-radius: 50%;
  border: 2px solid var(--ml-border-strong);
  background: var(--ml-bg);
  position: relative;
  transition: background-color 220ms var(--ml-ease), border-color 220ms var(--ml-ease);
}

/* Étape courante : pastille pleine (accent). */
.ml-am-etapes-pastille.is-courante .ml-am-etapes-rond {
  background: var(--ml-accent);
  border-color: var(--ml-accent);
}

/* Étape déjà faite : marquée d'une coche plutôt que remplie, pour rester
   visuellement distincte de l'étape courante. */
.ml-am-etapes-pastille.is-faite .ml-am-etapes-rond {
  background: var(--ml-accent-soft);
  border-color: var(--ml-accent);
}
.ml-am-etapes-pastille.is-faite .ml-am-etapes-rond::after {
  content: '';
  position: absolute;
  inset-inline-start: 5px;
  inset-block-start: 3px;
  inline-size: 9px;
  block-size: 5px;
  border-inline-start: 2px solid var(--ml-accent);
  border-block-end: 2px solid var(--ml-accent);
  rotate: -45deg;
}

.ml-am-etapes-trait {
  flex: 1 1 auto;
  block-size: 2px;
  margin-inline: 0.35em;
  background: var(--ml-border-strong);
  position: relative;
  overflow: hidden;
}
.ml-am-etapes-trait::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 0%;
  background: var(--ml-accent);
  transition: inline-size 320ms var(--ml-ease);
}
.ml-am-etapes-trait.is-rempli::before {
  inline-size: 100%;
}
@media (prefers-reduced-motion: reduce) {
  .ml-am-etapes-rond,
  .ml-am-etapes-trait::before {
    transition: none;
  }
}

.ml-am-progression {
  min-block-size: 1px;
}

/* Titre unique de la fenêtre (référencé par aria-labelledby du dialogue) :
   il résume l'étape 1, qui pose deux questions. Chaque question a sa
   propre légende de groupe (.ml-am-choix-legende) et son propre titre de
   section pour l'étape 2 (.ml-am-titre-resultats), voir plus bas. */
.ml-am-titre-fenetre {
  font-family: var(--ml-font-heading);
  margin: 0 0 var(--ml-space-sm);
}

.ml-am-titre-fenetre:focus-visible,
.ml-am-titre-resultats:focus-visible {
  outline: none;
}

.ml-am-titre-resultats {
  font-family: var(--ml-font-heading);
  font-size: var(--ml-fs-lg);
  margin: 0 0 var(--ml-space-sm);
}

/* ── Piste des étapes : viewport à hauteur animée par le script ──────────
   Le script mesure la hauteur naturelle de l'étape entrante et anime
   .ml-am-etapes-viewport vers cette valeur, pour que la fenêtre ne saute
   jamais. Hors transition, la hauteur reste 'auto' (posée par le script),
   afin qu'un contenu chargé plus tard (les résultats REST) ne soit jamais
   coupé. */
.ml-am-etapes-viewport {
  position: relative;
  overflow: hidden;
  transition: block-size 320ms var(--ml-ease);
}
@media (prefers-reduced-motion: reduce) {
  .ml-am-etapes-viewport {
    transition: none;
  }
}

.ml-am-etapes-piste {
  position: relative;
}

/* Chaque étape est visible par défaut (pas d'opacity: 0 statique) : c'est
   le script qui pose les classes de transition, seulement quand il va
   réellement animer. Sans script, chaque étape reste directement lisible
   dès qu'elle n'est plus [hidden]. */
.ml-am-etape {
  transition: opacity 260ms var(--ml-ease), translate 260ms var(--ml-ease);
}

/* Sortie : le contenu part vers le bas en s'effaçant. */
.ml-am-etape.ml-am-etape--sort {
  opacity: 0;
  translate: 0 16px;
  transition-duration: 260ms;
}

/* Entrée : posée puis retirée dans la foulée par le script pour que le
   navigateur anime bien depuis cet état de départ (arrivée du bas). */
.ml-am-etape.ml-am-etape--entre {
  opacity: 0;
  translate: 0 16px;
  transition: none;
}
.ml-am-etape:not(.ml-am-etape--entre):not(.ml-am-etape--sort) {
  opacity: 1;
  translate: 0 0;
  transition-duration: 320ms;
}

@media (prefers-reduced-motion: reduce) {
  .ml-am-etape,
  .ml-am-etape.ml-am-etape--sort,
  .ml-am-etape.ml-am-etape--entre {
    transition: none;
    opacity: 1;
    translate: 0;
  }
}

/* ── Groupe de choix : fieldset natif, sans bordure par défaut ──
   Un <fieldset> avec <legend> plutôt qu'un role="radiogroup" en boutons
   simples : la sémantique de groupe de choix unique (un seul coché à la
   fois, navigation aux flèches) vient nativement des <input type="radio">
   qu'il contient, sans aria-checked à tenir à jour ni JavaScript requis
   pour que le groupe reste utilisable. */
.ml-am-choix-groupe {
  border: 0;
  padding: 0;
  margin: 0 0 var(--ml-space-md);
  min-inline-size: 0;
}

.ml-am-choix-legende {
  font-family: var(--ml-font-heading);
  font-size: var(--ml-fs-sm);
  font-weight: 600;
  color: var(--ml-primary);
  padding: 0;
  margin: 0 0 var(--ml-space-xs);
}

/* ── Étape « pays » : un groupe par langue, un seul visible à la fois ──
   Le PHP rend un fieldset par langue ayant deux pays ou plus, tous masqués
   par défaut (attribut hidden). Le script affiche uniquement celui de la
   langue choisie. */
.ml-am-groupe-pays[hidden] {
  display: none;
}

/* Explique le comportement d'exclusivité entre « Peu importe » et les pays
   (une case à cocher qui se comporte comme un groupe exclusif n'est pas un
   comportement standard, il doit être annoncé). Texte discret mais lisible,
   jamais un gris trop clair sur le fond de la fenêtre. */
.ml-am-aide-pays {
  font-size: var(--ml-fs-sm);
  color: var(--ml-text-mid);
  margin: 0 0 var(--ml-space-sm);
}

.ml-am-btn-suite {
  margin-block-start: var(--ml-space-md);
  inline-size: 100%;
  justify-content: center;
}
@media (min-width: 480px) {
  .ml-am-btn-suite {
    inline-size: auto;
  }
}

/* ── Choix : boutons pleine largeur, lisibles au clavier comme au doigt ──
   Chaque bouton est un <label> qui enveloppe un <input type="radio"> masqué
   visuellement (jamais display: none, qui le sortirait de la navigation au
   clavier et des lecteurs d'écran). Le label porte l'apparence de bouton,
   l'input reste la source de vérité sémantique. */
.ml-am-choix-langues,
.ml-am-choix-ages,
.ml-am-choix-pays {
  display: grid;
  gap: var(--ml-space-sm);
  grid-template-columns: 1fr;
}

@media (min-width: 480px) {
  .ml-am-choix-langues,
  .ml-am-choix-pays {
    grid-template-columns: repeat(2, 1fr);
  }
  .ml-am-choix-ages {
    grid-template-columns: repeat(2, 1fr);
    max-inline-size: 360px;
  }
}

/* La grille des pays peut compter jusqu'à cinq choix (anglais) plus « Peu
   importe » : à partir d'une largeur confortable, une troisième colonne
   évite une longue colonne unique qui allongerait la fenêtre. */
@media (min-width: 700px) {
  .ml-am-choix-pays {
    grid-template-columns: repeat(3, 1fr);
  }
}

.ml-am-choix-input {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.ml-am-choix-btn {
  position: relative;
  display: block;
  inline-size: 100%;
  padding: 0.9em 1em;
  background: var(--ml-bg-alt);
  border: 2px solid var(--ml-border);
  border-radius: var(--ml-radius);
  color: var(--ml-text);
  font: inherit;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
}

.ml-am-choix-btn:hover {
  border-color: var(--ml-accent);
}

.ml-am-choix-input:focus-visible ~ span {
  outline: 2px solid var(--ml-accent);
  outline-offset: 2px;
}

/* UN CHOIX RETENU N'EST PAS UN BOUTON D'ACTION (constat de Marino). Il était
   violet plein, blanc dessus, exactement comme « Voir mes destinations » posé
   quelques centimètres plus bas : deux objets identiques qui ne font pas du tout
   la même chose, l'un enregistre un choix, l'autre fait avancer le parcours.

   Le violet plein reste donc réservé à l'action. Un choix retenu se marque
   autrement : fond violet dilué, texte violet foncé, et un liseré franc qui
   dit lequel est coché. Il se distingue du choix voisin, et il ne se confond
   plus avec le bouton du bas. */
.ml-am-choix-btn.is-selectionne {
  background: var(--ml-accent-soft);
  border-color: var(--ml-accent);
  color: var(--ml-accent-dark);
}

/* Une coche à gauche du libellé, pour ne pas reposer sur la seule couleur :
   c'est ce qui rend le choix lisible en niveaux de gris, sous un fort soleil,
   et pour qui distingue mal le violet du gris. Le libellé reste centré, la
   coche est posée en absolu et ne le décale pas. */
.ml-am-choix-btn.is-selectionne::before {
  content: '';
  position: absolute;
  inset-inline-start: 1em;
  inset-block-start: 50%;
  inline-size: 0.55em;
  block-size: 0.95em;
  translate: 0 -60%;
  rotate: 45deg;
  border-inline-end: 2px solid var(--ml-accent);
  border-block-end: 2px solid var(--ml-accent);
}

/* Les boutons d'âge sont volontairement plus petits que ceux des langues :
   c'est la seule distinction qui compte à cette étape, elle n'a pas besoin
   de la même emprise visuelle, et ça laisse la fenêtre respirer. */
.ml-am-choix-btn--petit {
  padding: 0.6em 0.9em;
  font-size: var(--ml-fs-sm);
  border-width: 1px;
}

/* ── Étape « formule » : en école, ou en immersion chez le professeur ──
   Deux choix seulement, mais qui portent chacun une explication : ce sont
   deux séjours sans rapport, et un mineur de quinze ans ne devine pas ce
   que veut dire « immersion chez le professeur ». Le texte fait donc partie
   du bouton, aligné à gauche, là où les autres choix du parcours tiennent
   en un mot centré. */
.ml-am-choix-formules {
  display: grid;
  gap: var(--ml-space-sm);
  grid-template-columns: 1fr;
}

.ml-am-choix-formule {
  text-align: start;
}

.ml-am-choix-formule > span {
  display: flex;
  flex-direction: column;
  gap: 0.25em;
}

.ml-am-choix-formule em {
  font-style: normal;
  font-weight: 400;
  font-size: var(--ml-fs-sm);
  color: var(--ml-text-mid);
  line-height: 1.45;
}

/* Sur fond violet, le texte d'explication doit rester lisible : la couleur
   grise du repos n'y passerait plus le contraste. */
.ml-am-choix-formule.is-selectionne em {
  color: rgba(255, 255, 255, 0.92);
}

/* ── Résultats, étape finale du parcours ── */
.ml-am-resultats {
  margin-block: var(--ml-space-md);
}

.ml-am-resultats h3 {
  font-size: var(--ml-fs-lg);
  margin-block-start: var(--ml-space-md);
}

.ml-am-resultats-pays-titre {
  font-size: var(--ml-fs-base);
  font-family: var(--ml-font-heading);
  color: var(--ml-text-mid);
  margin-block: var(--ml-space-sm) 0.5em;
}

/* Repli quand aucune fiche ml_ville ne correspond encore (silo figé non
   importé) : liste simple, sans photo. */
.ml-am-liste-villes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
  margin-block-end: var(--ml-space-sm);
}
.ml-am-liste-villes li {
  background: var(--ml-bg-alt);
  border-radius: var(--ml-radius);
  padding: 0.35em 0.75em;
}

/* Cartes de résultat de l'assistant : la pastille de langue (coin haut gauche)
   est masquée. La langue est déjà choisie dans le parcours, la répéter sur
   chaque carte est redondant, et ça libère la bande du haut pour la pilule
   « moins de 18 ans » de la tranche mineur (le badge compact vit dans
   assets/css/main.css, réaffiché sous .ml-am-resultats-villes.is-mineur). */
.ml-am-resultats .ml-carte-destination--compacte .ml-carte-destination-langue {
  display: none;
}

.ml-am-resultats-suite {
  margin-block-start: var(--ml-space-md);
}

.ml-am-resultats-suite a {
  color: var(--ml-accent);
  font-weight: 600;
}

/* ── Réduction de mouvement : la fenêtre s'affiche, un point c'est tout ── */

/* ══ MOBILE : LE GLOBE AU DESSUS, LA CARTE EN DESSOUS ════════════════════════
   Constat de Marino : le haut de page de l'accueil déborde en largeur sur
   téléphone.

   La cause tenait à la rangée. Le globe et la carte de Linguine sont côte à
   côte, et chacun a un plancher : le globe ne rétrécit pas (flex: none) et la
   carte ne descend pas sous 196px. Sur un écran de 360 à 400px, une fois
   retirées les marges du conteneur, la somme des deux planchers dépasse la
   place disponible et la rangée sort de la boîte. Un palier existait déjà sous
   380px pour retarder l'échéance, il ne faisait que déplacer le seuil.

   La rangée devient donc une COLONNE sous 600px, exactement ce que Marino
   proposait : la carte passe sous la mascotte. Plus rien ne se dispute la
   largeur, la carte prend toute celle du conteneur, et ses quatre pastilles de
   langue redeviennent des cibles confortables au pouce au lieu de deux colonnes
   à l'étroit.

   Le globe rétrécit en échange : empilés, les deux éléments se disputent
   maintenant la HAUTEUR, et le haut de page fait 88svh sans compter le titre et
   les deux boutons qui le précèdent. */
@media (max-width: 599px) {
  .ml-am-mascotte-hero .ml-am-mascotte-zone {
    flex-direction: column;
    align-items: center;
    gap: var(--ml-space-sm);
  }

  /* Le budget de hauteur passe de 42svh à 26svh : c'est la moitié de la
     colonne qu'il partage désormais avec la carte, plus la place du titre. */
  .ml-am-mascotte-hero .ml-am-mascotte-image {
    inline-size: max(96px, min(clamp(96px, 30vw, 180px), calc(26svh - 26px)));
  }

  /* La carte n'a plus de voisin : plus de plancher, plus de plafond, elle suit
     la largeur du conteneur. */
  .ml-am-mascotte-hero .ml-am-bulle-question {
    flex: 0 1 auto;
    min-inline-size: 0;
    inline-size: 100%;
    max-inline-size: 100%;
    margin-block-start: 0;
  }

  /* LA POINTE REMONTE ET SE CENTRE. Elle rattachait la carte au globe posé à
     sa GAUCHE : laissée telle quelle, elle désignerait le vide. Le globe est
     maintenant au dessus, la pointe va donc sur le bord haut, au milieu. */
  .ml-am-mascotte-hero .ml-am-bulle-question::before {
    inset-inline-start: 50%;
    inset-block-end: auto;
    inset-block-start: -7px;
    translate: -50% 0;
  }

  /* LA CARTE SE FAIT PLUS COURTE (demande de Marino) : la question tapée et
     les quatre pastilles cèdent la place à l'invitation courte ci dessus.
     Rien n'est retiré du document (voir ml_am_zone_mascotte() dans
     inc/assistant-modale.php pour les deux textes et le lien de repli sans
     JavaScript), seule la CSS choisit qui s'affiche. La mise en page en deux
     colonnes des pastilles n'a donc plus lieu d'être à cette largeur : ce
     n'est plus elles qui s'affichent, c'est l'invitation. */
  .ml-am-mascotte-hero .ml-am-bulle-question-titre,
  .ml-am-mascotte-hero .ml-am-bulle-langues {
    display: none;
  }
  .ml-am-mascotte-hero .ml-am-carte-invite-mobile {
    display: flex;
  }
}

/* Le palier de 380px n'a plus de rangée à sauver : il ne garde que le passage
   des pastilles sur une seule colonne, là où deux ne tiennent vraiment plus. */
@media (max-width: 359px) {
  .ml-am-mascotte-hero .ml-am-bulle-langues {
    grid-template-columns: 1fr;
  }
}

/* ══ LA MASCOTTE FIXE, PLUS GRANDE SUR TÉLÉPHONE ════════════════════════════
   Demande de Marino. Elle était calée sur « clamp(64px, 14vw, 108px) » : sur un
   écran de 390px, 14vw ne fait que 55px, c'est donc le plancher de 64 qui
   gagnait, et le globe se réduisait à une pastille qu'on ne reconnaît plus.
   Elle passe à 88px au plancher, soit un bon tiers de plus.

   Le SÉLECTEUR EST VOLONTAIREMENT SIMPLE, une seule classe : la mascotte du
   haut de page de l'accueil est visée ailleurs par deux classes, sa règle est
   donc plus spécifique et l'emporte quoi qu'il arrive. Elle garde sa taille,
   qui a déjà été réglée séparément. */
@media (max-width: 599px) {
	.ml-am-mascotte-image {
		inline-size: clamp(88px, 24vw, 124px);
	}
}
