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

   Le rôle d'un profil, posé inline dans un titre de panel : « Service
   admin ». Un seul usage dans l'app (frontend/index.html l.392, panel
   Service) :
     <h2><span data-i18n="profile.service_title"></span>
          <span class="tag">admin</span></h2>

   Le tag est un <span>, pas un contrôle. Contenu texte très court
   (admin, invité, test). NON INTERACTIF dans l'app (il vit dans un <h2>)
   : les états survol et focus clavier sont livrés pour les usages
   interactifs (tabindex posé par l'appelant) — le precedent Badge
   (primitives/badge.css), dont le tag est le proche parent (petit
   libellé en pastille).

   États (classes is-*, convention identique à badge.css ; l'app ne les
   pose pas encore — livrés comme le precedent Button / Badge) :
     <span class="tag is-hover">admin</span>            (survol, specimen)
     <span class="tag is-focus" tabindex="0">admin</span> (focus, specimen)
     <span class="tag" aria-disabled="true">test</span>   (désactivé)
     <span class="tag is-loading" aria-label="Rôle du profil en cours de
       vérification"><span class="tag__label">admin</span></span> (chargement)
     <span class="tag is-error">inconnu</span>            (erreur)
   Contrat : aria-label SEULEMENT en chargement (les autres états ont déjà
   un nom accessible : le libellé visible).

   Variant discret :
     <span class="tag muted">invité</span>
   Le rôle secondaire : texte --fg-muted, fond transparent — le signal
   vient de l'accent du variant par défaut, pas d'un bloc coloré.

   Règles (02-ANTI-SLOP.md + skill primitive) :
   - Aucune valeur en dur, ZÉRO exception : l'échelle red est importée et
     mappée par la clé de variation (themes/kindle-shelf.css) — l'état
     d'erreur ne demande aucune valeur littérale. Seuls littéraux du
     fichier : la durée du shimmer (1.4 s, structurelle, même usage toléré
     que badge.css) et les pourcentages de la bande — tokens.css ne définit
     aucune durée d'animation continue ; ajouter un token là-bas se
     demande, ne se décide pas.
   - Bordures = box-shadow à spread 1px (jamais de `border` dessinée) : la
     bordure 1px solid accent-2 de l'app devient --borders-base — l'accent
     est porté par le TEXTE --fg-accent, pas par un anneau coloré (budget
     de fonds/teintes de la page, voir en-tête ; même décision que
     .badge.fmt).
   - Rayon : --radius-pill (999 px) — la pastille est la signature du tag
     (le chip est l'autre composant à rayon plein).
   - Typo : --text-xs (12 px) + --lh-compact (les textes compacts
     partagent un seul interligne). Le 10,5 px de l'app n'est pas sur
     l'échelle fermée → absorbé par --text-xs. Les MAJUSCULES +
     interlettrage étendu (.08em) de l'app sont RETIRÉS : interdit absolu
     (02-ANTI-SLOP.md « Typographie » — « Pas de libellés en majuscules
     avec interlettrage étendu »). La hiérarchie vient de l'accent du
     texte, pas de la casse.
   - Graisse : --weight-medium (500) seule — pas de hiérarchie interne.
   - Aucune MARGIN sur le tag : l'espacement avec le mot précédent est
     l'affaire du parent (espace littéral du markup de l'app, l.392). Le
     margin-left: 6 px de l'app est hors grille et n'est pas absorbé.
   - Transitions : background-color + box-shadow, --duration-fast (2
     propriétés, 1 durée — le même jeu que Badge, 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).

   -------------------------------------------------------------------------
   DÉCISIONS CONTRAIGNANTES SPECIFIQUES A kindle-shelf (page specimen
   entière, audit-slop-json.js, un SEUL passage)
   -------------------------------------------------------------------------
   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 tag 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 (chaîne — jamais
                                                  --borders-focus SEUL)
     - 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 »).
   FONDS NON NEUTRES (plafond 3, SATURÉ : bronze-9 + bronze-10 du bouton
   primaire + red-9 du point .dot.no) : le tag n'ajoute RIEN — son fond
   est --bg-subtle (sand-2, neutre) ou transparent ; l'accent est une
   COULEUR DE TEXTE (--fg-accent), hors du compteur de fonds.
   TAILLES (plafond 6) : le tag consomme --text-xs (12 px), déjà présente
   sur la page — aucune taille nouvelle.
   ========================================================================= */

/* --- Repos ---------------------------------------------------------------- */
/* Remplace le .tag de l'app (l.238) : fond de surface discrète + bordure de
   base (l'app avait `border: 1px solid var(--accent-2)` — remplacée par la
   box-shadow de spread 1px, l'approche du design system). Padding 0 / 8 px
   (sur la grille ; l'app : 2/9 px hors grille). vertical-align: middle
   remplace le 2 px de l'app (le tag s'aligne sur l'axe du titre de panel). */
.tag {
  display: inline-flex;
  align-items: center;

  padding: 0 var(--space-3); /* 8 px — sur la grille */

  font-family: var(--font-ui, inherit);
  font-size: var(--text-xs);          /* 12 px — le 10,5 px de l'app est hors échelle */
  line-height: var(--lh-compact);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);

  color: var(--fg-accent);            /* l'accent du tag est porté par le texte */
  background: var(--bg-subtle);       /* sand-2 — neutre, budget non neutres saturé */

  border-radius: var(--radius-pill);
  box-shadow: var(--borders-base);    /* = l'ombre 1 de la page (déjà comptée) */

  white-space: nowrap;
  vertical-align: middle;

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

/* --- Variant discret (.muted) ------------------------------------------------ */
/* Le rôle secondaire (invité) : texte atténué, fond transparent — la
   pastille reste (bordure --borders-base), le signal s'efface. L'accent est
   réservé au rôle par défaut. */
.tag.muted {
  color: var(--fg-muted);
  background: transparent;
}

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

/* Survol : fond --bg-hover SEUL (pas d'ombre nouvelle — budget saturé, voir
   en-tête), pas d'agrandissement. Coupé sur le tag désactivé. */
.tag: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. */
.tag:focus-visible {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

/* --- Désactivé ----------------------------------------------------------------- */
/* Un rôle non applicable au profil (estompé, curseur de refus). Drivé par
   l'attribut ARIA posé par l'appelant (comme la carte). Pas de
   pointer-events : le tag n'est pas un contrôle, il n'y a rien à couper. */
.tag[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --- Chargement ------------------------------------------------------------------- */
/* Le rôle est en cours de vérification (le profil est re-synchronisé). Le
   libellé est masqué en VISIBILITÉ (visibility:hidden), pas en
   display:none : la boîte du texte reste, la largeur du tag 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) —
   même device que .badge.is-loading. */
.tag.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: tag-shimmer 1.4s linear infinite;
}
.tag.is-loading .tag__label {
  visibility: hidden;
}
@keyframes tag-shimmer {
  to { background-position: -200% 0; }
}

/* --- Erreur ----------------------------------------------------------------------- */
/* Le rôle n'a pas pu être vérifié (« inconnu ») : anneau --borders-error
   (RÉUTILISE l'ombre 3, déjà comptée) + texte --danger-11 (l'étape lisible
   sur le thème sombre FIXE, red est mappé par la clé de variation). Le tag
   ne s'excuse pas : le libellé dit ce qui s'est passé, le correctif est
   dans le message voisin. */
.tag.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. */
.tag.is-hover {
  background: var(--bg-hover);
}
.tag.is-focus {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

@media (prefers-reduced-motion: reduce) {
  .tag {
    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. */
  .tag.is-loading {
    animation: none;
    background-image: none;
  }
}
