/* =============================================================
   LES AVIS — ce que l'app répond après un geste, en surimpression

   Ils remplacent les MESSAGES ÉCLAIR (`.flash`), qui poussaient la
   page vers le bas depuis le haut de la coquille. Ce que le bandeau
   faisait mal, et qui a décidé le remplacement :

     · il DÉPLAÇAIT l'écran. Une confirmation qui pousse la première
       ligne de trois centimètres fait rater le clic suivant, et
       c'est pire sur un téléphone, où le bandeau prend le tiers de
       ce qu'on voit ;
     · il vivait DANS `.shell-main`, donc les écrans qui n'ont pas
       cette coquille — la connexion, le plan de contrôle — en
       avaient chacun une copie. Trois copies, et elles avaient
       DÉJÀ dérivé : celle d'`auth.css` portait `white-space:
       pre-line` et un cran de police de plus, celle d'`app.css` ni
       l'un ni l'autre. Le même message ne se lisait donc pas pareil
       selon la porte par laquelle on entrait.

   ── POURQUOI UNE FEUILLE À ELLE, ET PAS UNE SECTION D'`app.css` ──
   C'est l'exception que le crochet `@stack('styles')` d'`app.css`
   décrit : le défaut reste `app.css`. Sauf que le défaut suppose UNE
   coquille, et il y en a trois — `layouts/app` charge `app.css`,
   `layouts/plain` et le plan de contrôle chargent `auth.css`. Un
   composant commun aux trois n'a donc aucune feuille commune où
   atterrir, et le mettre dans les deux, c'est refaire à l'identique
   la dérive qu'on vient de payer sur `.flash`.

   Une feuille, chargée par les trois gabarits, est ce qui rend la
   dérive IMPOSSIBLE plutôt que surveillée.

   ── CE QUI RESTE À `.flash` ──────────────────────────────────────
   Le récapitulatif d'erreurs de validation en tête de formulaire
   (`profile/show`, `invitations/index`). Il n'est pas un avis : il
   doit rester à l'écran PENDANT qu'on corrige les champs, donc il
   ne peut pas être une chose qui s'efface. Sa règle vit dans
   `app.css`, où elle est désormais seule.
   ============================================================= */

/* LA PILE. Fixe, EN BAS AU CENTRE, et les avis s'empilent vers le
   haut : `toasts.js` ajoute à la fin, donc le dernier arrivé est
   celui qui touche le bord — le plus près du pouce, et du regard.

   ── ELLE ÉTAIT EN HAUT À DROITE, ET C'EST ÉRIC QUI L'A TRANCHÉ ────
   Le 2026-08-12, en la voyant tourner : « I was expecting a beautiful
   modern bottom screen toast. This looks like an old php thing. »
   Le coin haut droit est la place du bandeau qu'on venait de retirer,
   pas celle d'un avis : c'est là que vingt ans de `<div class="alert">`
   se sont posés, et l'œil y lit « la page a quelque chose à dire »
   plutôt que « voilà le résultat de ton geste ».

   En bas au centre, l'avis se pose SOUS le regard, à l'endroit d'où
   le geste vient de partir — c'est la convention de tout ce qui a été
   dessiné depuis, du téléphone au bureau, et elle n'est pas une mode :
   la réponse arrive là où l'attention est déjà.

   ── LE `z-index` PASSE AU-DESSUS DU MENU DES DESTINATIONS (1050) ──
   C'est la seule chose de l'app qui le dépasse, et c'est délibéré :
   un avis annonce le résultat du geste qu'on vient de poser, donc il
   n'a de valeur qu'à cet instant-là. Le prix a CHANGÉ DE COIN avec
   elle, et il a diminué : en haut à droite, un avis d'erreur — qui ne
   s'efface pas tout seul — couvrait le menu ouvert du rail ; en bas
   au centre il ne couvre plus rien qui s'ouvre. Il porte quand même
   sa croix, parce qu'elle reste le seul moyen de faire partir un
   refus.

   ── `pointer-events` : LA PILE EST UN CADRE, PAS UNE VITRE ────────
   Elle occupe TOUTE LA LARGEUR maintenant (c'est ce qui centre les
   avis sans `transform`), donc la ligne compte plus qu'avant : sans
   elle, la bande du bas de chaque écran avalerait les clics. Les avis
   eux-mêmes les reprennent. */
.toasts {
  position: fixed;
  /* Le repli est le cas ordinaire. `--toast-bottom` n'est posé QUE
     par `app.css`, sous 700 px, où les destinations deviennent une
     barre fixe en bas de l'écran : la pile monte alors au-dessus
     d'elle, sinon un avis d'erreur couvre la navigation tant qu'on ne
     l'a pas fermé. C'est `app.css` qui sait ça — la barre n'existe
     pas sur les écrans d'avant-connexion, qui chargent cette
     feuille-ci aussi. */
  bottom: var(--toast-bottom, var(--space-5));
  left: 0;
  right: 0;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  pointer-events: none;
}

/* L'AVIS — une CARTE QUI FLOTTE, et plus un panneau teinté.

   ── CE QUI A CHANGÉ, ET POURQUOI CE N'EST PAS DU GOÛT ─────────────
   Il portait le vocabulaire du bandeau qu'il remplaçait : bordure de
   la teinte, fond adouci, tout le bloc coloré. C'est exactement le
   dessin qu'Éric a reconnu comme vieux — et il l'est, littéralement :
   c'est l'`.alert-success` de Bootstrap 3, et l'œil l'a appris là.
   Un bloc entièrement teinté dit « attention, encart » ; une carte
   posée sur le contenu dit « voilà, c'est fait ».

   Donc : le fond redevient une SURFACE (`--c-surface`), le texte
   redevient du texte courant, et la couleur se concentre dans la
   pastille du signe. Elle y est plus forte qu'elle ne l'était diluée
   sur tout le bloc — un aplat plein plutôt qu'un lavis.

   Le liseré reste, et il n'est pas décoratif : en sombre, une
   surface qui flotte sur un fond presque aussi foncé n'a plus de
   bord, et l'ombre seule ne suffit pas à l'en détacher.

   L'OMBRE est double et large : la règle 2 la réserve aux menus et
   aux modales, et un avis en est un cas — il flotte au-dessus d'un
   contenu qui reste visible dessous. Deux couches (une serrée pour
   le contact, une lâche pour la hauteur) sont ce qui distingue une
   carte posée d'une carte collée. */
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  width: min(420px, 100%);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  background: var(--c-surface);
  color: var(--c-text);
  box-shadow: 0 1px 2px var(--c-shadow), 0 12px 32px var(--c-shadow-strong);
}

/* Le statut n'est JAMAIS porté par la couleur seule (§0, règle 8) :
   chaque état porte aussi un SIGNE, que la palette daltonien et les
   écrans en niveaux de gris rendent tout aussi bien. Le signe vit
   maintenant dans une pastille pleine — c'est ce qui permet au reste
   de la carte de rester neutre sans que l'état se perde.

   La couleur du glyphe est `--c-surface`, donc le fond de la carte :
   blanc sur l'aplat en clair, sombre sur l'aplat en sombre. Les deux
   sont les contrastes que ces paires de jetons tiennent déjà partout
   ailleurs — d'où le jeton plutôt qu'un blanc écrit à la main, qui
   deviendrait du blanc sur teal pâle le jour du mode daltonien.

   (Et un blanc écrit à la main dans CE commentaire suffisait à faire
   rougir `test_aucune_couleur_en_dur_hors_des_jetons` : son scanner
   lit les commentaires aussi. C'est voulu — une couleur citée en
   exemple est le premier endroit où elle revient.) */
.toast::before {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  /* Le cercle s'aligne sur la première LIGNE du texte, pas sur le
     haut de la carte : un avis de trois lignes le laisserait sinon
     flotter tout seul en tête. */
  margin-top: 1px;
  border-radius: var(--radius-pill);
  color: var(--c-surface);
  font-size: var(--mil-fs-1);
  font-weight: 700;
  line-height: 1;
}

.toast.is-ok::before { content: "✓"; background: var(--c-success); }

/* « ! » et non « ⚠ » : dans une pastille pleine, le triangle redessine
   un fond dans le fond, et à 13 px il devient une tache. Le point
   d'exclamation est le signe que le cercle plein attend — et il reste
   aussi lisible en niveaux de gris que le crochet d'à côté. */
.toast.is-error::before { content: "!"; background: var(--c-danger); }

/* Le cran 3, celui du texte courant, et c'est l'arbitrage qu'`auth.css`
   avait déjà fait pour `.flash` : sur un écran d'avant-connexion le
   message EST la page — « votre lien a expiré », « ce courriel ne
   correspond à personne ». C'est la seule phrase qui explique pourquoi
   la personne est encore là ; la rétrécir d'un cran sous le corps de
   texte serait dessiner contre ce qu'elle sert.

   `pre-line` vient du même endroit : certains messages du plan de
   contrôle sont écrits en plusieurs lignes parce que la dernière est
   un geste à poser — « créer une base vide, puis relancer avec
   --db=… ». Collapsées, elles redeviennent un pavé qu'on ne lit pas.

   `overflow-wrap` est neuf, et c'est la largeur fixe de la pile qui
   l'impose : une commande ou une URL sans espace n'a aucun point de
   coupure, donc elle sortirait de la carte par la droite. */
.toast-body {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--mil-fs-3);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* LA CROIX N'APPARAÎT QUE SI LE JAVASCRIPT EST LÀ (règle 4 : aucune
   commande ne reste muette). `toasts.js` pose `is-live` sur la pile ;
   sans lui, l'avis reste simplement affiché jusqu'à la navigation
   suivante — c'est-à-dire exactement ce que faisait le bandeau, donc
   rien n'est perdu, et surtout aucun bouton ne fait semblant.

   44 px, comme toutes les cibles de l'app : la croix est le seul
   moyen de faire partir un avis d'erreur, elle ne se rate pas au
   pouce. Les marges négatives lui rendent la place que son carré
   prend en trop dans le remplissage de la carte. */
.toast-dismiss {
  display: none;
  flex: none;
  width: 44px;
  height: 44px;
  margin: calc(var(--space-2) * -1) calc(var(--space-2) * -1) calc(var(--space-2) * -1) 0;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: inherit;
  font-size: var(--mil-fs-4);
  line-height: 1;
  cursor: pointer;
}

.toasts.is-live .toast-dismiss { display: flex; }
.toast-dismiss:hover { background: var(--c-hover-bg); }

/* L'ENTRÉE ET LA SORTIE — `--t-slow`, le cran que `tokens.css`
   annonce depuis toujours par son commentaire : « toasts, panneaux ».
   Le mouvement MONTE maintenant, de douze pixels : il dit d'où l'avis
   vient, et il vient du bas de l'écran depuis qu'il y vit. Descendre
   de huit, comme avant, le ferait entrer à contresens de sa propre
   place — le détail qui fait qu'une animation a l'air « à l'envers »
   sans qu'on sache dire pourquoi.

   Le glissement s'accompagne d'un `scale` très court : c'est ce qui
   sépare une carte qui ARRIVE d'une carte qui défile. Quatre pour
   cent, pas plus — au-delà ça rebondit, et un accusé de réception ne
   rebondit pas. */
@keyframes toast-entree {
  from { opacity: 0; transform: translateY(12px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

.toast { animation: toast-entree var(--t-slow) ease-out; }

/* Posée par `toasts.js` juste avant de retirer l'élément — il attend
   la fin de la transition, avec un filet au cas où elle ne partirait
   pas (onglet en arrière-plan). */
.toast.is-leaving {
  opacity: 0;
  transform: translateY(12px) scale(.96);
  transition: opacity var(--t-slow) ease-in, transform var(--t-slow) ease-in;
}

/* Qui a demandé moins de mouvement en reçoit zéro : l'avis apparaît et
   disparaît, sans glisser. `toasts.js` lit la même préférence et
   retire alors l'élément sans attendre de transition — sans ça, il
   attendrait un événement qui n'arrive jamais. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast.is-leaving { transition: none; }
}

/* IL Y AVAIT ICI UN `@media (max-width: 499px)` qui rendait à la pile
   toute la largeur : une carte de 380 px sur un écran de 375 se
   faisait rogner à droite par sa propre marge.

   Il est parti avec la largeur fixe. La pile s'étend maintenant d'un
   bord à l'autre et c'est l'AVIS qui se borne, en `min(420px, 100%)` :
   sur un téléphone il prend la place qu'il y a, sur un bureau il
   s'arrête à 420. Une règle qui se répare toute seule vaut mieux
   qu'un point de bascule à tenir à jour — et celui-ci était déjà faux
   d'un cran au zoom 150 %, où la carte grandit et l'écran non. */
