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

   Le badge est une métadonnée d'un livre, posée dans la carte (le
   conteneur .badges de card.css) : format (EPUB, AZW3, MOBI), langue
   (fr, ja, en), taille (2,4 Mo), année (1844). Le plus petit composant de
   l'app, très haute fréquence — 3 par carte, jusqu'à 1440 à l'écran.

   Markup de la primitive — c'est le contrat du markup que l'app produit
   (frontend/index.html, cardHtml l.631-635) ; la primitive le style tel
   quel, sans classe de variante inventée :

     <div class="badges">
       <span class="badge fmt">EPUB</span>
       <span class="badge">fr</span>
       <span class="badge">4,1 Mo</span>
     </div>

   États (classes is-*, convention identique à button.css / card.css ;
   l'app ne les pose pas encore — livrés comme le precedent Button) :
     <span class="badge is-hover">fr</span>            (survol, specimen)
     <span class="badge is-focus" tabindex="0">fr</span> (focus, specimen)
     <span class="badge" aria-disabled="true">en</span>  (désactivé/muet)
     <span class="badge is-loading" aria-label="Format en cours de
       détection"><span class="badge__label">EPUB</span></span> (chargement)
     <span class="badge is-error">DOCX non reconnu</span> (erreur)
   Contrat : aria-label SEULEMENT en chargement (les autres états ont déjà
   un nom accessible : le libellé visible ou le titre de la carte).

   Règles (02-ANTI-SLOP.md + skill primitive) :
   - Aucune valeur en dur, ZÉRO exception (contrairement à button.css, qui
     documente --success-9) : l'échelle red est importée et mappée par la
     clé de variation (themes/kindle-shelf.css) — --danger-9/10/11
     existent, l'état d'erreur ne demande aucune valeur littérale. Les
     pourcentages du shimmer (largeur/position de la bande) sont
     structurels (géométrie), pas des échelles — même usage toléré que
     card.css / l'ancien badge.css.
   - Bordures = box-shadow à spread 1px (jamais de `border` dessinée) : la
     boîte ne bouge pas et l'anneau de focus se combine à la bordure. Le
     `border: 0` initial n'existe pas ici — un <span> n'a pas de bordure UA
     à réinitialiser.
   - Rayon : --radius-badge (2 px) — le plus petit de la hiérarchie
     (badge ≠ champ ≠ carte ≠ dialogue).
   - Graisse : --weight-medium (500) seule — le badge n'a pas de
     hiérarchie interne ; elle vient de la taille et de l'accent du texte,
     pas du gras.
   - Transitions : background-color + box-shadow, --duration-fast (2
     propriétés, 1 durée — le même jeu que Button/Card, rien de neuf sur la
     page). Le shimmer est une animation de background-position, hors des
     plafonds de transition.
   - prefers-reduced-motion : transitions coupées, shimmer arrêté (la
     largeur figée reste, l'état passe par l'aria-label).

   -------------------------------------------------------------------------
   DECISIONS CONTRAIGNES SPECIFIQUES A kindle-shelf (page specimen entière)
   -------------------------------------------------------------------------
   L'audit (audit-slop-json.js) mesure la PAGE ENTIERE (un seul passage) et
   le plafond « 3 ombres distinctes » est SATURÉ par la primitive Button :
       1. --borders-base                    (repos)
       2. --borders-base + --borders-focus  (focus clavier)
       3. --borders-error                   (erreur)
   Le badge NE PEUT PAS introduire d'ombre nouvelle (pas d'élévation, pas
   d'anneau ad hoc). Il réutilise EXACTEMENT ces trois :
     - repos / survol / désactivé / chargement : --borders-base (ombre 1)
     - focus clavier                            : ombre 2
     - erreur                                   : --borders-error (ombre 3)
   Le SURVOL ne peut pas changer l'ombre (budget saturé) : il passe par le
   FOND SEUL (--bg-hover) — la règle de survol de la skill (« changement de
   fond via --bg-hover, pas d'agrandissement »).
   Le badge est NON INTERACTIF dans l'app (il vit dans une carte cliquable)
   ; les états survol/focus sont livrés pour les usages interactifs
   (pastille de statut cliquable, tabindex posé par l'appelant) et montrés
   dans le specimen.
   Coexistence avec card.css : celle-ci style déjà .badges .badge avec les
   MEMES tokens (autonomie de la carte dans le specimen, sans dépendre de
   ce <link>). Valeurs identiques → aucun conflit quel que soit l'ordre de
   chargement des deux feuilles.
   ========================================================================= */

/* --- Repos ---------------------------------------------------------------- */
/* Remplace le .badge de l'app (l.138) : fond de composant + bordure de base
   (l'app avait `border: 1px solid var(--line)` — remplacée par la box-shadow
   de spread 1px, l'approche du design system). Interligne --lh-compact :
   les textes compacts (tableau, liste, badge) partagent le même interligne,
   sinon les lignes ne s'alignent pas. */
.badge {
  display: inline-flex;
  align-items: center;

  /* 0 de padding vertical (l'interligne fait la hauteur), 8 px horizontal
     — sur la grille. */
  padding: 0 var(--space-3);

  font-family: var(--font-ui, inherit);
  font-size: var(--text-xs);          /* 12 px, texte compact */
  line-height: var(--lh-compact);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);

  color: var(--fg-default);
  background: var(--bg-component);

  border-radius: var(--radius-badge); /* 2 px — le plus petit rayon */
  box-shadow: var(--borders-base);    /* = l'ombre 1 de la page (déjà comptée) */

  white-space: nowrap;

  transition:
    background-color var(--duration-fast) var(--ease),
    box-shadow var(--duration-fast) var(--ease);
}

/* --- Variante format (.fmt) ------------------------------------------------ */
/* Le format (EPUB / AZW3 / MOBI) est l'unique badge qui porte l'accent :
   texte --fg-accent (bronze) sur fond NEUTRE (--bg-subtle) — pas de fond
   teinté (budget de fonds non neutres de la page), la hiérarchie vient du
   texte, pas d'un bloc coloré. La bordure reste --borders-base : l'accent
   est porté par la couleur du texte, pas par un anneau. */
.badge.fmt {
  color: var(--fg-accent);
  background: var(--bg-subtle);
}

/* --- États interactifs ------------------------------------------------------ */

/* Survol : fond --bg-hover SEUL (pas d'ombre nouvelle — budget saturé, voir
   en-tête), pas d'agrandissement. Coupé sur le badge désactivé. */
.badge:hover:not([aria-disabled="true"]) {
  background: var(--bg-hover);
}
.badge.fmt:hover:not([aria-disabled="true"]) {
  background: var(--bg-hover);
}
/* Dans .badges (la maison du badge, contrat), la règle statique
   .badges .badge.fmt de card.css porte la spécificité (0,3,0) — elle
   l'emporterait sur .badge:hover (0,2,0) pour le variant fmt si les deux
   feuilles sont chargées (l'app, le specimen). Cette règle (0,4,0) gagne
   quelle que soit l'ordre : l'état survol s'applique partout, y compris au
   badge fmt dans sa carte. */
.badges .badge:hover:not([aria-disabled="true"]) {
  background: var(--bg-hover);
}

/* Focus clavier : :focus-visible uniquement — jamais :focus seul, jamais
   d'anneau au clic souris. Réutilise l'ombre 2 (borders-base + borders-focus,
   liseré --bg-app + anneau accent). tabindex posé par l'appelant. */
.badge:focus-visible {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

/* --- Désactivé / muet -------------------------------------------------------- */
/* Une méta indisponible ou non applicable (langue non détectée, format
   inconnu) : estompée, curseur de refus. Drivée par l'attribut ARIA posé
   par l'appelant (comme la carte). Pas de pointer-events : le badge n'est
   pas un contrôle, il n'y a rien à couper. */
.badge[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --- Chargement --------------------------------------------------------------- */
/* La valeur est en cours de détection (le format se lit dans le fichier).
   Le libellé est masqué en VISIBILITÉ (visibility:hidden), pas en
   display:none : la boîte du texte reste, la largeur du badge est figée —
   aucun saut de mise en page (règle chargement de la skill). aria-label
   posé par l'appelant, UNIQUEMENT dans cet état (contrat).
   Shimmer : bande discrète dérivée des tokens (color-mix sur --fg-muted,
   PAS un hex), animation de background-position (hors transitions). */
.badge.is-loading {
  background-color: var(--bg-subtle);
  background-image: linear-gradient(
    90deg,
    transparent 30%,
    color-mix(in srgb, var(--fg-muted) 10%, transparent) 50%,
    transparent 70%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  background-position: 200% 0;
  animation: badge-shimmer 1.4s linear infinite;
}
.badge.is-loading .badge__label {
  visibility: hidden;
}
@keyframes badge-shimmer {
  to { background-position: -200% 0; }
}

/* --- Erreur -------------------------------------------------------------------- */
/* La valeur détectée est invalide (« DOCX non reconnu ») : anneau
   --borders-error (RÉUTILISE l'ombre 3, déjà comptée) + texte --danger-11
   (texte, pas fond — contraste correct sur le thème sombre fixe, red est
   mappé par la clé de variation). Le badge ne s'excuse pas : le libellé
   dit ce qui s'est passé, le correctif est dans le message voisin (toast /
   cerror de la carte). */
.badge.is-error {
  color: var(--danger-11, var(--fg-default));
  box-shadow: var(--borders-error);
}

/* --- Simulation d'états (page specimen uniquement) ------------------------------- */
/* Les vrais états viennent de la souris et du clavier ; ces classes posent
   exactement les mêmes valeurs token par token que les pseudo-classes. */
.badge.is-hover {
  background: var(--bg-hover);
}
/* Idem .badges : la classe de simulation doit gagner la règle statique
   .badges .badge.fmt (0,3,0) de card.css. */
.badges .badge.is-hover {
  background: var(--bg-hover);
}
.badge.is-focus {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

@media (prefers-reduced-motion: reduce) {
  .badge {
    transition: none;
  }
  /* Le shimmer s'arrête mais la largeur figée reste : l'état « chargement »
     passe toujours par l'aria-label et la pastille vide. */
  .badge.is-loading {
    animation: none;
    background-image: none;
  }
}
