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

   Le filtre de la recherche : format (Tous / EPUB / MOBI / PDF). Un filtre
   est binaire — actif ou inactif — d'où deux états de base :
     .chip        filtre inactif (défaut)
     .chip.on     filtre appliqué (accent plein)
   plus les états : survol, focus clavier, désactivé, chargement, erreur.

   C'est le contrat du markup que l'app produit (frontend/index.html) :
     <div class="filters"><div id="fmt-chips">
       <button class="chip on" data-fmt="" data-i18n="fmt.all"></button>
       <button class="chip" data-fmt="epub">EPUB</button>
       <button class="chip" data-fmt="mobi">MOBI</button>
       <button class="chip" data-fmt="pdf">PDF</button>
     </div></div>
   (l.333–336, 4 usages) ; la bordure 1px de l'app (l.94–100) devient
   box-shadow --borders-base — l'approche du design system. L'app pose .on
   elle-même (classList l.859–861) ; les classes is-* de simulation et
   .chip--loading / .chip--error sont livrées pour les états que l'app ne
   pose pas encore (precedent Button / Badge).
   Contrat : aria-label SEULEMENT en chargement (les autres états ont déjà
   un nom accessible : le libellé visible).

   Règles (02-ANTI-SLOP.md + skill primitive) :
   - Aucune valeur en dur, ZÉRO exception : la clé de variation
     (themes/kindle-shelf.css) importe et mappe l'échelle red
     (--danger-9/10/11 existent) — l'état d'erreur ne demande aucune valeur
     littérale. Seul littéral du fichier : les durées du spin (0.75 s /
     2 s), comme button.css / badge.css / field.css — 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
     boîte ne bouge pas et les ombres se combinent avec l'anneau de focus.
     Le `border: 0` initial est un reset du bord UA du <button>, pas une
     bordure dessinée.
   - Rayon : --radius-pill (999px = 100 % résolu) — le chip est le seul
     composant à rayon plein ; c'est sa signature (l'app aussi :
     border-radius: 999px).
   - Survol : changement de fond uniquement (--bg-hover / --accent-10),
     pas d'agrandissement.
   - Focus clavier : :focus-visible uniquement, jamais :focus seul. Chaîne
     --borders-base + --borders-focus (liseré --bg-app + anneau accent
     60 %) — le liseré suit le panneau sombre car --borders-focus est
     redéclaré dans le bloc [data-theme="dark"] de tokens.css.
   - Désactivé : :disabled natif — opacité + curseur. pointer-events: none
     volontairement OMIS (precedent Button) : la souris doit pouvoir pointer
     le chip désactivé pour voir l'état.
   - Chargement : spinner seul, libellé masqué en VISIBILITÉ (pas en
     display:none) : la boîte du texte reste, la largeur du chip 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).
   - Erreur : anneau --borders-error + texte --danger-11 qui dit quoi faire.
     --danger-11 et non --danger-9 : le texte d'erreur des primitives
     livrées (badge.css l.198, panel.css) est en --danger-11 — red-11 est
     l'étape lisible sur le thème sombre FIXE ; red-9 (#e11d48) n'est
     lisible qu'en fond/anneau. Le correctif est dans le message voisin,
     pas dans ce chip : pointer-events: none.
   - Transitions : background-color + box-shadow, --duration-fast (2
     propriétés, 1 durée — le même jeu que Button/Badge, rien de neuf sur
     la page). Le spin est une animation de transform, hors transitions.
   - Graisse : --weight-medium (500) seule — le chip n'a pas de hiérarchie
     interne ; la hiérarchie vient de la taille et du fond.

   -------------------------------------------------------------------------
   DECISIONS CONTRAIGNANTES 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 chip NE PEUT PAS introduire d'ombre nouvelle (pas d'élévation, pas
   d'anneau ad hoc). Il réutilise EXACTEMENT ces trois :
     - repos / actif (.on) / survol / désactivé / chargement : --borders-base
     - focus clavier : --borders-base + --borders-focus (chaîne — jamais
       --borders-focus seul, qui serait une 4e ombre distincte)
     - erreur : --borders-error
   Le SURVOL ne peut pas changer l'ombre (budget saturé) : il passe par le
   FOND SEUL (--bg-hover / --accent-10) — la règle de survol de la skill
   (« changement de fond via --bg-hover, pas d'agrandissement »).
   Fonds non neutres : .chip.on consomme --bg-solid (bronze-9) et
   .chip.on:hover --accent-10 (bronze-10) — les MEMES deux teintes que
   .btn.primary (déjà comptées sur la page, plafond 3 non dépassé).
   ========================================================================= */

/* --- Repos ---------------------------------------------------------------- */
/* Remplace le .chip de l'app (l.94–95) : fond de composant + bordure de base
   (l'app avait `background: none; border: 1px solid var(--line)` — la
   bordure devient box-shadow de spread 1px). Texte --fg-muted (l'app :
   --text-2, l'étape atténuée) : le filtre inactif est discret, la rangée
   n'est pas une action. Interligne --lh-compact : les textes compacts
   (tableau, liste, badge, libellé) partagent le même interligne, sinon les
   lignes ne s'alignent pas. */
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3); /* spinner ↔ libellé : 8 px, sur la grille */

  /* 4 px verticaux, 12 px horizontaux — sur la grille (l'app : 5/14 px,
     hors grille — remplacé par les tokens, c'est la décision d'absorption
     du :root local). */
  padding: var(--space-2) var(--space-4);

  font-family: var(--font-ui, inherit);
  font-size: var(--text-sm);  /* 13 px — le chip de l'app est en 13 px */
  line-height: var(--lh-compact);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);

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

  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;

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

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

/* --- Variante active (.on) ------------------------------------------------ */
/* Le filtre est appliqué : accent plein, texte sur solide. C'est l'état
   le plus visible de la rangée de filtres (l'app : contour accent + fond
   teinté 8 % — remplacé par un fond plein --bg-solid : un seul endroit
   audacieux, pas un demi-teinte). Consomme les deux teintes bronze déjà
   comptées par .btn.primary (budget de fonds non neutres, voir en-tête). */
.chip.on {
  color: var(--fg-on-solid);
  background: var(--bg-solid);
}

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

/* Survol : fond --bg-hover SEUL pour le chip inactif (pas d'ombre nouvelle
   — budget saturé, voir en-tête), pas d'agrandissement. Le chip actif
   monte d'une teinte bronze (bronze-10), comme .btn.primary:hover. Coupé
   sur le chip désactivé. */
.chip:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--fg-default);
}
.chip.on:hover:not(:disabled) {
  background: var(--accent-10);
}

/* Focus clavier : :focus-visible uniquement — jamais d'anneau au clic
   souris. La chaîne (bordure de base + anneau) est identique pour les deux
   variantes — elle RÉUTILISE l'ombre 2 de la page (jamais --borders-focus
   seul : ce serait une 4e ombre distincte). */
.chip:focus-visible {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

/* --- Désactivé --------------------------------------------------------------- */
/* Un filtre indisponible (ex. format absent de la bibliothèque) : estompé,
   curseur de refus. :disabled natif (l'app le posera ; le specimen l'utilise
   tel quel). Pas de pointer-events — voir en-tête. */
.chip:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --- Chargement ---------------------------------------------------------------- */
/* Le libellé du filtre se résout (l'app remplit les boutons via data-i18n
   au chargement) : spinner seul, libellé masqué en VISIBILITÉ — la boîte
   du texte reste, la largeur du chip est figée, zéro saut de mise en page.
   Clic coupé (rien à cliquer tant que le libellé n'existe pas). Le spinner
   passe en currentColor (--fg-muted) : un spinner d'état n'est pas un
   accent. aria-label posé par l'appelant, UNIQUEMENT dans cet état
   (contrat). */
.chip--loading {
  pointer-events: none;
}
.chip--loading .chip__label {
  visibility: hidden;
}

/* Spinner : anneau de 16 px (sur la grille), trait de 4 px, en MASQUE
   (pas de `border`) ; le fond est currentColor. Trois couches en
   intersection : donut (trou central, épaisseur sur la grille), disque
   plein (coupe les coins), arc de 270° (sans le tiers manquant, la
   rotation d'un anneau complet ne serait pas visible). Le masque ne lit
   que le canal alpha : `black` y est structurel, pas une couleur. Les
   angles sont structurels (forme de l'arc). */
.chip__spinner {
  display: none;
  width: var(--space-5);
  height: var(--space-5);
  flex-shrink: 0;
  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;
}
.chip--loading .chip__spinner {
  display: inline-block;
  animation: chip-spin 0.75s linear infinite;
}
@keyframes chip-spin {
  to { transform: rotate(360deg); }
}

/* --- Erreur ---------------------------------------------------------------------- */
/* Le filtre n'a pas pu être appliqué (ex. la liste des formats est
   indisponible) : anneau --borders-error (RÉUTILISE l'ombre 3 de la page,
   déjà comptée) + texte --danger-11 (l'étape lisible en sombre, comme
   badge.css / panel.css). Non cliquable — le correctif est dans le message
   voisin (le chip dit ce qui s'est passé, le message dit quoi faire). */
.chip--error {
  color: var(--danger-11, var(--fg-default));
  box-shadow: var(--borders-error);
  pointer-events: none;
}

/* --- 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. */
.chip.is-hover {
  background: var(--bg-hover);
  color: var(--fg-default);
}
.chip.on.is-hover {
  background: var(--accent-10);
}
.chip.is-focus {
  outline: none;
  box-shadow: var(--borders-base), var(--borders-focus);
}

@media (prefers-reduced-motion: reduce) {
  .chip {
    transition: none;
  }
  /* Le spin ralentit mais reste : l'information « chargement » doit passer. */
  .chip--loading .chip__spinner {
    animation-duration: 2s;
  }
}
