/* =========================================================================
   Toast — primitive du design system (kindle-shelf)

   La notification transitoire de l'app : un message qui monte au bas centre,
   s'y tient ~4 s, puis disparaît. Le template JS toast()
   (frontend/index.html l.454–460) assemble EXACTEMENT :
     <div class="toast [kind]">
       <span class="t-ic"><svg class="ic"><use href="#i-check | #i-warn"/></svg></span>
       <span>TEXTE</span>
     </div>
   injecté dans #toasts ; kind = "ok" (défaut, coche) ou "err" (triangle).
   ~6 déclencheurs (envoi réussi / échoué, profil sauvegardé, e-mail invalide,
   quota épuisé, renvoi historique, service aacid).

   CONTRAT : les classes .toast / .toast .t-ic / .toast.err / .toast.out et
   le conteneur #toasts doivent survivre. La primitive style ces sélecteurs
   tels quels ; le template JS est inchangé.

   Règles (02-ANTI-SLOP.md + skill primitive) :
   - AUCUNE valeur en dur — exception UNIQUE tolérée et documentée ici : la
     variable de statut --success-9 (#30a46c, valeur vendor green) pour la
     coche de succès. Elle n'existe ni dans tokens.css ni dans la clé de
     variation (seule l'échelle red est importée et mappée par
     themes/kindle-shelf.css). Elle est consommée comme COULEUR D'ICÔNE
     (currentColor), jamais comme fond : elle n'entre donc PAS dans le
     compteur « fonds non neutres » (l'audit ne lit que backgroundColor).
     --danger-* n'est pas ajouté : la clé mappe déjà red (--danger-9/10/11
     existent, comme chip.css / panel.css).
   - Bordures = box-shadow à spread 1px (jamais de `border` dessinée) : la
     bordure 1px de l'app (l.253) devient --borders-base. Le `border: 0`
     initial est un reset du bord UA, pas une bordure. Le `--shadow` de
     l'app (0 10px 30px, l.254) est du slop : remplacé par --borders-base.
   - ÉTAT DE LA PAGE — le plafond « 3 ombres distinctes » porte sur la page
     specimen ENTIÈRE et est SATURÉ par la primitive Button :
        1. --borders-base                    (repos)
        2. --borders-base + --borders-focus  (focus clavier)
        3. --borders-error                   (erreur)
     Le toast NE PEUT PAS introduire d'ombre nouvelle. Il réutilise :
        - repos / succès / chargement / entrée / sortie : --borders-base
          (l'ombre 1 de la page, déjà comptée)
        - erreur (.err) : --borders-error (l'ombre 3 de la page, déjà comptée)
     Pas d'élévation card-rest / card-hover (4e/5e ombre), pas d'ombre
     littérale.
   - Rayons : --radius-panel (8px) — déjà dans le set de la page. Le spinner
     de chargement est en MASQUE (radial + conic) → borderRadius: 0 (zéro
     rayon ajouté).
   - Survol / focus clavier / désactivé : ABSENTS PAR DÉCISION. Le toast est
     une NOTIFICATION NON-INTERACTIVE : pas de tabindex, pas d'action
     interne, pas de bouton de fermeture — il s'efface tout seul au bout de
     4 s. Comme le cadre de l'Empty, le Panel et le Skeleton : pas de survol
     de cadre, pas d'anneau (réservé aux contrôles interactifs). Un auditeur
     qui cherche .toast:hover / .toast:focus-visible / :disabled doit
     comprendre que c'est une décision, pas une omission.
   - Entrée / sortie : animations @keyframes, PAS de `transition`. Le plafond
     « 2 propriétés animées » (transition-property) porte sur la page entière
     et est SATURÉ par le Button (background-color + box-shadow). Une
     `transition: opacity, transform` sur le toast ajouterait 2 propriétés →
     ok:false. L'entrée (toast-in) et la sortie (toast-out) sont donc des
     @keyframes, hors transitions (comme le spin du Skeleton). Durée
     --duration-normal (300 ms) : elle tombe à 0 sous prefers-reduced-motion
     (tokens.css) → pas de mouvement.
   - Chargement : le slot .t-ic (place figée 16px) tient un spinner ; le
     texte reste visible : aucun saut de mise en page. Le conteneur porte
     SEUL l'aria-label — CONTRAT : aria-label UNIQUEMENT en chargement (les
     autres états ont déjà un nom accessible : le texte visible).
   - Erreur : --borders-error (l'ombre 3 de la page, réutilisée) + icône
     triangle en --danger-11 (l'étape lisible en sombre FIXE) + message qui
     dit ce qui s'est passé ET quoi faire (ANTI-SLOP : jamais d'erreur vague).
   - Icône .t-ic : slot carré 16px (--space-5, sur la grille). L'app pose
     18px sur svg.ic (l.46) — hors de l'échelle fermée ; la primitive part
     sur --space-5 (16px), la valeur la plus proche sur la grille. Trait 1.5
     (l'app l.46). Couleur par état : succès = --success-9 (coche), erreur =
     --danger-11 (triangle), chargement = --fg-muted (spinner neutre — un
     spinner d'état n'est pas un accent).
   - Graisses : --weight-regular (400) seule — le toast n'a pas de hiérarchie
     interne ; le signal vient de l'icône et de la position, pas de la graisse.
   ========================================================================= */

/* EXCEPTION DOCUMENTÉE — seule valeur en dur de la primitive.
   --success-9 (vendor green-9) n'existe ni dans tokens.css ni dans la clé de
   variation (seule l'échelle red est importée et mappée par
   themes/kindle-shelf.css, d'où --danger-* déjà disponible). Déclarée ici,
   consommée UNIQUEMENT en couleur d'icône (currentColor sur la coche de
   succès), jamais en fond → hors du compteur « fonds non neutres ». */
:root {
  --success-9: #30a46c;
}

/* --- Conteneur #toasts ---------------------------------------------------
   Remplace #toasts de l'app (l.250). Le conteneur centre la pile en bas de
   page et la laisse s'étendre aux messages longs ; z-index au-dessus de tout.
   `min(92vw, 42ch)` : relatif, aucun px en dur (l'app : 420px, hors échelle).
   Le conteneur ne capte pas les clics ; seuls les toasts sont interactifs
   (en réalité : rien, une notification n'est pas interactive — pointer-events
   posé pour l'app, sans effet sur la lecture au clavier). Le specimen ne rend
   pas #toasts (il montre les toasts à plat dans la section) : la règle est
   livrée pour l'app. */

#toasts {
  position: fixed;
  bottom: var(--space-6);      /* 20 px (l'app : 20px) */
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);         /* 8 px entre toasts (l'app : 8px) */
  width: min(92vw, 42ch);
  pointer-events: none;
}
#toasts .toast {
  pointer-events: auto;
}

/* --- Toast (repos) -------------------------------------------------------
   Remplace .toast de l'app (l.251–256). Fond de composant NEUTRE (sand-3) —
   jamais de fond teinté (le plafond « 3 fonds non neutres » est saturé par
   .btn.primary). La bordure 1px de l'app → --borders-base (l'ombre 1 de la
   page, déjà comptée). L'entrée est un @keyframes (voir en-tête). */

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);         /* icône ↔ texte : 8 px (l'app : 10px, off-grid) */

  padding: var(--space-3) var(--space-5);  /* 8/16 px, grille (l'app : 11/16) */

  font-family: var(--font-ui, inherit);
  font-size: var(--text-sm);   /* 13 px (l'app : 13.5px, hors échelle) */
  line-height: var(--lh-compact);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-wide);

  color: var(--fg-default);
  background: var(--bg-component);   /* neutre sand-3 (l'app : --panel-2) */

  border: 0;
  border-radius: var(--radius-panel);  /* 8 px (l'app : 10px, hors échelle) */
  box-shadow: var(--borders-base);    /* = l'ombre 1 de la page (déjà comptée) */

  min-width: 0;
  animation: toast-in var(--duration-normal) var(--ease);
}

/* --- Icône (.t-ic) --------------------------------------------------------
   Remplace .toast .t-ic de l'app (l.257). Slot carré 16px FIGÉ : la place est
   constante, donc le spinner de chargement se substitue à l'icône sans
   déplacer le texte. Couleur par défaut = succès (coche, --success-9). */

.toast .t-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--space-5);       /* 16 px (l'app : svg 18px, hors échelle) */
  height: var(--space-5);
  color: var(--success-9);     /* EXCEPTION documentée : vendor green-9 */
}
.toast .t-ic svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;           /* trait fin, comme l'app (l.46) */
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- Erreur (.err) ---------------------------------------------------------
   Remplace .toast.err de l'app (l.258–259). La bordure colorée de l'app
   (rgba(192,122,95,.5)) devient l'anneau --borders-error (l'ombre 3 de la
   page, réutilisée) — le device d'erreur du design system. L'icône passe au
   triangle en --danger-11 (l'étape lisible en sombre FIXE). Le message dit
   ce qui s'est passé ET quoi faire (le contenu porte l'information, pas le
   style). */

.toast.err {
  box-shadow: var(--borders-error);   /* = l'ombre 3 de la page (déjà comptée) */
}
.toast.err .t-ic {
  color: var(--danger-11, var(--fg-default));
}

/* --- Chargement -------------------------------------------------------------
   Le slot .t-ic (place figée) reçoit le spinner ; le texte reste visible :
   aucun saut de mise en page. Le conteneur porte SEUL l'aria-label (CONTRAT :
   aria-label UNIQUEMENT en chargement). Le spinner est NEUTRE (--fg-muted) —
   un spinner d'état n'est pas un accent. */

/* Le slot .t-ic devient l'ancre du spinner (position:relative) : le spinner
   s'y pose en inset:0 (centré comme l'Empty), le slot est figé (16px) → zéro
   saut. Le <svg> du template est masqué (display:none) : il ne dispute plus le
   flex et le spinner prend tout le slot. La couleur passe au neutre (--fg-muted)
   — le spinner (fond currentColor) hérite de ce neutre, un spinner d'état n'est
   pas un accent. */
.toast--loading .t-ic {
  position: relative;
  color: var(--fg-muted);
}
.toast--loading .t-ic svg {
  display: none;
}

/* Spinner de chargement : anneau en MASQUE (radial + conic), fond
   currentColor, 3 couches en intersection — même structure que
   .empty__spinner / .chip__spinner. 16px (le slot .t-ic), trait 4px.
   borderRadius: 0 (le masque fait le cercle) → zéro rayon ajouté. Le masque
   ne lit que le canal alpha : `black` y est structurel, pas une couleur. */

.toast__spinner {
  display: none;
  position: absolute;
  inset: 0;          /* remplit le slot .t-ic (figé 16px) — centrage parfait */
  width: 100%;
  height: 100%;
  background: currentColor;
  -webkit-mask-image:
    radial-gradient(farthest-side, transparent calc(100% - var(--space-2)), black 0),
    radial-gradient(closest-side, black 99%, transparent 100%),
    conic-gradient(black 270deg, transparent 0deg);
  -webkit-mask-composite: source-in, source-in;
  mask-image:
    radial-gradient(farthest-side, transparent calc(100% - var(--space-2)), black 0),
    radial-gradient(closest-side, black 99%, transparent 100%),
    conic-gradient(black 270deg, transparent 0deg);
  mask-composite: intersect, intersect;
}
.toast--loading .toast__spinner {
  display: block;
  animation: toast-spin 0.75s linear infinite;
}
@keyframes toast-spin {
  to { transform: rotate(360deg); }
}

/* --- Entrée / Sortie (keyframes, hors transitions) --------------------------
   Entrée : le toast monte et s'installe (toast-in, joue à l'insertion).
   Sortie : il s'estompe et redescend (toast-out, `forwards` → reste masqué ;
   le JS retire le nœud au bout). Durée --duration-normal : tombe à 0 sous
   prefers-reduced-motion (tokens.css) → pas de mouvement. */

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
}
.toast.out {
  animation: toast-out var(--duration-normal) var(--ease) forwards;
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(8px); }
}

/* --- Simulation d'états (page specimen uniquement) ---------------------------
   Le specimen est statique : ces classes FIGENT un frame de l'animation
   (même approche que .is-hover / .is-focus des autres primitives). Elles
   désactivent l'animation d'entrée pour montrer le frame congelé : l'entrée
   (montée à mi-chemin) et la sortie (estompage à mi-chemin). */

.toast.is-enter {
  animation: none;
  opacity: 0.5;
  transform: translateY(8px);
}
.toast.is-out {
  animation: none;
  opacity: 0.4;
  transform: translateY(8px);
}

@media (prefers-reduced-motion: reduce) {
  /* --duration-* tombe à 0 (tokens.css) : entrée/sortie instantanées.
     Le spin ralentit mais reste : l'information « chargement » doit passer. */
  .toast--loading .toast__spinner {
    animation-duration: 2s;
  }
}
