/*
 * ZeusMarket — couche de design
 * =============================
 *
 * Le sujet avant l'humeur : ZeusMarket ne vend pas « du premium », il vend
 * une marchandise a l'unite — le boost-mois — et cette marchandise n'existe
 * que par rapport a une echelle : 2 boosts pour le niveau 1, 7 pour le 2,
 * 14 pour le 3. Les deux seules questions d'un acheteur sont « combien il
 * m'en faut » et « combien ca coute l'unite ». Toute la page repond a ces
 * deux questions, et le design sort de la.
 *
 * ── Le geste unique ──
 * La jauge. Une mesure de 0 a 20, crantee a 2 / 7 / 14, remplie du bleu au
 * rose. Elle apparait a trois echelles — le heros, le planificateur, la
 * ligne de catalogue — et nulle part ailleurs. Tout le reste se tait.
 *
 * ── Les trois disciplines ──
 *   RAYON      pas un rayon unique partout : 0 pour le structurel (jauges,
 *              filets, lignes de catalogue), 4 pour les champs, 10 pour le
 *              bouton principal, 12 pour un panneau. Le rayon dit la
 *              hierarchie.
 *   OMBRE      aucune. Pas un seul rgba(0,0,0,.1). Sur fond sombre
 *              l'elevation vient d'une surface plus claire et d'un filet.
 *              La seule lueur de la page est sous le bouton principal.
 *   DEGRADE    deux endroits, pas trois : le remplissage d'une jauge et le
 *              bouton principal. Jamais derriere une section.
 *
 * ── Ou vivent les valeurs ──
 * Les couleurs, polices, rayons et rythmes de base sont dans
 * settings.json > global.properties. Le gabarit les emet dans un bloc
 * <style id="theme-styles"> pose APRES ce fichier : les redeclarer ici sur
 * :root ne servirait a rien. Pour surcharger, il faut monter en specificite
 * avec html[data-scheme="..."], ce que fait la section 1.
 *
 * ── Convention de couleur ──
 * Les jetons --cl-* et --z-* portent des triplets RVB separes par des
 * VIRGULES, lus en rgb(var(--x)) ou rgba(var(--x), .5). Des canaux separes
 * par des espaces avec une virgule d'alpha est du CSS invalide, silencieux.
 *
 * Sommaire
 *   1  Jetons propres au design
 *   2  Fondations
 *   3  Typographie
 *   4  L'echelle de boosts
 *   5  En-tete et navigation
 *   6  Heros
 *   7  Annonce et fil d'activite
 *   8  Boutons, champs, etats
 *   9  Mouvement
 */

/* ============================================================
   1  JETONS PROPRES AU DESIGN

   Tout ce que global.properties ne sait pas dire. Qualifie en
   html[...] pour passer devant le bloc <style> du gabarit.
   ============================================================ */

html {
  /* -- La charge. Trois arrets, un par palier : le bleu part de zero, le
        violet tient le milieu, la lavande claire marque le niveau 3.

        Ce sont les trois couleurs des planches de marque, et rien
        d'autre. La version precedente finissait sur un rose #FF6EC7
        emprunte a la gamme de boost de Discord : il n'est sur aucune
        planche, et il tirait tout le site vers le magenta — prix,
        etoiles, badges, bout de chaque jauge. -- */
  --z-charge: 59, 130, 246;   /* #3B82F6 */
  --z-boost: 139, 92, 246;    /* #8B5CF6 */
  --z-halo: 196, 181, 253;    /* #C4B5FD */

  /* -- Piste de jauge vide. Bleutee, jamais grise : un gris neutre sur un
        fond bleute se voit tout de suite comme une piece rapportee. -- */
  --z-track: 28, 37, 71;      /* #1C2547 */

  /* -- Filet de controle. WCAG 1.4.11 veut 3:1 pour la bordure d'un
        champ ou d'un bouton — le filet decoratif a 16 % n'y suffit pas. -- */
  --z-edge: 106, 119, 163;    /* #6A77A3 */

  /* -- Rayons. C'est la hierarchie qui les fixe, pas une valeur unique. -- */
  --z-r-flat: 0;
  --z-r-field: 4px;
  --z-r-action: 10px;
  --z-r-panel: 12px;
  --z-r-pill: 999px;          /* pastilles, compteurs, filtres */

  /* -- La seule lueur du site : sous le bouton principal (1.3, 2026-10-21).
        Les copies en dur passent toutes par ce jeton. -- */
  --z-lueur: 0 6px 26px -12px rgba(var(--cl-accent), 0.9);

  /* -- Hauteur des trois styles de bouton et des champs ; la paire du
        heros et le bouton du planificateur prennent la grande. -- */
  --z-h-action: 44px;
  --z-h-action-lg: 48px;

  /* -- Rythme. Grille de 4, avec les pas qui servent vraiment. -- */
  --z-1: 0.25rem;
  --z-2: 0.5rem;
  --z-3: 0.75rem;
  --z-4: 1rem;
  --z-5: 1.5rem;
  --z-6: 2rem;
  --z-7: 3rem;
  --z-8: 4rem;

  /* -- Mesure de lecture. 42rem faisait 80 caracteres en Karla : trop long
        pour un oeil qui revient a la ligne. -- */
  --z-measure: 62ch;

  /* -- Courbes. Une entree qui decelere, une sortie plus rapide que
        l'entree : c'est ce qui fait qu'une fermeture n'a pas l'air de
        trainer. -- */
  --z-ease: cubic-bezier(0.2, 0.7, 0.25, 1);
  --z-ease-out: cubic-bezier(0.4, 0, 1, 1);
}

/* Le levier de rayon des boutons du gabarit. Le bloc <style> de
   master.njk pose --radius-button = var(--radius-input) = 4 px : c'est
   lui qui arrondit les 107 rounded-btn du theme. Qualifie en
   html[data-scheme] pour passer devant :root. */
html[data-scheme] { --radius-button: var(--z-r-action); }

html[data-scheme="light"] {
  /* En clair, chaque arret doit foncer pour rester lisible sur du
     quasi blanc : la lavande #C4B5FD y tomberait a 1,3:1. L'ordre de
     luminance est conserve, le troisieme arret reste le plus clair
     des trois. */
  --z-charge: 37, 99, 235;    /* #2563EB */
  --z-boost: 109, 40, 217;    /* #6D28D9 */
  --z-halo: 124, 58, 237;     /* #7C3AED */
  --z-track: 221, 225, 240;   /* #DDE1F0 */
  --z-edge: 108, 117, 158;    /* #6C759E */

  /* En clair, le violet doit foncer pour rester lisible et porter du blanc
     (7,1:1). Aucune valeur unique ne passe AA sur fond quasi blanc ET
     quasi noir : l'accent est donc par schema, obligatoirement. */
  --cl-accent: 109, 40, 217;
  --cl-on-accent: 255, 255, 255;

  /* Meme raison pour les trois couleurs d'etat. Les valeurs sombres sont
     reglees pour tenir 8 a 10:1 sur #070B18 ; sur fond quasi blanc elles
     tombent a 1,8:1 et « in stock » devenait illisible. Mesures :
       succes        #0E7A52  4,96 sur le fond, 5,35 sur une surface
       avertissement #8A5A00  5,49 / 5,93
       erreur        #B3261E  6,06 / 6,54
     Toutes portent aussi du blanc en aplat. */
  --cl-success: 14, 122, 82;
  --cl-warning: 138, 90, 0;
  --cl-error: 179, 38, 30;
}

/* ============================================================
   2  FONDATIONS
   ============================================================ */

html {
  scroll-behavior: smooth;
  /* La barre d'achat collante et l'en-tete ne doivent pas recouvrir la
     cible d'une ancre. */
  scroll-padding-top: 5.5rem;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  background-color: rgb(var(--cl-background));
  color: rgb(var(--cl-t-primary));
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Le fond est plat, assume. Pas de nappe, pas d'aurore, pas de grille :
   le seul endroit ou la couleur monte, c'est dans une jauge. */

::selection {
  background: rgba(var(--z-halo), 0.3);
  color: rgb(var(--cl-t-primary));
}

/* -- Focus. built.css ne pose de :focus-visible que sur les champs de
      formulaire : liens, boutons et navigation n'avaient aucun repere, sur
      toutes les pages. WCAG 2.4.7. -- */
:focus-visible {
  outline: 2px solid rgb(var(--z-halo));
  outline-offset: 2px;
  /* Plus de border-radius ici : il changeait le rayon de l'element au
     focus (un bouton a 10 px passait a 4). Le contour suit seul. */
}

/* Le contour ne doit jamais etre rogne par le parent qui defile. */
:where(a, button, [role="button"], input, select, textarea, summary):focus-visible {
  position: relative;
  z-index: 2;
}

img, svg, video, canvas { display: block; max-width: 100%; }

hr {
  border: 0;
  border-top: 1px solid var(--cl-border);
}

/* ============================================================
   3  TYPOGRAPHIE

   Saira porte l'affichage et TOUS les chiffres : la page est faite de
   nombres (2 / 7 / 14, 0,119 EUR, 10 476, 789) et c'est eux qui portent
   la personnalite. Karla porte la prose.

   Pas de monospace pour les petites etiquettes de donnees — c'est une
   signature de gabarit. Les chiffres tabulaires de Saira alignent aussi
   bien, sans changer de voix.
   ============================================================ */

h1, h2, h3, h4,
.sa-hero-title,
.sa-section-title,
.sa-page-product-title {
  font-family: var(--ff-heading);
  font-weight: 700;
  letter-spacing: -0.021em;
  text-wrap: balance;
}

.sa-hero-title,
.text-display {
  font-size: clamp(2.5rem, 6.4vw, 4.25rem);
  line-height: 0.98;
  letter-spacing: -0.032em;
}

.sa-section-title,
.text-h2 {
  font-size: clamp(1.75rem, 3.4vw, 2.375rem);
  line-height: 1.08;
}

h3, .text-h3 { font-size: 1.375rem; line-height: 1.2; }

body {
  font-family: var(--ff-body);
  line-height: 1.6;
}

/* La mesure de lecture s'applique a la prose, pas aux grilles. */
.sa-hero-subtitle,
.sa-section-subtitle,
.zprose {
  max-width: var(--z-measure);
  text-wrap: pretty;
}

.sa-hero-subtitle {
  font-size: 1.0625rem;
  color: rgb(var(--cl-t-muted));
}

/* -- Les chiffres. Partout ou ils s'alignent en colonne ou se mettent a
      jour en place, ils doivent avoir la meme chasse : sans ca un compteur
      qui monte fait sauter tout ce qui le suit. -- */
.zfig,
.zscale-need b,
.zscale-price,
.sa-product-card-price,
.sa-page-product-price,
.sa-page-cart-price,
.sa-stats-item .text-h2,
.sa-page-customer-reseller-stat,
.zrow-unit,
.zrow-price,
.bp-readout,
.bp-count,
.bp-cta-price,
.bp-tier,
.ilc-compte,
.ilc-ecart {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  letter-spacing: -0.01em;
}

/* ============================================================
   4  L'ECHELLE DE BOOSTS

   Le geste unique. Pas de positionnement absolu pour les etiquettes :
   une grille dont les colonnes valent les ecarts de l'echelle
   (0->2, 2->7, 7->14, 14->20 = 2fr 5fr 7fr 6fr) fait tomber chaque
   etiquette exactement sur son cran, a toute largeur.
   ============================================================ */

.zscale {
  --zs-max: 20;
  --zs-fill: 14;
  --zs-h: 14px;
  width: 100%;
}

.zscale-track {
  position: relative;
  height: var(--zs-h);
  background: rgb(var(--z-track));
  border-radius: var(--z-r-flat);
}

/* Le degrade couvre TOUTE l'echelle et c'est le decoupage qui fait le
   remplissage. Si on retrecissait un element au lieu de le rogner, la
   couleur du cran 7 changerait avec le niveau affiche — ce qui n'a aucun
   sens pour une mesure. */
.zscale-fill {
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    90deg,
    rgb(var(--z-charge)) 0%,
    rgb(var(--z-boost)) 62%,
    rgb(var(--z-halo)) 100%
  );
  clip-path: inset(0 calc(100% - var(--zs-fill) / var(--zs-max) * 100%) 0 0);
}

/* Le cran est une encoche dans la barre, a la couleur de la page : il se
   lit aussi bien sur la piste vide que sur le remplissage. */
.zscale-tick {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: calc(var(--at) / var(--zs-max) * 100%);
  width: 2px;
  margin-left: -1px;
  background: rgb(var(--cl-background));
}

.zscale-rungs {
  display: grid;
  grid-template-columns: 2fr 5fr 7fr 6fr;
  margin-top: var(--z-3);
}

.zscale-rung {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-inline: var(--z-3);
  border-left: 1px solid rgba(var(--z-edge), 0.55);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur) var(--z-ease);
}

a.zscale-rung:hover { border-left-color: rgb(var(--z-halo)); }
a.zscale-rung:hover .zscale-price { color: rgb(var(--z-halo)); }

.zscale-lvl {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.9375rem;
  line-height: 1.3;
}

.zscale-need {
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  line-height: 1.35;
}

.zscale-need b {
  font-weight: 700;
  color: rgb(var(--cl-t-primary));
}

.zscale-price {
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  line-height: 1.35;
  transition: color var(--dur) var(--z-ease);
}

/* Sous 40rem la grille proportionnelle rendrait la premiere etiquette
   illisible (5/20 d'un ecran de 360px = 90px). On passe a trois colonnes
   egales : la piste garde la mesure exacte au-dessus, les etiquettes
   redeviennent lisibles en dessous. */
@media (max-width: 40rem) {
  .zscale { --zs-h: 12px; }
  .zscale-rungs { grid-template-columns: repeat(3, 1fr); }
  .zscale-lead { display: none; }
  .zscale-rung { padding-inline: var(--z-2); }
  .zscale-lvl { font-size: 0.875rem; }
  .zscale-need,
  .zscale-price { font-size: 0.75rem; }
}

/* ============================================================
   5  EN-TETE ET NAVIGATION
   ============================================================ */

.sa-navbar {
  background: rgba(var(--cl-background), 0.86);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid var(--cl-border);
}

.sa-navbar-link {
  position: relative;
  font-size: 0.9375rem;
  border-radius: var(--z-r-field);
}

/* Le lien actif est marque par un filet sous le mot, pas par une pastille
   remplie : une pastille ferait un troisieme niveau de surface pour rien. */
.sa-navbar-link[aria-current="page"],
.sa-navbar-link.is-active { color: rgb(var(--cl-t-primary)); }

.sa-navbar-link[aria-current="page"]::after,
.sa-navbar-link.is-active::after {
  content: "";
  position: absolute;
  left: var(--z-2);
  right: var(--z-2);
  bottom: 2px;
  height: 2px;
  background: rgb(var(--z-halo));
}

/* ============================================================
   6  HEROS
   ============================================================ */

.sa-hero { border-bottom: 1px solid var(--cl-border); }

.sa-hero-scale { margin-top: var(--z-7); }

.sa-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-3);
}

/* ============================================================
   7  ANNONCE ET FIL D'ACTIVITE

   Le bandeau defilait en boucle, en permanence. Du mouvement non
   declenche, a vie, qu'aucun lecteur ne demande et qui empeche de finir
   une phrase. Il devient une ligne fixe.
   ============================================================ */

.sa-announcement {
  border-bottom: 1px solid var(--cl-border);
  background: rgb(var(--cl-surface));
  font-size: 0.8125rem;
}

/* ============================================================
   8  BOUTONS, CHAMPS, ETATS
   ============================================================ */

.sa-btn,
.sa-button {
  font-family: var(--ff-heading);
  font-weight: 600;
  letter-spacing: -0.005em;
}

/* -- Le bouton principal. Aplat violet, encre SOMBRE dessus.
      Du blanc sur #8B5CF6 tombe a 4,23:1 et echoue ; l'encre de page y
      tient 5,71:1. C'est aussi ce qui l'eloigne du bouton violet a texte
      blanc par defaut. -- */
.sa-btn-primary,
.sa-add-to-cart,
.sbuy-cta,
.bp-cta {
  border-radius: var(--z-r-action);
  background-image: linear-gradient(
    100deg,
    rgb(var(--z-charge)) -30%,
    rgb(var(--cl-accent)) 55%,
    rgb(var(--z-halo)) 160%
  );
  color: rgb(var(--cl-on-accent));
  border: 0;
  transition: filter var(--dur) var(--z-ease), transform 90ms var(--z-ease);
}

.sa-btn-primary:hover,
.sa-add-to-cart:hover:not(:disabled),
.sbuy-cta:hover,
.bp-cta:hover { filter: brightness(1.08); }

.sa-btn-primary:active,
.sa-add-to-cart:active:not(:disabled),
.sbuy-cta:active,
.bp-cta:active { transform: translateY(1px); }

/* La seule lueur de la page, et elle est sous le seul bouton principal. */
.sa-add-to-cart:not(:disabled),
.bp-cta { box-shadow: var(--z-lueur); }

/* -- Champs. Le filet doit tenir 3:1 (WCAG 1.4.11) : le filet decoratif a
      16 % n'y suffit pas, d'ou --z-edge. -- */
input[type="text"],
input[type="email"],
input[type="search"],
input[type="url"],
input[type="number"],
input[type="password"],
textarea,
select,
.sa-input,
.ilc-input {
  border-radius: var(--z-r-field);
  /* 1.3 : filet plein. A 0,7 il tombait a 2,61:1 sur une surface
     relevee (WCAG 1.4.11) ; plein, 3,98:1. */
  border: 1px solid rgb(var(--z-edge));
  background: rgb(var(--cl-surface));
  color: rgb(var(--cl-t-primary));
  transition: border-color var(--motion-fast, 200ms) var(--ease-state, ease);
}

input:hover,
textarea:hover,
select:hover { border-color: rgb(var(--cl-t-muted)); }

/* Au focus, le filet ne change plus : l'anneau de la section 2 dit le
   focus, sur tous les champs. */

::placeholder { color: rgba(var(--cl-t-muted), 0.75); }

/* ============================================================
   5b MARQUE ET LIENS DE NAVIGATION
   ============================================================ */

/* 1.4 : .zbrand-long / .zbrand-bref sont morts, navbar.njk ne rend plus
   qu'un nom, « ZeusMarket », a toutes les largeurs. */

/* Un libelle de navigation qui passe a la ligne fait grandir l'en-tete et
   casse l'alignement de toute la barre. */
.sa-navbar-link,
.sa-navbar-links a { white-space: nowrap; }

/* ============================================================
   7b LE FIL D'ACTIVITE

   Une ligne, pas un bandeau. Les entrees sont empilees au meme endroit
   et seule celle qui porte .is-on se voit ; c'est le script qui deplace
   la classe, mais le gabarit la pose deja sur la premiere — sans
   JavaScript la premiere ligne reste donc visible.
   ============================================================ */

.zact {
  border-bottom: 1px solid var(--cl-border);
  font-size: 0.8125rem;
  /* 1.4 : le pas d'une section apres le filet du fil (80 px a 1440 avant,
     160 attendus = 2 x min(--section-py, 14vw), comme entre les autres) */
  margin-bottom: min(var(--section-py), 14vw);
}

.zact-in {
  display: flex;
  align-items: center;
  gap: var(--z-4);
  min-height: 44px;
}

.zact-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  flex: 0 0 auto;
  color: rgb(var(--cl-t-muted));
  font-family: var(--ff-heading);
  font-weight: 600;
}

.zact-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgb(var(--z-halo));
  flex: 0 0 auto;
}

.zact-list {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.zact-item {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: var(--z-2);
  min-width: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 260ms var(--z-ease);
}

.zact-item.is-on {
  opacity: 1;
  visibility: visible;
}

.zact-stars {
  color: rgb(var(--z-halo));
  letter-spacing: 0.08em;
  flex: 0 0 auto;
  font-size: 0.75rem;
}

.zact-what {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.zact-when {
  flex: 0 0 auto;
  color: rgb(var(--cl-t-muted));
}

.zact-all {
  flex: 0 0 auto;
  color: rgb(var(--cl-t-muted));
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--dur) var(--z-ease), border-color var(--dur) var(--z-ease);
}

.zact-all:hover {
  color: rgb(var(--cl-t-primary));
  border-bottom-color: rgb(var(--z-halo));
}

@media (max-width: 48rem) {
  .zact-in { gap: var(--z-3); }
  .zact-tag span:not(.zact-dot) { display: none; }
  .zact-when { display: none; }
}

/* ============================================================
   10  LE PLANIFICATEUR — LA MAQUETTE DE SERVEUR

   Le reste du planificateur (etapes, jauge a cases, choix, recapitulatif)
   vit en section 34. Ne restent ici que les regles de la maquette, qui
   n'a pas change de forme.
   ============================================================ */

/* ---- la maquette de serveur ---- */

.bp-server {
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface-raised));
  overflow: hidden;
}

.bp-srv-banner {
  position: relative;
  height: 92px;
  overflow: hidden;
}

/* Sans hauteur imposee, un SVG en viewBox 400x100 avec
   preserveAspectRatio="slice" se deploie a sa taille intrinseque : la
   banniere devenait un losange de 700px de haut en travers de la page. */
.bp-srv-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.bp-srv-shine {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgb(var(--cl-surface-raised)) 100%);
}

.bp-srv-main {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  padding: var(--z-4) var(--z-4) var(--z-2);
  margin-top: -22px;
  position: relative;
}

.bp-srv-icon {
  position: relative;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  background: rgb(var(--z-track));
  border: 2px solid rgb(var(--cl-surface-raised));
  overflow: hidden;
}

.bp-srv-av {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.0625rem;
  color: rgb(var(--cl-t-primary));
}

.bp-srv-gif {
  position: absolute;
  right: 2px;
  bottom: 2px;
  font-size: 0.5rem;
  font-style: normal;
  font-weight: 700;
  padding: 1px 3px;
  border-radius: 3px;
  background: rgb(var(--z-halo));
  color: rgb(var(--cl-background));
}

.bp-srv-id {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bp-srv-name {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1rem;
}

.bp-srv-lvlbadge,
.bp-srv-tag {
  font-family: var(--ff-heading);
  font-size: 0.625rem;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-accent), 0.18);
  color: rgb(var(--cl-accent));
  white-space: nowrap;
}

.bp-srv-counts {
  display: flex;
  gap: var(--z-3);
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

/* Passe 2 : la maquette sans serveur lu n'invente pas de chiffres. La place
   du nombre reste marquee par un trait neutre, la ligne garde sa forme. */
.bp-srv-counts > span:empty {
  display: inline-block;
  align-self: center;
  width: 2.5em;
  height: 0.6em;
  border-radius: 999px;
  background: rgba(var(--cl-t-primary), 0.14);
}

.bp-srv-vanity {
  padding: 0 var(--z-4) var(--z-3);
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.bp-srv-perks,
.bp-srv-looks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-1);
  padding: 0 var(--z-4) var(--z-3);
}

.bp-srv-perk,
.bp-srv-look {
  font-size: 0.6875rem;
  line-height: 1.5;
  padding: 2px 7px;
  border-radius: var(--z-r-field);
  border: 1px solid var(--cl-border);
  color: rgb(var(--cl-t-muted));
  white-space: nowrap;
  transition: color var(--dur) var(--z-ease), border-color var(--dur) var(--z-ease);
}

.bp-srv-perk.is-on,
.bp-srv-look.is-on {
  color: rgb(var(--cl-t-primary));
  border-color: rgba(var(--cl-accent), 0.55);
}

.bp-srv-boost {
  padding: var(--z-3) var(--z-4) var(--z-4);
  border-top: 1px solid var(--cl-border);
}

.bp-srv-boost-head {
  display: flex;
  justify-content: space-between;
  font-size: 0.6875rem;
  color: rgb(var(--cl-t-muted));
  margin-bottom: var(--z-2);
}

/* La barre de boost de la maquette : meme piste, meme charge, en petit. */
.bp-srv-boost-rail {
  position: relative;
  height: 6px;
  background: rgb(var(--z-track));
  overflow: hidden;
}

.bp-srv-boost-rail::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    90deg,
    rgb(var(--z-charge)) 0%,
    rgb(var(--z-boost)) 62%,
    rgb(var(--z-halo)) 100%
  );
  clip-path: inset(0 calc(100% - var(--f, 0%)) 0 0);
  transition: clip-path 220ms var(--z-ease);
}

/* ============================================================
   11  EN-TETES DE SECTION

   Casse de phrase, alignes a gauche, sans etiquette au-dessus. Le titre
   se suffit : une etiquette en capitales espacees au-dessus de chaque
   titre est une signature de gabarit, pas une information.
   ============================================================ */

.sa-section-header {
  margin-bottom: var(--z-6);
  text-align: left;
}

.sa-section-subtitle {
  margin-top: var(--z-3);
  color: rgb(var(--cl-t-muted));
}

/* ============================================================
   12  LE CATALOGUE — UNE LISTE DE PRIX

   Pas une grille de cartes. La marchandise se vend a l'unite et
   l'argument est le taux : des lignes se comparent du regard, des
   cartes non. Et une liste ne peut pas devenir le kit de cartes SaaS
   par accident.

   Sur telephone la ligne s'empile — elle ne defile JAMAIS
   lateralement : un tableau qui deborde est la premiere cause de
   defilement horizontal, et le site en avait deja trois.
   ============================================================ */

.sa-products--list .sa-products-items,
.sa-page-products .sa-products-items {
  gap: 0;
}

.sa-product-card--compact-row {
  border: 0;
  border-bottom: 1px solid var(--cl-border);
  border-radius: var(--z-r-flat);
  background: transparent;
  box-shadow: none;
  padding: var(--z-4) var(--z-3);
  position: relative;
  transition: background-color var(--dur) var(--z-ease);
}

/* Le filet d'accent au bord gauche remplace l'ombre et le changement de
   fond : il marque la ligne visee sans deplacer un seul pixel. */
.sa-product-card--compact-row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: rgb(var(--cl-accent));
  transform: scaleY(0);
  transform-origin: center;
  transition: transform var(--dur) var(--z-ease);
}

.sa-product-card--compact-row:hover,
.sa-product-card--compact-row:focus-visible {
  background: rgba(var(--cl-t-primary), 0.035);
}

.sa-product-card--compact-row:hover::before,
.sa-product-card--compact-row:focus-visible::before {
  transform: scaleY(1);
}

.sa-product-card--compact-row .sa-product-card-media {
  border-radius: var(--z-r-action);
  overflow: hidden;
}

.sa-product-card-title {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.25;
  letter-spacing: -0.014em;
}

.sa-product-card-stock { font-size: 0.8125rem; }

.sa-product-card-price {
  font-size: 1.125rem;
  font-weight: 700;
}

/* -- La jauge a la troisieme echelle : 40 px sur une ligne de
      catalogue. Meme piste, meme charge que le heros et le
      planificateur. Elle est cadree sur 14 (le niveau 3) parce que
      c'est ce que la ligne mesure : la distance qui reste au palier
      le plus haut. -- */
.pc-boost {
  display: flex;
  flex-direction: column;
  gap: var(--z-1);
  min-width: 112px;
}

.pc-boost-rail {
  position: relative;
  height: 6px;
  background: rgb(var(--z-track));
  border-radius: var(--z-r-flat);
  overflow: hidden;
}

.pc-boost-rail span {
  display: block;
  height: 100%;
  background-image: linear-gradient(
    90deg,
    rgb(var(--z-charge)) 0%,
    rgb(var(--z-boost)) 62%,
    rgb(var(--z-halo)) 100%
  );
  background-size: 112px 100%;
}

.pc-boost-meta {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  font-size: 0.6875rem;
  color: rgb(var(--cl-t-muted));
  white-space: nowrap;
}

.pc-boost-lvl {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: var(--ff-heading);
  font-weight: 700;
  color: rgb(var(--cl-t-primary));
}

/* Le losange n'avait ni largeur ni hauteur : un SVG en viewBox 16x16
   sans dimension se deploie a la taille de son conteneur et barrait la
   page entiere. */
.pc-boost-lvl svg {
  width: 9px;
  height: 9px;
  flex: 0 0 auto;
}

.pc-boost-lvl.lv-1 svg { color: rgb(var(--z-charge)); }
.pc-boost-lvl.lv-2 svg { color: rgb(var(--z-boost)); }
.pc-boost-lvl.lv-3 svg { color: rgb(var(--z-halo)); }

.pc-boost-n { font-variant-numeric: tabular-nums; }

/* (2026-10-07) Les regles .zval — le prix par boost et par mois — sont
   parties avec le comparateur qui les posait. */
@media (max-width: 40rem) {
  .pc-boost { min-width: 100%; }
}
}

/* Au telephone le titre est tronque par l'utilitaire `truncate`, et
   comme la pastille de format vit DANS le titre, c'est « Redeem key »
   qui disparait — l'information qui distingue justement deux packs au
   meme prix. Sur une ligne de liste, mieux vaut deux lignes de titre
   qu'une moitie de titre. */
@media (max-width: 40rem) {
  .sa-product-card--compact-row .sa-product-card-title {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
}

/* ============================================================
   13  LES CHIFFRES DE LA BOUTIQUE

   Quatre mesures sur une ligne, separees par un filet. Le filet n'est
   pas un ornement : il dit ou finit une mesure et ou commence la
   suivante, ce qu'un simple ecart ne dit pas.
   ============================================================ */

.sa-stats-items {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  border-top: 1px solid var(--cl-border);
  border-bottom: 1px solid var(--cl-border);
}

.sa-stats-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--z-5) var(--z-4);
  text-align: left;
}

.sa-stats-item + .sa-stats-item { border-left: 1px solid var(--cl-border); }

.sa-stats-item .text-h2 {
  font-size: clamp(1.75rem, 3.6vw, 2.5rem);
  line-height: 1;
  font-weight: 700;
}

@media (max-width: 52rem) {
  .sa-stats-items { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sa-stats-item:nth-child(odd) { border-left: 0; }
  .sa-stats-item:nth-child(n + 3) { border-top: 1px solid var(--cl-border); }
}

/* ============================================================
   14  LES QUATRE PROMESSES

   Quatre cartes de meme forme en rangee sont exactement le signe qu'on
   evite. Elles deviennent une liste de definitions : un filet, un
   intitule, une phrase. On lit plus vite et rien ne pretend etre un
   objet cliquable.
   ============================================================ */

.sa-features-items {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  border-top: 1px solid var(--cl-border);
}

.sa-features-item {
  border-bottom: 1px solid var(--cl-border);
  padding: var(--z-5) var(--z-5) var(--z-5) 0;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}

.sa-features-item:nth-child(even) {
  padding-left: var(--z-5);
  border-left: 1px solid var(--cl-border);
}

@media (max-width: 44rem) {
  .sa-features-items { grid-template-columns: minmax(0, 1fr); }
  .sa-features-item:nth-child(even) {
    padding-left: 0;
    border-left: 0;
  }
}

/* 1.4 (2026-10-21) : la variante « checklist » de features.njk porte
   border border-theme rounded-card bg-surface p-4 sur chaque item : une
   boite par promesse, et l'item impair (padding-left 0) collait son texte
   au filet. Meme construction que #stats, avec une specificite qui passe
   devant les utilitaires. */
.sa-features .sa-features-items .sa-features-item {
  border-width: 0 0 1px 0;
  border-radius: var(--z-r-flat);
  background: transparent;
  padding: var(--z-5) var(--z-5) var(--z-5) 0;
}

.sa-features .sa-features-items .sa-features-item:nth-child(even) {
  border-left-width: 1px;
  padding-left: var(--z-5);
}

@media (max-width: 44rem) {
  .sa-features .sa-features-items .sa-features-item:nth-child(even) {
    border-left-width: 0;
    padding-left: 0;
  }
}

/* ============================================================
   15  LES AVIS

   Des citations, pas des cartes. Ce qui compte dans un avis c'est le
   texte et le produit achete ; un cadre autour n'ajoute rien et fait
   huit rectangles identiques de plus.
   ============================================================ */

.sa-feedback-card {
  background: transparent;
  border: 0;
  border-top: 1px solid var(--cl-border);
  border-radius: 0;
  box-shadow: none;
  padding: var(--z-5) 0 var(--z-4);
}

.sa-feedbacks-link {
  color: rgb(var(--cl-t-muted));
  text-decoration: none;
  border-bottom: 1px solid var(--cl-border);
  transition: color var(--dur) var(--z-ease), border-color var(--dur) var(--z-ease);
}

.sa-feedbacks-link:hover {
  color: rgb(var(--cl-t-primary));
  border-bottom-color: rgb(var(--z-halo));
}

/* Le titre de la vitrine porte des nombres composes : « 4- and 5-star »,
   « 5-Sterne-Bewertungen ». Le trait d'union est une occasion de coupure,
   et l'equilibrage (text-balance du gabarit) la choisissait : « Recent 4-
   and 5- / star reviews » a 1440, « Aktuelle 5- / Sterne-Bewertungen » a
   390. Le remplissage ordinaire coupe plus loin, apres le nombre et son
   nom (« 5-star / reviews », « 5-Sterne- / Bewertungen »). Mesure en, fr,
   de x 390, 768, 1024, 1440, 1920 : passe1/engineer/titre-avis-*.jsonl. */
.sa-feedbacks .sa-section-title { text-wrap: wrap; }

/* ============================================================
   16  LE BLOC REVENDEURS
   ============================================================ */

.sa-text-block--card .sa-text-block-body,
.sa-text-block--accent-tint .sa-text-block-body {
  max-width: var(--z-measure);
  color: rgb(var(--cl-t-muted));
}

.sa-text-block--card {
  border-radius: var(--z-r-panel);
}

/* ============================================================
   17  LES QUESTIONS

   Un accordeon calme : un filet entre chaque entree, aucun cadre.
   ============================================================ */

.sa-faq-item {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--cl-border);
  border-radius: 0;
  box-shadow: none;
}

.sa-faq-item:first-child { border-top: 1px solid var(--cl-border); }

.sa-faq-item button,
.sa-faq-item summary {
  font-family: var(--ff-heading);
  font-weight: 600;
  min-height: 56px;
}

.sa-faq-support-card {
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface));
  box-shadow: none;
}

/* -- Le filtre de questions, injecte par le script -- */
.zfaq {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  margin-bottom: var(--z-4);
}

.zfaq-champ {
  flex: 1 1 auto;
  max-width: 24rem;
  min-height: 44px;
  padding: 0 var(--z-3);
}

.zfaq-compte {
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   18  LE PIED DE PAGE ET LA PASTILLE DISCORD
   ============================================================ */

.sa-footer {
  border-top: 1px solid var(--cl-border);
}

.sa-footer-link {
  color: rgb(var(--cl-t-muted));
  text-decoration: none;
  transition: color var(--dur) var(--z-ease);
}

.sa-footer-link:hover { color: rgb(var(--cl-t-primary)); }

/* La pastille de presence Discord : deux nombres et leurs intitules.
   Le point ne clignote pas — un point qui pulse en permanence est du
   mouvement non declenche, et il ne dit rien de plus qu'un point. */
.zlive {
  display: inline-flex;
  align-items: baseline;
  gap: var(--z-1);
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

.zlive-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgb(var(--z-halo));
  align-self: center;
  margin-right: 2px;
  flex: 0 0 auto;
}

.zlive-n {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: rgb(var(--cl-t-primary));
}

.zlive-l { font-size: 0.6875rem; }

.zlive-sep {
  width: 1px;
  height: 0.75em;
  align-self: center;
  background: var(--cl-border);
  margin: 0 var(--z-2);
}

/* ============================================================
   12b LA LIGNE DE CATALOGUE : ALIGNER LES COLONNES

   En flex, une ligne sans jauge et sans taux place son prix a un autre
   x que la ligne du dessus : la liste ne se compare plus du regard,
   ce qui etait toute la raison de passer en lignes. Une grille tient
   les colonnes meme quand une cellule est vide.

   Colonnes : vignette, intitule, prix, taux a l'unite.
   ============================================================ */

@media (min-width: 40rem) {
  .sa-product-card--compact-row {
    display: grid;
    grid-template-columns: 88px minmax(0, 1fr) 7rem 7rem;
    align-items: center;
    column-gap: var(--z-4);
  }

  /* Le bloc prix porte un ml-auto utilitaire : en grille il pousserait la
     cellule au lieu de son contenu. */
  .sa-product-card--compact-row > :nth-child(3) {
    margin-left: 0;
    justify-self: end;
  }
}

.sa-product-card--compact-row > :first-child { width: 88px; }

/* Le chevron double le filet d'accent qui apparait au survol et la ligne
   entiere est deja un lien : il ne dit rien de plus. */
.sa-product-card--compact-row > :nth-child(3) > svg { display: none; }

.sa-product-card--compact-row .pc-boost { max-width: 190px; }

/* Le format de livraison, colle au nom du produit dans le gabarit. */
.pc-name-tag {
  display: inline-block;
  margin-left: var(--z-2);
  padding: 1px 6px;
  border-radius: var(--z-r-field);
  border: 1px solid var(--cl-border);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.625rem;
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: rgb(var(--cl-t-muted));
  vertical-align: 2px;
}

/* ============================================================
   19  LE PANNEAU XTREMEZ

   Une iframe d'une autre origine ne peut pas se dimensionner elle-meme :
   on reserve la hauteur relevee a chaque largeur, sinon la page saute
   quand elle arrive.

   Le seuil se teste par @container et non par @media : la hauteur depend
   de la largeur du CADRE, qui varie avec les marges de la page. A 48rem
   les deux filets ramenaient l'iframe a 766px, un pixel sous le point de
   rupture de xtremez, et leur mise en page basculait en colonne. D'ou
   52rem.
   ============================================================ */

.xz { container-type: inline-size; }

.xz-head { margin-bottom: var(--z-4); }

.xz-title {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -0.024em;
}

.xz-text {
  margin-top: var(--z-3);
  max-width: var(--z-measure);
  color: rgb(var(--cl-t-muted));
}

.xz-frame {
  /* La barre de navigation de xtremez, rognee sur demande du client :
     marge negative sur l'iframe, debordement masque sur le cadre.
     Mettre --xz-crop a 0 la rend. */
  --xz-crop: 65px;
  position: relative;
  height: var(--xz-h);
  overflow: hidden;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface));
}

/* Releve le 2026-09-20 : xtremez bascule EXACTEMENT a 768 px de
   largeur d'iframe. Le cadre portant 2 px de filet, le seuil du
   conteneur est 770. A 52rem on reservait 1590 px la ou ils rendaient
   deja 888 : 700 px de vide sur une tablette. */
@container (max-width: 769px) {
  .xz-frame { height: var(--xz-hm); }
}

.xz-frame iframe {
  position: relative;
  display: block;
  width: 100%;
  height: calc(100% + var(--xz-crop));
  margin-top: calc(var(--xz-crop) * -1);
  border: 0;
  z-index: 1;
}

/* Le message d'attente est pose DERRIERE l'iframe et rien ne le retire :
   la page distante est opaque, elle le recouvre des qu'elle peint. Pas
   d'evenement @load a gagner, donc pas de voile bloque a vie sur une
   connexion rapide. */
.xz-wait {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  flex-direction: column;
  gap: var(--z-4);
  padding: var(--z-5);
}

.xz-skel {
  /* 1.7 : la forme du panneau reel (titre, 3 tuiles, 2 lignes, pied),
     relevee sur avant/obs-status-stock-1440.png ; statique. */
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-content: start;
  gap: var(--z-4);
  width: 100%;
  max-inline-size: 46rem;
  margin-inline: auto;
  background: none;
  border-radius: 0;
}

.xz-skel > [data-f] {
  grid-column: 1 / -1;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--z-track), 0.55);
}
.xz-skel > [data-f="titre"] {
  justify-self: center;
  width: 40%;
  height: 32px;
  border-radius: var(--z-r-field);
}
.xz-skel > [data-f="tuile"] { grid-column: auto; height: 150px; }
.xz-skel > [data-f="ligne"] { height: 145px; }
.xz-skel > [data-f="pied"] { height: 160px; }

@container (max-width: 769px) {
  .xz-skel > [data-f="tuile"] { grid-column: 1 / -1; }
}

/* La phrase d'attente et le chargeur, EN HAUT, dans le premier ecran du
   cadre (ils etaient colles en bas, a 1 100 px). */
.xz-wait-txt {
  order: -1;
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

/* .xz-spin (l'ancien disque tournant) n'a plus d'usage : retire. */

@keyframes xz-turn { to { transform: rotate(360deg); } }

.xz-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-2) var(--z-4);
  align-items: baseline;
  margin-top: var(--z-3);
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.xz-foot a {
  color: rgb(var(--cl-t-primary));
  text-decoration: none;
  border-bottom: 1px solid var(--cl-border);
}

.xz-foot a:hover { border-bottom-color: rgb(var(--z-halo)); }

/* -- Le repli du panneau de stock. Le bouton n'existe que sur telephone :
      au-dessus de 48rem le panneau tient dans un ecran et se replier
      n'apporterait qu'un clic de plus. -- */
.xz-fold { display: block; }

.xz-fold-sum {
  display: none;
  list-style: none;
  cursor: pointer;
}

.xz-fold-sum::-webkit-details-marker { display: none; }

@media (max-width: 48rem) {
  .xz-fold-sum {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 46px;
    margin-bottom: var(--z-3);
    border: 1px solid rgba(var(--z-edge), 0.8);
    border-radius: var(--z-r-action);
    font-family: var(--ff-heading);
    font-weight: 600;
    font-size: 0.875rem;
    color: rgb(var(--cl-t-primary));
    transition: border-color var(--dur) var(--z-ease);
  }

  .xz-fold-sum:hover { border-color: rgb(var(--cl-t-primary)); }
}

.xz-fold[open] .xz-fold-open,
.xz-fold:not([open]) .xz-fold-close { display: none; }

/* Passe 2 (2.5) : le rechargement n'a pas abouti. La ligne d'etat des
   lecteurs d'ecran devient visible (custom.js), sobre : un filet, pas un
   bandeau. */
.xz-alerte {
  margin: 0 0 var(--z-3);
  padding-left: var(--z-3);
  border-left: 2px solid rgb(var(--cl-warning));
  font-size: 0.8125rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-primary));
}

/* ============================================================
   20  LE VERIFICATEUR DE LIEN D'INVITATION

   La piece la plus utile de la fiche produit : elle dit AVANT paiement
   si le lien tiendra, et combien de boosts il manque. Elle merite donc
   d'etre traitee comme un instrument, pas comme une note de bas de page.
   ============================================================ */

.ilc { margin-top: var(--z-4); }

.ilc-label {
  display: block;
  margin-bottom: var(--z-2);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
}

.ilc-field { position: relative; }

.ilc-input {
  width: 100%;
  min-height: 46px;
  padding: 0 var(--z-4);
  font-size: 0.9375rem;
}

/* L'etat se lit sur le filet du champ : vert quand le lien est bon,
   ambre quand quelque chose gene, rouge quand il ne passera pas. La
   couleur ne porte jamais seule — le texte sous le champ le dit aussi. */
.ilc-field.is-ok .ilc-input { border-color: rgb(var(--cl-success)); }
.ilc-field.is-temporary .ilc-input { border-color: rgb(var(--cl-warning)); }
.ilc-field.is-blocked .ilc-input,
.ilc-field.is-invalid .ilc-input,
.ilc-field.is-format .ilc-input { border-color: rgb(var(--cl-error)); }

/* Passe 2 (2.4) : « reessayer » (429, 5xx, reseau) reste neutre — le lien
   n'est pas en cause — avec un bouton pour relancer la lecture. */
.ilc-reessai {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--z-2) var(--z-3);
}
.ilc-reessayer {
  min-height: 44px;
  padding: 0 var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  background: transparent;
  color: rgb(var(--cl-t-primary));
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
}
.ilc-reessayer:hover { border-color: rgba(var(--cl-accent), 0.6); }
.ilc-reessayer:focus-visible { outline: 2px solid rgb(var(--cl-accent)); outline-offset: 2px; }

/* Ce que la verification ne voit pas : une ligne discrete, pas un
   avertissement. */
.ilc-note {
  display: block;
  font-size: 0.75rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-muted));
}

.ilc-spinner {
  position: absolute;
  right: var(--z-3);
  top: 50%;
  margin-top: -7px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid rgba(var(--z-edge), 0.5);
  border-top-color: rgb(var(--cl-accent));
  animation: xz-turn 800ms linear infinite;
}

.ilc-out { margin-top: var(--z-2); }

.ilc-msg {
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

.ilc-bad { color: rgb(var(--cl-error)); }

.ilc-serv {
  display: flex;
  flex-direction: column;
  gap: var(--z-3);
  margin-top: var(--z-2);
  padding: var(--z-4);
  border: 1px solid var(--cl-border);
  border-left-width: 3px;
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-surface-raised));
}

.ilc-serv.is-ok { border-left-color: rgb(var(--cl-success)); }
.ilc-serv.is-warn { border-left-color: rgb(var(--cl-warning)); }
.ilc-serv.is-bad { border-left-color: rgb(var(--cl-error)); }

.ilc-tete {
  display: flex;
  align-items: center;
  gap: var(--z-3);
}

.ilc-icone {
  width: 40px;
  height: 40px;
  border-radius: 12px;
  flex: 0 0 auto;
}

.ilc-ident {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.ilc-ident b {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.9375rem;
  color: rgb(var(--cl-t-primary));
}

.ilc-ident i {
  font-style: normal;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.ilc-niv {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--z-2) var(--z-3);
  font-size: 0.8125rem;
}

.ilc-pastille {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.6875rem;
  padding: 2px 7px;
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-accent), 0.18);
  color: rgb(var(--cl-accent));
}

.ilc-compte { color: rgb(var(--cl-t-primary)); }

.ilc-ecart { color: rgb(var(--z-halo)); }
.ilc-ecart.is-max { color: rgb(var(--cl-success)); }

.ilc-verdict {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: var(--z-3);
  border-top: 1px solid var(--cl-border);
  font-size: 0.8125rem;
}

.ilc-calcul {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  color: rgb(var(--cl-t-muted));
}

.ilc-calcul b { color: rgb(var(--cl-t-primary)); }

.ilc-issue { font-weight: 500; }
.ilc-verdict.is-ok .ilc-issue { color: rgb(var(--cl-success)); }
.ilc-verdict.is-warn .ilc-issue { color: rgb(var(--cl-warning)); }
.ilc-verdict.is-bad .ilc-issue { color: rgb(var(--cl-error)); }

/* Le pack qui suffirait, quand celui-ci ne suffit pas. */
.ilc-mieux {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-3);
  min-height: 44px;
  padding: var(--z-2) var(--z-3);
  border: 1px solid rgba(var(--cl-accent), 0.45);
  border-radius: var(--z-r-action);
  text-decoration: none;
  font-size: 0.8125rem;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}

.ilc-mieux:hover {
  border-color: rgb(var(--cl-accent));
  background: rgba(var(--cl-accent), 0.08);
}

.ilc-mieux-txt { display: flex; gap: 6px; min-width: 0; }
.ilc-mieux-txt b { color: rgb(var(--cl-t-primary)); }
.ilc-mieux-txt span { color: rgb(var(--cl-t-muted)); }

.ilc-mieux-prix {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}

.ilc-blocs {
  display: flex;
  flex-direction: column;
  gap: var(--z-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ilc-bloc {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-left: var(--z-3);
  border-left: 2px solid var(--cl-border);
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

.ilc-bloc b {
  font-family: var(--ff-heading);
  color: rgb(var(--cl-t-primary));
}

.ilc-bloc.is-dur { border-left-color: rgb(var(--cl-error)); }
.ilc-bloc.is-doux { border-left-color: rgb(var(--cl-warning)); }

/* ============================================================
   21  LA BARRE D'ACHAT COLLANTE (telephone)
   ============================================================ */

.sbuy {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: rgba(var(--cl-background), 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--cl-border);
  padding: var(--z-2) var(--z-4) max(var(--z-2), env(safe-area-inset-bottom));
  transform: translateY(100%);
  transition: transform 220ms var(--z-ease);
}

.sbuy.is-up { transform: translateY(0); }

@media (min-width: 64rem) { .sbuy { display: none; } }

.sbuy-in {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  max-width: var(--container-max);
  margin-inline: auto;
}

.sbuy-id {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.sbuy-name {
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sbuy-price {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1rem;
}

.sbuy-cta {
  flex: 0 0 auto;
  min-height: 46px;
  padding: 0 var(--z-5);
  font-family: var(--ff-heading);
  font-weight: 700;
}

/* Passe 2 (2.6) : la ligne « ne peut pas etre commande » sous le prix. */
.sbuy-note {
  font-size: 0.75rem;
  line-height: 1.35;
  color: rgb(var(--cl-t-muted));
}

/* ============================================================
   22  LES PIECES POSEES PAR LE SCRIPT

   .zv / .zv-in restent volontairement sans regle. Le script les pose
   pour un fondu-montant section par section — exactement le mouvement
   qui fait lire une page comme generee. Sans declaration visuelle, la
   classe est inerte et le contenu arrive simplement present.
   ============================================================ */

/* -- Transition de page : une barre fine en haut, et un voile qui
      n'apparait qu'au-dela de 400 ms d'attente. Le voile est donc un
      retour, pas un ornement : sur une navigation rapide on ne le voit
      jamais. -- */
.zpt-piste {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 90;
  pointer-events: none;
  opacity: 0;
  transition: opacity 150ms var(--z-ease);
}

.zpt.is-active .zpt-piste { opacity: 1; }

.zpt-barre {
  display: block;
  height: 100%;
  transform-origin: left center;
  transform: scaleX(0);
  background-image: linear-gradient(
    90deg,
    rgb(var(--z-charge)),
    rgb(var(--z-boost)) 60%,
    rgb(var(--z-halo))
  );
  transition: transform 180ms var(--z-ease);
}

.zpt-voile {
  position: fixed;
  inset: 0;
  z-index: 89;
  display: grid;
  place-items: center;
  background: rgba(var(--cl-background), 0.92);
  opacity: 0;
  visibility: hidden;
  transition: opacity 200ms var(--z-ease), visibility 200ms;
}

.zpt.is-voile .zpt-voile {
  opacity: 1;
  visibility: visible;
}

.zpt-sceau {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--z-3);
}

.zpt-sceau svg {
  width: 40px;
  height: 46px;
  stroke: rgb(var(--cl-accent));
  stroke-width: 4;
  stroke-linecap: square;
}

.zpt-nom {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: rgb(var(--cl-t-muted));
}

/* -- Retour en haut et aide flottante. Les deux vivent en bas a droite :
      l'aide est le geste frequent, donc elle est en dessous, sous le
      pouce ; le retour en haut se range au-dessus d'elle. Sous 64rem la
      barre d'achat (z-index 40) occupe le bas, les deux remontent. -- */
.zsup,
.totop {
  position: fixed;
  right: var(--z-4);
  z-index: 45;
  border-radius: var(--z-r-action);
  border: 1px solid var(--cl-border);
  background: rgba(var(--cl-surface), 0.94);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: rgb(var(--cl-t-primary));
  text-decoration: none;
  transition: border-color var(--dur) var(--z-ease), transform var(--dur) var(--z-ease);
}

.zsup {
  bottom: var(--z-4);
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  min-height: 44px;
  padding: 0 var(--z-4);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
}

.zsup-ico {
  display: inline-flex;
  color: rgb(var(--cl-accent));
}

.zsup:hover,
.totop:hover { border-color: rgba(var(--z-halo), 0.7); }

.totop {
  bottom: calc(var(--z-4) + 52px);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(6px);
}

.totop.is-up {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.totop svg { width: 18px; height: 18px; }

@media (max-width: 64rem) {
  .zsup { bottom: calc(var(--z-4) + 64px); }
  .totop { bottom: calc(var(--z-4) + 116px); }
  .zsup-txt { display: none; }
  .zsup { padding: 0; width: 44px; justify-content: center; }
}

/* -- Bouton copier -- */
.zcopy {
  display: inline-flex;
  align-items: center;
  gap: var(--z-1);
  min-height: 32px;
  padding: 0 var(--z-2);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-field);
  background: transparent;
  color: rgb(var(--cl-t-muted));
  font-size: 0.75rem;
  cursor: pointer;
  transition: color var(--dur) var(--z-ease), border-color var(--dur) var(--z-ease);
}

.zcopy:hover { color: rgb(var(--cl-t-primary)); }
.zcopy-ok { color: rgb(var(--cl-success)); border-color: rgb(var(--cl-success)); }
.zcopy-no { color: rgb(var(--cl-error)); border-color: rgb(var(--cl-error)); }

.zfmt { font-variant-numeric: tabular-nums; }

.zlive-on .zlive-n,
.zlive-n.zlive-on { color: rgb(var(--cl-success)); }

/* Alpine masque ses blocs avec x-cloak jusqu'a l'initialisation. Sans
   cette regle, tous les etats d'un composant apparaissent en meme temps
   pendant une fraction de seconde. */
[x-cloak] { display: none !important; }

/* ============================================================
   23  CIBLES TACTILES ET REPLIS

   WCAG 2.5.8 (AA) demande 24x24 px, 2.5.5 (AAA) demande 44x44. On tient
   AA partout, et AAA la ou ca compte vraiment : les boutons a icone
   seule, les actions principales, et tout ce qui est touche au doigt.

   Le seuil generalise a 44 px sur (pointer: coarse) plutot que sur une
   largeur d'ecran : c'est le doigt qui impose la taille, pas la
   fenetre. Une tablette large au doigt a le meme besoin qu'un telephone.
   ============================================================ */

/* -- Boutons a icone seule : 44 px partout, souris comprise. Ils n'ont
      aucun libelle pour rattraper un clic manque. -- */
/* Le :not([class*=":hidden"]) n'est pas une precaution de style, c'est
   une correction : poser display: inline-flex sur .sa-navbar-actions
   button ecrasait le display: none du bouton burger (.lg:hidden), qui
   reapparaissait au-dessus de 1024px et faisait deborder la barre de
   46px — un defilement horizontal sur toute la page. Une regle qui
   impose un display doit toujours epargner ce qui est masque. */
.sa-navbar-cart:not([class*=":hidden"]):not(.hidden),
.sa-scheme-toggle:not([class*=":hidden"]):not(.hidden),
.sa-navbar button[class*="p-2"]:not([class*=":hidden"]):not(.hidden),
.sa-navbar-actions button:not([class*=":hidden"]):not(.hidden),
.sa-navbar-actions > a:not([class*=":hidden"]):not(.hidden) {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* -- Actions : jamais moins de 44 px de haut. -- */
.sa-btn,
.sa-button,
.sa-add-to-cart,
.sa-faq-support-card-link,
.bp-cta,
.sbuy-cta,
.zsup,
.totop {
  min-height: 44px;
}

/* -- Champs : un champ de 36 px est penible au doigt et desaligne les
      boutons qui l'accompagnent. -- */
.sa-navbar input,
.sa-searchbar input,
input.input,
.zfaq-champ,
.ilc-input {
  min-height: 44px;
}

/* -- Liens de navigation et de pied de page : au moins 24 px (AA) en
      toutes circonstances, la zone cliquable etant etendue par le
      remplissage, pas par la taille du texte. -- */
.sa-navbar-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
}

.sa-footer-link { min-height: 28px; }


/* -- Le reste des cibles, nommement. Les liens qui vivent DANS une
      phrase (le bandeau d'annonce, les liens d'une reponse de FAQ) sont
      exemptes par 2.5.8 : on leur donne quand meme 24 px, ce qui est le
      plancher AA, sans casser l'interligne du texte. -- */
/* inline-BLOCK et non inline-flex : un conteneur flex supprime les
   espaces entre ses enfants, et ces liens contiennent du balisage
   (« <b>10356</b> products delivered » devenait « 10356products »).
   Le remplissage vertical suffit a atteindre les 24 px de 2.5.8. */
.sa-announcement-item,
.xz-foot a,
.xz-wait a,
.sa-faq-item a,
.sa-footer-link,
.zact-all,
.sa-feedbacks-link {
  display: inline-block;
  padding-block: 4px;
  min-height: 24px;
}

/* Le bouton de connexion n'herite pas de .sa-btn : il est ecrit a la main
   dans la barre de navigation. */
.sa-navbar button.items-center,
.sa-navbar-logo {
  min-height: 44px;
}

/* Les liens a icone seule du pied de page (Discord, Telegram). */
.sa-footer a[class*="p-2"]:not([class*=":hidden"]):not(.hidden) {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* Le bouton du menu, lui, doit bien etre agrandi — mais seulement la ou
   il s'affiche. On l'agrandit dans une requete media qui s'arrete juste
   avant le seuil lg de Tailwind (1024px) : au-dessus, la regle ne
   s'applique pas du tout et son display: none reste intact. */
@media (max-width: 1023.98px) {
  .sa-navbar button[class*="lg:hidden"] {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* -- Au doigt, tout passe a 44 px. Ce bloc DOIT rester le dernier de la
      section : les regles ci-dessus ont la meme specificite, et a
      specificite egale c'est l'ordre du fichier qui tranche. Place plus
      haut, il etait ecrase par le min-height: 24px des liens en
      inline-block. -- */
@media (pointer: coarse) {
  .sa-navbar-link,
  .sa-footer-link,
  .sa-announcement-item,
  .xz-foot a,
  .xz-wait a,
  .sa-faq-item a,
  .zact-all,
  .sa-feedbacks-link,
  .sa-faq-item button,
  .sa-faq-item summary,
  .sa-page-faq-item button,
  .sa-page-faq-item summary { min-height: 44px; }

  .sa-product-card--compact-row { padding-block: var(--z-5); }
}

/* ============================================================
   24  REPLIS DE PEINTURE

   Un bouton dont le texte est l'encre sombre depend entierement de son
   aplat. Si le degrade ne peint pas — extension, mode economie, moteur
   ancien — le texte devient illisible sur la surface. Une couleur de
   fond pleine dessous coute un octet et supprime ce mode de panne.
   ============================================================ */

.sa-btn-primary,
.sa-btn--primary.sa-btn--solid,
.sa-add-to-cart,
.sbuy-cta,
.bp-cta {
  background-color: rgb(var(--cl-accent));
}

/* Le gabarit nomme le bouton principal .sa-btn--primary.sa-btn--solid,
   pas .sa-btn-primary : sans cette regle il restait au style d'origine. */
.sa-btn--primary.sa-btn--solid {
  border-radius: var(--z-r-action);
  background-image: linear-gradient(
    100deg,
    rgb(var(--z-charge)) -30%,
    rgb(var(--cl-accent)) 55%,
    rgb(var(--z-halo)) 160%
  );
  color: rgb(var(--cl-on-accent));
  border: 0;
  box-shadow: var(--z-lueur);
  transition: filter var(--dur) var(--z-ease), transform 90ms var(--z-ease);
}

.sa-btn--primary.sa-btn--solid:hover { filter: brightness(1.08); }
.sa-btn--primary.sa-btn--solid:active { transform: translateY(1px); }

/* Le bouton secondaire : un contour franc, pas un aplat concurrent.
   Deux aplats cote a cote se disputent le regard et aucun ne gagne. */
.sa-btn--secondary.sa-btn--outline {
  border-radius: var(--z-r-action);
  border: 1px solid rgba(var(--z-edge), 0.8);
  background: transparent;
  color: rgb(var(--cl-t-primary));
  box-shadow: none;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}

.sa-btn--secondary.sa-btn--outline:hover {
  border-color: rgb(var(--cl-t-primary));
  background: rgba(var(--cl-t-primary), 0.05);
}

/* ============================================================
   25  CORRECTIONS RELEVEES A LA CAPTURE
   ============================================================ */

/* -- Les chiffres de la boutique. Le gabarit intercale un element de
      filet entre chaque mesure : la grille recevait donc 7 enfants pour
      4 colonnes et se repliait en 2x2 avec des filets orphelins. On
      masque les filets du gabarit et on remet les notres sur les
      mesures elles-memes, via le combinateur general qui ignore ce qui
      s'intercale. -- */
.sa-stats-items > :not(.sa-stats-item) { display: none; }

.sa-stats-item + .sa-stats-item { border-left: 0; }

.sa-stats-items > .sa-stats-item ~ .sa-stats-item {
  border-left: 1px solid var(--cl-border);
}

@media (max-width: 52rem) {
  .sa-stats-items > .sa-stats-item ~ .sa-stats-item { border-left: 1px solid var(--cl-border); }
  .sa-stats-items > .sa-stats-item:nth-of-type(odd) { border-left: 0; }
  .sa-stats-items > .sa-stats-item:nth-of-type(n + 3) { border-top: 1px solid var(--cl-border); }
}

/* -- Les avis. La variante en deux colonnes laissait 40 % de la largeur
      vide sur toute la hauteur de la liste. L'en-tete devient collant :
      la note et le lien restent sous les yeux pendant qu'on parcourt
      les avis, et la colonne cesse d'etre un trou. -- */
@media (min-width: 64rem) {
  .sa-feedbacks .sa-section-header,
  .sa-feedbacks > div > div:first-child {
    position: sticky;
    top: 6rem;
    align-self: start;
  }
}

/* Une fleche collee au libelle d'un lien. Le lien dit deja ou il mene. */
.sa-feedbacks-link svg,
.sa-faq-support-card-link svg { display: none; }

/* ============================================================
   26  LA FICHE PRODUIT

   La description est ecrite par le theme a partir du nom du produit
   (product-description.njk) et sort en HTML nu : <h2>, <ul>, <table>,
   <blockquote>. built.css n'habille que les classes e-* de son editeur,
   donc rien ne s'y appliquait — c'etait un mur de texte.
   ============================================================ */

.sa-page-product-title {
  font-size: clamp(1.875rem, 4vw, 3rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
}

.sa-page-product-price {
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.1;
}

/* -- La prose de la fiche -- */

.editor {
  max-width: 68ch;
  line-height: 1.65;
}

.editor > * + * { margin-top: var(--z-4); }

.editor h2 {
  margin-top: var(--z-7);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.25rem;
  line-height: 1.2;
  letter-spacing: -0.018em;
}

.editor h2:first-child { margin-top: 0; }

.editor h3 {
  margin-top: var(--z-5);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.0625rem;
}

.editor p { color: rgb(var(--cl-t-primary)); }

.editor strong,
.editor b { font-weight: 700; }

/* La puce est un tiret court dans l'accent, pas un rond : sur une liste
   de perks, un rond ajoute une forme de plus alors que le tiret suit la
   ligne du texte. */
.editor ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.editor ul > li {
  position: relative;
  padding-left: var(--z-5);
  margin-top: var(--z-2);
}

.editor ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 10px;
  height: 2px;
  background: rgb(var(--cl-accent));
}

.editor ol {
  margin: 0;
  padding-left: var(--z-5);
}

.editor ol > li { margin-top: var(--z-2); }

.editor ol > li::marker {
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: rgb(var(--cl-t-muted));
}

/* Une mise en garde, pas un ornement : elle porte ce qui fait echouer
   une commande (lien temporaire, serveur plein). */
.editor blockquote {
  margin: var(--z-4) 0 0;
  padding: var(--z-3) 0 var(--z-3) var(--z-4);
  border-left: 2px solid rgb(var(--z-halo));
  color: rgb(var(--cl-t-muted));
}

.editor code {
  font-family: var(--ff-heading);
  font-size: 0.9375em;
  padding: 1px 5px;
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-t-primary), 0.07);
}

/* -- La fiche technique. Un vrai tableau de specifications : l'intitule
      a droite dans sa colonne, la valeur a gauche de la sienne, un filet
      par ligne. C'est la forme la plus rapide a lire pour comparer deux
      packs, et c'est exactement ce que fait un acheteur. -- */
.editor table {
  width: 100%;
  max-width: 34rem;
  border-collapse: collapse;
  margin-top: var(--z-4);
}

.editor table th,
.editor table td {
  padding: var(--z-2) 0;
  border-bottom: 1px solid var(--cl-border);
  text-align: left;
  vertical-align: baseline;
}

.editor table th {
  width: 11rem;
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  padding-right: var(--z-4);
}

.editor table td {
  font-variant-numeric: tabular-nums;
  color: rgb(var(--cl-t-primary));
}

.editor table tr:last-child th,
.editor table tr:last-child td { border-bottom: 0; }

/* -- Onglets de la fiche -- */
.sa-page-product [role="tab"],
.sa-page-product .tab {
  font-family: var(--ff-heading);
  font-weight: 600;
  min-height: 44px;
}

/* -- L'image : rayon d'action, comme toutes les vignettes. -- */
.sa-page-product img { border-radius: var(--z-r-action); }

/* Le chevron colle au libelle « Buy now ». Le bouton dit deja ce qu'il
   fait ; la fleche n'ajoute qu'un signe de gabarit. Le rouet d'attente,
   lui, reste : il porte un etat. */
.sa-product-form button[type="submit"] svg:not(.animate-spin) { display: none; }

/* Le gabarit borne la liste a max-w-3xl (768px). Pour une grille de
   cartes c'est raisonnable ; pour une liste de prix a quatre colonnes
   ca serre tout a gauche et laisse un tiers de la page vide. */
.sa-products--list .sa-products-items,
.sa-page-products .sa-products-items { max-width: none; }

/* ============================================================
   27  LA FAQ, DEUX NOMENCLATURES

   Le theme nomme les entrees .sa-faq-item dans la section d'accueil et
   .sa-page-faq-item sur /faq. Ne traiter que la premiere laissait la
   page entiere au style d'origine.
   ============================================================ */

.sa-page-faq-item {
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--cl-border);
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  overflow: visible;
}

.sa-page-faq-item:first-child { border-top: 1px solid var(--cl-border); }

.sa-page-faq-item button,
.sa-page-faq-item summary {
  font-family: var(--ff-heading);
  font-weight: 600;
  min-height: 56px;
}

/* Un accordeon sur deux colonnes deplace la colonne voisine chaque fois
   qu'on ouvre une entree : on perd la ligne qu'on etait en train de
   lire. Une seule colonne, bornee a la mesure de lecture. */
.sa-page-faq-items,
.sa-faq-items {
  grid-template-columns: minmax(0, 1fr) !important;
  display: block;
  max-width: 54rem;
  gap: 0;
}

/* -- Le filtre de questions. La loupe n'avait aucune dimension : un SVG
      en viewBox 24x24 sans width ni height se deploie a la taille de son
      conteneur, et elle occupait 700px de haut en travers de la page. -- */
.zfaq {
  position: relative;
  display: block;
  max-width: 24rem;
  margin-bottom: var(--z-5);
}

.zfaq-loupe {
  position: absolute;
  left: var(--z-3);
  top: 50%;
  width: 16px;
  height: 16px;
  margin-top: -8px;
  color: rgb(var(--cl-t-muted));
  pointer-events: none;
}

.zfaq-champ {
  width: 100%;
  padding-left: calc(var(--z-3) * 2 + 16px);
  padding-right: 4.5rem;
}

.zfaq-compte {
  position: absolute;
  right: var(--z-3);
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
}

/* ============================================================
   9  MOUVEMENT

   Niveau 4 sur 10 (DIRECTION.md, « Mouvement », 2026-10-19). Tout
   mouvement repond a un evenement : une arrivee (une fois), une action,
   un changement d'etat. Rien ne tourne en boucle hors stock en direct et
   chargeurs. Au chargement, la jauge du heros se remplit une fois ; les
   chiffres s'affichent a leur valeur finale (plus de compteur qui part
   de 0, custom.js). Mouvement reduit : la regle ci-dessous ramene toute
   animation a son etat final.
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {
  .sa-hero-scale .zscale-fill {
    animation: zs-rise 900ms var(--z-ease) both;
  }
}

@keyframes zs-rise {
  from { clip-path: inset(0 100% 0 0); }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================
   28  LA MASCOTTE

   Une regle gouverne tout ce bloc : les lutins portent leur propre
   fond, rgb(6,10,19), et ce fond n'est invisible que sur le fond de
   page sombre, rgb(7,11,24). Partout ailleurs — sur une surface
   relevee, en schema clair — il faut lui poser un socle sombre, sinon
   un carre apparait. Le socle n'est pas un ornement, c'est la
   condition pour que le decoupage tienne.

   Mouvement (2026-10-20, budget 4/10) : plus aucune boucle. Le
   flottement, la respiration, la pulsation de la lueur et les particules
   tournaient sans fin et tenaient le fil principal de l'accueil occupe
   a 100 % a processeur x4 (32 i/s). La mascotte bouge quand on lui fait
   quelque chose : arrivee, survol, clic, reaction (section 28e).
   ============================================================ */

html {
  /* Doit rester egal au fond des planches decoupees. Si les images sont
     regenerees sur un autre fond, changer ici aussi. */
  --zm-fond: 7, 11, 24;
}

.zm-hors-ecran {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -- Le composant -- */

.zm {
  display: block;
  width: var(--zm-px, 96px);
  max-width: 100%;
  line-height: 0;
  pointer-events: none;
}

.zm-corps {
  display: block;
  position: relative;
  line-height: 0;
}

.zm-img {
  display: block;
  width: 100%;
  height: auto;
  -webkit-user-select: none;
  user-select: none;
}

/* Le carre impose (aspect-ratio: 1, object-fit: contain) est parti avec
   le fond cuit : les vignettes detourees font 200x194 en haut de planche
   et 200x192 en bas, et un carre les letterboxait. Les attributs width et
   height du gabarit donnent desormais le rapport exact, ce qui reserve
   aussi la bonne place avant le chargement.

   L'ombre portee suit la silhouette et non un rectangle — c'est ce que
   le detourage permet. Elle est la profondeur demandee, et elle remplace
   l'ancien disque opaque : celui-la cachait le carre, celle-ci pose le
   lutin sur la surface. */
.zm-img {
  /* 1.3 (2026-10-21) : plus d'ombre portee. DIRECTION : aucune ombre. */
  filter: none;
}

@media (prefers-reduced-motion: reduce) {
  .zm-img { filter: none; }
}

/* En schema clair la meme ombre fait un halo gris autour des oreilles :
   sur du #F5F6FB, 55 % de noir se lit, alors que sur du #070B18 il
   disparait dans le fond. Elle devient une ombre de contact, basse et
   courte, qui pose le lutin sans le cerner. */
/* 2026-10-20 : retiree. Sur le heros, cette ombre dessinait un carre
   gris de la taille de l'image (fond de page 245,246,251 -> 230,232,238
   dans le cadre, mesure a 390 en clair ; sans le filtre, plus rien). Le
   carre en clair, c'etait elle. DIRECTION.md : aucune ombre. */
html[data-scheme="light"] .zm-img { filter: none; }

/* -- Les yeux, en calques separes au-dessus du corps.

      Ils se deplacent en POURCENTAGE de leur propre largeur, jamais en
      pixels : le heros fait 369 px sur un grand ecran et 176 px sur un
      telephone, une course en pixels serait deux fois trop grande sur
      le second. En pourcentage, elle suit l'echelle toute seule.

      La course est plafonnee a 4 % / 3 %, soit six pixels a la taille
      d'origine. Au-dela, le trou rebouche dans le corps depasserait du
      lutin. -- */

.zm--heros .zm-corps { position: relative; }

.zm-oeil {
  position: absolute;
  line-height: 0;
  pointer-events: none;
  transform: translate3d(calc(var(--zm-ox, 0) * 1%), calc(var(--zm-oy, 0) * 1%), 0);
  will-change: transform;
}

.zm-oeil img {
  display: block;
  width: 100%;
  height: auto;
}

.zm-oeil--g { left: 24.907%; top: 22.917%; width: 26.766%; }
.zm-oeil--d { left: 56.506%; top: 24.132%; width: 27.509%; }

/* La planche coupe le corps du heros a hauteur des jambes : c'est le
   cadrage de l'illustration fournie, pas un defaut de decoupe. Un fondu
   sur le dernier sixieme transforme la coupure en disparition. */
.zm--heros .zm-img {
  -webkit-mask-image: linear-gradient(180deg, #000 90%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 90%, transparent 100%);
}

/* -- Le socle est mort avec le carre. --

   Il posait un disque opaque a la couleur du fond de page sous chaque
   lutin des qu'il quittait ce fond : surface relevee, schema clair. Ce
   n'etait pas un ornement, c'etait un cache. Les lutins detoures n'ont
   plus rien a cacher, et le disque, lui, se verrait.

   La classe zm--socle est retiree de ses neuf points d'appel. On la
   neutralise ici plutot que de la laisser mordre si elle revient d'un
   copier-coller. -- */

.zm--socle { background: none; }

/* -- L'integration physique : un lutin accroche au bord d'un bloc. --

   C'est la demande centrale — « assis sur les cartes, accoude aux
   sections, a cheval sur les bords ». Elle etait impossible tant que
   chaque lutin trainait son rectangle de fond ; elle tient en trois
   lignes maintenant qu'ils ont un alpha.

   Le conteneur prend zm-hote, le lutin une pose. Il deborde de son bloc
   sans jamais decaler quoi que ce soit ni intercepter un clic. -- */

.zm-hote { position: relative; }

.zm-pose {
  position: absolute;
  z-index: 2;
  line-height: 0;
  pointer-events: none;
}

/* Assis sur le bord haut, a cheval : le bas du corps passe DERRIERE le
   bloc, ce qui est exactement ce qui fait croire qu'il est pose dessus
   et non colle devant.

   Le chevauchement passe par translate et NON par une marge en pour
   cent. Une marge en pourcentage se resout sur la LARGEUR du bloc
   conteneur : sur la liste de prix, large de 1152 px, -22 % valaient
   -253 px et le lutin atterrissait au milieu du catalogue, en travers
   de la colonne des prix. Un translate en pourcentage, lui, se resout
   sur la hauteur de l'element. */
.zm-pose--assis-hd { right: clamp(0.5rem, 4%, 3rem); bottom: 100%; }
.zm-pose--assis-hg { left: clamp(0.5rem, 4%, 3rem);  bottom: 100%; }
.zm-pose--assis-hd .zm,
.zm-pose--assis-hg .zm { transform: translateY(32%); }

/* Appuye au bord BAS, debout au pied du bloc : le haut du corps passe
   derriere la derniere ligne.

   Toutes les poses restent a GAUCHE quand la page porte la mascotte
   perchee : celle-la est fixe contre le bord droit, et deux lutins qui
   se chevauchent au meme moment ne font plus qu'une tache. */
.zm-pose--appui-bg { left: clamp(0.5rem, 4%, 3rem); top: 100%; }
.zm-pose--appui-bg .zm { transform: translateY(-15%); }

/* Accoude au bord lateral : il sort du bloc d'un bon tiers.

   Ancre en HAUT et non en bas. Sur une colonne d'avis de 1512 px de
   long, bottom: 0 le posait a un endroit ou personne ne s'arrete ; en
   haut il est a hauteur du resume « 4,82 sur 5 », c'est-a-dire la ou le
   regard se pose en arrivant sur la section. */
.zm-pose--accoude-d { left: 100%; top: 0; }
.zm-pose--accoude-g { right: 100%; top: 0; }
.zm-pose--accoude-d .zm { transform: translateX(-30%); }
.zm-pose--accoude-g .zm { transform: translateX(30%); }

/* Accoudee a la colonne d'avis de l'accueil, elle passait sur le titre
   de la colonne voisine (« Recent 5-star reviews », 1440) et mordait de
   30 % sur la premiere carte. Elle descend sous le titre, le resume et
   le lien (20rem), et ne mord plus : elle reste dans la colonne de
   gauche. En pile (sous 64rem), la colonne d'avis prend toute la
   largeur et il n'y a plus de cote ou s'accouder : elle s'efface. */
.sa-feedbacks .zm-pose--accoude-g { top: 20rem; }
.sa-feedbacks .zm-pose--accoude-g .zm { transform: none; }

@media (max-width: 63.99rem) {
  .zm-pose--accoude-d,
  .zm-pose--accoude-g { display: none; }
}

/* Sous 48rem un lutin qui deborde sort de l'ecran ou mord sur le texte :
   les poses laterales rentrent, les poses hautes retrecissent. */
@media (max-width: 48rem) {
  .zm-pose--accoude-d,
  .zm-pose--accoude-g { display: none; }
  /* On vise .zm et pas la pose : le gabarit pose --zm-px EN LIGNE sur
     le lutin, et un style en ligne bat une feuille. */
  .zm-pose--assis-hd .zm,
  .zm-pose--assis-hg .zm,
  .zm-pose--appui-bg .zm { width: 88px; }
  .zm-pose--assis-hd .zm,
  .zm-pose--assis-hg .zm { transform: translateY(26%); }
  .zm-pose--appui-bg .zm { transform: translateY(-12%); }
}

/* ------------------------------------------------------------
   28a  LE HEROS
   ------------------------------------------------------------ */

.sa-hero-haut {
  display: grid;
  gap: var(--z-6);
  align-items: center;
}

@media (min-width: 62rem) {
  .sa-hero-haut {
    grid-template-columns: minmax(0, 1fr) clamp(17rem, 32%, 25rem);
    gap: var(--z-7);
  }
}

.zm-scene {
  position: relative;
  display: grid;
  place-items: center;
  isolation: isolate;
  justify-self: center;
  width: min(100%, var(--zm-scene-px, 25rem));
  /* Sans minmax(0, 1fr), la colonne implicite prend le min-content de
     l'image — ses 400 px — et deborde la scene sur les petits ecrans. */
  grid-template-columns: minmax(0, 1fr);
  /* Trois moteurs ecrivent ici, chacun dans ses variables, et la
     transformation les compose : la boucle rAF tient rx / ry / sc,
     GSAP tient ey et bond, personne ne se marche dessus. */
  transform:
    perspective(1100px)
    translateY(calc(var(--zm-ey, 0) * 1px))
    rotateX(var(--zm-rx, 0deg))
    rotateY(var(--zm-ry, 0deg))
    scale(calc(var(--zm-sc, 1) * var(--zm-bond, 1)));
  opacity: var(--zm-eo, 1);
  transform-style: preserve-3d;
}

.zm-scene .zm {
  position: relative;
  z-index: 2;
  grid-area: 1 / 1;
}

/* La lueur. Elle se decale a contresens du basculement : c'est ce
   decalage, plus que la rotation, qui donne le relief. */
/* Deux calques, et il en faut deux.

   CSS n'interpole PAS une propriete personnalisee non declaree : une
   image-cle qui ferait varier --zm-halo-o sauterait d'un palier a
   l'autre au lieu de fondre. La pulsation vit donc sur le
   pseudo-element, en opacite et en echelle ordinaires, pendant que le
   parent garde la parallaxe (boucle rAF) et le fondu d'entree (GSAP).
   Chacun son calque, chacun ses proprietes. */
.zm-halo {
  grid-area: 1 / 1;
  z-index: 1;
  position: relative;
  width: 104%;
  aspect-ratio: 1;
  transform: translate3d(var(--zm-hx, 0px), var(--zm-hy, 0px), 0);
  opacity: calc(var(--zm-halo-e, 1) * var(--zm-halo-h, 1));
  transition: opacity var(--dur) var(--z-ease);
  pointer-events: none;
}

.zm-halo::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background:
    radial-gradient(closest-side,
      rgba(var(--z-boost), 0.34) 0%,
      rgba(var(--z-charge), 0.16) 48%,
      rgba(var(--z-charge), 0) 72%);
  opacity: 0.88;
}

/* Le survol appuie sur la lueur plutot que sur la taille : agrandir la
   mascotte au survol la fait sortir de sa colonne, l'eclairer non. */
.zm-scene:hover .zm-halo { --zm-halo-h: 1.32; }

/* Les particules. Six, pas trente : elles doivent se remarquer une par
   une, sinon c'est de la neige. Elles ont leur propre calque : en freres
   directs de la lueur et du lutin, leur nth-child aurait commence a 3. */
.zm-pts {
  grid-area: 1 / 1;
  z-index: 3;
  opacity: var(--zm-pts-o, 1);
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.zm-pt {
  grid-area: 1 / 1;
  width: 4px;
  height: 4px;
  border-radius: 999px;
  background: rgb(var(--z-halo));
  box-shadow: none;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

/* Les particules derivaient sans fin : une boucle d'ambiance, interdite
   (DIRECTION.md, « Mouvement »). Le calque reste dans le gabarit
   (hero.njk), il ne se peint plus. */
.zm-pts { display: none; }

.zm-pt:nth-child(1) { --zm-dx: -42%; --zm-dy:  18%; --zm-d: 0s;   --zm-t: 7.5s; }
.zm-pt:nth-child(2) { --zm-dx:  38%; --zm-dy:  26%; --zm-d: 1.3s; --zm-t: 8.4s; background: rgb(var(--z-charge)); box-shadow: none; }
.zm-pt:nth-child(3) { --zm-dx: -30%; --zm-dy: -22%; --zm-d: 2.6s; --zm-t: 9.1s; }
.zm-pt:nth-child(4) { --zm-dx:  44%; --zm-dy: -10%; --zm-d: 3.9s; --zm-t: 7.9s; background: rgb(var(--z-boost)); box-shadow: none; }
.zm-pt:nth-child(5) { --zm-dx: -46%; --zm-dy:  -4%; --zm-d: 5.1s; --zm-t: 8.8s; }
.zm-pt:nth-child(6) { --zm-dx:  26%; --zm-dy:  34%; --zm-d: 6.2s; --zm-t: 9.6s; background: rgb(var(--z-charge)); box-shadow: none; }

/* Sous 62rem la mascotte passe entre le bloc de texte et la jauge.
   Onze rem, c'est assez pour la reconnaitre et assez peu pour ne pas
   repousser la jauge sous la ligne de flottaison. */
@media (max-width: 61.99rem) {
  .zm-scene { --zm-scene-px: 11rem; }
  .zm-pt { display: none; }
}

/* ------------------------------------------------------------
   28b  LES POSTES FIXES
   ------------------------------------------------------------ */

/* -- Barre de navigation. Vingt-huit pixels, a gauche du nom, et rien
      d'autre : c'est une marque, pas une illustration.

      Elle ne parait que si la boutique n'affiche pas son logo : sinon la
      barre portait deux signes de marque cote a cote, l'eclair Z et le
      chat, avant meme le nom. Voir navbar.njk. -- */
.zm-nav {
  flex: 0 0 auto;
  line-height: 0;
}

.zm-nav .zm {
  width: 28px;
}

/* La marque ne tient pas partout, et on ne fait pas deborder une barre
   pour un ornement. Mesure faite largeur par largeur :
     sous 30rem   — barre compacte deja pleine, +11 px de debordement
     64 a 68rem   — le menu complet vient d'apparaitre, +34 px
   Entre les deux (30 a 64rem) la barre est repliee derriere le menu
   burger et il reste la place ; au-dessus de 68rem aussi. */
@media (max-width: 29.99rem),
       (min-width: 64rem) and (max-width: 67.99rem) {
  .zm-nav { display: none; }
}

/* -- Pied de page. Elle regarde la boutique depuis le coin, tres pale,
      derriere le contenu. Aucune requete de plus : c'est le fichier du
      heros, deja en cache. -- */
.sa-footer { position: relative; overflow: hidden; }

.zm-pied {
  position: absolute;
  right: -2.5rem;
  bottom: -3.5rem;
  z-index: 0;
  width: 15rem;
  opacity: 0.09;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(closest-side, #000 52%, transparent 88%);
  mask-image: radial-gradient(closest-side, #000 52%, transparent 88%);
}

.sa-footer > *:not(.zm-pied) { position: relative; z-index: 1; }

@media (max-width: 48rem) {
  .zm-pied { width: 10rem; right: -2rem; bottom: -2.5rem; opacity: 0.07; }
}

/* -- Le bouton du planificateur. La mascotte sort de derriere son bord
      droit au survol.

      Elle ne sort ni au-dessus ni en dessous, et sur ce bouton-la
      seulement. Mesure faite : au-dessus du bouton du planificateur il y
      a la rangee d'options (« 1 month / 3 months / Invite link »), et
      au-dessus de « Add to cart » un bloc de choix — dans les deux cas
      elle recouvrait une commande cliquable. A droite du bouton du
      planificateur, en revanche, il y a 298 px de vide.

      « Add to cart » n'a pas de vide autour : « Buy now » le colle. Sur
      celui-la, la reponse au survol est la mascotte du coin, qui change
      d'expression — elle ne masque rien.

      A la souris uniquement : au doigt il n'y a pas de survol. -- */
@media (hover: hover) and (pointer: fine) and (min-width: 48rem) {
  .bp-cta { position: relative; }

  .bp-cta::after {
    content: "";
    position: absolute;
    left: calc(100% - 16px);
    top: 50%;
    width: 58px;
    height: 58px;
    background-image: var(--zm-guetteur);
    background-size: contain;
    background-repeat: no-repeat;
    opacity: 0;
    transform: translate(-46%, -50%) scale(0.82);
    transform-origin: 0 50%;
    transition:
      opacity 180ms var(--z-ease-out),
      transform 280ms var(--z-ease);
    pointer-events: none;
  }

  .bp-cta:hover::after {
    opacity: 1;
    transform: translate(0, -50%) scale(1);
  }
}

/* -- Le compagnon du catalogue. Un seul lutin pour toutes les lignes,
      qui vole de l'une a l'autre. Quinze lutins et quinze boucles pour
      un effet qu'on ne voit que sur la ligne survolee, c'etait payer
      cher une chose qu'on ne regarde pas.

      Il se pose a cheval sur le bord droit de la ligne : a l'interieur
      il couvrirait le prix, a l'exterieur il sortirait de la colonne.
      Son socle opaque est parti avec le detourage — il ne reste que la
      lueur, et la ligne se lit maintenant sous lui. -- */

.zm-compagnon {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
  width: 54px;
  height: 54px;
  border-radius: 999px;
  background: radial-gradient(closest-side at 50% 44%,
    rgba(var(--z-boost), 0.30), transparent 72%);
  opacity: 0;
  line-height: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

.zm-compagnon img {
  display: block;
  width: 100%;
  height: auto;
}

/* ------------------------------------------------------------
   28c  L'ASSISTANT FLOTTANT
   ------------------------------------------------------------ */

.zma {
  position: fixed;
  right: var(--z-4);
  bottom: var(--z-4);
  z-index: 46;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--z-2);
}

.zma[hidden] { display: none; }

/* Range : il y a du texte ou une commande sous le lanceur (custom.js,
   « Le lanceur ne recouvre rien »). Il s'efface sans quitter sa place ;
   le focus clavier le fait revenir. Un fondu, pas un glissement, et rien
   sous mouvement reduit (regle de la section 9). */
.zma {
  transition: opacity var(--motion-fast, 200ms) var(--z-ease), visibility 0s;
}

.zma.est-range:not(:focus-within) {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--motion-fast, 200ms) var(--z-ease), visibility 0s linear var(--motion-fast, 200ms);
}

/* Le lanceur reste en bas, quoi qu'il arrive. Dans l'ordre du document
   il vient AVANT le panneau qu'il commande — c'est ce que le clavier et
   les lecteurs d'ecran doivent rencontrer — mais a l'ecran il doit
   rester sous le pouce : sans ces ordres, ouvrir le panneau poussait le
   bouton au milieu de l'ecran. */
.zma-bulle   { order: 1; }
.zma-panneau { order: 2; }
.zma-bouton  { order: 3; }

/* -- Le lanceur -- */

.zma-bouton {
  position: relative;
  width: 58px;
  height: 58px;
  padding: 0;
  border: 1px solid var(--cl-border);
  border-radius: 999px;
  background: rgb(var(--zm-fond));
  overflow: hidden;
  cursor: pointer;
  transition:
    border-color var(--dur) var(--z-ease),
    box-shadow var(--dur) var(--z-ease),
    transform 140ms var(--z-ease);
}

.zma-bouton .zm {
  width: 100%;
  background: none;
}

.zma-bouton .zm-img {
  width: 118%;
  max-width: none;
  margin: -9% 0 0 -9%;
}

.zma-bouton:hover,
.zma-bouton:focus-visible {
  border-color: rgba(var(--z-boost), 0.8);
  box-shadow: 0 0 0 4px rgba(var(--z-boost), 0.16);
}

.zma-bouton:active { transform: scale(0.94); }

.zma-pastille {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: rgb(var(--z-halo));
  box-shadow: 0 0 0 2px rgb(var(--zm-fond));
}

.zma-pastille[hidden] { display: none; }

/* -- La bulle -- */

.zma-bulle {
  position: relative;
  max-width: min(17rem, calc(100vw - 2.5rem));
  padding: var(--z-3) 2rem var(--z-3) var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--cl-surface), 0.96);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.zma-bulle[hidden] { display: none; }

.zma-bulle-txt {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-primary));
}

.zma-bulle-lien {
  display: inline-block;
  margin-top: var(--z-1);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
  color: rgb(var(--cl-accent));
  text-decoration: underline;
  text-underline-offset: 3px;
}

.zma-bulle-lien[hidden] { display: none; }

.zma-bulle-x {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--z-r-field);
  background: none;
  color: rgb(var(--cl-t-muted));
  cursor: pointer;
}

.zma-bulle-x:hover { color: rgb(var(--cl-t-primary)); }

/* -- Le panneau -- */

.zma-panneau {
  width: min(20rem, calc(100vw - 2rem));
  padding: var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--cl-surface), 0.97);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

.zma-panneau[hidden] { display: none; }

.zma-tete-panneau {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-2);
}

.zma-titre {
  margin: 0;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1rem;
  color: rgb(var(--cl-t-primary));
}

.zma-x {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--z-r-field);
  background: none;
  color: rgb(var(--cl-t-muted));
  cursor: pointer;
}

.zma-x:hover { color: rgb(var(--cl-t-primary)); }

.zma-mot {
  margin: var(--z-2) 0 var(--z-4);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

.zma-act {
  display: block;
  padding: var(--z-3);
  margin-top: var(--z-2);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
  color: rgb(var(--cl-t-primary));
  text-decoration: none;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}

.zma-act:hover {
  border-color: rgba(var(--z-boost), 0.7);
  background-color: rgba(var(--cl-t-primary), 0.03);
}

.zma-act-note {
  display: block;
  margin-top: 2px;
  font-family: var(--ff-body);
  font-weight: 400;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

/* custom.js accroche une pastille de presence a tout lien discord.gg :
   « 635 members, 76 online right now ». Sur l'action principale du
   panneau d'aide, c'est exactement le renseignement utile — quelqu'un
   est la, maintenant. On ne l'ecarte donc pas, on lui fait une place :
   deux rangees, l'icone a cheval sur les deux. */
.zma-act--fort {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 2px var(--z-3);
  margin-top: 0;
  border: 0;
  background-image: linear-gradient(
    100deg,
    rgb(var(--z-charge)) -30%,
    rgb(var(--cl-accent)) 55%,
    rgb(var(--z-halo)) 160%
  );
  color: rgb(var(--cl-on-accent));
  transition: filter var(--dur) var(--z-ease);
}

.zma-act--fort > svg {
  grid-row: 1 / span 2;
  align-self: center;
}

.zma-act--fort .zlive {
  grid-column: 2;
  font-size: 0.75rem;
  color: rgba(var(--cl-on-accent), 0.78);
}

.zma-act--fort .zlive-n { color: rgb(var(--cl-on-accent)); }

.zma-act--fort .zlive-sep { background: rgba(var(--cl-on-accent), 0.35); }

.zma-act--fort:hover {
  filter: brightness(1.08);
  background-color: transparent;
}

/* -- Le partage du coin.

      Avant la mascotte, le coin bas-droit portait deja trois choses :
      l'aide Discord, le retour en haut, et la pile de notifications
      (z-60, donc par-dessus les deux autres). L'assistant reprend la
      place de l'aide, mais il est plus haut de 14 px et il ouvre un
      panneau : entasser le retour en haut au-dessus aurait fait une
      colonne de trois objets flottants sur un telephone.

      Le retour en haut part donc a gauche. C'est un geste rare et
      secondaire, le coin gauche est vide, et la colonne de droite
      redevient lisible : lanceur en bas, notifications juste dessus. -- */

/* Le lien d'aide disparait des que JavaScript prend la main, avant meme
   le premier rendu : l'assistant en reprend la fonction. */
.zma-actif .zsup { display: none; }

.zma-actif .totop {
  left: var(--z-4);
  right: auto;
  bottom: var(--z-4);
}

.zma-actif .sa-toasts { bottom: calc(var(--z-4) + 68px); }

@media (max-width: 64rem) {
  .zma { bottom: calc(var(--z-4) + 64px); }
  .zma-actif .totop { bottom: calc(var(--z-4) + 64px); }
  .zma-actif .sa-toasts { bottom: calc(var(--z-4) + 132px); }
}

/* ------------------------------------------------------------
   28d  LES ETATS VIDES
   ------------------------------------------------------------ */

/* Au doigt, les deux croix de l'assistant passent a 44 px comme tout le
   reste de la boutique (section 23). A la souris elles restent petites :
   une croix de 44 px a cote d'un titre de 16 px pese trop. */
@media (pointer: coarse) {
  .zma-x,
  .zma-bulle-x {
    width: 44px;
    height: 44px;
  }

  .zma-bulle { padding-right: 2.75rem; }
  .zma-bulle-x { top: 2px; right: 2px; }
}

.zm-etat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--z-3);
  padding: var(--z-7) var(--z-4);
  text-align: center;
}

.zm-etat .zm { width: clamp(6rem, 22vw, 8.5rem); }

/* -- Ce qu'on sait avant de payer : la livraison, et le prix de
      chaque moyen. Les frais ne sont visibles qu'a la derniere etape
      dans la fenetre de paiement ; ici ils sont de +15 % sur la carte,
      ce qui n'est pas un detail qu'on decouvre apres avoir saisi son
      adresse. -- */

.zpay {
  margin-top: var(--z-4);
  padding: var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface));
}

.zpay-tete {
  display: flex;
  align-items: center;
  gap: var(--z-3);
}

.zpay-tete .zm { flex: 0 0 auto; }

.zpay-mot {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

.zpay-mot strong {
  display: block;
  font-family: var(--ff-heading);
  font-weight: 700;
  color: rgb(var(--cl-t-primary));
}

.zpay-titre {
  margin: var(--z-4) 0 var(--z-2);
  padding-top: var(--z-3);
  border-top: 1px solid var(--cl-border);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.zpay-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--z-1);
}

/* Le nom a gauche, le prix a droite, un filet de points entre les
   deux : c'est la forme d'un menu, et c'est exactement ce que
   l'acheteur est en train de lire. */
.zpay-item {
  display: flex;
  align-items: baseline;
  gap: var(--z-2);
  font-size: 0.8125rem;
}

.zpay-item::after {
  content: "";
  order: 1;
  flex: 1 1 auto;
  height: 0;
  border-bottom: 1px dotted var(--cl-border);
  transform: translateY(-0.2em);
}

.zpay-nom {
  order: 0;
  color: rgb(var(--cl-t-primary));
}

.zpay-frais {
  order: 2;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: rgb(var(--cl-warning));
}

.zpay-frais--nul {
  font-weight: 600;
  color: rgb(var(--cl-success));
}

/* ------------------------------------------------------------
   28e  LE MOUVEMENT

   Tout ce qui bouge sans qu'on le lui demande est ici, et seulement
   ici. Rien ne tourne hors ecran : .zm-dort est pose par un
   IntersectionObserver.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  /* Plus de flottement, de respiration ni de pulsation sans fin (.zm--flotte,
     .zm--respire, la lueur, les particules) : des boucles que personne
     n'avait declenchees. Les classes restent posees par les gabarits et
     ne font plus rien. Il reste ce qui repond : l'entree (une fois), le
     sursaut au clic, la bulle et le panneau qui s'ouvrent. */
  .zm-entree { animation: zm-entree 560ms var(--z-ease) both; }
  .zm-saute { animation: zm-saute 420ms var(--z-ease); }

  .zma-bulle { animation: zm-entree 300ms var(--z-ease) both; }
  .zma-panneau { animation: zm-panneau 220ms var(--z-ease) both; }

  .zm-dort,
  .zm-dort * { animation-play-state: paused !important; }
}

@keyframes zm-entree {
  from { opacity: 0; transform: translate3d(0, 12px, 0) scale(0.97); }
}

@keyframes zm-panneau {
  from { opacity: 0; transform: translate3d(0, 10px, 0) scale(0.98); }
}

@keyframes zm-saute {
  0%   { transform: scale(1); }
  30%  { transform: scale(0.94); }
  62%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}

/* ============================================================
   29  LA SCENE DE PAIEMENT

   La <dialog> du paiement est ouverte avec showModal() : elle vit dans
   le top layer, au-dessus de tout z-index, et son voile est fixe a
   rgba(0,0,0,.75). Notre scene se voit donc a 25 %, quoi qu'on fasse.

   Tout le dessin decoule de ce chiffre. A 25 %, un filet de 1 px
   disparait, un texte de 13 px devient illisible, une nuance se perd.
   Ne survivent que des masses lumineuses et du mouvement lent. La
   scene est donc faite de trois choses seulement — un degrade qui
   derive, une lueur qui respire, des particules — et le texte est
   place a gauche, la ou la modale (512 px, centree) ne vient jamais.

   Avant que la modale s'ouvre, en revanche, la scene est a pleine
   lumiere pendant une demi-seconde. C'est le seul moment ou la
   mascotte se voit vraiment, et c'est pour lui qu'elle est grande.
   ============================================================ */

.zck {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  align-items: center;
  overflow: hidden;
  background: rgb(var(--zm-fond));
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms var(--z-ease);
}

.zck[hidden] { display: none; }

.zck.est-ouvert {
  opacity: 1;
  pointer-events: auto;
}

/* Une fois la modale de paiement ouverte, la scene ne doit plus rien
   intercepter : elle est decorative, et le clic hors modale doit
   arriver au voile de la <dialog> qui la ferme. */
.zck.est-derriere { pointer-events: none; }

/* -- Le fond. Deux degrades qui derivent l'un contre l'autre : c'est
      ce qui reste lisible a 25 % d'opacite. -- */

.zck-fond {
  position: absolute;
  inset: -25%;
  background:
    radial-gradient(42% 46% at 22% 34%, rgba(var(--z-charge), 0.55), transparent 70%),
    radial-gradient(38% 42% at 78% 68%, rgba(var(--z-boost), 0.50), transparent 72%),
    radial-gradient(30% 34% at 62% 18%, rgba(var(--z-halo), 0.26), transparent 74%);
  will-change: transform;
}

.zck-lueur {
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(60rem, 120vw);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 999px;
  background:
    radial-gradient(closest-side,
      rgba(var(--z-boost), 0.30),
      rgba(var(--z-charge), 0.12) 52%,
      transparent 74%);
  will-change: opacity, transform;
}

.zck-pts {
  position: absolute;
  inset: 0;
}

.zck-pt {
  position: absolute;
  bottom: -6%;
  width: 5px;
  height: 5px;
  border-radius: 999px;
  background: rgb(var(--z-halo));
  box-shadow: none;
  opacity: 0;
  will-change: transform, opacity;
}

.zck-pt:nth-child(1) { left:  8%; --zck-d: 0s;   --zck-t: 12s; }
.zck-pt:nth-child(2) { left: 21%; --zck-d: 2.1s; --zck-t: 14s; background: rgb(var(--z-charge)); box-shadow: none; }
.zck-pt:nth-child(3) { left: 34%; --zck-d: 4.4s; --zck-t: 11s; }
.zck-pt:nth-child(4) { left: 47%; --zck-d: 6.2s; --zck-t: 15s; background: rgb(var(--z-boost)); box-shadow: none; }
.zck-pt:nth-child(5) { left: 61%; --zck-d: 1.3s; --zck-t: 13s; }
.zck-pt:nth-child(6) { left: 74%; --zck-d: 8.0s; --zck-t: 12.5s; background: rgb(var(--z-charge)); box-shadow: none; }
.zck-pt:nth-child(7) { left: 86%; --zck-d: 3.4s; --zck-t: 16s; }
.zck-pt:nth-child(8) { left: 94%; --zck-d: 9.6s; --zck-t: 12s; background: rgb(var(--z-boost)); box-shadow: none; }

/* -- Le rail. Colonne de gauche : la modale fait 512 px et reste
      centree, elle n'y vient jamais. -- */

.zck-rail {
  position: relative;
  justify-self: start;
  width: min(23rem, 34vw);
  margin-left: clamp(var(--z-5), 6vw, 7rem);
  text-align: left;
}

/* -- La mascotte de la scene.

      Les lutins portent leur propre fond, rgb(7,11,24), et ne se
      fondent que sur cette couleur-la. Ici le decor est un degrade
      colore : sans rien, on verrait la boite du lutin.

      D'ou les deux calques. Une lueur d'abord, large et derriere ;
      puis un disque de la couleur exacte du fond des lutins, qui
      s'eteint vers ses bords. Le lutin se pose au centre du disque,
      sur sa propre couleur, et le disque disparait dans le degrade.
      Ce n'est pas une pastille decorative : c'est la condition pour
      que le decoupage tienne sur un fond colore. -- */

.zck-mascotte {
  position: relative;
  width: fit-content;
  margin-bottom: var(--z-5);
  isolation: isolate;
}

.zck-mascotte::after,
.zck-mascotte::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  border-radius: 999px;
  pointer-events: none;
}

.zck-mascotte::after {
  z-index: -2;
  width: 215%;
  aspect-ratio: 1;
  background:
    radial-gradient(closest-side,
      rgba(var(--z-boost), 0.55),
      rgba(var(--z-charge), 0.22) 48%,
      transparent 72%);
}

/* La partie pleine du disque doit couvrir le lutin en entier, sinon
   son bord tombe dans le degrade et le carre reapparait. Le disque
   fait 172 % de la largeur du lutin ; la partie pleine s'arrete a
   62 % du rayon, soit 0,53 de cette largeur — juste au-dela de la
   demi-largeur du lutin. */
.zck-mascotte::before {
  z-index: -1;
  width: 172%;
  aspect-ratio: 1;
  background:
    radial-gradient(closest-side,
      rgb(var(--zm-fond)) 62%,
      rgba(var(--zm-fond), 0) 93%);
}

.zck-mascotte .zm { position: relative; z-index: 0; }

.zck[data-etat="paye"] .zck-mascotte::after {
  background:
    radial-gradient(closest-side,
      rgba(var(--cl-success), 0.55),
      rgba(var(--z-charge), 0.18) 48%,
      transparent 72%);
}

.zck[data-etat="echec"] .zck-mascotte::after {
  background:
    radial-gradient(closest-side,
      rgba(var(--cl-error), 0.5),
      rgba(var(--z-boost), 0.16) 48%,
      transparent 72%);
}

.zck-etat {
  margin: 0;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: clamp(1.25rem, 2.2vw, 1.75rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: rgb(var(--cl-t-primary));
}

.zck-detail {
  margin: var(--z-2) 0 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

.zck-echappe {
  display: inline-block;
  margin-top: var(--z-4);
  padding-block: var(--z-2);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
  color: rgb(var(--cl-accent));
  text-decoration: underline;
  text-underline-offset: 3px;
}

.zck-echappe[hidden] { display: none; }

/* -- Sous 68rem il n'y a plus de colonne libre a gauche : la modale
      occupe la largeur. Le rail se range en bas, sur une ligne, et la
      mascotte tombe a 64 px — assez pour etre la, assez peu pour ne
      rien couvrir. -- */

@media (max-width: 68rem) {
  .zck { align-items: end; }

  .zck-rail {
    width: auto;
    justify-self: stretch;
    display: flex;
    align-items: center;
    gap: var(--z-3);
    margin: 0 var(--z-4) calc(var(--z-4) + env(safe-area-inset-bottom));
  }

  .zck-mascotte { margin: 0; flex: 0 0 auto; }
  .zck-mascotte .zm { width: 76px; }
  .zck-mots { min-width: 0; }
  .zck-etat { font-size: 1rem; }
  .zck-detail { font-size: 0.8125rem; }
  .zck-echappe { margin-top: var(--z-1); font-size: 0.8125rem; }
}

/* -- Les etats. Le texte change, la lueur change avec lui : c'est
      elle qui porte l'information a 25 % d'opacite, pas les mots. -- */

.zck[data-etat="paye"] .zck-lueur {
  background:
    radial-gradient(closest-side,
      rgba(var(--cl-success), 0.42),
      rgba(var(--z-charge), 0.14) 52%,
      transparent 74%);
}

.zck[data-etat="echec"] .zck-lueur {
  background:
    radial-gradient(closest-side,
      rgba(var(--cl-error), 0.38),
      rgba(var(--z-boost), 0.10) 52%,
      transparent 74%);
}

/* ------------------------------------------------------------
   29b  LES COMMANDES D'ACHAT AU DOIGT

   Releve au doigt, a 390 px : le pas de quantite fait 36x34, son
   champ 48x34, « Buy now » 316x42, les onglets 42 de haut. Tout cela
   passe le minimum AA (24x24) mais rate la regle que la boutique
   s'est donnee (section 23), et ce sont precisement les commandes
   qu'on manipule pour acheter. On les remet a 44.
   ------------------------------------------------------------ */

@media (pointer: coarse) {
  /* Le fil d'Ariane faisait 20 px de haut, sous le minimum AA lui-meme
     (24x24). On l'epaissit par le remplissage, sans toucher au
     rythme de la ligne. */
  .sa-breadcrumbs a {
    display: inline-block;
    padding-block: 12px;
  }

  .sa-product-form button,
  .sa-product-form input[type="number"],
  .sa-product-form input[inputmode="numeric"],
  /* Les onglets de la fiche n'ont ni role ni classe propre : on les
     prend par leur bordure basse, qui est ce qui les distingue. */
  .sa-page-product button.border-b-2,
  .sa-page-cart a.inline-flex,
  .zm-etat a.inline-flex {
    min-height: 44px;
  }

  .sa-product-form button:not([class*="w-full"]):not(.sa-add-to-cart) {
    min-width: 44px;
  }
}

/* ------------------------------------------------------------
   29a  LE MOUVEMENT DE LA SCENE
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  .zck.est-ouvert .zck-fond { animation: zck-derive 26s ease-in-out infinite; }
  .zck.est-ouvert .zck-lueur { animation: zck-souffle 7.4s ease-in-out infinite; }
  .zck.est-ouvert .zck-pt { animation: zck-monte var(--zck-t, 13s) linear var(--zck-d, 0s) infinite; }

  /* A la reussite, la lueur donne un coup avant de reprendre son
     souffle. C'est la seule fois ou la scene hausse le ton. */
  .zck[data-etat="paye"] .zck-lueur { animation: zck-eclat 1.1s var(--z-ease) 1, zck-souffle 7.4s ease-in-out 1.1s infinite; }
}

@keyframes zck-derive {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  33%      { transform: translate3d(3%, -2.5%, 0) scale(1.06); }
  66%      { transform: translate3d(-2.5%, 2%, 0) scale(1.03); }
}

@keyframes zck-souffle {
  0%, 100% { opacity: 0.72; transform: translate(-50%, -50%) scale(1); }
  50%      { opacity: 1;    transform: translate(-50%, -50%) scale(1.09); }
}

@keyframes zck-eclat {
  0%   { opacity: 0.8;  transform: translate(-50%, -50%) scale(0.92); }
  38%  { opacity: 1;    transform: translate(-50%, -50%) scale(1.22); }
  100% { opacity: 0.72; transform: translate(-50%, -50%) scale(1); }
}

@keyframes zck-monte {
  0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.6); }
  12%  { opacity: 0.9; }
  80%  { opacity: 0.55; }
  100% { opacity: 0; transform: translate3d(2vw, -108vh, 0) scale(1.15); }
}


/* ============================================================
   30  CE QUE L'AUDIT DU 2026-09-20 A MESURE

   Dix pages, trois largeurs (360 / 768 / 1440), sur la production.
   Chaque regle ci-dessous porte le releve qui la motive : rien n'est
   corrige « au cas ou ». Ce bloc est le dernier du fichier, donc a
   specificite egale c'est lui qui tranche.
   ============================================================ */

/* -- 5 px de defilement horizontal a 360 px, sur les dix pages. --

   Compte fait sur la page : le groupe d'actions de la barre (recherche,
   schema, panier, menu — quatre cibles de 44 px separees de 6) mesure
   194 px, la marque 131, l'ecart impose par gap-6 en mesure 24 et les
   marges interieures 32. Total 381 pour 360 disponibles.

   Les 44 px ne bougent pas : c'est le doigt qui les impose, pas la
   fenetre. On reprend les 21 px sur les ecarts, qui ne servent plus a
   rien des qu'il n'y a plus de place. Reste 15 px de marge. */
@media (max-width: 29.99rem) {
  .sa-navbar .wrap {
    padding-inline: 12px;
    gap: 8px;
  }

  .sa-navbar .wrap > div.ml-auto,
  .sa-navbar .wrap > div:last-child { gap: 2px; }
}

/* -- Cibles sous 44 px au doigt. --

   Trois familles relevees a 360 et a 768 : les liens de tri des avis
   (38 px de haut), la pagination (38), et le bouton « Filter » du
   catalogue (38). La section 23 les avait manquees parce qu'elle
   nommait les classes une par une et que celles-la n'y etaient pas.

   Le :not([class*=":hidden"]) n'est pas decoratif : imposer un display
   a un element que Tailwind masque le fait reapparaitre. La lecon a
   deja coute un debordement de 46 px sur la barre de navigation. */
@media (pointer: coarse) {
  .sa-page-feedback-link:not([class*=":hidden"]),
  .sa-pagination a:not([class*=":hidden"]),
  .sa-pagination span[class*="inline-flex"] {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  /* Les fleches precedent / suivant n'ont qu'un chevron a porter : px-2
     les laissait a 18 px de large, sous le plancher de 24 de la 2.5.8.
     Les numeros, eux, font deja 42 a 47. */
  .sa-pagination a[rel="next"],
  .sa-pagination a[rel="prev"],
  .sa-pagination span[class*="rounded-l-input"],
  .sa-pagination span[class*="rounded-r-input"] {
    min-width: 44px;
    justify-content: center;
  }

  /* Celui-la porte lui-meme la classe lg:hidden, donc le :not ci-dessus
     l'aurait exclu. Il n'a de toute facon besoin que d'une hauteur : on
     ne lui impose aucun display, donc rien a epargner. */
  .sa-page-products button[class*="lg:hidden"] { min-height: 44px; }
}

/* -- Le planificateur perdait 150 px en haut, a chaque chargement. --

   Son <svg class="bp-sprite"> ne contient que des <defs><symbol> : rien
   a peindre. Mais un <svg> sans attribut de taille prend la taille par
   defaut du remplacement, 300x150, et il etait en display: block dans
   le flux. Le panneau s'ouvrait donc sur un rectangle vide aussi haut
   qu'un paragraphe, avant son titre.

   Ni display: none ni visibility: hidden : Safari cesse alors de
   resoudre les <use> qui pointent dedans. On le sort du flux et on le
   reduit a rien, ce qui laisse les symboles referencables. */
.bp-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* -- Les etoiles des avis. --

   Le gabarit les peint en text-warning, un ambre qui n'existe nulle
   part ailleurs : la palette va du bleu au rose. Elles prennent le rose
   d'accent, qui est deja la couleur de texte accentuee du theme
   (7,8:1 sur le fond sombre, 5,9:1 sur blanc en schema clair). */
.zm-etoile { color: rgb(var(--z-halo)); }

/* -- Le heros respirait de 48 px entre la promesse et la jauge, alors
      que la colonne de texte s'arrete deja 56 px plus haut que la
      mascotte : l'ecart lu faisait 104 px et coupait la demonstration
      de la promesse qu'elle demontre. -- */
.sa-hero-scale { margin-top: var(--z-6); }

/* -- La mascotte du planificateur. --

   Elle repond au curseur : cinq expressions pour vingt positions, une
   par palier. Elle est posee dans le pied, a cote du verdict, parce que
   c'est la meme phrase dite deux fois — une en mots, une en visage.
   Ailleurs elle aurait ete une illustration de plus.

   Le socle n'est pas decoratif. Les planches de la mascotte sont
   peintes sur le fond de page (#070B18) et aucun detourage n'est
   possible, la fourrure est noire : pose sur une surface relevee comme
   .bp, le lutin montre son carre. La pastille ramene le fond de page
   sous lui. */
.bp-mine {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background:
    radial-gradient(closest-side at 50% 44%,
      rgba(var(--z-boost), 0.18),
      rgba(var(--zm-fond), 1) 78%);
}

.bp-mine-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Le changement d'expression se joue tout seul : chaque palier porte sa
   propre classe, donc passer de est-heureux a est-excite fait gagner a
   l'element une classe qu'il n'avait pas, et l'animation rejoue. Aucune
   minuterie, aucun etat a tenir en JavaScript. */
@media (prefers-reduced-motion: no-preference) {
  .bp-mine.est-fatigue,
  .bp-mine.est-confus,
  .bp-mine.est-heureux,
  .bp-mine.est-excite,
  .bp-mine.est-amoureux { animation: bp-mine-pop 460ms var(--z-ease); }
}

@keyframes bp-mine-pop {
  0%   { transform: scale(0.82) rotate(-5deg); }
  55%  { transform: scale(1.07) rotate(2deg); }
  100% { transform: scale(1) rotate(0deg); }
}

/* -- Les icones du registre etaient des carres noirs. --

   Les <symbol> du sprite sont dessines au trait : aucun n'a d'attribut
   fill, et la valeur par defaut de fill en SVG est NOIR, pas none. Six
   des douze lignes affichaient donc un pave sombre a la place d'une
   icone. Poser fill: none et stroke: currentColor sur le <svg> suffit :
   ce sont des proprietes heritees, elles traversent le <use> jusqu'au
   contenu du symbole, et les rares formes qui portent un fill explicite
   (les pupilles, le triangle de lecture) gardent le leur, un attribut
   de presentation battant une valeur heritee. */
.bp-row-ico svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* -- La mascotte du verificateur d'invitation. --

   Deux colonnes : le lutin, puis tout ce qui se dit. Il garde sa place
   d'un etat a l'autre, donc rien ne saute quand la reponse arrive —
   c'est l'interet de n'en avoir qu'un.

   Socle obligatoire : la fiche produit pose ce bloc sur une surface
   relevee, et une planche de mascotte n'existe que sur le fond de page. */
.ilc-out {
  display: flex;
  align-items: flex-start;
  gap: var(--z-3);
}

.ilc-dits {
  flex: 1 1 auto;
  min-width: 0;
}

.ilc-mine {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin-top: 2px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background:
    radial-gradient(closest-side at 50% 44%,
      rgba(var(--z-boost), 0.18),
      rgba(var(--zm-fond), 1) 78%);
}

.ilc-mine-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Meme mecanique que dans le planificateur : chaque issue porte sa
   classe, donc en changer rejoue l'animation sans une ligne de script.
   Le sommeil pendant l'attente respire au lieu de sursauter. */
@media (prefers-reduced-motion: no-preference) {
  .ilc-mine.est-confus,
  .ilc-mine.est-etonne,
  .ilc-mine.est-determine,
  .ilc-mine.est-heureux { animation: bp-mine-pop 460ms var(--z-ease); }

  .ilc-mine.est-fatigue { animation: ilc-somnole 2.2s ease-in-out infinite; }
}

@keyframes ilc-somnole {
  0%, 100% { transform: scale(1) translateY(0); }
  50%      { transform: scale(0.97) translateY(1.5px); }
}

/* -- L'attente. --

   Le petit disque qui tourne dans le coin du champ disait « quelque
   chose se passe » sans dire ou. Une ligne qui balaie le bas du champ le
   dit sur toute sa largeur, dans le meme vocabulaire que les jauges de
   la page : une charge qui avance, du bleu vers le rose. Le disque
   disparait, il faisait double emploi. */
.ilc-field.is-checking .ilc-input { border-color: rgba(var(--cl-accent), 0.55); }

.ilc-spinner {
  right: 0;
  left: 0;
  top: auto;
  bottom: 0;
  width: auto;
  height: 2px;
  margin: 0;
  border: 0;
  border-radius: 0;
  overflow: hidden;
  background: transparent;
  animation: none;
}

.ilc-spinner::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 42%;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    transparent,
    rgb(var(--z-charge)),
    rgb(var(--cl-accent)),
    rgb(var(--z-halo)),
    transparent
  );
}

@media (prefers-reduced-motion: no-preference) {
  .ilc-spinner::after { animation: ilc-balaye 1.15s var(--z-ease) infinite; }
}

@media (prefers-reduced-motion: reduce) {
  .ilc-spinner::after { width: 100%; opacity: 0.7; }
}

@keyframes ilc-balaye {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(238%); }
}

/* -- Les avis : la colonne de gauche suivait le regard, elle reste. --

   Le titre, la note et le lien tenaient dans 300 px en face d'une
   colonne d'avis de 1 880. Sur grand ecran, les deux tiers de la
   section etaient donc un titre isole au-dessus de rien. La colonne
   devient collante : elle accompagne la lecture au lieu de disparaitre
   apres le premier avis.

   align-self: flex-start est indispensable — dans un conteneur flex un
   enfant est etire sur toute la hauteur par defaut, et un element
   etire ne peut pas coller. */
@media (min-width: 64rem) {
  .sa-feedbacks-aside {
    position: sticky;
    top: 6rem;
    align-self: flex-start;
  }
}

/* Le grand guillemet decoratif en tete de chaque avis repetait ce que
   la section 15 dit deja — des citations, pas des cartes — et coutait
   26 px par avis, huit fois de suite. */
.sa-feedback-card--quote > .relative > svg:first-of-type { display: none; }

/* -- Deux cibles restees sous 44 px, vues au second balayage. --

   « Reset filters » (134x38) n'apparait que dans le tiroir de filtres,
   ferme au premier passage ; « Read more » (59x16) que sur un avis
   assez long pour etre tronque. Les deux se touchent au doigt.

   Aucun display impose ici ne peut ressusciter un element masque :
   Alpine pose display: none EN LIGNE sur « Read more », et un style en
   ligne bat une feuille. */
@media (pointer: coarse) {
  .sa-page-products form a[class*="w-full"],
  .sa-feedback-card button[class*="mt-2"] {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* -- Le fil d'Ariane. --

   « Home » mesurait 37 x 44 : la hauteur y etait, la largeur suivait le
   mot. WCAG 2.5.8 (AA) se contente de 24 x 24 et l'exception « en ligne »
   couvre meme le cas, mais la regle de la maison est 44 et rien n'empeche
   de la tenir ici : le texte reste cale a gauche, seule la zone cliquable
   s'etend. */
@media (pointer: coarse) {
  .sa-breadcrumbs a { min-width: 44px; }
}


/* ============================================================
   31  LA MAQUETTE DE SERVEUR MONTRE CE QU'ELLE PROMET

   Le balisage portait `lv-0` a `lv-3` et `has-tag` depuis le premier
   jour — et aucune regle ne s'en servait. La carte etait donc la meme a
   zero boost et a vingt : banniere peinte, pastille GIF, badge de
   niveau, URL personnalisee, tout etait la d'entree. Le planificateur
   expliquait en chiffres ce qu'il refusait de montrer.

   Desormais chaque avantage apparait au palier ou Discord le donne :

     niveau 0   rien. Pas de banniere, pas de GIF, pas de badge, aucun
                mouvement — c'est le point de comparaison, il doit etre
                mort.
     niveau 1   fond d'invitation teinte, icone animee (la pastille GIF
                et son reflet), badge de niveau.
     niveau 2   la banniere se peint.
     niveau 3   la banniere s'anime, l'URL personnalisee arrive.
     +3 en rab  le Server Tag.
     +6 en rab  les styles de role : le tag passe en degrade.

   REGLE ABSOLUE ICI : rien ne doit changer la HAUTEUR de la carte. Le
   fader est juste en dessous ; une carte qui grandit deplace la barre
   sous le doigt en pleine manipulation. Tout se joue donc en opacite,
   en couleur et en largeur — jamais en hauteur.
   ============================================================ */

.bp-srv-banner {
  background: rgb(var(--z-track));
  transition: background var(--dur) var(--z-ease);
}

/* Le fond d'invitation personnalise : c'est ce que le niveau 1 achete,
   et c'est tout ce qu'il achete cote decor. */
.bp-server.lv-1 .bp-srv-banner {
  background:
    radial-gradient(120% 120% at 70% 110%,
      rgba(var(--cl-accent), 0.30), rgba(var(--z-charge), 0.10) 60%,
      rgb(var(--z-track)) 100%);
}

/* La banniere elle-meme n'arrive qu'au niveau 2. */
.bp-srv-art {
  opacity: 0;
  transition: opacity 420ms var(--z-ease);
}

.bp-server.lv-2 .bp-srv-art,
.bp-server.lv-3 .bp-srv-art { opacity: 1; }

/* Ce qui manque, dit en toutes lettres plutot qu'en rectangle vide. */
.bp-srv-vide {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding-bottom: 12px;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
  pointer-events: none;
  transition: opacity 260ms var(--z-ease);
}

.bp-server.lv-2 .bp-srv-vide,
.bp-server.lv-3 .bp-srv-vide { opacity: 0; }

/* -- L'icone. Au niveau 0 elle est plate et muette. -- */

.bp-srv-gif,
.bp-srv-lvlbadge { transition: opacity 260ms var(--z-ease), transform 260ms var(--z-ease); }

.bp-server.lv-0 .bp-srv-gif,
.bp-server.lv-0 .bp-srv-lvlbadge {
  display: none;
}

/* Le Server Tag ne s'achete pas avec un niveau mais avec trois boosts
   EN PLUS du niveau 3 : il etait pourtant affiche en permanence. */
.bp-srv-tag { display: none; }
.bp-server.has-tag .bp-srv-tag { display: inline; }

/* Styles de role ameliores : six boosts de rab. Le degrade sur le tag
   est exactement ce qu'ils donnent, des couleurs de role en degrade. */
.bp-server.has-roles .bp-srv-tag {
  background-image: linear-gradient(100deg,
    rgb(var(--z-charge)), rgb(var(--cl-accent)) 55%, rgb(var(--z-halo)));
  color: rgb(var(--cl-on-accent));
}

/* -- L'URL personnalisee : une ligne dans les deux cas, donc aucune
      variation de hauteur. -- */
.bp-srv-vanity {
  opacity: 0.55;
  font-style: italic;
  transition: opacity var(--dur) var(--z-ease), color var(--dur) var(--z-ease);
}

.bp-srv-vanity.est-acquis {
  opacity: 1;
  font-style: normal;
  color: rgb(var(--cl-t-primary));
}

/* ------------------------------------------------------------
   31a  LE MOUVEMENT DE LA MAQUETTE

   Il n'y en a aucun au niveau 0, et c'est le point : la difference
   entre une carte morte et une carte qui respire est ce que le client
   achete. Chaque animation correspond a un avantage nomme.

   2026-10-20 (budget 4/10) : chaque effet joue UNE fois quand le palier
   est atteint, puis se pose. Ils tournaient sans fin (reflet, balayage,
   huit etoiles, soleil), meme hors ecran. Une animation ne rejoue que si
   son NOM change : chaque palier a donc sa copie des images-cles
   (-1/-2/-3), et franchir un palier rejoue l'effet. Les classes bp-*
   sont intactes.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {

  /* Icone animee — a partir du niveau 1. Un reflet qui passe, lent,
     comme un avatar en GIF. */
  .bp-server:not(.lv-0) .bp-srv-icon::after {
    content: "";
    position: absolute;
    inset: -40%;
    background: linear-gradient(115deg,
      transparent 42%, rgba(255, 255, 255, 0.22) 50%, transparent 58%);
    transform: translateX(120%);
    animation: bp-srv-reflet 1.4s var(--z-ease) both;
    pointer-events: none;
  }

  .bp-server.lv-2 .bp-srv-icon::after { animation-name: bp-srv-reflet-2; }
  .bp-server.lv-3 .bp-srv-icon::after { animation-name: bp-srv-reflet-3; }

  /* Banniere animee — niveau 3 seulement. Une lueur qui traverse, et
     les etoiles qui s'allument l'une apres l'autre. */
  .bp-server.lv-3 .bp-srv-banner::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 45%;
    background: linear-gradient(100deg,
      transparent, rgba(var(--z-halo), 0.22) 45%, rgba(255, 255, 255, 0.14) 55%, transparent);
    transform: translateX(340%);
    animation: bp-srv-balaye 2.2s var(--z-ease) 300ms both;
    pointer-events: none;
  }

  .bp-server.lv-3 .bp-srv-stars circle {
    animation: bp-srv-scintille 1.4s ease-in-out both;
  }

  .bp-server.lv-3 .bp-srv-stars circle:nth-child(2) { animation-delay: 0.4s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(3) { animation-delay: 1.1s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(4) { animation-delay: 0.7s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(5) { animation-delay: 1.8s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(6) { animation-delay: 2.3s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(7) { animation-delay: 1.4s; }
  .bp-server.lv-3 .bp-srv-stars circle:nth-child(8) { animation-delay: 2.9s; }

  .bp-server.lv-3 .bp-srv-sun { animation: bp-srv-respire 1.6s ease-in-out both; }

  /* Au passage d'un palier, les chiffres du serveur se rallument. Chaque
     niveau porte sa classe : en changer suffit a rejouer l'animation,
     sans une ligne de script. */
  .bp-server.lv-1 .bp-srv-perk,
  .bp-server.lv-2 .bp-srv-perk,
  .bp-server.lv-3 .bp-srv-perk { animation: bp-srv-rallume 600ms var(--z-ease); }
}

/* Un passage, de gauche a droite, puis plus rien : le reflet finit hors
   de l'icone (overflow), le balayage hors de la banniere. */
@keyframes bp-srv-reflet   { from { transform: translateX(-120%); } to { transform: translateX(120%); } }
@keyframes bp-srv-reflet-2 { from { transform: translateX(-120%); } to { transform: translateX(120%); } }
@keyframes bp-srv-reflet-3 { from { transform: translateX(-120%); } to { transform: translateX(120%); } }

@keyframes bp-srv-balaye {
  from { transform: translateX(-160%); }
  to   { transform: translateX(340%); }
}

/* Les etoiles s'allument, chacune a son tour (les delais ci-dessus), et
   restent allumees. */
@keyframes bp-srv-scintille {
  0%   { opacity: 0.35; }
  60%  { opacity: 1; }
  100% { opacity: 0.85; }
}

@keyframes bp-srv-respire {
  0%, 100% { opacity: 0.8; transform: scale(1); transform-origin: 318px 74px; }
  50%      { opacity: 1;   transform: scale(1.06); transform-origin: 318px 74px; }
}

@keyframes bp-srv-rallume {
  0%   { border-color: rgba(var(--cl-accent), 0.9); color: rgb(var(--cl-t-primary)); }
  100% { border-color: var(--cl-border); }
}

/* La pastille GIF et le badge de niveau arrivent en apparaissant, pas
   en clignotant : meme mecanique que la mascotte, la classe du palier
   change et l'animation rejoue. */
@media (prefers-reduced-motion: no-preference) {
  .bp-server.lv-1 .bp-srv-lvlbadge,
  .bp-server.lv-2 .bp-srv-lvlbadge,
  .bp-server.lv-3 .bp-srv-lvlbadge,
  .bp-server.has-tag .bp-srv-tag { animation: bp-mine-pop 420ms var(--z-ease); }
}


/* ============================================================
   33  LE PANNEAU EN DIRECT : SA HAUTEUR, ET SON BOUTON

   Deux defauts mesures le 2026-09-21.

   LA HAUTEUR. Les valeurs reservees dataient d'avant une refonte de
   xtremez. Hauteurs relevees sur leurs pages, largeur par largeur, en
   retirant les 65 px de barre rognee :

     largeur du cadre    stock          rachat
     >= 768              953  -> 888    732 -> 667
     479 a 767          1521  -> 1456   807 -> 742
     <= 478             1649  -> 1584   932 -> 867

   On reservait 830 pour le stock : 123 px etaient coupes, et le cadre
   defilait a l'interieur d'un cadre plus court. Trois paliers au lieu
   de deux, parce que la courbe a une marche a 768 ET une pente
   continue en dessous — un seul palier bas obligeait a reserver 1600
   partout, soit 300 px de vide sur une tablette.

   LE BOUTON. Leur panneau n'a aucun rafraichissement, verifie dans le
   DOM des deux cadres : ses chiffres ne bougent qu'au chargement de la
   page. Celui-ci recharge le cadre — le seul levier qu'une page hote
   ait sur un document d'une autre origine.
   ============================================================ */

.xz-head {
  display: flex;
  align-items: flex-start;
  gap: var(--z-4);
  margin-bottom: var(--z-4);
}

.xz-mots { flex: 1 1 auto; min-width: 0; }

.xz-mine {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  margin-top: 4px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  background:
    radial-gradient(closest-side at 50% 44%,
      rgba(var(--z-boost), 0.16),
      rgba(var(--zm-fond), 1) 78%);
}

.xz-mine .zm { width: 100%; }

/* -- Le bouton. C'est un <a> : sans JavaScript il ouvre le panneau
      entier, qui recharge les chiffres lui aussi. -- */
.xz-maj {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  min-height: 44px;
  padding: 0 var(--z-4);
  border: 1px solid rgba(var(--z-edge), 0.8);
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-surface));
  color: rgb(var(--cl-t-primary));
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color var(--dur) var(--z-ease),
              background-color var(--dur) var(--z-ease),
              transform 90ms var(--z-ease);
}

.xz-maj:hover {
  border-color: rgb(var(--cl-accent));
  background: rgba(var(--cl-accent), 0.12);
}

.xz-maj:active { transform: translateY(1px); }

.xz-maj-ico {
  width: 16px;
  height: 16px;
  flex: none;
}

/* Pendant le rechargement : le bouton refuse un deuxieme clic, et son
   icone tourne. Le mot change aussi — la couleur ne porte jamais seule. */
.xz.est-en-maj .xz-maj {
  pointer-events: none;
  color: rgb(var(--cl-t-muted));
  border-color: var(--cl-border);
}

@media (prefers-reduced-motion: no-preference) {
  .xz.est-en-maj .xz-maj-ico { animation: xz-turn 900ms linear infinite; }
}

/* Sous 40rem le bouton passe sous le texte : a cette largeur, garder
   trois colonnes ecrase le titre a six caracteres par ligne. */
@media (max-width: 40rem) {
  .xz-head { flex-wrap: wrap; }
  .xz-mots { flex: 1 1 60%; }
  .xz-maj { order: 3; width: 100%; justify-content: center; }
}

/* -- Le troisieme palier de hauteur. L'ordre compte : a 480 les deux
      requetes s'appliquent, et c'est la derniere ecrite qui tranche. -- */
@container (max-width: 480px) {
  .xz-frame { height: var(--xz-hs); }
}


/* ============================================================
   34  LA MASCOTTE PERCHEE SUR LE BORD HAUT

   Demandee « grande, en haut, appuyee sur le bord, le regard vers le
   contenu ». Le premier ecran n'a pas de place pour elle : la barre de
   navigation est collante et occupe toute la largeur jusqu'a 111 px, le
   titre tient de 144 a 816 et la mascotte du heros de 927 a 1296. Il ne
   reste aucune colonne libre.

   Elle prend donc le relais APRES le heros. On ne voit jamais les deux
   en meme temps, le premier ecran ne bouge pas d'un pixel, et la
   promesse est tenue : une grande mascotte accrochee au bord haut, qui
   regarde le contenu defiler.

   Le detail qui fait tout : z-index 4 contre 5 pour la barre. La barre
   lui coupe le haut du crane, donc elle a l'air APPUYEE dessus au lieu
   de flotter sous elle.
   ============================================================ */

.zm-perche {
  position: fixed;
  /* 116 et pas 34 : la barre collante inclut le bandeau d'annonce et
     descend jusqu'a 111 px, meme page defilee. A 34 elle avalait les
     83 px du crane et il ne restait que le buste. Ici la tete affleure
     son filet, et le flottement de 11 px ne l'y renvoie pas. */
  top: 116px;
  /* Collee au bord de la fenetre, pas de la grille. Mesure faite sur
     1440 : le contenu s'arrete a 1238 px (le prix unitaire du catalogue
     est la colonne la plus a droite) et une silhouette de 206 px collee
     au bord commence a 1234. Elle effleure la grille sans couvrir un
     prix — c'est la limite a tenir, un ornement ne masque pas un
     chiffre. */
  right: 0;
  z-index: 4;
  /* 168 px etait la taille d'un lutin qui ne pouvait RIEN chevaucher :
     au-dela, son rectangle de fond aurait mordu sur le contenu. Detouree,
     elle peut deborder, et « grande » redevient possible. La borne haute
     s'arrete a 232 : au-dela elle couvrirait la colonne des prix du
     catalogue, mesuree a 1310 px de bord droit sur une fenetre de 1440. */
  width: clamp(130px, 14vw, 206px);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, -26px, 0) scale(var(--zm-p-sc, 1));
  transition: opacity 420ms var(--z-ease),
              transform 420ms var(--z-ease),
              visibility 0s linear 420ms;
}

/* Le curseur qui s'approche. Elle ne recoit aucun evenement — elle est
   en pointer-events: none pour ne jamais voler un clic — donc c'est la
   distance qui est mesuree, pas un survol. Elle se redresse un peu et
   tourne le regard vers la main qui vient. */
.zm-perche.est-proche { --zm-p-sc: 1.06; }

.zm-perche.est-la {
  opacity: 1;
  visibility: visible;
  transform: translate3d(0, 0, 0) scale(var(--zm-p-sc, 1));
  transition-delay: 0s, 0s, 0s;
}

.zm-perche .zm { width: 100%; }

/* La lueur, et plus rien d'autre.

   Elle portait jusqu'ici DEUX degrades empiles : la lueur violette, et
   dessous un disque du fond de page, opaque au centre. Ce disque n'etait
   pas un effet, c'etait un cache — perchee, elle survole le contenu, et
   le carre de sa planche se voyait des qu'elle passait sur une carte.

   Detouree, elle n'a plus rien a cacher. Le disque opaque est retire :
   ce qui reste est la lueur seule, et on voit enfin la carte AU TRAVERS
   de ce qui l'entoure. */
.zm-perche-lueur {
  position: absolute;
  left: 50%;
  top: 46%;
  width: 165%;
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 999px;
  background:
    radial-gradient(closest-side,
      rgba(var(--z-boost), 0.26),
      rgba(var(--z-charge), 0.11) 52%,
      transparent 72%);
  pointer-events: none;
}

/* La lueur ne respire plus (boucle d'ambiance, retiree le 2026-10-20) :
   elle est posee, et l'arrivee de la mascotte (.est-la) suffit.

   Elle ne recouvre plus rien. Fixe contre le bord droit, elle mordait
   sur tout ce qui va jusqu'au bord de la grille : le cadre du stock et
   son bouton Refresh, les dates des avis (mesure 1440, passe1/engineer/
   chevauchement-*.jsonl). Elle tient donc dans la gouttiere, la marge
   entre la grille (--container-max) et le bord de la fenetre, moins un
   filet de 8 px ; sans gouttiere d'au moins 104 px (fenetre de moins de
   88rem pour la grille de 1200 px), elle ne parait pas. */
.zm-perche {
  width: min(clamp(130px, 14vw, 206px),
             calc((100vw - var(--container-max, 1200px)) / 2 - 8px));
}

@media (max-width: 87.99rem) {
  .zm-perche { display: none; }
}

/* La raison d'origine de la coupure a 62rem etait le rectangle : sans
   marge a droite, un bloc opaque de 168 px mordait sur le texte. Une
   silhouette detouree, elle, se pose dessus sans le cacher — on peut
   donc la garder sur tablette, en plus petit et collee au bord.

   Sur un telephone elle s'efface toujours : a 390 px de large, meme
   detouree, elle couvre un tiers de l'ecran et l'assistant flottant
   (.zma) occupe deja ce coin. */
@media (max-width: 62rem) {
  .zm-perche {
    width: clamp(108px, 17vw, 150px);
    right: 0;
    top: 104px;
  }
}

@media (max-width: 44rem) {
  .zm-perche { display: none; }
}


/* ===================================================================
   13. LA PAGE JURIDIQUE
   ===================================================================

   Ce que cette page est vraiment : trois reglements de produit, pas un
   contrat. Un acheteur y arrive avec une question unique — « est-ce que
   j'ai droit a un remplacement, et quelle preuve faut-il ? » — souvent
   sur un telephone, souvent contrarie.

   La page depense donc sa couleur a un seul endroit : le VERDICT. Chaque
   clause dit d'un coup d'oeil si elle demande quelque chose a l'acheteur
   (ambre) ou si elle lui refuse un recours (rouge). Tout le reste — les
   bandes de produit, les blocs de section, le rail — reste sourd, gris
   bleute, sans ombre. Une page ou tout crie ne dit rien.

   Deux verdicts et pas trois : un feu tricolore reclamerait un vert, et
   rien dans ce document n'accorde quoi que ce soit sans condition.

   Mesure d'origine avant reprise, sur zeusmarket.io/terms-of-service :
     - 5168 px de haut au bureau, 6675 px sur telephone ;
     - 45 paragraphes vides survivants ;
     - 56 elements sur 263 rendus en Times New Roman ;
     - le sommaire en liste nue, position: static, sans fond ni etiquette ;
     - les h2 a 16 px, leur taille venant d'un style en ligne.
   ------------------------------------------------------------------- */

:root {
  /* Hauteur de l'en-tete colle + air. custom.js la remesure. */
  --legal-offset: 6rem;
  --legal-rail: 17rem;
}

/* -- 13.1 La bande de provenance ----------------------------------- */

.legal-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--z-4) var(--z-6);
  padding-bottom: var(--z-5);
  /* De l'air pour le lutin assis sur le bord haut du rail : a 3rem il
     chevauchait le filet de la bande de provenance. */
  margin-bottom: var(--z-8);
  border-bottom: 1px solid var(--cl-border);
}

.legal-meta-lede {
  max-width: 44ch;
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: rgb(var(--cl-t-muted));
}

.legal-meta-lede:empty { display: none; }

/* L'avertissement, dit ici une fois en entier. Chaque resume n'en porte
   plus que le rappel en quatre mots : dix-huit copies d'une meme phrase
   finissent par n'etre lues nulle part. */
.legal-legende {
  flex-basis: 100%;
  order: 3;
  max-width: 68ch;
  margin: var(--z-2) 0 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

.legal-meta-side {
  display: flex;
  align-items: center;
  gap: var(--z-5);
  margin-left: auto;
}

.legal-meta-date {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: rgb(var(--cl-t-muted));
}

.legal-meta-label {
  display: block;
  color: rgba(var(--cl-t-muted), 0.8);
}

.legal-meta-date time {
  color: rgb(var(--cl-t-primary));
  font-variant-numeric: tabular-nums;
}

.legal-pdf {
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  min-height: 2.75rem;
  padding: 0 var(--z-4);
  border: 1px solid rgba(var(--z-edge), 0.5);
  border-radius: var(--z-r-action);
  background: transparent;
  color: rgb(var(--cl-t-primary));
  font-family: var(--ff-heading);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}

.legal-pdf:hover {
  border-color: rgba(var(--z-boost), 0.7);
  background: rgba(var(--z-boost), 0.1);
}

/* -- 13.2 La grille : rail + document ------------------------------ */

.legal-doc { position: relative; }

.legal-doc .sa-page-terms-body { max-width: 68ch; }

@media (min-width: 68rem) {
  .legal-doc--rail {
    display: grid;
    grid-template-columns: var(--legal-rail) minmax(0, 1fr);
    gap: 0 var(--z-8);
    align-items: start;
  }
  .legal-doc--rail > .legal-rail { grid-column: 1; grid-row: 1 / span 2; }
  .legal-doc--rail > .sa-page-terms-body { grid-column: 2; grid-row: 1; }
  .legal-doc--rail > .legal-aide { grid-column: 2; grid-row: 2; }
}

/* -- 13.3 Le rail : d'abord un selecteur de produit ---------------- */

.legal-rail-dedans {
  position: relative;
  padding: var(--z-5) var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--cl-t-primary), 0.03);
}

@media (min-width: 68rem) {
  .legal-rail { position: sticky; top: var(--legal-offset); }
  .legal-rail-dedans {
    max-height: calc(100vh - var(--legal-offset) - var(--z-6));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* La jauge de lecture. Elle occupe le bord gauche du panneau : une barre
   posee en travers du haut se lit comme un chargement, pas comme une
   position. */
.legal-jauge {
  position: absolute;
  inset: var(--z-4) auto var(--z-4) 0;
  width: 2px;
  border-radius: 2px;
  background: rgb(var(--z-track));
}

.legal-jauge-fil {
  width: 100%;
  height: calc(var(--legal-progres, 0) * 100%);
  border-radius: 2px;
  background: linear-gradient(to bottom, rgb(var(--z-charge)), rgb(var(--z-boost)));
}

.legal-rail-liste,
.legal-rail-sections {
  margin: 0;
  padding: 0;
  list-style: none;
}

.legal-rail-produit + .legal-rail-produit { margin-top: var(--z-1); }

.legal-rail-lien {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  padding: var(--z-2) var(--z-3);
  border-radius: var(--z-r-field);
  color: rgb(var(--cl-t-muted));
  font-size: 0.875rem;
  line-height: 1.35;
  text-decoration: none;
  transition: color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}

.legal-rail-lien:hover {
  color: rgb(var(--cl-t-primary));
  background: rgba(var(--cl-t-primary), 0.05);
}

.legal-rail-lien--produit {
  font-family: var(--ff-heading);
  font-size: 0.9375rem;
  font-weight: 600;
  color: rgb(var(--cl-t-primary));
}

/* La marque du produit. Pas un numero : on ne lit pas ces trois
   reglements dans l'ordre, on lit celui qu'on a achete. */
.legal-rail-marque {
  flex: none;
  width: 3px;
  height: 1.05em;
  border-radius: 2px;
  background: rgb(var(--z-track));
  transition: background-color var(--dur) var(--z-ease), height var(--dur) var(--z-ease);
}

.legal-rail-produit.is-ouvert .legal-rail-marque,
.legal-rail-lien--produit.is-on .legal-rail-marque {
  background: rgb(var(--z-boost));
}

.legal-rail-sections {
  overflow: hidden;
  max-height: 0;
  margin-left: calc(var(--z-3) + 3px + var(--z-2));
  border-left: 1px solid var(--cl-border);
  opacity: 0;
  transition: max-height 320ms var(--z-ease), opacity 200ms var(--z-ease);
}

.legal-rail-produit.is-ouvert .legal-rail-sections {
  max-height: 24rem;
  opacity: 1;
}

.legal-rail-sections .legal-rail-lien {
  padding-left: var(--z-3);
  border-radius: 0 var(--z-r-field) var(--z-r-field) 0;
  font-size: 0.8125rem;
}

.legal-rail-lien.is-on {
  color: rgb(var(--cl-t-primary));
  background: rgba(var(--z-boost), 0.12);
}

.legal-rail-sections .legal-rail-lien.is-on {
  box-shadow: inset 2px 0 0 rgb(var(--z-boost));
}

.legal-rail-outils {
  margin-top: var(--z-4);
  padding-top: var(--z-3);
  border-top: 1px solid var(--cl-border);
}

.legal-rail-tout {
  min-height: 2.25rem;
  padding: 0 var(--z-2);
  border: 0;
  background: none;
  color: rgb(var(--cl-t-muted));
  font-size: 0.8125rem;
  cursor: pointer;
}

.legal-rail-tout:hover { color: rgb(var(--cl-t-primary)); }

/* -- 13.4 Le rail sur telephone -----------------------------------

   Pas un rail retreci : autre chose. Sur un ecran de 390 px la seule
   question qui vaut d'etre repondue en permanence est « dans quel
   reglement je suis ». On garde donc les trois produits en rangee
   collee sous l'en-tete, et on laisse tomber la liste des sections :
   les chevrons de chaque produit sont a portee de pouce juste dessous.
   ------------------------------------------------------------------ */

@media (max-width: 67.999rem) {
  .legal-rail {
    position: sticky;
    top: var(--legal-offset);
    z-index: 3;
    margin-bottom: var(--z-5);
  }
  .legal-rail-dedans {
    padding: var(--z-2);
    background: rgba(var(--zm-fond), 0.88);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
  }
  .legal-rail-liste {
    display: flex;
    gap: var(--z-1);
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
  }
  .legal-rail-liste::-webkit-scrollbar { display: none; }
  .legal-rail-produit { flex: none; scroll-snap-align: start; }
  .legal-rail-produit + .legal-rail-produit { margin-top: 0; }
  .legal-rail-lien--produit { min-height: 2.75rem; white-space: nowrap; }
  .legal-rail-sections,
  .legal-rail-outils,
  .legal-doc > .legal-lutin { display: none; }
  .legal-jauge {
    inset: auto 0 0 0;
    width: auto;
    height: 2px;
  }
  .legal-jauge-fil {
    width: calc(var(--legal-progres, 0) * 100%);
    height: 100%;
    background: linear-gradient(to right, rgb(var(--z-charge)), rgb(var(--z-boost)));
  }
}

/* -- 13.5 Le produit : une bande, pas une carte -------------------- */

.legal-part + .legal-part { margin-top: var(--z-8); }

.legal-part-tete {
  display: flex;
  align-items: center;
  gap: var(--z-4);
  padding-bottom: var(--z-3);
  border-bottom: 2px solid rgba(var(--z-boost), 0.55);
  scroll-margin-top: var(--legal-offset);
}

.editor .legal-part-tete h2 {
  flex: 1;
  margin: 0;
  font-family: var(--ff-heading);
  font-size: clamp(1.5rem, 1.15rem + 1.3vw, 1.9rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: rgb(var(--cl-t-primary));
  scroll-margin-top: var(--legal-offset);
}

.legal-part-bouton {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-field);
  background: transparent;
  color: rgb(var(--cl-t-muted));
  cursor: pointer;
  transition: color var(--dur) var(--z-ease), border-color var(--dur) var(--z-ease);
}

.legal-part-bouton:hover {
  color: rgb(var(--cl-t-primary));
  border-color: rgba(var(--z-edge), 0.6);
}

.legal-part-bouton svg {
  width: 1.125rem;
  height: 1.125rem;
  transition: transform 220ms var(--z-ease);
}

.legal-part.is-ferme .legal-part-bouton svg { transform: rotate(-90deg); }

.legal-part-corps {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 280ms var(--z-ease), opacity 180ms var(--z-ease);
}

/* UN seul enfant, et c'est la condition du procede : la grille ne met a
   0fr que les rangees qu'elle a declarees. Quinze enfants faisaient
   quatorze rangees implicites en auto, et la section repliee gardait
   ses 2727 px — invisible parce que l'opacite tombait, mais toujours
   dans le flux et toujours lue a voix haute. */
.legal-part-interieur {
  min-height: 0;
  overflow: hidden;
}

.legal-part.is-ferme .legal-part-corps {
  grid-template-rows: 0fr;
  opacity: 0;
}

/* -- 13.6 La section : un bloc sourd ------------------------------- */

.legal-bloc {
  margin-top: var(--z-6);
  padding: var(--z-5) var(--z-5) var(--z-5) var(--z-5);
  border: 1px solid var(--cl-border);
  border-left: 2px solid rgba(var(--z-edge), 0.45);
  border-radius: 0 var(--z-r-panel) var(--z-r-panel) 0;
  background: rgba(var(--cl-t-primary), 0.022);
}

.editor .legal-bloc > h3 {
  margin: 0;
  font-family: var(--ff-heading);
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.25;
  color: rgb(var(--cl-t-primary));
  scroll-margin-top: var(--legal-offset);
}

.editor .legal-bloc h4 {
  margin: var(--z-6) 0 0;
  padding-top: var(--z-5);
  border-top: 1px solid var(--cl-border);
  font-family: var(--ff-heading);
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.3;
  color: rgb(var(--cl-t-primary));
  scroll-margin-top: var(--legal-offset);
}

.editor .legal-bloc h4:first-of-type { border-top: 0; }

.legal-part-interieur > * + *,
.legal-bloc > * + * { margin-top: var(--z-4); }

.legal-bloc > h4 + *,
.legal-bloc > h3 + * { margin-top: var(--z-3); }

.legal-bloc > ul,
.legal-bloc > ol { margin-bottom: var(--z-4); }

.editor .legal-part p,
.editor .legal-part li {
  color: rgba(var(--cl-t-primary), 0.9);
  font-size: 1rem;
  line-height: 1.7;
}

.editor .legal-part-interieur > p { max-width: var(--z-measure); }

/* -- 13.7 Le verdict : le seul endroit ou la page hausse la voix ---

   Le resume est en VERRE et le texte qui engage ne l'est pas : la
   difference de matiere dit, avant qu'on lise l'avertissement, que ces
   deux choses ne sont pas du meme ordre.
   ------------------------------------------------------------------ */

.legal-resume {
  margin: var(--z-4) 0 var(--z-5);
  padding: var(--z-4);
  border: 1px solid rgba(var(--z-edge), 0.3);
  border-radius: var(--z-r-action);
  background: rgba(var(--cl-t-primary), 0.045);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
}

/* Un verdict sans resume n'est pas un panneau : c'est une pastille
   posee sous son titre. Quatre clauses de ce document sont deja plus
   courtes que ne le serait leur resume. */
.legal-resume--pastille {
  margin: var(--z-3) 0 var(--z-4);
  padding: 0;
  border: 0;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}

.legal-resume--pastille .legal-verdict { margin-bottom: 0; }

/* La marque du titre. L'emoji etait dans le texte depuis toujours ; il
   n'avait simplement jamais de taille a lui, et suivait celle du titre
   — un pictogramme de 30 px a cote de mots de 30 px. */
.legal-glyphe {
  display: inline-grid;
  place-items: center;
  width: 1.85em;
  height: 1.85em;
  margin-right: 0.15em;
  vertical-align: -0.42em;
  border-radius: var(--z-r-field);
  background: rgba(var(--z-boost), 0.12);
  font-size: 0.52em;
  line-height: 1;
}

.editor .legal-bloc > h3 .legal-glyphe,
.editor .legal-bloc h4 .legal-glyphe { font-size: 0.72em; }

/* Le filet neon, reserve a ce qui refuse un recours. Cinq fois sur la
   page, jamais ailleurs. */
.legal-resume--exclu {
  border-color: rgba(var(--cl-error), 0.42);
  box-shadow: none; /* 1.3 : plus de lueur rouge, le filet suffit */
}

.legal-resume--requis { border-color: rgba(var(--cl-warning), 0.3); }

.legal-verdict {
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  margin: 0 0 var(--z-3);
  padding: 0.3rem 0.6rem 0.3rem 0.45rem;
  border-radius: var(--radius-badge);
  font-family: var(--ff-heading);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.2;
}

.legal-verdict svg { width: 0.9rem; height: 0.9rem; flex: none; }

.editor .legal-verdict--exclu,
.legal-verdict--exclu {
  color: rgb(var(--cl-error));
  background: rgba(var(--cl-error), 0.14);
}

.editor .legal-verdict--requis,
.legal-verdict--requis {
  color: rgb(var(--cl-warning));
  background: rgba(var(--cl-warning), 0.14);
}

.editor .legal-resume-texte {
  margin: 0;
  max-width: 58ch;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: rgba(var(--cl-t-primary), 0.86);
}

.legal-resume-cle {
  color: rgb(var(--cl-t-muted));
  font-family: var(--ff-heading);
  font-weight: 600;
}

.legal-resume-cle::after { content: " — "; }

.editor .legal-resume-avis {
  margin: var(--z-2) 0 0;
  font-size: 0.75rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-muted));
}

/* -- 13.8 Listes ---------------------------------------------------

   Une seule liste de ce document est vraiment une SEQUENCE : les cinq
   plans que l'enregistrement d'un remplacement Netflix doit montrer,
   dans cet ordre. Elle est la seule numerotee. Les exclusions de
   garantie n'ont pas d'ordre, et un 01/02/03 dessus pretendrait le
   contraire.
   ------------------------------------------------------------------ */

.editor .legal-part ol {
  counter-reset: etape;
  list-style: none;
  padding-left: 0;
}

.editor .legal-part ol > li {
  position: relative;
  padding-left: 2.25rem;
  margin-top: var(--z-3);
}

.editor .legal-part ol > li::before {
  counter-increment: etape;
  content: counter(etape);
  position: absolute;
  left: 0;
  top: 0.1em;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: rgba(var(--z-boost), 0.16);
  color: rgb(var(--z-halo));
  font-family: var(--ff-heading);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* -- 13.9 Le panneau d'aide ---------------------------------------- */

.legal-aide {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--z-5);
  margin-top: var(--z-8);
  padding: var(--z-5) var(--z-5) var(--z-5) var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--z-boost), 0.07);
}

.legal-aide-mots { min-width: 0; }

.legal-aide-titre {
  margin: 0;
  font-family: var(--ff-heading);
  font-size: 1.0625rem;
  font-weight: 600;
  color: rgb(var(--cl-t-primary));
}

.legal-aide-texte {
  margin: var(--z-1) 0 var(--z-3);
  max-width: 46ch;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: rgb(var(--cl-t-muted));
}

.legal-aide-lien {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: 0 var(--z-5);
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
  font-family: var(--ff-heading);
  font-size: 0.875rem;
  font-weight: 700;
  text-decoration: none;
}

.legal-aide-lien:hover { filter: brightness(1.08); }

/* -- 13.10 Les lutins ----------------------------------------------

   Assis sur le bord haut du rail, et debout contre le panneau d'aide :
   les deux seuls endroits de la page ou rien n'est en train d'etre lu.
   Aucun lutin ne s'approche d'une clause — une mascotte souriante a cote
   d'un « no refund » en adoucirait la lecture, et ce serait la seule
   chose vraiment malhonnete que cette page pourrait faire.
   ------------------------------------------------------------------ */

.legal-lutin { display: none; }
.legal-lutin.zm-pose { display: block; }

.legal-doc > .legal-lutin {
  left: calc(var(--legal-rail) - 7.5rem);
  top: 0;
  bottom: auto;
  right: auto;
}
.legal-doc > .legal-lutin .zm {
  width: 84px;
  transform: translateY(-62%);
}

.legal-aide { padding-left: 7.5rem; }
.legal-aide > .legal-lutin {
  left: var(--z-3);
  bottom: 0;
  right: auto;
  top: auto;
}
.legal-aide > .legal-lutin .zm {
  width: 108px;
  transform: translateY(9%);
}

@media (max-width: 40rem) {
  .legal-aide {
    flex-direction: column;
    align-items: flex-start;
    padding-left: var(--z-5);
    padding-top: 4.5rem;
  }
  .legal-aide > .legal-lutin {
    left: auto;
    right: var(--z-4);
    bottom: auto;
    top: 0;
  }
  .legal-aide > .legal-lutin .zm { width: 84px; transform: translateY(-16%); }
}

/* -- 13.11 Mouvement ----------------------------------------------

   Rien ici : la remise a zero generale de prefers-reduced-motion est
   posee plus haut, en !important sur *. Une regle locale sans
   !important n'aurait pas pu gagner contre elle, et aurait seulement
   donne l'impression que le sujet etait traite deux fois.
   ------------------------------------------------------------------ */

/* -- 13.12 Schema clair -------------------------------------------- */

html[data-scheme="light"] .legal-rail-dedans,
html[data-scheme="light"] .legal-bloc {
  background: rgba(var(--zm-fond), 0.028);
}

html[data-scheme="light"] .legal-resume {
  background: rgba(var(--zm-fond), 0.045);
}

/* -- 13.13 L'impression -------------------------------------------

   Un bouton « Download PDF » qui appelle une bibliotheque produirait un
   second document, a rejouer a chaque modification du texte. La page
   s'imprime elle-meme : ce qui sort est toujours le texte en ligne, et
   ca marche reseau coupe.

   Tout ce qui etait replie est rouvert par le script avant l'appel :
   un contrat imprime avec des sections manquantes serait pire que pas
   de bouton du tout.

   Les resumes NE SONT PAS imprimes. Sur papier, detache de son
   avertissement et de sa matiere de verre, un resume se lirait comme une
   clause. Le PDF ne contient que le texte qui engage.
   ------------------------------------------------------------------ */

.legal-empreinte { display: none; }

@media print {
  .sa-header,
  .sa-footer,
  .sa-announcement,
  .sa-breadcrumbs,
  .zm-perche,
  .zma,
  .zhaut,
  .legal-rail,
  .legal-pdf,
  .legal-part-bouton,
  .legal-aide,
  .legal-resume,
  .legal-lutin,
  .zm-pose,
  /* La legende decrit les resumes, et les resumes ne sont pas
     imprimes : la laisser annoncerait sur le papier une chose que le
     papier ne contient pas. */
  .legal-legende {
    display: none !important;
  }

  @page { margin: 18mm 16mm 22mm; }

  html, body {
    background: #fff !important;
    color: #000 !important;
  }

  .legal-doc,
  .legal-doc--rail {
    display: block !important;
    max-width: none !important;
  }

  .sa-page-terms-body,
  .legal-doc .sa-page-terms-body { max-width: none !important; }

  /* Le titre de la page est peint par le composant d'en-tete de section,
     en encre claire faite pour un fond sombre. Sur blanc il partait a
     5 % de gris : lisible a l'ecran, absent a l'impression. */
  .sa-page-terms h1,
  .sa-page-terms .section-header,
  .sa-page-terms .section-header * {
    color: #000 !important;
    background: none !important;
    -webkit-text-fill-color: #000 !important;
    background-clip: border-box !important;
    -webkit-background-clip: border-box !important;
  }

  .editor,
  .editor p,
  .editor li,
  .editor h2,
  .editor h3,
  .editor h4,
  .editor strong,
  .editor em,
  .editor span {
    color: #000 !important;
    background: none !important;
    -webkit-text-fill-color: #000 !important;
  }

  /* La marque du titre garde son emoji, perd son carre teinte : une
     pastille violette qui ne s'imprime pas laisse un trou. */
  .legal-glyphe {
    width: auto !important;
    height: auto !important;
    background: none !important;
    font-size: 1em !important;
    vertical-align: baseline !important;
  }

  .editor p,
  .editor li { font-size: 10.5pt; line-height: 1.5; }

  .legal-part,
  .legal-bloc {
    break-inside: auto;
    border: 0 !important;
    padding: 0 !important;
    margin: 0 0 10pt !important;
    background: none !important;
  }

  .legal-part { break-before: page; }
  .legal-part:first-of-type { break-before: auto; }

  .legal-part-corps { display: block !important; opacity: 1 !important; }

  .legal-part-tete {
    border-bottom: 1pt solid #000 !important;
    padding-bottom: 4pt;
    margin-bottom: 10pt;
  }

  .editor .legal-part-tete h2 { font-size: 15pt; }
  .editor .legal-bloc > h3 { font-size: 12pt; margin-top: 16pt !important; margin-bottom: 6pt !important; }
  .editor .legal-bloc h4 { font-size: 10.5pt; margin-top: 12pt !important; margin-bottom: 4pt !important; border-top: 0 !important; padding-top: 0; }
  .editor .legal-part p { margin-top: 6pt !important; }
  .editor .legal-part ul,
  .editor .legal-part ol { margin: 6pt 0 !important; }

  h2, h3, h4 { break-after: avoid; }

  .editor .legal-part ol > li::before {
    background: none !important;
    color: #000 !important;
    border: 0.5pt solid #000;
  }

  .legal-meta {
    border-bottom: 0.5pt solid #000 !important;
    color: #000 !important;
  }
  .legal-meta-lede, .legal-meta-date, .legal-meta-label, .legal-meta-date time { color: #000 !important; }

  /* Une fois, a la fin, et pas en position: fixed. Chromium repeint un
     element fixe sur chaque page A L'INTERIEUR de la zone de contenu :
     il se serait pose sur les dernieres lignes de chaque page. Le
     navigateur ajoute deja son propre pied de page avec l'adresse et la
     date ; celui-ci est la pour le cas ou l'acheteur l'a desactive. */
  .legal-empreinte {
    display: block;
    margin-top: 14pt;
    padding-top: 4pt;
    border-top: 0.5pt solid #999;
    color: #444;
    font-size: 8pt;
    break-inside: avoid;
  }
  .legal-empreinte span + span::before { content: " · "; }
}


/* ===================================================================
   14. LA PAGE INTROUVABLE
   ===================================================================

   Celle que SellAuth sert aujourd'hui pese 2 Ko, porte sa propre feuille
   de style et n'a ni en-tete ni pied : c'est une page de la plateforme,
   pas une page de la boutique. On en ecrit une qui appartient au site.

   CE QU'ELLE DOIT FAIRE. Personne n'arrive ici volontairement. Annoncer
   « erreur 404 » n'avance a rien ; ce qui avance, c'est de proposer les
   trois endroits ou le visiteur voulait aller. Un seul bouton « retour a
   la boutique » le renvoie a la case depart et lui laisse le travail.

   La mascotte est CONFUSE, pas souriante. Elle est perdue avec lui, ce
   qui est la seule attitude honnete a cet endroit — un lutin ravi devant
   une page manquante se moquerait du visiteur.
   ------------------------------------------------------------------- */

.sa-perdu {
  display: grid;
  place-items: center;
  min-height: min(82vh, 780px);
  padding-block: var(--z-8);
}

.sa-perdu-dedans {
  /* 1.7 : tout a gauche (DIRECTION : aucun titre centre) ; la mascotte a
     droite du titre au-dessus de 48rem, au-dessus de lui en dessous. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  text-align: left;
}

@media (min-width: 48rem) {
  .sa-perdu-dedans {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--z-6);
    align-items: start;
  }
  .sa-perdu-lutin { grid-column: 2; grid-row: 1 / span 2; margin-bottom: 0; }
  .sa-perdu-titre, .sa-perdu-texte { grid-column: 1; }
  .sa-perdu-titre { grid-row: 1; }
  .sa-perdu-texte { grid-row: 2; }
  .sa-perdu-portes, .sa-perdu-retour { grid-column: 1 / -1; }
}

/* Le lutin n'est pas pose A COTE du texte : il le surplombe, et le code
   d'erreur passe sous lui. C'est ce chevauchement qui fait qu'il
   appartient a la page au lieu de la decorer. */
.sa-perdu-lutin {
  line-height: 0;
  /* Assez pour que le lutin surplombe le texte, pas assez pour manger le
     code d'erreur : a -1.5rem ses pattes passaient devant les lettres. */
  margin-bottom: calc(-1 * var(--z-3));
  pointer-events: none;
}

.sa-perdu-code {
  position: relative;
  z-index: 1;
  margin: 0 0 var(--z-3);
  font-family: var(--ff-heading);
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: rgb(var(--z-halo));
}

.sa-perdu-titre {
  margin: 0 0 var(--z-4);
  max-width: 18ch;
  font-family: var(--ff-heading);
  font-size: clamp(1.9rem, 1.3rem + 2.6vw, 3rem);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: rgb(var(--cl-t-primary));
  text-wrap: balance;
}

.sa-perdu-texte {
  margin: 0 0 var(--z-7);
  max-width: 46ch;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: rgb(var(--cl-t-muted));
}

/* Les trois portes. Une rangee sur grand ecran, une colonne sur
   telephone — et la premiere seule est accentuee : proposer trois
   chemins egaux, c'est ne pas repondre. */
.sa-perdu-portes {
  display: grid;
  gap: var(--z-3);
  width: 100%;
  max-width: 54rem;
  margin-bottom: var(--z-6);
}

@media (min-width: 52rem) {
  .sa-perdu-portes { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.sa-perdu-porte {
  display: flex;
  flex-direction: column;
  gap: var(--z-1);
  min-height: 5.5rem;
  padding: var(--z-4) var(--z-5);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--cl-t-primary), 0.03);
  text-align: left;
  text-decoration: none;
  transition: border-color var(--dur) var(--z-ease),
              background-color var(--dur) var(--z-ease),
              transform var(--dur) var(--z-ease);
}

.sa-perdu-porte:hover {
  border-color: rgba(var(--z-edge), 0.65);
  background: rgba(var(--cl-t-primary), 0.06);
  transform: translateY(-2px);
}

.sa-perdu-porte--fort {
  border-color: rgba(var(--z-boost), 0.55);
  background: rgba(var(--z-boost), 0.1);
}

.sa-perdu-porte--fort:hover {
  border-color: rgba(var(--z-boost), 0.85);
  background: rgba(var(--z-boost), 0.16);
}

.sa-perdu-porte-nom {
  font-family: var(--ff-heading);
  font-size: 1rem;
  font-weight: 600;
  color: rgb(var(--cl-t-primary));
}

.sa-perdu-porte-quoi {
  font-size: 0.875rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-muted));
}

.sa-perdu-retour {
  margin: 0;
  font-size: 0.875rem;
}

.sa-perdu-retour a {
  /* Une cible au doigt, pas seulement un mot souligne : c'est le dernier
     recours de quelqu'un qui est deja perdu. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--z-3);
  color: rgb(var(--cl-t-muted));
  text-decoration: underline;
  text-underline-offset: 3px;
}

.sa-perdu-retour a:hover { color: rgb(var(--cl-t-primary)); }

@media (max-width: 52rem) {
  .sa-perdu-lutin .zm { width: 150px; }
  .sa-perdu { min-height: 0; padding-block: var(--z-7); }
}

/* ==========================================================================
   LA BARRE DE BASCULE DE LANGUE
   ==========================================================================

   Elle annonce que le SITE a choisi la langue, pas le visiteur, et offre
   le retour en un clic. Discrete par construction : elle informe, elle ne
   reclame rien.

   ECRITE EN PROPRIETES LOGIQUES — padding-inline, margin-inline-start,
   border-block-end. Le reste de ce fichier compte 103 declarations
   gauche/droite physiques qu'il faudra retourner pour l'arabe ; celle-ci
   se retournera toute seule. On ne creuse pas le trou qu'on va devoir
   combler.

   Pas d'animation d'entree : la barre est posee par du JavaScript simple
   au chargement, et une transition Alpine sur ce theme reste bloquee —
   c'est le defaut corrige le meme jour.
   ========================================================================== */

.zla {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding-block: 0.5rem;
  padding-inline: 1rem;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: rgb(var(--cl-t-muted));
  background: rgb(var(--cl-surface-raised));
  border-block-end: var(--border-w) solid rgb(var(--cl-border));
}

.zla[hidden] { display: none; }

.zla-txt {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.zla-lien {
  flex: none;
  font-weight: 600;
  color: rgb(var(--cl-accent));
  text-decoration: underline;
  text-underline-offset: 2px;
}

.zla-lien:hover { color: rgb(var(--cl-t-primary)); }

/* Pousse la croix au bout, quel que soit le sens de lecture. */
.zla-x {
  flex: none;
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 32 px : au-dessus du seuil tactile utile, sans peser dans une barre
     de 13 px de texte. */
  inline-size: 2rem;
  block-size: 2rem;
  border-radius: var(--radius-btn, 8px);
  color: rgb(var(--cl-t-muted));
}

.zla-x:hover {
  color: rgb(var(--cl-t-primary));
  background: rgba(var(--cl-t-primary), 0.06);
}

@media (max-width: 480px) {
  /* Sur un telephone, la phrase et le lien ne tiennent pas cote a cote :
     on laisse la phrase passer a la ligne plutot que de la tronquer. */
  .zla { flex-wrap: wrap; row-gap: 0.25rem; }
  .zla-txt { white-space: normal; overflow: visible; }
  .zla-x { margin-inline-start: auto; }
}

/* ============================================================
   35  LA FICHE PRODUIT QUI VEND : NOTE, DUREE, GARDE-FOU
   ============================================================ */

/* La note de la boutique, dans la ligne du titre. Discrete : elle se lit
   d'un coup d'oeil, elle ne concurrence ni le nom ni le prix. */
.zrate {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  text-decoration: none;
}
.zrate b {
  font-family: var(--ff-heading);
  color: rgb(var(--cl-t-primary));
  font-variant-numeric: tabular-nums;
}
.zrate-etoiles { display: inline-flex; gap: 1px; }
.zrate-vide { color: rgba(var(--cl-t-primary), 0.25); }
.zrate:hover span:last-child { text-decoration: underline; text-underline-offset: 2px; }
@media (pointer: coarse) { .zrate { min-height: 44px; } }

/* « Gardez-les 3 mois ». Une carte entiere cliquable, teintee d'accent
   mais sans aplat : c'est une proposition, pas le bouton d'achat. */
.zdp { margin-top: var(--z-3); }
.zdp-lien {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--z-3);
  border: 1px solid rgba(var(--cl-accent), 0.4);
  border-radius: var(--z-r-action);
  background: rgba(var(--cl-accent), 0.06);
  color: rgb(var(--cl-t-muted));
  font-size: 0.8125rem;
  line-height: 1.45;
  text-decoration: none;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}
.zdp-lien:hover {
  border-color: rgb(var(--cl-accent));
  background: rgba(var(--cl-accent), 0.1);
}
.zdp-haut {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-2);
}
.zdp-titre {
  font-family: var(--ff-heading);
  font-size: 0.9375rem;
  color: rgb(var(--cl-t-primary));
}
.zdp-gain {
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
  font-family: var(--ff-heading);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.zdp-detail {
  color: rgb(var(--cl-t-primary));
  font-variant-numeric: tabular-nums;
}
.zdp-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  font-weight: 600;
  color: rgb(var(--cl-t-primary));
}
.zdp-fleche {
  color: rgb(var(--cl-accent));
  transition: transform var(--dur) var(--z-ease);
}
.zdp-lien:hover .zdp-fleche { transform: translateX(3px); }
[dir="rtl"] .zdp-fleche { transform: scaleX(-1); }
[dir="rtl"] .zdp-lien:hover .zdp-fleche { transform: scaleX(-1) translateX(3px); }

/* Le garde-fou. Rouge d'erreur, parce que c'en est une — la commande
   echouera — mais les deux issues restent a egalite de taille : on
   informe, on ne piege pas. */
.ilc-garde {
  margin-top: var(--z-3);
  padding: var(--z-3);
  border: 1px solid rgba(var(--cl-error), 0.55);
  border-radius: var(--z-r-action);
  background: rgba(var(--cl-error), 0.08);
}
.ilc-garde-titre {
  margin: 0 0 4px;
  font-family: var(--ff-heading);
  font-weight: 700;
  color: rgb(var(--cl-t-primary));
}
.ilc-garde-texte {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}
.ilc-garde-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-2);
  margin-top: var(--z-3);
}
.ilc-garde-actions button {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 0 var(--z-3);
  border-radius: var(--z-r-action);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
}
.ilc-garde-corriger {
  border: 0;
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
}
.ilc-garde-passer {
  border: 1px solid var(--cl-border);
  background: transparent;
  color: rgb(var(--cl-t-muted));
}
.ilc-garde-passer:hover { color: rgb(var(--cl-t-primary)); }

@media (prefers-reduced-motion: reduce) {
  .zdp-lien, .zdp-fleche { transition: none; }
}

/* ============================================================
   36  LE RACHAT EN UN CLIC (espace client)
   ============================================================ */

/* Une ligne : la date de fin a gauche, les actions a droite. Elle
   s'efface quand rien ne presse ; elle se colore quand la fin
   approche ou vient de passer — c'est le seul moment ou elle compte. */
.zrc {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-2) var(--z-3);
  margin-top: var(--z-3);
  padding: var(--z-2) var(--z-3);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  background: rgba(var(--cl-t-primary), 0.03);
}
.zrc-quand {
  margin: 0;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  font-variant-numeric: tabular-nums;
}
.zrc-quand.is-bientot {
  color: rgb(var(--cl-t-primary));
  font-weight: 600;
}
.zrc-quand.is-bientot::before,
.zrc-quand.is-fini::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-inline-end: 7px;
  border-radius: 50%;
  vertical-align: 1px;
  background: rgb(var(--cl-accent));
}
.zrc-quand.is-fini::before { background: rgb(var(--cl-warning)); }
.zrc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-2);
}
.zrc-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 0 var(--z-3);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  background: transparent;
  color: rgb(var(--cl-t-primary));
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease);
}
.zrc-btn:hover { border-color: rgba(var(--cl-accent), 0.6); background: rgba(var(--cl-accent), 0.08); }
.zrc-btn--fort {
  border-color: transparent;
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
}
.zrc-btn--fort:hover { background: rgb(var(--cl-accent)); filter: brightness(1.08); }
.zrc-btn:disabled { opacity: 0.6; cursor: progress; }
.zrc-gain {
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(var(--cl-accent), 0.16);
  color: rgb(var(--cl-t-primary));
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.zrc-err {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.8125rem;
  color: rgb(var(--cl-error));
}
.zrc-err a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
@media (pointer: coarse) { .zrc-btn { min-height: 44px; } }
@media (prefers-reduced-motion: reduce) { .zrc-btn { transition: none; } }

/* ============================================================
   37  LA VIDEO PROMO
   ============================================================ */

/* L'en-tete suit celui des autres sections de l'accueil. */
.zvp-tete { margin-bottom: var(--z-4); }
.zvp-titre {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -0.024em;
  color: rgb(var(--cl-t-primary));
}
.zvp-texte {
  margin-top: var(--z-3);
  max-width: var(--z-measure);
  color: rgb(var(--cl-t-muted));
}

/* Le cadre. Le halo est BLEU, celui de la video, pas l'accent violet du
   site : il prolonge la lumiere qui monte du bas de l'image au lieu de
   l'encadrer d'une autre couleur. L'affiche est le fond du cadre — elle
   est la des le premier rendu, sans requete. */
.zvp-cadre {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  overflow: hidden;
  isolation: isolate;
  border-radius: var(--z-r-panel);
  border: 1px solid rgba(96, 150, 255, 0.22);
  background: #03060e var(--zvp-affiche) center / cover no-repeat;
  box-shadow: none; /* 1.3 : halo bleu et ombre retires, le filet reste */
}
.zvp-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.6s var(--z-ease);
}
.zvp.est-vu .zvp-video { opacity: 1; }

/* Le grand bouton de lecture : visible tant que rien ne joue. */
.zvp-lancer {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: #fff;
  cursor: pointer;
}
.zvp-lancer-rond {
  display: grid;
  place-items: center;
  width: 84px;
  height: 84px;
  padding-inline-start: 5px;
  border-radius: 50%;
  background: rgba(8, 14, 30, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.22);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: none;
  transition: transform 0.35s var(--z-ease), background-color 0.35s var(--z-ease);
}
.zvp-lancer:hover .zvp-lancer-rond { transform: scale(1.06); background: rgba(22, 72, 190, 0.55); }
.zvp.est-lecture .zvp-lancer,
.zvp.est-fin .zvp-lancer,
.zvp.est-attente .zvp-lancer { display: none; }

/* Le chargement : un anneau, seulement si le visiteur a clique et attend. */
.zvp-charge {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: none;
  place-items: center;
  pointer-events: none;
}
.zvp.est-attente .zvp-charge { display: grid; }
.zvp-anneau {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.16);
  border-top-color: #fff;
  animation: zvp-tourne 0.9s linear infinite;
}
@keyframes zvp-tourne { to { transform: rotate(360deg); } }

/* Les commandes : du verre sombre, en bas a gauche. La pause est une
   obligation (WCAG 2.2.2 : ce qui bouge seul plus de cinq secondes doit
   pouvoir s'arreter) ; le son est l'invitation. */
.zvp-barre {
  position: absolute;
  inset-inline-start: 14px;
  inset-block-end: 16px;
  z-index: 3;
  display: flex;
  gap: 8px;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.3s var(--z-ease), transform 0.3s var(--z-ease);
}
.zvp.est-vu .zvp-barre { opacity: 1; transform: none; }
.zvp-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-width: 38px;
  height: 38px;
  padding: 0 12px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(6, 10, 22, 0.58);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.25s var(--z-ease), border-color 0.25s var(--z-ease);
}
.zvp-btn:hover { background: rgba(22, 60, 150, 0.62); border-color: rgba(255, 255, 255, 0.3); }
.zvp-i-play, .zvp-i-son { display: none; }
.zvp:not(.est-lecture) .zvp-i-pause { display: none; }
.zvp:not(.est-lecture) .zvp-i-play { display: block; }
.zvp.est-son .zvp-i-muet { display: none; }
.zvp.est-son .zvp-i-son { display: block; }
/* Le son refuse par le navigateur : le bouton se signale, deux fois. */
.zvp.est-son-refuse .zvp-btn--son { animation: zvp-appel var(--motion-emphasis, 560ms) var(--ease-state) 2; }
/* 1.3 : plus d'anneau qui enfle ; le filet du bouton passe a halo et
   revient, deux fois (declenche par le refus du son). */
@keyframes zvp-appel {
  0%, 100% { border-color: rgba(255, 255, 255, 0.18); }
  50% { border-color: rgb(var(--z-halo)); }
}

/* La fin, apres une lecture avec le son. */
.zvp-fin {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: var(--z-4);
  /* Assez sombre pour que le logo de la derniere image ne dispute pas
     l'attention aux deux boutons poses dessus. */
  background: rgba(3, 6, 14, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}
.zvp-fin[hidden] { display: none; }
.zvp-fin-cta,
.zvp-fin-rejouer {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 46px;
  padding: 0 20px;
  border-radius: var(--z-r-action);
  font-weight: 700;
  font-size: 0.9375rem;
  text-decoration: none;
  cursor: pointer;
}
.zvp-fin-cta {
  border: 0;
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
}
.zvp-fin-rejouer {
  border: 1px solid rgba(255, 255, 255, 0.24);
  background: rgba(6, 10, 22, 0.5);
  color: #fff;
}

/* Le fil de progression, au ras du bas. */
.zvp-progres {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 3;
  height: 3px;
  background: rgba(255, 255, 255, 0.08);
}
.zvp-progres span {
  display: block;
  height: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg, #2f7bff, #8fd0ff);
}
[dir="rtl"] .zvp-progres span { transform-origin: right center; }

/* -- Le bouton des fiches produit, sous l'image. -- */
.zvp-racine--bouton { margin-top: var(--z-3); }
.zvp-ouvrir {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  width: 100%;
  padding: 8px;
  padding-inline-end: var(--z-3);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgba(var(--cl-t-primary), 0.03);
  color: rgb(var(--cl-t-primary));
  text-align: start;
  cursor: pointer;
  transition: border-color 0.25s var(--z-ease), background-color 0.25s var(--z-ease);
}
.zvp-ouvrir:hover { border-color: rgba(96, 150, 255, 0.5); background: rgba(47, 123, 255, 0.07); }
.zvp-ouvrir-vignette {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 112px;
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  overflow: hidden;
  background: #03060e var(--zvp-affiche) center / cover no-repeat;
  color: #fff;
  box-shadow: none;
}
.zvp-ouvrir-vignette svg {
  width: 30px;
  height: 30px;
  padding: 7px 6px 7px 8px;
  border-radius: 50%;
  background: rgba(8, 14, 30, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.25);
}
.zvp-ouvrir-mots { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.zvp-ouvrir-mots b { font-family: var(--ff-heading); font-size: 0.9375rem; }
.zvp-ouvrir-mots span { font-size: 0.8125rem; color: rgb(var(--cl-t-muted)); }

/* -- La fenetre. -- */
.zvp-dialog {
  width: min(1120px, 94vw);
  max-width: none;
  max-height: none;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
}
.zvp-dialog::backdrop {
  background: rgba(2, 4, 10, 0.84);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.zvp-dialog[open] { animation: zvp-entre 0.35s var(--z-ease); }
@keyframes zvp-entre { from { opacity: 0; transform: translateY(10px) scale(0.985); } }
.zvp-fermer {
  position: absolute;
  inset-block-end: calc(100% + 10px);
  inset-inline-end: 0;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(8, 14, 30, 0.7);
  color: #fff;
  cursor: pointer;
}
.zvp-fermer:hover { background: rgba(22, 60, 150, 0.7); }

@media (pointer: coarse) {
  .zvp-btn { height: 44px; min-width: 44px; }
}
@media (max-width: 640px) {
  .zvp-lancer-rond { width: 64px; height: 64px; }
  .zvp-barre { inset-inline-start: 10px; inset-block-end: 12px; gap: 10px; }
  /* Sur un cadre de 200 px de haut, deux pastilles de 44 px en couvraient
     le quart. Elles ne font plus que 32 px a l'oeil ; la zone de toucher,
     elle, reste a 44 px grace au ::before qui deborde de 6 px. */
  .zvp-btn { position: relative; height: 32px; min-width: 32px; padding: 0 9px; }
  .zvp-btn::before { content: ''; position: absolute; inset: -6px; }
  .zvp-btn--son { padding: 0; width: 32px; }
  .zvp-btn--son span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .zvp-fin { gap: 8px; }
  .zvp-fin-cta, .zvp-fin-rejouer { min-height: 44px; padding: 0 14px; font-size: 0.875rem; }
  .zvp-ouvrir-vignette { width: 96px; }
}
@media (prefers-reduced-motion: reduce) {
  .zvp-video, .zvp-barre, .zvp-lancer-rond, .zvp-btn, .zvp-ouvrir { transition: none; }
  .zvp-dialog[open], .zvp.est-son-refuse .zvp-btn--son { animation: none; }
}


/* ============================================================
   34  LE PLANIFICATEUR, DEUXIEME GENERATION (2026-10-01)

   Trois etapes, parce que c'est une vraie sequence : le serveur tel
   qu'il est, l'objectif, le pack. Les pastilles numerotees et le fil
   qui les relie ne sont pas de la decoration — le fil se remplit du
   degrade de la charge a mesure que chaque etape est reglee. C'est la
   jauge du site, a la verticale, une quatrieme fois.

   Le seul geste appuye reste la jauge : vingt CASES, comme la barre de
   boosts de Discord. Hachurees = deja sur le serveur ; degrade = ce
   qu'on achete ; rayees au-dela de 14 = le rab qui paie le Tag et les
   styles de role. Tout le reste se tait.

   A droite, un recapitulatif colle au defilement : la maquette du
   serveur, le pack, la frise de duree, le prix et le bouton. On regle
   a gauche, on voit le resultat a droite, sans jamais le perdre.

   REGLE CONSERVEE : rien de ce qui bouge pendant qu'on tire la
   poignee ne change de HAUTEUR au-dessus d'elle.
   ============================================================ */

.bp {
  /* Fin de Passe 1 (revue 1.8) : plus de carte dans la carte. Le
     planificateur n'a ni fond, ni filet, ni rayon ; ses panneaux
     interieurs gardent les leurs. */
  border: 0;
  border-radius: 0;
  background: none;
  padding: clamp(1.25rem, 3vw, 2.25rem) 0;
  /* L'en-tete collant fait 111 px : un saut vers le planificateur
     (bulle de l'assistant, ancre #planner) ne doit pas cacher son titre. */
  scroll-margin-top: 7.5rem;
}

.bp-sr {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.bp-head { margin-bottom: var(--z-6); }

.bp-heading {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -0.024em;
  max-width: 22ch;
  margin: 0;
}

.bp-lede {
  margin: var(--z-3) 0 0;
  max-width: 52ch;
  font-size: 0.9375rem;
  color: rgb(var(--cl-t-muted));
}

.bp-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 24.5rem);
  gap: clamp(1.5rem, 3.4vw, 2.75rem);
  align-items: start;
}

@media (max-width: 59.99rem) {
  .bp-body { grid-template-columns: minmax(0, 1fr); }
}

/* ---- les etapes et leur fil ---- */

.bp-steps { min-width: 0; }

.bp-step {
  position: relative;
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr);
  column-gap: var(--z-4);
  padding-bottom: var(--z-7);
}

.bp-step:last-of-type { padding-bottom: 0; }

/* Le fil. Il part sous la pastille et s'arrete au-dessus de la
   suivante ; une etape reglee le remplit du degrade de charge. */
.bp-step::before {
  content: "";
  position: absolute;
  inset-inline-start: calc(1rem - 1px);
  top: 2.5rem;
  bottom: 0.5rem;
  width: 2px;
  border-radius: 2px;
  background: rgb(var(--z-track));
}

.bp-step:last-of-type::before { display: none; }

.bp-step::after {
  content: "";
  position: absolute;
  inset-inline-start: calc(1rem - 1px);
  top: 2.5rem;
  bottom: 0.5rem;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, rgb(var(--z-charge)), rgb(var(--z-boost)));
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform 420ms var(--z-ease);
}

.bp-step.est-fait:not(:last-of-type)::after { transform: scaleY(1); }
.bp-step:last-of-type::after { display: none; }

.bp-step-mark {
  position: relative;
  z-index: 1;
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--z-edge), 0.8);
  background: rgb(var(--cl-surface));
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.875rem;
  color: rgb(var(--cl-t-primary));
  transition: background-color 220ms var(--z-ease), border-color 220ms var(--z-ease), color 220ms var(--z-ease);
}

.bp-step-mark > * { grid-area: 1 / 1; }

.bp-step-mark svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  display: none;
}

/* Reglee : aplat de charge, ENCRE SOMBRE (le blanc sur #8B5CF6 echoue). */
.bp-step.est-fait .bp-step-mark {
  background: rgb(var(--z-boost));
  border-color: rgb(var(--z-boost));
  color: rgb(var(--cl-background));
}

.bp-step.est-fait .bp-step-mark b,
.bp-step.est-alerte .bp-step-mark b,
.bp-step.est-attention .bp-step-mark b { display: none; }
.bp-step.est-fait .bp-step-ok { display: block; }
.bp-step.est-alerte .bp-step-ko,
.bp-step.est-attention .bp-step-ko { display: block; }

/* Rouge : la livraison echouerait. Ambre : le pack ne couvre pas tout. */
.bp-step.est-alerte .bp-step-mark {
  border-color: rgb(var(--cl-error));
  color: rgb(var(--cl-error));
}

.bp-step.est-attention .bp-step-mark {
  border-color: rgb(var(--cl-warning));
  color: rgb(var(--cl-warning));
}

.bp-step-in { min-width: 0; }

.bp-step-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-3);
  min-height: 2rem;
}

.bp-step-title {
  margin: 0;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.125rem;
  letter-spacing: -0.01em;
}

.bp-step-hint {
  margin: var(--z-1) 0 var(--z-4);
  max-width: 60ch;
  font-size: 0.875rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

/* L'etat d'une etape, en toutes lettres. */
.bp-statut {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  border: 1px solid rgba(var(--z-edge), 0.55);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.75rem;
  line-height: 1.4;
  color: rgb(var(--cl-t-muted));
  white-space: nowrap;
}

.bp-statut::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.bp-statut.is-attente { color: rgb(var(--z-halo)); border-color: rgba(var(--z-halo), 0.45); }
.bp-statut.is-ok { color: rgb(var(--cl-success)); border-color: rgba(var(--cl-success), 0.5); }
.bp-statut.is-note { color: rgb(var(--cl-warning)); border-color: rgba(var(--cl-warning), 0.5); }
.bp-statut.is-alerte,
.bp-statut.is-erreur { color: rgb(var(--cl-error)); border-color: rgba(var(--cl-error), 0.5); }

/* Au telephone, le retrait de 48 px sous chaque pastille coutait 13 %
   de la largeur : les choix de l'etape 3 passaient sur quatre lignes et
   le champ coupait son propre exemple. La pastille remonte a cote du
   titre, le fil disparait, un filet separe les etapes, et tout le
   contenu reprend la pleine largeur. .bp-step-in passe en
   display: contents pour que ses enfants deviennent ceux de la grille. */
@media (max-width: 36rem) {
  .bp-step {
    grid-template-columns: 1.75rem minmax(0, 1fr);
    column-gap: var(--z-3);
    padding-bottom: var(--z-6);
  }

  .bp-step + .bp-step {
    padding-top: var(--z-5);
    border-top: 1px solid var(--cl-border);
  }

  .bp-step::before,
  .bp-step::after { display: none; }

  .bp-step-in { display: contents; }
  .bp-step-in > * { grid-column: 1 / -1; }
  .bp-step-in > .bp-step-head { grid-column: 2; }

  .bp-step-mark {
    width: 1.75rem;
    height: 1.75rem;
    align-self: center;
    font-size: 0.8125rem;
  }
}

/* ---- 1  le serveur ---- */

.bp-inv-field {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  min-height: 52px;
  padding: 4px;
  padding-inline-start: var(--z-3);
  border: 1px solid rgba(var(--z-edge), 0.8);
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-background));
  transition: border-color 160ms var(--z-ease), box-shadow 160ms var(--z-ease);
}

.bp-inv-field:focus-within {
  border-color: rgb(var(--cl-accent));
  /* 1.3 : l'anneau de focus du site, pas un halo */
  outline: 2px solid rgb(var(--z-halo));
  outline-offset: 2px;
}

.bp-inv-field.is-found { border-color: rgba(var(--cl-success), 0.8); }
.bp-inv-field.is-found.a-souci { border-color: rgba(var(--cl-error), 0.8); }
.bp-inv-field.is-format,
.bp-inv-field.is-invalid { border-color: rgb(var(--cl-error)); }
.bp-inv-field.is-busy,
.bp-inv-field.is-offline { border-color: rgb(var(--cl-warning)); }

.bp-inv-ico {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: rgb(var(--cl-t-muted));
  stroke-width: 1.8;
  stroke-linecap: round;
}

/* Les regles de champ du theme (bordure, fond, hauteur) visent
   input[type=text] : on les annule ici, c'est le cadre qui porte tout. */
.bp .bp-inv-field input[type="text"],
.bp .bp-inv-field input[type="text"]:focus {
  flex: 1 1 auto;
  min-width: 0;
  height: 44px;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  outline: none;
  box-shadow: none;
  background: transparent;
  font-family: var(--ff-body, inherit);
  font-size: 1rem;
  color: rgb(var(--cl-t-primary));
}

.bp .bp-inv-field input::placeholder { color: rgba(var(--cl-t-muted), 0.8); }

.bp-inv-clear {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: rgb(var(--cl-t-muted));
  cursor: pointer;
}

.bp-inv-clear:hover { color: rgb(var(--cl-t-primary)); background: rgba(var(--cl-t-primary), 0.06); }

.bp-inv-clear svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.bp-inv-go {
  position: relative;
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 var(--z-4);
  display: inline-grid;
  place-items: center;
  border: 1px solid rgba(var(--cl-accent), 0.5);
  border-radius: 8px;
  background: rgba(var(--cl-accent), 0.14);
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
  color: rgb(var(--cl-t-primary));
  cursor: pointer;
  transition: background-color 150ms var(--z-ease), transform 90ms var(--z-ease);
}

.bp-inv-go > * { grid-area: 1 / 1; }
.bp-inv-go:hover:not(:disabled) { background: rgba(var(--cl-accent), 0.24); }
.bp-inv-go:active:not(:disabled) { transform: translateY(1px); }
.bp-inv-go:disabled { cursor: default; opacity: 0.55; }

.bp-spin {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(var(--cl-t-primary), 0.25);
  border-top-color: rgb(var(--cl-t-primary));
  visibility: hidden;
  animation: bp-tourne 700ms linear infinite;
}

.bp-inv-field.is-checking .bp-inv-go-t { visibility: hidden; }
.bp-inv-field.is-checking .bp-spin { visibility: visible; }
.bp-inv-field.is-checking .bp-inv-go:disabled { opacity: 1; }

@keyframes bp-tourne { to { transform: rotate(360deg); } }

.bp-inv-msg {
  margin: var(--z-2) 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-muted));
}

.bp-inv-msg:empty { display: none; }
.bp-inv-msg.is-format,
.bp-inv-msg.is-invalid { color: rgb(var(--cl-error)); }
.bp-inv-msg.is-busy,
.bp-inv-msg.is-offline { color: rgb(var(--cl-warning)); }

/* Le serveur lu. */
.bp-srv-lu {
  margin-top: var(--z-3);
  padding: var(--z-3);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-surface-raised));
}

.bp-srv-lu-row {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  min-width: 0;
}

.bp-srv-lu-ico {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 14px;
  background: rgb(var(--z-track));
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.9375rem;
}

.bp-srv-lu-ico img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bp-srv-lu-id {
  min-width: 0;
  display: grid;
  gap: 2px;
}

.bp-srv-lu-nom {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1rem;
}

.bp-srv-lu-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--z-3);
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

/* L'attente : la forme de la reponse, pas un tourniquet de plus. */
.bp-os {
  display: block;
  border-radius: 4px;
  background: linear-gradient(90deg,
    rgb(var(--z-track)) 0%, rgba(var(--z-edge), 0.35) 50%, rgb(var(--z-track)) 100%);
  background-size: 200% 100%;
  animation: bp-lueur 1.2s linear infinite;
}

.bp-os-ico { width: 44px; height: 44px; border-radius: 14px; flex: 0 0 auto; }
.bp-os-lignes { flex: 1 1 auto; display: grid; gap: 8px; }
.bp-os-l1 { width: 55%; height: 12px; }
.bp-os-l2 { width: 32%; height: 10px; }

@keyframes bp-lueur { to { background-position: -200% 0; } }

.bp-blocs {
  list-style: none;
  margin: var(--z-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--z-2);
}

.bp-bloc {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: var(--z-2);
  align-items: start;
  padding: var(--z-2) var(--z-3);
  border-inline-start: 3px solid rgb(var(--cl-warning));
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-warning), 0.08);
}

.bp-bloc.is-dur {
  border-inline-start-color: rgb(var(--cl-error));
  background: rgba(var(--cl-error), 0.09);
}

.bp-bloc > svg {
  width: 18px;
  height: 18px;
  margin-top: 1px;
  fill: none;
  stroke: rgb(var(--cl-warning));
  stroke-width: 1.8;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.bp-bloc.is-dur > svg { stroke: rgb(var(--cl-error)); }

.bp-bloc summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-2);
  min-height: 24px;
  cursor: pointer;
  list-style: none;
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.875rem;
  color: rgb(var(--cl-t-primary));
}

.bp-bloc summary::-webkit-details-marker { display: none; }

.bp-bloc summary::after,
.bp-perks-sum::after {
  content: "";
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-inline-end: 3px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  opacity: 0.7;
  transition: transform 180ms var(--z-ease);
}

.bp-bloc details[open] summary::after,
.bp-perks[open] > .bp-perks-sum::after { transform: rotate(-135deg) translate(-2px, 0); }

.bp-bloc p {
  margin: var(--z-1) 0 var(--z-1);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
}

/* Les boosts deja la, a la main. */
.bp-have {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--z-2) var(--z-4);
  margin-top: var(--z-4);
  padding-top: var(--z-4);
  border-top: 1px solid var(--cl-border);
}

.bp-have-txt {
  flex: 1 1 12rem;
  display: grid;
  gap: 2px;
}

.bp-have-lbl {
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.9375rem;
}

.bp-have-src {
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.bp-stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid rgba(var(--z-edge), 0.8);
  border-radius: var(--z-r-action);
  overflow: hidden;
}

.bp-stepper button {
  width: 44px;
  height: 44px;
  border: 0;
  background: transparent;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.125rem;
  color: rgb(var(--cl-t-primary));
  cursor: pointer;
  transition: background-color 150ms var(--z-ease);
}

.bp-stepper button:hover:not(:disabled) { background: rgba(var(--cl-accent), 0.14); }
/* Passe 2 : desactive = couleur discrete, pas une opacite. A .35 le « − »
   tombait a 2,85:1 ; t-muted reste lisible (3:1 au moins pour un
   composant d'interface) tout en se distinguant du « + » actif. */
.bp-stepper button:disabled { opacity: 1; color: rgb(var(--cl-t-muted)); cursor: default; }

.bp-stepper output {
  min-width: 3ch;
  text-align: center;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.0625rem;
  font-variant-numeric: tabular-nums;
}

.bp-lien {
  min-height: 44px;
  padding: 0 var(--z-1);
  border: 0;
  background: none;
  font-size: 0.8125rem;
  color: rgb(var(--z-halo));
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(var(--z-halo), 0.45);
  cursor: pointer;
}

.bp-lien:hover { text-decoration-color: currentColor; }

/* ---- 2  l'objectif ---- */

.bp-readout {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--z-2) var(--z-4);
  margin-top: var(--z-2);
}

.bp-figure {
  display: flex;
  align-items: baseline;
  gap: var(--z-2);
}

.bp-count {
  font-size: clamp(2.75rem, 6vw, 3.5rem);
  font-weight: 700;
  line-height: 1;
}

.bp-unit {
  font-size: 0.9375rem;
  color: rgb(var(--cl-t-muted));
}

.bp-delta {
  padding: 2px 10px;
  border-radius: 999px;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

.bp-delta:empty { display: none; }
.bp-delta.is-plus { background: rgba(var(--z-boost), 0.18); color: rgb(var(--z-halo)); }
.bp-delta.is-zero { border: 1px solid rgba(var(--z-edge), 0.6); color: rgb(var(--cl-t-muted)); }

.bp-tier {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  margin-inline-start: auto;
}

.bp-tier-blocks {
  display: flex;
  gap: 3px;
}

.bp-tier-blocks span {
  width: 20px;
  height: 5px;
  background: rgb(var(--z-track));
  transition: background-color 220ms var(--z-ease);
}

.bp-tier-blocks span:nth-child(1).is-on { background: rgb(var(--z-charge)); }
.bp-tier-blocks span:nth-child(2).is-on { background: rgb(var(--z-boost)); }
.bp-tier-blocks span:nth-child(3).is-on { background: rgb(var(--z-halo)); }

.bp-tier-label {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.875rem;
  transition: color 220ms var(--z-ease);
}

.bp-tier.is-flash .bp-tier-label { color: rgb(var(--z-halo)); }

/* Les raccourcis : on pense en niveaux, pas en boosts. */
.bp-cibles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--z-2);
  margin-top: var(--z-4);
}

.bp-cible {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  min-height: 40px;
  padding: 0 14px;
  align-items: center;
  border: 1px solid rgba(var(--z-edge), 0.6);
  border-radius: 999px;
  background: transparent;
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-primary));
  cursor: pointer;
  transition: border-color 150ms var(--z-ease), background-color 150ms var(--z-ease), transform 90ms var(--z-ease);
}

.bp-cible-n {
  font-variant-numeric: tabular-nums;
  color: rgb(var(--cl-t-muted));
  transition: color 150ms var(--z-ease);
}

.bp-cible:hover:not(:disabled) { border-color: rgba(var(--cl-accent), 0.8); }
.bp-cible:active:not(:disabled) { transform: scale(0.97); }

.bp-cible.is-on {
  border-color: rgb(var(--z-boost));
  background: rgba(var(--z-boost), 0.2);
}

.bp-cible.is-on .bp-cible-n { color: rgb(var(--z-halo)); }

.bp-cible:disabled {
  cursor: default;
  opacity: 0.45;
  border-style: dashed;
}

@media (pointer: coarse) {
  .bp-cible { min-height: 44px; }
}

/* La commande. */
.bp-control {
  position: relative;
  margin-top: var(--z-5);
}

.bp-manche {
  display: flex;
  align-items: flex-end;
  gap: var(--z-3);
  /* La jauge est une echelle de 0 a 20, lue de gauche a droite dans toutes
     les langues : les cases, la poignee (transform), le barreme et le
     calcul du pointeur sont physiques. En arabe et en hebreu, sans cela,
     les cases se remplissaient par la droite et la poignee par la gauche. */
  direction: ltr;
}

.bp-piste {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  padding-bottom: 4px;
  cursor: pointer;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.bp-control.is-dragging .bp-piste { cursor: grabbing; }

.bp-scale {
  position: relative;
  height: 26px;
}

.bp-tick {
  position: absolute;
  bottom: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 3px;
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
  pointer-events: none;
}

.bp-tick i {
  display: block;
  width: 1px;
  height: 7px;
  background: rgba(var(--z-edge), 0.7);
}

/* Les vingt cases. Une case = un boost, comme dans Discord. */
.bp-cases {
  position: relative;
  display: grid;
  grid-template-columns: repeat(20, minmax(0, 1fr));
  gap: 3px;
  height: 18px;
}

.bp-case {
  border-radius: 3px;
  background-color: rgb(var(--z-track));
  transition: background-color 140ms var(--z-ease);
}

/* Le degrade est UN seul degrade etale sur toute la jauge : chaque case
   en montre sa tranche. Taille = 20 cases + 19 gouttieres de 3 px ;
   position = i/19 (voir la demonstration dans le journal du depot). */
.bp-case.is-added,
.bp-case.is-rab {
  background-image: linear-gradient(90deg,
    rgb(var(--z-charge)) 0%, rgb(var(--z-boost)) 62%, rgb(var(--z-halo)) 100%);
  background-size: calc(2000% + 57px) 100%;
  background-position: calc(var(--i) * 100% / 19) 0;
}

/* Le rab au-dela de 14 : raye, parce qu'il n'achete pas un niveau. */
.bp-case.is-rab {
  background-image:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.34) 0 2px, transparent 2px 5px),
    linear-gradient(90deg,
      rgb(var(--z-charge)) 0%, rgb(var(--z-boost)) 62%, rgb(var(--z-halo)) 100%);
  background-size: auto, calc(2000% + 57px) 100%;
  background-position: 0 0, calc(var(--i) * 100% / 19) 0;
}

/* Deja sur le serveur : hachure bleutee, pas de charge. */
.bp-case.is-owned {
  background-image: repeating-linear-gradient(135deg,
    rgba(var(--z-edge), 0.75) 0 2px, rgba(var(--z-edge), 0.28) 2px 5px);
}

/* La poignee, posee au bord exact de la derniere case prise.

   Deplacee en transform (le compositeur s'en charge ; animer `left`
   recalculait la mise en page a chaque image). Le calque est de LARGEUR
   NULLE et avance en unites de conteneur : un calque pleine largeur
   translate de 70 % debordait de la page d'autant (53 px mesures en
   arabe) — un transform compte dans le debordement defilable. --fn est
   la fraction b/20 posee par le gabarit. Sans requetes de conteneur, le
   repli pose simplement `left`. */
.bp-cases { container-type: inline-size; }

.bp-poignee {
  position: absolute;
  top: 0;
  left: var(--f, 0%);
  width: 0;
  height: 100%;
  pointer-events: none;
}

@supports (width: 1cqw) {
  .bp-poignee {
    left: 0;
    transform: translateX(calc(var(--fn, 0) * 100cqw));
    transition: transform 160ms var(--z-ease);
  }
}

.bp-poignee::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: rgb(var(--cl-t-primary));
  border: 3px solid rgb(var(--cl-background));
  box-shadow: 0 0 0 2px rgba(var(--cl-accent), 0.85); /* l'anneau, sans lueur */
  transition: transform 120ms var(--z-ease);
}

.bp-piste:hover .bp-poignee::after { transform: scale(1.08); }
.bp-control.is-dragging .bp-poignee { transition: none; }
.bp-control.is-dragging .bp-poignee::after { transform: scale(1.16); }

/* Le curseur natif : invisible, sans pointeur, il ne sert que le clavier
   et les lecteurs d'ecran. Son repere de focus se lit donc sur la jauge. */
.bp-range {
  position: absolute;
  left: 0;
  right: 0;
  top: 26px;
  width: 100%;
  height: 18px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.bp-piste:has(.bp-range:focus-visible) .bp-cases {
  outline: 2px solid rgb(var(--z-halo));
  outline-offset: 5px;
  border-radius: 6px;
}

.bp-pas {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  margin-bottom: -9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ff-heading);
  font-size: 1.125rem;
  font-weight: 700;
  line-height: 1;
  color: rgb(var(--cl-t-primary));
  background: rgb(var(--cl-surface-raised));
  border: 1px solid rgba(var(--z-edge), 0.8);
  border-radius: var(--z-r-action);
  cursor: pointer;
  transition: border-color 150ms var(--z-ease), background-color 150ms var(--z-ease), transform 90ms var(--z-ease);
}

.bp-pas:hover:not(:disabled) {
  border-color: rgb(var(--cl-accent));
  background: rgba(var(--cl-accent), 0.12);
}

.bp-pas:active:not(:disabled) { transform: translateY(1px); }
.bp-pas:disabled { opacity: 0.35; cursor: default; }

.bp-legende {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--z-1) var(--z-4);
  margin-top: var(--z-4);
  padding-inline: calc(44px + var(--z-3));
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.bp-leg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.bp-leg::before {
  content: "";
  width: 12px;
  height: 10px;
  border-radius: 2px;
}

.bp-leg.is-owned::before {
  background: repeating-linear-gradient(135deg,
    rgba(var(--z-edge), 0.75) 0 2px, rgba(var(--z-edge), 0.28) 2px 5px);
}

.bp-leg.is-added::before {
  background: linear-gradient(90deg, rgb(var(--z-charge)), rgb(var(--z-boost)));
}

.bp-leg.is-rab::before {
  background:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.34) 0 2px, transparent 2px 5px),
    rgb(var(--z-halo));
}

.bp-leg-max {
  margin-inline-start: auto;
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 30rem) {
  .bp-legende { padding-inline: 0; }
}

/* Le verdict et la mascotte : la meme phrase, en mots et en visage. */
.bp-dit {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  margin-top: var(--z-5);
  padding-block: var(--z-2);
  padding-inline: var(--z-2) var(--z-4);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-action);
  background: rgb(var(--cl-surface-raised));
}

.bp-dit .bp-mine { width: 52px; height: 52px; }

.bp-verdict {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-primary));
}

/* Ce que chaque niveau debloque. */
.bp-perks {
  margin-top: var(--z-5);
  border-top: 1px solid var(--cl-border);
}

.bp-perks-sum {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  min-height: 48px;
  cursor: pointer;
  list-style: none;
}

.bp-perks-sum::-webkit-details-marker { display: none; }

.bp-perks-titre {
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.9375rem;
}

.bp-perks-new {
  padding: 1px 9px;
  border-radius: 999px;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.75rem;
  background: rgba(var(--z-boost), 0.18);
  color: rgb(var(--z-halo));
}

.bp-perks-new:empty { display: none; }

.bp-perks-count {
  margin-inline-start: auto;
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

.bp-groupes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--z-4) var(--z-5);
  padding: var(--z-1) 0 var(--z-2);
}

@media (max-width: 36rem) {
  .bp-groupes { grid-template-columns: minmax(0, 1fr); }
}

.bp-groupe-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--z-2);
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--cl-border);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.8125rem;
}

.bp-groupe-s {
  font-weight: 600;
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
  white-space: nowrap;
}

.bp-groupe.is-new .bp-groupe-s,
.bp-groupe.is-on .bp-groupe-s { color: rgb(var(--z-halo)); }
.bp-groupe.is-owned .bp-groupe-s { color: rgb(var(--cl-success)); }

.bp-row {
  display: flex;
  align-items: center;
  gap: var(--z-2);
  padding: 5px 0;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  transition: color var(--dur) var(--z-ease);
}

.bp-row.is-new,
.bp-row.is-on,
.bp-row.is-owned { color: rgb(var(--cl-t-primary)); }

.bp-row-ico {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  opacity: 0.55;
}

.bp-row.is-owned .bp-row-ico { opacity: 0.85; }
.bp-row.is-new .bp-row-ico,
.bp-row.is-on .bp-row-ico { opacity: 1; color: rgb(var(--z-halo)); }

.bp-row-ico svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bp-row-name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bp-row-leader {
  flex: 1 1 auto;
  min-width: var(--z-3);
  height: 1px;
  align-self: flex-end;
  margin-bottom: 7px;
  background-image: repeating-linear-gradient(90deg,
    rgba(var(--z-edge), 0.5) 0 1px, transparent 1px 4px);
}

.bp-row-need {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ff-heading);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 0.75rem;
}

.bp-row.is-new .bp-row-need b,
.bp-row.is-on .bp-row-need b { color: rgb(var(--z-halo)); }

.bp-row-new {
  font-style: normal;
  font-size: 0.6875rem;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(var(--z-boost), 0.22);
  color: rgb(var(--z-halo));
}

.bp-row-etat {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bp-row.is-owned .bp-row-etat { color: rgb(var(--cl-success)); }
.bp-row.is-on .bp-row-etat { color: rgb(var(--z-halo)); }
.bp-row.is-locked .bp-row-etat { opacity: 0.6; }

/* ---- 3  le pack ---- */

.bp-opt {
  min-width: 0;
  margin: 0 0 var(--z-4);
  padding: 0;
  border: 0;
}

.bp-opt:last-child { margin-bottom: 0; }

.bp-opt-l {
  margin-bottom: var(--z-2);
  padding: 0;
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
}

.bp-choix-grille {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--z-2);
}

.bp-choix {
  position: relative;
  display: grid;
  align-content: start;
  gap: 3px;
  min-height: 76px;
  padding-block: var(--z-3);
  padding-inline: var(--z-4) calc(var(--z-4) + 22px);
  border: 1px solid rgba(var(--z-edge), 0.6);
  border-radius: var(--z-r-action);
  cursor: pointer;
  transition: border-color 150ms var(--z-ease), background-color 150ms var(--z-ease), box-shadow 150ms var(--z-ease);
}

.bp-choix:hover { border-color: rgba(var(--cl-accent), 0.75); }

/* La pastille de choix, en haut a droite : on voit ce qui est pris sans
   lire les couleurs. */
.bp-choix::after {
  content: "";
  position: absolute;
  top: var(--z-3);
  inset-inline-end: var(--z-3);
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid rgba(var(--z-edge), 0.9);
  transition: border-color 150ms var(--z-ease), background-color 150ms var(--z-ease), box-shadow 150ms var(--z-ease);
}

.bp-choix.is-on {
  border-color: rgb(var(--z-boost));
  background: rgba(var(--z-boost), 0.1);
  box-shadow: inset 0 0 0 1px rgb(var(--z-boost));
}

.bp-choix.is-on::after {
  border-color: rgb(var(--z-boost));
  background: rgb(var(--z-boost));
  box-shadow: inset 0 0 0 3px rgb(var(--cl-surface));
}

.bp-choix.is-off { opacity: 0.5; cursor: not-allowed; }

.bp-choix:has(input:focus-visible) {
  outline: 2px solid rgb(var(--z-halo));
  outline-offset: 2px;
}

.bp-choix-t {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.9375rem;
}

.bp-choix-t svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bp-choix-p {
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1.125rem;
  font-variant-numeric: tabular-nums;
}

.bp-choix-s,
.bp-choix-d {
  font-size: 0.75rem;
  line-height: 1.4;
  color: rgb(var(--cl-t-muted));
}

.bp-choix-d { font-size: 0.8125rem; }
.bp-choix-s:empty { display: none; }

.bp-choix-eco {
  position: absolute;
  top: -9px;
  inset-inline-end: calc(var(--z-3) + 24px);
  padding: 2px 8px;
  border-radius: 999px;
  font-style: normal;
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.6875rem;
  background: rgb(var(--z-halo));
  color: rgb(var(--cl-background));
}

/* ---- le recapitulatif ---- */

/* Isolation : un changement de prix ou de niveau ne repeint que son
   panneau, pas la page. Mesure (Event Timing, CPU x4) : le recapitulatif
   pesait 40 ms de rendu par pas avant cela. */
.bp-ticket > .bp-server,
.bp-sum { contain: layout paint; }
.bp-step { contain: layout; }

/* Le lutin qui guette au survol du bouton (section 28) deborde a droite :
   dans le recapitulatif il serait coupe par le bord du panneau, et la
   mascotte du verdict est deja la. */
.bp-sum .bp-cta::after { content: none; }

.bp-ticket {
  display: grid;
  gap: var(--z-4);
  min-width: 0;
}

/* Colle au defilement seulement s'il tient dans la fenetre : un
   recapitulatif colle plus haut que l'ecran cache son propre bouton. */
@media (min-width: 60rem) and (min-height: 50rem) {
  .bp-ticket {
    position: sticky;
    top: 7.5rem;
  }
}

/* La mascotte perchee s'efface tant que le planificateur est a l'ecran
   (voir boostPlanner.init) : elle couvrait la maquette du recapitulatif. */
html.bp-en-vue .zm-perche.est-la {
  opacity: 0;
  visibility: hidden;
}

.bp-srv-name { min-width: 0; }

.bp-srv-nom {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bp-srv-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bp-srv-perk {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
}

/* Deux textes de la maquette sont poses sur un degrade : en muted a 55 %
   ils tombaient a 2,5:1 et 3,3:1. Encre pleine. */
.bp-srv-vide {
  color: rgb(var(--cl-t-primary));
  opacity: 1;
}

.bp-srv-vanity {
  opacity: 1;
  color: rgb(var(--cl-t-muted));
}

.bp-sum {
  padding: var(--z-4) var(--z-5) var(--z-5);
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface-raised));
}

.bp-sum-titre {
  margin: 0 0 var(--z-3);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 1rem;
}

.bp-sum-lignes {
  display: grid;
  gap: var(--z-2);
  margin: 0;
}

.bp-sum-l {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--z-3);
  padding-bottom: var(--z-2);
  border-bottom: 1px dashed rgba(var(--z-edge), 0.35);
  font-size: 0.875rem;
}

.bp-sum-l dt { color: rgb(var(--cl-t-muted)); }

.bp-sum-l dd {
  margin: 0;
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  text-align: end;
  font-family: var(--ff-heading);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.bp-fit {
  padding: 1px 8px;
  border-radius: 999px;
  font-style: normal;
  font-weight: 700;
  font-size: 0.6875rem;
}

.bp-fit:empty { display: none; }
.bp-fit.is-exact { background: rgba(var(--cl-success), 0.16); color: rgb(var(--cl-success)); }
.bp-fit.is-rab { background: rgba(var(--z-boost), 0.2); color: rgb(var(--z-halo)); }
.bp-fit.is-court { background: rgba(var(--cl-warning), 0.16); color: rgb(var(--cl-warning)); }

/* La frise : aujourd'hui, un segment par mois, la date de fin. */
.bp-frise { margin-top: var(--z-4); }

.bp-frise-rail {
  display: grid;
  grid-template-columns: repeat(var(--mois, 1), minmax(0, 1fr));
  height: 6px;
  border-radius: 3px;
  overflow: hidden;
  background: linear-gradient(90deg, rgb(var(--z-charge)), rgb(var(--z-boost)) 62%, rgb(var(--z-halo)));
}

.bp-frise-rail span { box-shadow: inset -3px 0 0 rgb(var(--cl-surface-raised)); }

/* De droite a gauche, le temps aussi : « aujourd'hui » est a droite, la
   charge part donc de la droite. */
html[dir="rtl"] .bp-frise-rail { background: linear-gradient(270deg, rgb(var(--z-charge)), rgb(var(--z-boost)) 62%, rgb(var(--z-halo))); }
html[dir="rtl"] .bp-frise-rail span { box-shadow: inset 3px 0 0 rgb(var(--cl-surface-raised)); }
.bp-frise-rail span:last-child { box-shadow: none; }

.bp-frise-bornes {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--z-3);
  margin-top: var(--z-2);
  font-size: 0.75rem;
  color: rgb(var(--cl-t-muted));
}

.bp-frise-fin {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: rgb(var(--cl-t-primary));
  font-family: var(--ff-heading);
  font-weight: 600;
}

.bp-frise-fin svg,
.bp-avis svg,
.bp-mieux svg {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Le pack juste au-dessus, quand il fait franchir un niveau pour peu. */
.bp-mieux {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--z-2);
  width: 100%;
  margin-top: var(--z-4);
  padding: var(--z-3);
  border: 1px dashed rgba(var(--z-boost), 0.7);
  border-radius: var(--z-r-action);
  background: rgba(var(--z-boost), 0.08);
  font: inherit;
  font-size: 0.8125rem;
  line-height: 1.4;
  text-align: start;
  color: rgb(var(--cl-t-primary));
  cursor: pointer;
  transition: background-color 150ms var(--z-ease), border-color 150ms var(--z-ease);
}

.bp-mieux:hover { background: rgba(var(--z-boost), 0.15); border-style: solid; }
.bp-mieux svg { color: rgb(var(--z-halo)); }

.bp-mieux b {
  font-family: var(--ff-heading);
  font-weight: 700;
  color: rgb(var(--z-halo));
  white-space: nowrap;
}

.bp-avis {
  display: flex;
  align-items: flex-start;
  gap: var(--z-2);
  margin: var(--z-4) 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: rgb(var(--cl-t-muted));
}

.bp-avis svg { margin-top: 2px; color: rgb(var(--cl-success)); }

.bp-avis.is-alerte {
  padding: var(--z-2) var(--z-3);
  border-inline-start: 3px solid rgb(var(--cl-error));
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-error), 0.09);
  color: rgb(var(--cl-t-primary));
}

.bp-avis.is-alerte svg { color: rgb(var(--cl-error)); }

.bp-sum-pied { margin-top: var(--z-5); }

.bp-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-3);
  width: 100%;
  min-height: 52px;
  padding: 0 var(--z-5);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: 0.9375rem;
  text-decoration: none;
}

.bp-cta > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bp-cta-price {
  flex: 0 0 auto;
  font-weight: 700;
  font-size: 1.0625rem;
}

.bp-sum-note {
  margin: var(--z-2) 0 0;
  font-size: 0.75rem;
  text-align: center;
  color: rgb(var(--cl-t-muted));
}

/* Au telephone : le bouton reste sous le pouce tant qu'on regle, et
   s'efface de lui-meme quand le recapitulatif arrive — c'est la fin de
   sa colonne qui le libere, sans une ligne de script. */
.bp-dock { display: none; }

@media (max-width: 59.99rem) {
  .bp-dock {
    display: block;
    position: sticky;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
    z-index: 4;
    margin-top: var(--z-6);
    padding: 6px;
    border: 1px solid var(--cl-border);
    border-radius: 16px;
    background: rgba(var(--cl-surface), 0.88);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    box-shadow: none; /* 1.3 : l'elevation = fond opaque + filet */
  }

  .bp-dock .bp-cta { min-height: 48px; box-shadow: none; }
}

/* ---- contrastes mesures (scripts/bp-a11y.mjs, calques composes) ---- */

/* Le badge de niveau de la maquette : violet sur violet a 18 %, 3,07:1
   (anterieur a cette refonte). La lavande y tient 9:1. */
.bp-srv-lvlbadge,
.bp-srv-tag { color: rgb(var(--z-halo)); }

/* Le Tag en degrade (styles de role) : un aplat dessous, au cas ou le
   degrade ne peindrait pas — encre sombre sur #8B5CF6 = 4,6:1. */
.bp-server.has-roles .bp-srv-tag { background-color: rgb(var(--z-boost)); }

/* En clair, la « lavande » du schema (#7C3AED) posee sur une pastille
   teintee de violet tombait a 4,07:1. L'accent fonce (#6D28D9) y tient
   plus de 6:1 ; le vert de succes perd sa teinte de fond. */
html[data-scheme="light"] .bp-cible.is-on .bp-cible-n,
html[data-scheme="light"] .bp-fit.is-rab,
html[data-scheme="light"] .bp-delta.is-plus,
html[data-scheme="light"] .bp-perks-new,
html[data-scheme="light"] .bp-row-new,
html[data-scheme="light"] .bp-mieux b,
html[data-scheme="light"] .bp-mieux svg,
html[data-scheme="light"] .bp-lien,
html[data-scheme="light"] .bp-srv-lvlbadge,
html[data-scheme="light"] .bp-srv-tag,
html[data-scheme="light"] .bp-groupe.is-new .bp-groupe-s,
html[data-scheme="light"] .bp-groupe.is-on .bp-groupe-s,
html[data-scheme="light"] .bp-row.is-on .bp-row-etat { color: rgb(var(--cl-accent)); }

html[data-scheme="light"] .bp-fit.is-exact { background: transparent; box-shadow: inset 0 0 0 1px rgba(var(--cl-success), 0.5); }

/* Au doigt, le bouton d'effacement passe a 44 px comme tout le reste. */
@media (pointer: coarse) {
  .bp-inv-clear { width: 44px; height: 44px; }
}

/* ---- le mouvement : chaque animation repond a un geste ---- */

@media (prefers-reduced-motion: no-preference) {
  .bp-count.is-bump { animation: bp-bump 140ms var(--z-ease); }

  /* Un avantage qui vient de se debloquer s'allume une fois. */
  .bp-row.is-new,
  .bp-row.is-on { animation: bp-allume 520ms var(--z-ease); }

  /* On a voulu descendre sous les boosts deja la : ils tressaillent. */
  .bp-control.is-bute .bp-case.is-owned { animation: bp-bute 420ms var(--z-ease); }

  .bp-cta.is-tic .bp-cta-price { animation: bp-tic 200ms var(--z-ease); }

  .bp-srv-lu-row:not(.is-attente),
  .bp-bloc { animation: bp-arrive 240ms var(--z-ease) both; }

  .bp-frise-rail span { animation: bp-trace 360ms var(--z-ease) both; transform-origin: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
  .bp-os { animation: none; }
  .bp-spin { animation-duration: 1.6s; }
  .bp-step::after,
  .bp-poignee,
  .bp-case { transition: none; }
}

@keyframes bp-bump { 50% { transform: translateY(-2px); } }

@keyframes bp-allume {
  0%   { background-color: rgba(var(--z-boost), 0.22); }
  100% { background-color: transparent; }
}

@keyframes bp-bute {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-2px); }
  75% { transform: translateX(2px); }
}

/* Le prix change : un glissement de 3 px, a 70 % d'opacite au depart.
   Plus bas, il ressemblait a un rechargement. */
@keyframes bp-tic {
  0%   { opacity: 0.7; transform: translateY(3px); }
  100% { opacity: 1; transform: none; }
}

@keyframes bp-arrive {
  0%   { opacity: 0; transform: translateY(4px); }
  100% { opacity: 1; transform: none; }
}

@keyframes bp-trace {
  0%   { transform: scaleX(0.4); opacity: 0; }
  100% { transform: none; opacity: 1; }
}

/* fin du planificateur (34) */

/* ============================================================
   36  LA BARRE DE NAVIGATION : LE BURGER JUSQU'A 80rem

   Mesure en production (2026-10-01), six liens : la barre debordait de
   104 px en anglais, 139 en francais, 272 en russe a 1024 px, et le russe
   debordait encore a 1280. Les liens ne tiennent en ligne qu'a partir de
   80rem ; en dessous, le menu burger prend le relais, comme au telephone.
   Le panneau du menu garde son x-show : son style en ligne display:none
   l'emporte tant qu'il est ferme.
   ============================================================ */
@media (min-width: 64rem) and (max-width: 79.99rem) {
  .sa-navbar .hidden.lg\:flex { display: none; }
  .sa-navbar .lg\:hidden { display: block; }
  /* Sans les liens, plus rien ne pousse les actions a droite : leur
     ml-auto est annule par lg:ml-0 a partir de 64rem. */
  .sa-navbar .ml-auto.lg\:ml-0 { margin-left: auto; }
}

/* Le russe a les libelles les plus longs (et la production porte le logo,
   absent de la preview) : mesure en ligne, la barre debordait encore de
   101 px a 1280, 58 a 1366, 21 a 1440. Burger jusqu'a 90rem, puis des
   liens un peu plus serres. */
@media (min-width: 64rem) and (max-width: 89.99rem) {
  html[lang="ru"] .sa-navbar .hidden.lg\:flex { display: none; }
  html[lang="ru"] .sa-navbar .lg\:hidden { display: block; }
  html[lang="ru"] .sa-navbar .ml-auto.lg\:ml-0 { margin-left: auto; }
}
@media (min-width: 90rem) {
  html[lang="ru"] .sa-navbar-link { padding-left: .5rem; padding-right: .5rem; }
}

/* Au-dela, custom.js mesure : si les liens ne tiennent pas, burger. */
@media (min-width: 64rem) {
  .sa-navbar--compacte .hidden.lg\:flex { display: none; }
  .sa-navbar--compacte .lg\:hidden { display: block; }
  .sa-navbar--compacte .ml-auto.lg\:ml-0 { margin-left: auto; }
}


/* ============================================================
   38  LE PORTFOLIO (fiche « Design & Motion Pack »)

   Treize oeuvres reelles sous la fiche, filtrables par format.
   Gabarit : snippets/portfolio.njk (genere par
   outils/portfolio/publier.py) ; comportement : custom.js,
   « Le portfolio ». Les surfaces et filets suivent les jetons du
   theme : la section vit aussi en schema clair. Seuls l'interieur
   des cadres (les oeuvres) et la fenetre restent sombres — une
   video se regarde sur du noir.
   ============================================================ */

.zpf { margin-top: var(--z-8); scroll-margin-top: 6rem; }
.zpf-tete { max-width: 46rem; }
/* 1.7 : le sourcil « PORTFOLIO » est retire du gabarit (generateur
   outils/portfolio/publier.py) : le titre suffit. */
.zpf-titre {
  margin-top: var(--z-2);
  font-family: var(--ff-heading);
  font-weight: 700;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  line-height: 1.08;
  letter-spacing: -0.024em;
  color: rgb(var(--cl-t-primary));
}
.zpf-texte { margin-top: var(--z-3); max-width: var(--z-measure); color: rgb(var(--cl-t-muted)); }

/* -- Les filtres : une rangee qui defile au doigt plutot que de passer
      sur trois lignes ; les bords s'effacent pour dire qu'il y a la suite. -- */
.zpf-filtres {
  display: flex;
  gap: 6px;
  margin-top: var(--z-5);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.zpf-filtres::-webkit-scrollbar { display: none; }
@media (min-width: 48rem) { .zpf-filtres { flex-wrap: wrap; overflow: visible; } }
@media (max-width: 47.99rem) {
  .zpf-filtres {
    margin-inline: calc(var(--z-4) * -1);
    padding-inline: var(--z-4);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--z-4), #000 calc(100% - var(--z-6)), transparent);
            mask-image: linear-gradient(90deg, transparent, #000 var(--z-4), #000 calc(100% - var(--z-6)), transparent);
  }
}
.zpf-filtre {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--z-2);
  min-height: 40px;
  padding: 0 12px;
  border-radius: var(--z-r-pill);
  border: 1px solid rgb(var(--z-edge)); /* 1.3 : filet plein, 3:1 */
  background: rgba(var(--cl-t-primary), 0.03);
  color: rgb(var(--cl-t-primary));
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: border-color 0.2s var(--z-ease), background-color 0.2s var(--z-ease), color 0.2s var(--z-ease);
}
.zpf-filtre span {
  min-width: 1.5em;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
  background: rgba(var(--cl-t-primary), 0.08);
  color: rgb(var(--cl-t-muted));
}
.zpf-filtre:hover { border-color: rgba(var(--cl-accent), 0.6); }
.zpf-filtre[aria-pressed="true"] {
  border-color: rgb(var(--cl-accent));
  background: rgb(var(--cl-accent));
  color: rgb(var(--cl-on-accent));
}
.zpf-filtre[aria-pressed="true"] span { background: rgba(var(--cl-on-accent), 0.18); color: rgb(var(--cl-on-accent)); }

/* -- La grille -- */
.zpf-grille {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--z-4);
  margin-top: var(--z-5);
  padding: 0;
  list-style: none;
}
@media (min-width: 32.5rem) { .zpf-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .zpf-grille { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--z-5); } }
.zpf-item[hidden] { display: none; }
.zpf-item.est-entree { animation: zpf-entre 0.45s var(--z-ease) both; animation-delay: calc(var(--n, 0) * 40ms); }
@keyframes zpf-entre { from { opacity: 0; transform: translateY(8px); } }

.zpf-carte {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  padding: 0;
  text-align: start;
  border-radius: var(--z-r-panel);
  border: 1px solid var(--cl-border);
  background: rgb(var(--cl-surface));
  color: rgb(var(--cl-t-primary));
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.25s var(--z-ease), transform 0.25s var(--z-ease);
}
.zpf-carte:hover { border-color: rgba(var(--cl-accent), 0.55); transform: translateY(-2px); }

/* Le cadre de l'oeuvre : toujours 16:9, toujours sombre. */
.zpf-media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  isolation: isolate;
  background: #04070f;
}
.zpf-media > img,
.zpf-media > video,
.zpf-cadre > img,
.zpf-cadre > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.zpf-media video,
.zpf-cadre video { opacity: 0; transition: opacity 0.5s var(--z-ease); }
.zpf-carte.est-anime video { opacity: 1; }
.zpf-media > img { transition: transform 0.6s var(--z-ease); }
.zpf-carte:hover .zpf-media > img { transform: scale(1.03); }

/* Les oeuvres qui ne sont pas en 16:9 (signature, en-tete) : posees au
   centre d'un fond qui rappelle ou elles vivent. */
.zpf-media--contenir {
  display: grid;
  place-items: center;
  padding: 7%;
  background:
    radial-gradient(70% 90% at 50% 100%, rgba(47, 123, 255, 0.18), transparent 70%),
    #060a16;
}
.zpf-cadre {
  position: relative;
  display: block;
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: #04070f;
  outline: 1px solid rgba(255, 255, 255, 0.1);
}

/* Les visuels fixes : quatre images qui se relaient en fondu. 1.7 (budget
   du mouvement) : un seul tour, au survol ou au focus de la carte, plus
   de boucle de 24 s sans fin. Au repos, la premiere image. */
.zpf-diapo { position: absolute; inset: 0; }
.zpf-diapo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
.zpf-carte:is(:hover, :focus-visible) .zpf-diapo img {
  animation: zpf-diapo 8s linear both;
  animation-delay: calc(var(--i) * 2s);
}
.zpf-diapo img:first-child { opacity: 1; }
/* la premiere reste dessous ; les suivantes passent dessus, puis s'effacent */
.zpf-carte .zpf-diapo img:first-child { animation: none; }
@keyframes zpf-diapo {
  0% { opacity: 0; }
  3%, 25% { opacity: 1; }
  28%, 100% { opacity: 0; }
}

.zpf-jouer {
  position: absolute;
  inset-inline-start: 12px;
  inset-block-end: 12px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding-inline-start: 2px;
  border-radius: 50%;
  color: #fff;
  background: rgba(6, 10, 22, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.22);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background-color 0.25s var(--z-ease), transform 0.25s var(--z-ease);
}
.zpf-carte:hover .zpf-jouer { background: rgb(var(--cl-accent)); transform: scale(1.06); }
.zpf-duree {
  position: absolute;
  inset-inline-end: 12px;
  inset-block-end: 12px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: #fff;
  background: rgba(6, 10, 22, 0.62);
  border: 1px solid rgba(255, 255, 255, 0.16);
}
.zpf-legende { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px 14px; }
.zpf-cat {
  /* 1.7 : casse de phrase, en retrait, apres le nom (plus de capitales
     espacees en violet) */
  font-family: var(--ff-body);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0;
  color: rgb(var(--cl-t-muted));
}
.zpf-nom { font-family: var(--ff-heading); font-size: 1rem; font-weight: 700; line-height: 1.25; }
.zpf-nom span { font-weight: 500; color: rgb(var(--cl-t-muted)); }

/* -- Le lien sous la galerie -- */
.zpf-lien {
  display: flex;
  align-items: center;
  gap: var(--z-3);
  margin-top: var(--z-3);
  padding: 10px 14px 10px 10px;
  border-radius: var(--z-r-panel);
  border: 1px solid var(--cl-border);
  background: rgba(var(--cl-t-primary), 0.03);
  color: rgb(var(--cl-t-primary));
  text-decoration: none;
  transition: border-color 0.2s var(--z-ease), background-color 0.2s var(--z-ease);
}
.zpf-lien:hover { border-color: rgba(var(--cl-accent), 0.55); background: rgba(var(--cl-accent), 0.06); }
.zpf-lien-pile { position: relative; flex: none; width: 92px; height: 52px; }
.zpf-lien-pile img {
  position: absolute;
  width: 68px;
  height: 38px;
  object-fit: cover;
  border-radius: 6px;
  outline: 1px solid rgba(255, 255, 255, 0.14);
  background: #04070f;
}
.zpf-lien-pile img:nth-child(1) { left: 0; top: 14px; transform: rotate(-6deg); }
.zpf-lien-pile img:nth-child(2) { left: 12px; top: 7px; transform: rotate(-1deg); }
.zpf-lien-pile img:nth-child(3) { left: 24px; top: 0; transform: rotate(4deg); }
.zpf-lien-mots { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.zpf-lien-mots b { font-family: var(--ff-heading); font-size: 0.9375rem; }
.zpf-lien-mots span { font-size: 0.8125rem; color: rgb(var(--cl-t-muted)); }
.zpf-lien svg { margin-inline-start: auto; flex: none; color: rgb(var(--cl-accent)); }

/* -- La fenetre : sombre dans les deux schemas -- */
.zpf-dlg {
  width: min(1120px, 94vw);
  max-width: none;
  max-height: 96vh;
  padding: 0;
  border: 0;
  background: transparent;
  color: #F4F7FF;
  overflow: visible;
}
.zpf-dlg::backdrop {
  background: rgba(2, 4, 10, 0.86);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.zpf-dlg[open] { animation: zpf-dlg 0.35s var(--z-ease); }
@keyframes zpf-dlg { from { opacity: 0; transform: translateY(10px) scale(0.985); } }
.zpf-dlg-boite {
  display: flex;
  flex-direction: column;
  max-height: 96vh;
  border-radius: var(--z-r-panel);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: #0a1020;
  overflow: hidden;
}
.zpf-dlg-tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--z-4);
  padding: 14px 16px 14px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.zpf-dlg-mots { min-width: 0; }
.zpf-dlg-titre {
  margin-top: 2px;
  font-family: var(--ff-heading);
  font-size: 1.125rem;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.zpf-dlg .zpf-cat { color: rgb(var(--cl-t-muted)); }
.zpf-rond {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
  cursor: pointer;
  transition: background-color 0.2s var(--z-ease), border-color 0.2s var(--z-ease);
}
.zpf-rond:hover { background: rgba(77, 141, 255, 0.22); border-color: rgba(140, 180, 255, 0.5); }
.zpf-rond:disabled { opacity: 0.35; cursor: default; background: transparent; }

.zpf-scene {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  place-items: center;
  background: #000;
}
.zpf-scene video,
.zpf-scene .zpf-scene-img {
  display: block;
  width: 100%;
  max-height: calc(96vh - 150px);
  aspect-ratio: var(--ratio, 16 / 9);
  object-fit: contain;
  background: #000;
}
/* L'attente : l'affiche, un anneau, un pourcentage — jamais un cadre noir muet. */
.zpf-attente {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 12px;
  background: rgba(2, 4, 10, 0.45);
  color: #fff;
  font-size: 0.875rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.zpf-attente[hidden] { display: none; }
.zpf-anneau {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.16);
  border-top-color: #fff;
  animation: zvp-tourne 0.9s linear infinite;
}
.zpf-erreur { display: grid; gap: 12px; justify-items: center; padding: 40px 20px; text-align: center; color: #C9D3E3; }
.zpf-erreur button {
  min-height: 40px;
  padding: 0 16px;
  border-radius: var(--z-r-action);
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}

/* Les visuels : l'image en grand, les vignettes dessous. */
.zpf-visionneuse { width: 100%; display: grid; gap: 10px; padding-bottom: 12px; background: #000; }
.zpf-vignettes { display: flex; gap: 8px; justify-content: center; padding: 0 12px; overflow-x: auto; }
.zpf-vignettes button {
  flex: none;
  width: 88px;
  aspect-ratio: 16 / 9;
  padding: 0;
  border-radius: 6px;
  border: 2px solid transparent;
  overflow: hidden;
  opacity: 0.55;
  cursor: pointer;
  background: #04070f;
  transition: opacity 0.2s var(--z-ease), border-color 0.2s var(--z-ease);
}
.zpf-vignettes button img { width: 100%; height: 100%; object-fit: cover; display: block; }
.zpf-vignettes button[aria-current="true"] { opacity: 1; border-color: #4D8DFF; }

/* La signature : dans le courriel ou elle vivra. */
.zpf-courriel {
  width: min(760px, 92%);
  margin: 28px auto;
  border-radius: 12px;
  overflow: hidden;
  background: #F5F7FB;
  color: #1B2333;
}
.zpf-courriel-barre { display: flex; align-items: center; gap: 7px; height: 36px; padding: 0 14px; background: #E4E9F2; border-bottom: 1px solid #D3DAE6; }
.zpf-courriel-barre i { width: 10px; height: 10px; border-radius: 50%; background: #FF5F57; }
.zpf-courriel-barre i:nth-child(2) { background: #FEBC2E; }
.zpf-courriel-barre i:nth-child(3) { background: #28C840; }
.zpf-courriel-corps { padding: 20px 24px 24px; font-size: 0.9375rem; line-height: 1.6; color: #3A4458; }
.zpf-courriel-corps b { display: block; margin-bottom: 6px; font-size: 1.0625rem; color: #1B2333; }
.zpf-courriel-corps img { display: block; width: 100%; max-width: 560px; height: auto; margin-top: 16px; border-radius: 8px; }

.zpf-dlg-pied {
  display: flex;
  align-items: center;
  gap: var(--z-4);
  padding: 12px 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.zpf-nav { display: flex; gap: 8px; flex: none; }
.zpf-specs { flex: 1 1 auto; min-width: 0; font-size: 0.875rem; color: #9AA9C4; }
.zpf-commander {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 18px;
  border: 0;
  border-radius: var(--z-r-action);
  background: linear-gradient(135deg, #4D8DFF, #7B63FF);
  color: #fff;
  font-weight: 700;
  cursor: pointer;
  transition: filter 0.2s var(--z-ease);
}
.zpf-commander:hover { filter: brightness(1.08); }
.zpf-commander b { padding-inline-start: 10px; border-inline-start: 1px solid rgba(255, 255, 255, 0.35); font-variant-numeric: tabular-nums; }
.zpf-commander[hidden] { display: none; }
@media (max-width: 40rem) {
  .zpf-dlg { width: 100vw; max-height: 100dvh; }
  .zpf-dlg-boite { border-radius: 0; max-height: 100dvh; }
  .zpf-dlg-pied { flex-wrap: wrap; }
  .zpf-specs { order: 3; flex-basis: 100%; }
  .zpf-commander { margin-inline-start: auto; }
  .zpf-scene video, .zpf-scene .zpf-scene-img { max-height: calc(100dvh - 210px); }
}
@media (prefers-reduced-motion: reduce) {
  .zpf-carte, .zpf-media > img, .zpf-jouer, .zpf-media video, .zpf-filtre { transition: none; }
  .zpf-carte:hover { transform: none; }
  .zpf-carte:hover .zpf-media > img { transform: none; }
  .zpf-item.est-entree, .zpf-dlg[open] { animation: none; }
  .zpf-diapo img { animation: none; }
}

/* ============================================================
   39  LE CATALOGUE, DEUXIEME GENERATION (2026-10-07)

   snippets/catalogue.njk + « Le catalogue » dans custom.js. Remplace la
   liste de prix (section 12) sur l'accueil et sur /products.

   Une carte par PACK. L'ordre de lecture est celui de l'achat : l'image,
   le nombre de boosts, le format, le niveau atteint, la duree, la
   disponibilite, le PRIX REEL, le bouton. Aucun prix derive : ni par
   boost, ni par mois, ni pourcentage d'economie.

   Le degrade ne vit que dans trois choses, toutes des jauges ou des
   actions : la jauge de niveau, le repere de la categorie active, le
   bouton d'achat. Aucune ombre portee au repos : l'elevation vient de la
   surface et du filet ; la seule lumiere suit le pointeur.

   Deux proprietes de deplacement, jamais la meme : `translate` pour le
   survol, `transform` pour les animations de mise en page (FLIP) — sinon
   l'une annule l'autre en plein mouvement.
   ============================================================ */

.zc { --zc-gap: 20px; position: relative; }
.zc .sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---- la barre ---- */
.zc-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "top top" "opts opts" "hint count";
  align-items: center;
  gap: 14px 24px;
  margin-bottom: 22px;
}
.zc-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; min-width: 0; }
.zc-row--top { grid-area: top; }
.zc-row--opts { grid-area: opts; }
.zc-plus { display: none; }
.zc-row--top { justify-content: space-between; border-bottom: 1px solid var(--cl-border); }
.zc-row--opts { gap: 12px 28px; }

/* Les categories : des onglets dont le repere est un bout de jauge. */
.zc-cats {
  position: relative;
  display: flex;
  gap: 2px;
  margin-bottom: -1px;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.zc-cats::-webkit-scrollbar { display: none; }
.zc-cat {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  min-height: 48px;
  padding: 0 14px;
  border: 0;
  background: none;
  color: rgb(var(--cl-t-muted));
  font-family: var(--ff-heading);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: color var(--dur) var(--z-ease);
}
.zc-cat:hover { color: rgb(var(--cl-t-primary)); }
.zc-cat[aria-pressed="true"] { color: rgb(var(--cl-t-primary)); }
.zc-cat b {
  min-width: 22px;
  padding: 1px 6px;
  border-radius: var(--z-r-field);
  background: rgba(var(--cl-t-primary), 0.07);
  font-size: 0.75rem;
  font-weight: 700;
  text-align: center;
  color: rgb(var(--cl-t-muted));
  transition: background-color var(--dur) var(--z-ease), color var(--dur) var(--z-ease);
}
.zc-cat[aria-pressed="true"] b { background: rgba(var(--z-halo), 0.16); color: rgb(var(--z-halo)); }
/* En clair, le violet sur son propre voile tombait a 4,2:1 a 12 px. */
html[data-scheme="light"] .zc-cat[aria-pressed="true"] b { color: #5B21B6; }
/* Sans script : un trait sous l'onglet actif. Avec : le repere glisse. */
.zc-cat[aria-pressed="true"]::after {
  content: "";
  position: absolute;
  left: 14px; right: 14px; bottom: 0;
  height: 3px;
  background: linear-gradient(90deg, rgb(var(--z-charge)), rgb(var(--z-boost)) 62%, rgb(var(--z-halo)));
}
.zc--js .zc-cat::after { display: none; }
.zc-cat-jauge {
  position: absolute;
  left: 0; bottom: 0;
  width: var(--zc-w, 0);
  height: 3px;
  transform: translateX(var(--zc-x, 0));
  background: linear-gradient(90deg, rgb(var(--z-charge)), rgb(var(--z-boost)) 62%, rgb(var(--z-halo)));
  opacity: 0;
  pointer-events: none;
}
.zc--js .zc-cat-jauge { opacity: 1; }
.zc--anime .zc-cat-jauge {
  transition: transform 0.42s cubic-bezier(0.2, 0.8, 0.2, 1), width 0.42s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* La recherche. Le champ passe en z-index 2 au focus (section 2 : le
   contour ne doit pas etre rogne) ; la loupe, le « / » et la croix doivent
   donc passer au-dessus, sinon ils disparaissent des qu'on tape. */
.zc-search { position: relative; flex: 0 1 320px; margin: 4px 0 10px auto; }
.zc-search-ic, .zc-kbd, .zc-search-x { z-index: 3; }
.zc-search-ic {
  position: absolute; left: 12px; top: 50%; translate: 0 -50%;
  color: rgb(var(--cl-t-muted)); pointer-events: none;
}
.zc-search-in {
  width: 100%;
  height: 42px;
  padding: 0 40px 0 36px;
  border: 1px solid rgb(var(--z-edge));
  border-radius: var(--z-r-field);
  background: rgb(var(--cl-surface));
  color: rgb(var(--cl-t-primary));
  font: inherit;
  font-size: 0.9375rem;
  transition: border-color var(--dur) var(--z-ease), box-shadow var(--dur) var(--z-ease);
  -webkit-appearance: none;
  appearance: none;
}
.zc-search-in::placeholder { color: rgb(var(--cl-t-muted)); opacity: 1; }
.zc-search-in::-webkit-search-cancel-button { -webkit-appearance: none; display: none; }
.zc-search-in:hover { border-color: rgb(var(--z-edge)); }
.zc-search-in:focus { border-color: rgb(var(--z-edge)); }
.zc-kbd {
  position: absolute; right: 10px; top: 50%; translate: 0 -50%;
  min-width: 22px; height: 22px; padding: 0 6px;
  display: inline-grid; place-items: center;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-field);
  font-family: var(--ff-heading); font-size: 0.75rem; font-weight: 600;
  color: rgb(var(--cl-t-muted));
  pointer-events: none;
  transition: opacity var(--dur) var(--z-ease);
}
.zc-search-in:focus ~ .zc-kbd, .zc-search.a-texte .zc-kbd { opacity: 0; }
.zc-search-x {
  position: absolute; right: 6px; top: 50%; translate: 0 -50%;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border: 0; border-radius: var(--z-r-field);
  background: none; color: rgb(var(--cl-t-muted)); cursor: pointer;
}
.zc-search-x:hover { color: rgb(var(--cl-t-primary)); background: rgba(var(--cl-t-primary), 0.06); }
.zc-search-x[hidden] { display: none; }
@media (hover: none) { .zc-kbd { display: none; } }

/* Les reglages des boosts : format de livraison et duree. */
.zc-opts { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 28px; }
.zc-opts[hidden], .zc-grid[hidden] { display: none; }
.zc-opt { display: flex; align-items: center; gap: 10px; }
.zc-opt-l { font-size: 0.8125rem; color: rgb(var(--cl-t-muted)); white-space: nowrap; }
.zc-seg {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--cl-border);
  border-radius: calc(var(--z-r-field) + 3px);
  background: rgb(var(--cl-surface));
}
.zc-seg button {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 34px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--z-r-field);
  background: none;
  color: rgb(var(--cl-t-muted));
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease), box-shadow var(--dur) var(--z-ease);
}
.zc-seg button:hover { color: rgb(var(--cl-t-primary)); }
.zc-seg button[aria-pressed="true"] {
  background: rgba(var(--z-boost), 0.16);
  color: rgb(var(--cl-t-primary));
  box-shadow: inset 0 0 0 1px rgba(var(--z-boost), 0.55);
}
.zc-seg button svg { flex: none; opacity: 0.85; }

.zc-sort { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.zc-select { position: relative; display: inline-flex; }
.zc-select select {
  height: 40px;
  padding: 0 34px 0 12px;
  border: 1px solid rgb(var(--z-edge));
  border-radius: var(--z-r-field);
  background: rgb(var(--cl-surface));
  color: rgb(var(--cl-t-primary));
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.zc-select select:hover { border-color: rgb(var(--z-edge)); }
.zc-select svg { position: absolute; right: 11px; top: 50%; translate: 0 -50%; pointer-events: none; color: rgb(var(--cl-t-muted)); }

.zc-hint {
  grid-area: hint;
  max-width: 78ch;
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: rgb(var(--cl-t-muted));
  text-wrap: pretty;
}
.zc-hint span[hidden], .zc-state[hidden] { display: none; }
.zc-count {
  grid-area: count;
  justify-self: end;
  margin: 0;
  font-size: 0.8125rem;
  color: rgb(var(--cl-t-muted));
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.zc-souscats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.zc-souscats a {
  padding: 6px 12px; border: 1px solid var(--cl-border); border-radius: var(--z-r-field);
  font-size: 0.875rem; color: rgb(var(--cl-t-muted)); text-decoration: none;
}
.zc-souscats a[aria-current="page"] { color: rgb(var(--cl-t-primary)); border-color: rgba(var(--z-boost), 0.6); }

/* ---- la grille ---- */
.zc-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: var(--zc-gap);
}

/* ---- la carte ---- */
.zc-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-panel);
  background: rgb(var(--cl-surface));
  isolation: isolate;
  transition:
    translate 0.35s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.25s var(--z-ease);
}
.zc-card[hidden] { display: none; }

/* La lumiere qui suit le pointeur : sur le filet, et a peine dans la
   surface. Posee par custom.js (--mx/--my), seulement au survol. */
.zc-card::before,
.zc-card::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s var(--z-ease);
}
.zc-card::before {
  z-index: -1;
  background: radial-gradient(420px circle at var(--mx, 50%) var(--my, 0%), rgba(var(--z-charge), 0.09), transparent 60%);
}
.zc-card::after {
  z-index: 3;
  padding: 1px;
  background: radial-gradient(260px circle at var(--mx, 50%) var(--my, 0%), rgba(var(--z-halo), 0.75), rgba(var(--z-boost), 0.25) 45%, transparent 70%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
@media (hover: hover) and (pointer: fine) {
  .zc-card:hover { translate: 0 -3px; border-color: rgba(var(--z-halo), 0.24); }
  .zc-card:hover::before, .zc-card:hover::after { opacity: 1; }
}

/* Le focus clavier entoure la carte entiere, pas le seul titre. */
.zc-card:has(.zc-link:focus-visible) { outline: 2px solid rgb(var(--z-halo)); outline-offset: 3px; }
.zc-link:focus-visible { outline: none; }

/* ---- l'image ---- */
.zc-media {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: calc(var(--z-r-panel) - 1px) calc(var(--z-r-panel) - 1px) 0 0;
  background: #03050B;
  perspective: 900px;
}
.zc-scene {
  position: absolute;
  inset: 0;
  transform: rotateX(var(--zc-rx, 0deg)) rotateY(var(--zc-ry, 0deg)) scale(var(--zc-s, 1));
  transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.zc-scene[hidden] { display: none; }
.zc-scene > .zb,
.zc-scene > .zc-img { width: 100%; height: 100%; }
.zc-scene > img.zc-img { object-fit: cover; }
@media (hover: hover) and (pointer: fine) {
  .zc-card:hover .zc-scene { --zc-s: 1.045; }
}
.zc-card.est-incline .zc-scene { transition-duration: 0.18s; }

/* LA BANNIERE DE LA CARTE, en deux couches.

   Dessous, la banniere CSS du produit, POSEE : sans titres, pastilles,
   notification, curseur ni telephone (la carte dit tout cela dans la
   langue du visiteur), figee sur la pose de repos de sa boucle — chaque
   animation part 10 s apres son debut (un tour complet de --cy) et reste
   en pause. C'est l'affiche : sans JavaScript, sous prefers-reduced-motion,
   en economie de donnees, ou si la video ne vient pas, c'est elle qu'on voit.

   Dessus, la meme scene FILMEE (zcv-<scene>.mp4, outils/catalogue/vitrine.py,
   meme regle de mise en scene : VITRINE_CSS dans banc.mjs) : foudre,
   etincelles, compteur, sol, cartes qui respirent. custom.js ne la charge
   qu'a l'approche, ne la joue qu'a l'ecran, et la fond par-dessus l'affiche.
   Pourquoi pas la scene CSS animee : neuf scenes completes tombaient a
   31 i/s sur une puce Intel UHD, sans ralentissement simule ; une video
   H.264 est decodee par le materiel. */
.zc-media .zb2-t,
.zc-media .zb2-pills,
.zc-media .zb2-curseur,
.zc-media .zb2-clic,
.zc-media .zb2-toast,
.zc-media .zb2-tel { display: none !important; }
@container (min-width: 301px) {
  .zc-media .zb2-scene { top: 14cqw; width: 40cqw; height: 25.2cqw; margin-left: -20cqw; }
  .zc-media .zb2-scene--gamme { top: 16cqw; width: 32cqw; height: 20.2cqw; margin-left: -16cqw; }
}
.zc-media .zb * { animation-delay: -10s !important; animation-play-state: paused !important; }

.zc-vid {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--z-ease);
}
.zc-vid.est-pret { opacity: 1; }
/* Le bas de la banniere fond dans la carte au lieu de s'y couper net. */
.zc-media::before {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 22%;
  z-index: 2;
  background: linear-gradient(180deg, rgba(var(--cl-surface), 0), rgba(var(--cl-surface), 0.55));
  pointer-events: none;
}
html[data-scheme="light"] .zc-media::before { background: linear-gradient(180deg, rgba(3, 5, 11, 0), rgba(3, 5, 11, 0.35)); }
/* Une fois la video posee, l'affiche dessous n'a plus a etre peinte. */
.zc-scene.a-video > .zb { visibility: hidden; }

/* En haut a DROITE : le coin gauche est celui du compteur de boosts. */
.zc-flag {
  position: absolute;
  top: 12px; right: 12px;
  z-index: 2;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px 4px 7px;
  border: 1px solid rgba(196, 181, 253, 0.45);
  border-radius: var(--z-r-field);
  background: rgba(7, 11, 24, 0.8);
  color: #DCD3FF;
  font-family: var(--ff-heading);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.2;
}
.zc-flag svg { color: #C4B5FD; }

/* ---- le corps ---- */
.zc-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex: 1;
  padding: 18px 20px 20px;
}
.zc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; }
.zc-title {
  margin: 0;
  min-width: 0;
  font-family: var(--ff-heading);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.015em;
}
.zc-link { color: rgb(var(--cl-t-primary)); text-decoration: none; }
/* Toute la carte est le lien ; les boutons passent au-dessus. */
.zc-link::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.zc-num {
  font-size: 2.125rem;
  font-weight: 700;
  line-height: 0.9;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}
.zc-unit { font-size: 1.0625rem; font-weight: 600; color: rgb(var(--cl-t-muted)); letter-spacing: -0.005em; }

.zc-tag {
  display: inline-flex; align-items: center; gap: 5px;
  flex: none;
  padding: 3px 8px;
  border: 1px solid var(--cl-border);
  border-radius: var(--z-r-field);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.3;
  color: rgba(var(--cl-t-primary), 0.86);
  white-space: nowrap;
}
.zc-tag--link { border-color: rgba(var(--z-charge), 0.55); color: #93BBFF; }
.zc-tag--key { border-color: rgba(var(--z-boost), 0.6); color: #C4B5FD; }
html[data-scheme="light"] .zc-tag--link { color: #1D4ED8; }
html[data-scheme="light"] .zc-tag--key { color: #5B21B6; }

/* La jauge a sa plus petite echelle : 0 a 20, crans a 2, 7 et 14. */
.zc-level { display: flex; align-items: center; gap: 10px; min-height: 20px; }
.zc-gauge {
  position: relative;
  flex: 0 0 104px;
  height: 6px;
  background: rgb(var(--z-track));
  overflow: hidden;
}
.zc-gauge::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--p);
  background-image: linear-gradient(90deg, rgb(var(--z-charge)), rgb(var(--z-boost)) 62%, rgb(var(--z-halo)));
  background-size: 104px 100%;
}
.zc-gauge i {
  position: absolute;
  top: 0; bottom: 0;
  left: var(--x);
  width: 2px;
  margin-left: -1px;
  background: rgb(var(--cl-surface));
}
.zc-lvl { font-family: var(--ff-heading); font-weight: 700; font-size: 0.875rem; color: rgb(var(--cl-t-primary)); white-space: nowrap; }

.zc-specs { display: flex; flex-direction: column; gap: 12px; }
.zc-dur {
  position: relative;
  z-index: 2;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--cl-border);
  border-radius: calc(var(--z-r-field) + 3px);
  background: rgba(var(--cl-t-primary), 0.025);
}
.zc-dur button,
.zc-dur--seul span {
  min-height: 34px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--z-r-field);
  background: none;
  color: rgb(var(--cl-t-muted));
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--dur) var(--z-ease), background-color var(--dur) var(--z-ease), box-shadow var(--dur) var(--z-ease);
}
.zc-dur--seul span { display: grid; place-items: center; cursor: default; color: rgb(var(--cl-t-primary)); }
.zc-dur button:hover { color: rgb(var(--cl-t-primary)); }
.zc-dur button[aria-pressed="true"] {
  background: rgb(var(--cl-surface-raised));
  color: rgb(var(--cl-t-primary));
  box-shadow: inset 0 0 0 1px rgba(var(--z-boost), 0.55);
}

.zc-state { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; margin: 0; font-size: 0.8125rem; line-height: 1.4; }
.zc-st { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; }
.zc-st::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
.zc-st--in { color: rgb(var(--cl-success)); }
.zc-st--in::before { box-shadow: none; }
.zc-st--out { color: rgb(var(--cl-error)); }
.zc-dl { display: inline-flex; align-items: center; gap: 6px; color: rgb(var(--cl-t-muted)); }
.zc-dl svg { color: rgb(var(--z-halo)); flex: none; }

/* ---- le prix et le bouton ---- */
.zc-buy {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--cl-border);
}
.zc-price { display: flex; align-items: baseline; gap: 6px; margin: 0; min-width: 0; }
.zc-price b {
  display: inline-block;
  font-family: var(--ff-heading);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.025em;
  color: rgb(var(--cl-t-primary));
  white-space: nowrap;
}
.zc-from { font-size: 0.8125rem; color: rgb(var(--cl-t-muted)); }
.zc-cta {
  position: relative;
  z-index: 2;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  flex: none;
  min-height: 44px;
  min-width: 104px;
  padding: 0 22px;
  border-radius: var(--z-r-action);
  background: linear-gradient(135deg, rgb(var(--z-charge)) 0%, rgb(var(--z-boost)) 100%);
  color: #070B18;
  font-family: var(--ff-heading);
  font-size: 0.9375rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  transition: filter 0.2s var(--z-ease), scale 0.12s var(--z-ease), box-shadow 0.25s var(--z-ease);
}
.zc-cta:hover { filter: brightness(1.1) saturate(1.05); box-shadow: 0 6px 22px -8px rgba(var(--z-boost), 0.75); }
.zc-cta:active, .zc-cta.est-parti { scale: 0.97; filter: brightness(0.94); }
.zc-cta--ghost {
  background: none;
  color: rgb(var(--cl-t-primary));
  box-shadow: inset 0 0 0 1px rgb(var(--z-edge));
}
.zc-cta--ghost:hover { filter: none; box-shadow: inset 0 0 0 1px rgb(var(--z-halo)); background: rgba(var(--cl-t-primary), 0.04); }
html[data-scheme="light"] .zc-cta:not(.zc-cta--ghost) { color: #fff; }

/* ---- epuise : visible, honnete, en retrait ---- */
/* Un voile plutot qu'un filtre : un filtre pose sur une scene animee la
   repeint a chaque image. Et surtout pas de pause : figee a son depart,
   l'entree de la banniere laisse la scene vide. */
.zc-card.is-out .zc-media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(7, 11, 24, 0.35), rgba(7, 11, 24, 0.62));
  pointer-events: none;
}
.zc-card.is-out .zc-price b { color: rgb(var(--cl-t-muted)); }

/* La mascotte perchee (fixe, a droite) tombait sur la recherche, le tri et
   le bord des cartes : elle s'efface tant que le catalogue est a l'ecran
   (custom.js, Catalogue.loin). Ici et pas dans la section du planificateur,
   que outils/planificateur/verser.py reecrit en entier. */
html.zc-en-vue .zm-perche.est-la { opacity: 0; visibility: hidden; }

/* La regle generale des champs (section 8) pose un filet a 70 % qui ne
   tient pas 3:1 sur le fond de page (2,75:1 mesure) ; `input[type=search]`
   passe devant une simple classe, d'ou le prefixe .zc. */
.zc .zc-search-in, .zc .zc-select select { border-color: rgb(var(--z-edge)); }
.zc .zc-search-in:focus { border-color: rgb(var(--z-edge)); }

/* Le voile des produits epuises passe aussi au-dessus de la video. */
.zc-card.is-out .zc-media::after { z-index: 2; }

/* ---- vide ---- */
.zc-vide {
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 48px 24px 40px;
  border: 1px dashed var(--cl-border);
  border-radius: var(--z-r-panel);
  text-align: center;
}
.zc-vide[hidden] { display: none; }
.zc-vide-t { margin: 10px 0 0; font-family: var(--ff-heading); font-weight: 700; font-size: 1.125rem; color: rgb(var(--cl-t-primary)); }
.zc-vide-h { margin: 0; max-width: 46ch; font-size: 0.875rem; color: rgb(var(--cl-t-muted)); }
.zc-reset {
  margin-top: 14px;
  min-height: 44px;
  padding: 0 18px;
  border: 0;
  border-radius: var(--z-r-action);
  background: none;
  box-shadow: inset 0 0 0 1px rgb(var(--z-edge));
  color: rgb(var(--cl-t-primary));
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.zc-reset:hover { box-shadow: inset 0 0 0 1px rgb(var(--z-halo)); }

/* ---- les tailles ---- */
@media (max-width: 63.99rem) {
  .zc-search { flex-basis: 100%; order: -1; margin: 0 0 4px; }
  /* La place manque pour trois commandes sur une ligne : le tri passe a
     la ligne et s'aligne a gauche, dans le fil des deux autres. */
  .zc-sort { margin-left: 0; }
  .zc-row--top { gap: 8px; }
  .zc-cats { flex: 1 1 100%; }
}
@media (max-width: 40rem) {
  .zc { --zc-gap: 16px; }
  .zc-bar {
    grid-template-areas: "top top" "plus count" "opts opts" "hint hint";
    gap: 12px;
    margin-bottom: 18px;
  }
  .zc-plus {
    grid-area: plus;
    justify-self: start;
    display: inline-flex; align-items: center; gap: 8px;
    min-height: 44px;
    padding: 0 14px;
    border: 1px solid rgb(var(--z-edge));
    border-radius: var(--z-r-field);
    background: rgb(var(--cl-surface));
    color: rgb(var(--cl-t-primary));
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
  }
  .zc-plus[aria-expanded="true"] { border-color: rgba(var(--z-boost), 0.6); }
  .zc-plus b {
    min-width: 20px; padding: 0 6px; border-radius: var(--z-r-field);
    background: rgba(var(--z-halo), 0.18); color: rgb(var(--z-halo)); font-size: 0.75rem;
  }
  .zc-plus b[hidden] { display: none; }
  .zc--replie .zc-row--opts, .zc--replie .zc-hint { display: none; }
  /* Fin de Passe 1 (2026-10-23) : replies DES LE PREMIER RENDU, pas apres
     custom.js. Sinon, au telephone, les reglages ouverts poussaient les
     cartes sous l'ecran, custom.js les repliait vers 7 s (CPU x4, 1,6 Mb/s)
     et les cartes remontaient : le grand nombre de la banniere devenait le
     LCP a ce moment-la (6,9 s) et la grille sautait (CLS 0,34). html[data-
     scheme] est pose par le script de tete : sans JavaScript, rien n'est
     replie et les reglages restent utilisables. */
  html[data-scheme] .zc:not(.zc--init) .zc-row--opts,
  html[data-scheme] .zc:not(.zc--init) .zc-hint { display: none; }
  .zc-count { align-self: center; }
  .zc-cat { padding: 0 11px; }
  .zc-row--opts { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; }
  .zc-opts { display: contents; }
  .zc-opt { flex-direction: column; align-items: stretch; gap: 6px; min-width: 0; }
  .zc-opt:first-child { grid-column: 1 / -1; }
  .zc-opt .zc-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
  .zc-opt .zc-seg button { padding: 0 8px; }
  .zc-sort { flex-direction: column; align-items: stretch; gap: 6px; margin-left: 0; }
  .zc-select select { width: 100%; }
  .zc-body { padding: 16px 16px 18px; gap: 12px; }
  .zc-num { font-size: 2rem; }
  .zc-price b { font-size: 1.625rem; }
}
@media (pointer: coarse) {
  .zc-seg button, .zc-dur button, .zc-dur--seul span { min-height: 44px; }
  .zc-search-in, .zc-select select { height: 46px; }
  .zc-search-x { width: 44px; height: 44px; right: 1px; }
}

@media (prefers-reduced-motion: reduce) {
  .zc-card, .zc-scene, .zc-cta, .zc-cat-jauge { transition: none !important; }
  .zc-card:hover { translate: none; }
  .zc-card:hover .zc-scene { --zc-s: 1; }
}

/* ============================================================
   8b  LE SYSTEME DES BOUTONS, DES CHAMPS ET DES RAYONS (1.3)

   specs/passe1-spec.md, 2026-10-21. Trois styles pour trois roles :
   principal (ce qui fait avancer), contour (l'alternative), lien (aller
   voir). Un levier de rayon (--radius-button, section 1), une lueur
   (--z-lueur, sous le principal seulement), un etat desactive.

   Ce bloc est place ICI, juste avant la section 40 generee, et non dans
   la section 8 : les boutons qu'il regle ont deja leurs regles dans les
   sections 19, 34, 37, 38 et 39, plus bas que la 8. Ecrit apres elles,
   il gagne sans !important et sans gonfler les selecteurs. La section 8
   garde la base ; ici vit le systeme.
   ============================================================ */

/* -- Principal -- */
:is(.sa-btn-primary, .sa-btn--primary.sa-btn--solid, .sa-add-to-cart, .sbuy-cta, .bp-cta,
    .zc-cta:not(.zc-cta--ghost), .zbouton--principal, .sa-faq-support-card-link) {
  min-height: var(--z-h-action);
  border: 0;
  border-radius: var(--z-r-action);
  background-color: rgb(var(--cl-accent));
  background-image: linear-gradient(100deg,
    rgb(var(--z-charge)) -30%, rgb(var(--cl-accent)) 55%, rgb(var(--z-halo)) 160%);
  background-size: 180% 100%;
  background-position: 0% 50%;
  color: rgb(var(--cl-on-accent));
  font-family: var(--ff-heading);
  font-weight: 600;
  text-decoration: none;
  box-shadow: var(--z-lueur);
  filter: none;
  scale: none;
  transition:
    background-position var(--motion-fast) var(--ease-state),
    box-shadow var(--motion-fast) var(--ease-state),
    transform var(--motion-micro) var(--ease-state);
}

/* Neuf cartes, neuf lueurs : au catalogue, la lueur ne vient qu'au
   survol ou au focus. */
.zc-cta:not(.zc-cta--ghost) { box-shadow: none; }
.zc-cta:not(.zc-cta--ghost):is(:hover, :focus-visible) { box-shadow: var(--z-lueur); }

/* Le degrade glisse au survol ; plus de filter: brightness. */
:is(.sa-btn-primary, .sa-btn--primary.sa-btn--solid, .sa-add-to-cart, .sbuy-cta, .bp-cta,
    .zc-cta:not(.zc-cta--ghost), .zbouton--principal, .sa-faq-support-card-link):hover:not(:disabled) {
  background-position: 100% 50%;
  filter: none;
}

:is(.sa-btn-primary, .sa-btn--primary.sa-btn--solid, .sa-add-to-cart, .sbuy-cta, .bp-cta,
    .zc-cta, .zbouton--principal, .sa-faq-support-card-link):active:not(:disabled),
.zc-cta.est-parti {
  transform: translateY(1px);
  scale: none;
  filter: none;
}

/* -- Contour -- */
:is(.sa-btn--secondary.sa-btn--outline, .sa-buy-now, .zbouton--contour, .xz-maj, .xz-fold-sum,
    .zc-plus, .bp-inv-go, .bp-pas, .zvp-btn) {
  border: 1px solid rgb(var(--z-edge));
  border-radius: var(--z-r-action);
  background-color: transparent;
  background-image: none;
  color: rgb(var(--cl-t-primary));
  font-family: var(--ff-heading);
  font-weight: 600;
  box-shadow: none;
  transition:
    border-color var(--motion-fast) var(--ease-state),
    transform var(--motion-micro) var(--ease-state);
}

:is(.sa-btn--secondary.sa-btn--outline, .sa-buy-now, .zbouton--contour, .xz-maj, .zc-plus,
    .bp-inv-go) { min-height: var(--z-h-action); }

@media (max-width: 48rem) {
  .xz-fold-sum { min-height: var(--z-h-action); }
}

:is(.sa-btn--secondary.sa-btn--outline, .sa-buy-now, .zbouton--contour, .xz-maj, .xz-fold-sum,
    .zc-plus, .bp-inv-go, .bp-pas):hover:not(:disabled) {
  border-color: rgb(var(--z-halo));
  background-color: transparent;
}

:is(.sa-btn--secondary.sa-btn--outline, .sa-buy-now, .zbouton--contour, .xz-maj, .xz-fold-sum,
    .zc-plus, .bp-inv-go, .bp-pas):active:not(:disabled) { transform: translateY(1px); }

/* Les commandes du lecteur video gardent leur fond : elles sont posees
   sur l'image. Elles prennent la boite et le rayon d'une action. */
.zvp-btn {
  border-color: rgba(255, 255, 255, 0.18);
  background-color: rgba(7, 11, 24, 0.55);
  color: #fff;
}

/* La paire du heros et le planificateur : la grande hauteur, la meme
   pour les deux voisins (2 px d'ecart avant). */
.sa-hero :is(.sa-btn--primary.sa-btn--solid, .sa-btn--secondary.sa-btn--outline) {
  height: var(--z-h-action-lg);
  min-height: var(--z-h-action-lg);
  padding-block: 0;
}
.bp-cta { min-height: var(--z-h-action-lg); }

/* -- Lien -- */
:is(.zbouton--lien, .sa-feedbacks-link, .zact-all, .xz-foot a, .xz-wait a, .sa-perdu-retour a) {
  border-bottom: 0;
  color: rgb(var(--z-halo));
  font-family: var(--ff-body);
  font-weight: 500;
  text-decoration: underline 1px rgba(var(--z-halo), 0.5);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--motion-fast) var(--ease-state), color var(--motion-fast) var(--ease-state);
}

:is(.zbouton--lien, .sa-feedbacks-link, .zact-all, .xz-foot a, .xz-wait a, .sa-perdu-retour a):hover {
  color: rgb(var(--z-halo));
  text-decoration-color: rgb(var(--z-halo));
}

/* Les actions qui retirent (panier : Remove, Clear cart) ont la forme
   d'un lien mais n'appellent pas l'oeil : gris, rouge au survol. */
.zbouton.zbouton--discret {
  color: rgb(var(--cl-t-muted));
  text-decoration-color: rgba(var(--cl-t-muted), 0.5);
}
.zbouton.zbouton--discret:hover {
  color: rgb(var(--cl-error));
  text-decoration-color: currentColor;
}

@media (pointer: coarse) {
  :is(.zbouton--lien, .sa-feedbacks-link, .zact-all, .sa-perdu-retour a):not(.hidden) {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

.zbouton--principal:not(.hidden),
.zbouton--contour:not(.hidden) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* -- Desactive : une seule forme, lisible comme telle (texte 4,99:1 sur
      la piste en sombre, 4,97:1 en clair). opacity: 1 neutralise les
      disabled:opacity-50/-60 du gabarit (meme specificite, plus bas). -- */
:is(.sa-add-to-cart, .sa-buy-now, .zbouton, .sbuy-cta, .bp-cta, .bp-inv-go, .bp-pas, .zc-cta):is(:disabled, [aria-disabled="true"]) {
  border-color: transparent;
  background-color: rgb(var(--z-track));
  background-image: none;
  color: rgb(var(--cl-t-muted));
  box-shadow: none;
  opacity: 1;
  cursor: not-allowed;
  transform: none;
  filter: none;
}
/* Pendant la verification, bp-inv-go est desactive : c'est un etat
   « en cours », pas « indisponible ». */
.bp-inv-field.is-checking .bp-inv-go:disabled {
  border-color: rgb(var(--z-edge));
  background-color: transparent;
  cursor: progress;
}

/* -- Champs : hauteur, police, focus visible partout -- */
:is(.zc-search-in, .zc-sort select, .zfaq-champ) {
  min-height: var(--z-h-action);
  font-size: 1rem;
}
.input-stepper { min-height: var(--z-h-action); }
.input-stepper input {
  font-family: var(--ff-heading);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.input-stepper input:focus-visible {
  outline: 2px solid rgb(var(--z-halo));
  outline-offset: 2px;
}

/* -- Rayons hors systeme ramenes aux jetons (spec 1.3 C) -- */
.zvp-ouvrir-vignette { border-radius: var(--z-r-field); }
.zpf-cadre { border-radius: var(--z-r-action); }
.zc-media { border-radius: var(--z-r-panel) var(--z-r-panel) 0 0; }
:is(.bp-case, .bp-frise-rail) { border-radius: var(--z-r-flat); }
.bp-srv-gif { border-radius: var(--z-r-field); }
.bp-dock { border-radius: var(--z-r-panel); }
.bp-srv-icon { border-radius: 50%; }
.zma-bouton { border-radius: 50%; }
:is(.zpf-filtre, .zpf-duree, .bp-cible, .bp-statut, .bp-mine, .bp-fit, .xz-mine, .zma-pastille) {
  border-radius: var(--z-r-pill);
}
.splide__slide { border-radius: var(--z-r-field); }
/* l'image est rognee par la piste (rounded-card + overflow) : angle droit */
.splide__slide img { border-radius: var(--z-r-flat); }
.splide__arrow {
  width: 40px;
  height: 40px;
  border: 1px solid rgb(var(--z-edge));
  border-radius: var(--z-r-action);
}
@media (pointer: coarse) {
  .splide__arrow { width: 44px; height: 44px; }
}

/* Les 12 svg a rayon 2 px (releve de la spec) sont les drapeaux des
   selecteurs de langue et de devise (rounded-[2px] du gabarit) : des
   icones decoratives, donc a angle droit. */
svg.rounded-\[2px\] { border-radius: var(--z-r-flat); }


/* -- Phase C (2026-10-21) --------------------------------------------- */

/* Les icones de fonction de la marque (zm-fonction, 1.2b) : titres du
   stock et du rachat (20 px, halo), libelle du verificateur (16 px),
   menu mobile (16 px, encre attenuee). */
.zfn-titre {
  display: inline-flex;
  vertical-align: -0.12em;
  margin-inline-end: var(--z-2);
  color: rgb(var(--z-halo));
}
.ilc-label { display: flex; align-items: center; gap: var(--z-2); }
.ilc-label .zfn { flex: none; color: rgb(var(--z-halo)); }
.zfn-nav { display: inline-flex; flex: none; color: rgb(var(--cl-t-muted)); }
.zfn-ok { stroke: rgb(var(--cl-success)); }
.zc-tag .ze { flex: none; }

/* Les selecteurs de langue et de devise : des controles de barre, pas des
   boutons a filet (spec 1.3 B). Fond transparent, survol 5 %. */
.sa-navbar :is(.language-selector, .currency-selector) > button {
  border-color: transparent;
  background-color: transparent;
}
.sa-navbar :is(.language-selector, .currency-selector) > button:hover {
  background-color: rgba(var(--cl-t-primary), 0.05);
}

/* Les segments du catalogue : le rayon d'un champ, plus de rayon derive ;
   segments colles, coupes par le conteneur, separes par un filet. */
.zc-seg,
.zc-dur {
  gap: 0;
  padding: 0;
  overflow: hidden;
  border-radius: var(--z-r-field);
}
.zc-seg button,
.zc-dur button,
.zc-dur--seul span { border-radius: 0; }
.zc-seg button + button,
.zc-dur button + button { border-left: 1px solid var(--cl-border); }

/* Les commandes de la video : 44 px, comme toute action. */
.zvp-btn { height: 44px; min-width: 44px; }

/* Les fleches de la galerie Design : la regle de la bibliotheque passait
   devant (.splide .splide__arrow). */
.splide .splide__arrow {
  width: 40px;
  height: 40px;
  border: 1px solid rgb(var(--z-edge));
  border-radius: var(--z-r-action);
  opacity: 1;
}
@media (pointer: coarse) {
  .splide .splide__arrow { width: 44px; height: 44px; }
}

/* Le desactive, aussi pour les fleches de galerie et les variantes en
   rupture (tuiles du Design pack) : plus d'opacity .3/.5. */
.splide .splide__arrow:disabled,
.sa-product-form [role="option"]:disabled {
  border-color: transparent;
  background-color: rgb(var(--z-track));
  color: rgb(var(--cl-t-muted));
  opacity: 1;
  cursor: not-allowed;
}

/* Le lien de retour de la 404 commence au bord, comme le texte. */
.sa-perdu-retour a { padding-inline: 0; }

/* Le lien d'evitement (master.njk) : hors ecran, au style « lien » quand
   le clavier l'atteint (premier arret de tabulation). Le contenu cible
   (main#contenu) recoit le focus sans contour : c'est un repere, pas une
   commande. */
.zevite {
  position: absolute;
  top: var(--z-2);
  left: var(--z-2);
  z-index: 100;
  padding: var(--z-2) var(--z-3);
  border-radius: var(--z-r-field);
  background: rgb(var(--cl-background));
  color: rgb(var(--z-halo));
  font-family: var(--ff-body);
  font-weight: 500;
  text-decoration: underline 1px rgba(var(--z-halo), 0.5);
  text-underline-offset: 3px;
  transform: translateY(-200%);
}
.zevite:focus-visible { transform: none; }
#contenu:focus { outline: none; }

/* -- Corrections de fin de Passe 1 (QA et revue 1.8, 2026-10-23) -- */

/* Le lien de la bulle de l'assistant : 4,13:1 avec l'accent ; le halo
   tient au moins 4,5:1 dans les deux schemas. 44 px au doigt. */
.zma-bulle-lien { color: rgb(var(--z-halo)); }
@media (pointer: coarse) {
  .zma-bulle-lien { display: inline-flex; align-items: center; min-height: 44px; }
}

/* Les icones des commandes video sont posees sur l'image : claires dans
   les deux schemas (elles disparaissaient en clair). */
html[data-scheme] .zvp-btn { color: #E8ECFA; }

/* 404 au telephone : la mascotte ne recouvre plus le titre. */
@media (max-width: 47.99rem) {
  .sa-perdu-lutin { margin-bottom: var(--z-4); }
  .sa-perdu-lutin .zm { width: 150px; }
}

/* Le « − » desactive du planificateur : la regle commune (8b) perdait
   contre .bp-pas:disabled { opacity: .35 } sur une page ; 2,9:1 releve. */
.bp .bp-pas:disabled {
  opacity: 1;
  border-color: transparent;
  background-color: rgb(var(--z-track));
  color: rgb(var(--cl-t-muted));
}

/* Le contour de carte en Saira 600 comme les autres boutons ; les liens de
   tri des avis et les filtres du portfolio a 44 px. */
.zc-cta--ghost { font-weight: 600; }
.sa-page-feedback-link { display: inline-flex; align-items: center; min-height: 44px; }
.zpf-filtre { min-height: 44px; }

/* Le lien d'evitement : 44 px de haut. */
.zevite { display: inline-flex; align-items: center; min-height: 44px; }

/* ==zq:debut== */
/* ============================================================
   40  LA MARQUE EN MOUVEMENT

   Source : outils/marque/zq.css — verse ici par outils/marque/verser.mjs.
   Ne pas editer cette section dans custom.css : elle serait ecrasee.

   Une seule idee, la CHARGE : l'energie entre par une arete, parcourt
   la geometrie du Z, remplit la forme de bas en haut comme la jauge du
   site, puis se pose. Tout ce qui bouge dans la marque est une phase de
   ce cycle, rien d'autre :

     trace      l'arete s'allume, une ligne de precision
     charge     la forme se remplit, du bas vers le haut
     arc        une etincelle descend la diagonale du Z
     balayage   un reflet passe, parallele a la diagonale
     pose       la forme se pose, un seul petit depassement

   Hierarchie : une marque ne bouge que si elle REPOND a quelque chose
   (arrivee sur le site, survol, action reussie, attente). Au repos,
   rien ne tourne en boucle, sauf ce qui dit « c'est en direct ».
   ============================================================ */

html {
  /* -- Rythme. Six durees, chacune ~1,7 fois la precedente. Toute
        animation du site en prend une ; aucune valeur au hasard. -- */
  --motion-micro: 120ms;     /* pression, couleur */
  --motion-fast: 200ms;      /* survol, focus, petit retour */
  --motion-normal: 320ms;    /* apparition d'un composant */
  --motion-emphasis: 560ms;  /* succes, erreur, bascule d'etat */
  --motion-scene: 1100ms;    /* entree de la marque */
  --motion-cinematic: 2400ms;/* revelation (video, intro) */

  /* -- Courbes. Une arrivee qui decelere fort (la charge qui se pose),
        un depart qui accelere, un changement d'etat symetrique, un
        ressort amorti a un seul depassement de 4,6 %, et le courant
        lineaire de l'energie qui circule. -- */
  --ease-arrive: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-leave: cubic-bezier(0.7, 0, 0.84, 0);
  --ease-state: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-current: linear;
  --ease-spring: cubic-bezier(0.34, 1.3, 0.64, 1);

  /* -- Les couleurs de la marque. Le Z garde son cyan electrique vers
        le bleu : c'est ce qu'on reconnait. L'energie est un cyan tres
        clair, jamais du blanc pur. -- */
  --zq-c1: #46D8FF;
  --zq-c2: #1FA2FF;
  --zq-c3: #2A5BFF;
  --zq-energie: #A6F1FF;
  --zq-ombre: #061A5C;
  --zq-ok: rgb(var(--cl-success, 52, 211, 153));
  --zq-err: rgb(var(--cl-error, 248, 113, 113));
}

@supports (transition-timing-function: linear(0, 1)) {
  html {
    --ease-spring: linear(0, 0.034, 0.118, 0.232, 0.357, 0.483, 0.602, 0.707,
      0.797, 0.871, 0.93, 0.974, 1.005, 1.026, 1.039, 1.045, 1.046, 1.044, 1.04,
      1.034, 1.028, 1.023, 1.017, 1.013, 1.009, 1.005, 1.003, 1.001, 1, 0.999,
      0.998, 0.998, 1);
  }
}

html[data-scheme="light"] {
  /* Sur quasi blanc, le cyan du haut tomberait a 1,9:1 : la marque
     fonce d'un cran, l'ordre des teintes est conserve. */
  --zq-c1: #0BA6E8;
  --zq-c2: #1680EA;
  --zq-c3: #1D46D6;
  --zq-energie: #38C8FF;
  --zq-ombre: #0B1F66;
}

/* -- Les definitions partagees (degrades, decoupes) vivent dans un
      <svg> de taille nulle en tete de page. Jamais display:none : un
      degrade place dans un sous-arbre non rendu ne peint plus. -- */
.zq-defs { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; }

/* ------------------------------------------------------------
   40a  LE SIGNE
   ------------------------------------------------------------ */

.zq {
  display: block;
  flex: none;
  overflow: visible;
  /* La pose part du centre optique : le Z est plus lourd en bas. */
  transform-origin: 50% 54%;
}
.zq-face { fill: url(#zq-face); }
.zq-rim {
  fill: none;
  stroke: url(#zq-rim);
  stroke-width: 3.4;
  opacity: 0;
  transition: opacity var(--motion-fast) var(--ease-arrive);
}
.zq-halo { fill: url(#zq-halo); opacity: 0; }
.zq-balai { fill: url(#zq-balai); transform: translateX(-118px); }
.zq-arc {
  fill: none;
  stroke: var(--zq-energie);
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 0.16 1.2;
  stroke-dashoffset: 0.16;
  opacity: 0;
}
.zq-trait {
  fill: none;
  stroke: var(--zq-energie);
  stroke-width: var(--zq-trait, 2.2);
  stroke-linejoin: round;
  stroke-dasharray: 1 1;
  stroke-dashoffset: 1;
  opacity: 0;
}

/* Relief : le rebord eclaire (lumiere en haut a gauche, arete basse
   dans l'ombre) et le contre-jour. Seulement a partir de 40 px : en
   dessous, un biseau n'est plus qu'une bavure. */
.zq--relief .zq-rim { opacity: 1; }
.zq--relief .zq-halo { opacity: 1; }

/* -- Survol et focus : un reflet passe une fois, le rebord s'allume,
      le signe monte d'un pixel. Rien ne reste allume apres. -- */
.zq-hote { --zq-y: 0px; }
.zq-hote .zq {
  translate: 0 var(--zq-y);
  transition: translate var(--motion-fast) var(--ease-arrive);
}
@media (hover: hover) and (pointer: fine) {
  .zq-hote:hover { --zq-y: -1px; }
  .zq-hote:hover .zq-rim { opacity: 0.85; }
  .zq-hote:hover .zq-balai { animation: zq-balai 680ms var(--ease-state) both; }
}
.zq-hote:focus-visible .zq-rim { opacity: 0.85; }
.zq-hote:focus-visible .zq-balai { animation: zq-balai 680ms var(--ease-state) both; }

/* -- Micro-vie : un reflet, quatre fois, espaces de 16 s, puis plus
      rien. Assez pour que la marque respire pendant qu'on decouvre la
      page ; jamais une boucle a vie.
      Quatre passages de 880 ms, et non une animation de 16 s repetee
      quatre fois : dans celle-la, le reflet restait immobile 94 % du
      temps mais l'animation, active, recalculait le style a chaque
      image (un transform SVG ne passe pas par le compositeur) : 60
      recalculs par seconde pendant 71 s, mesure a l'accueil le
      2026-10-21. Entre deux passages, plus rien ne tourne. -- */
.zq--vivant .zq-balai {
  animation:
    zq-balai-repos 880ms var(--ease-state) 7s both,
    zq-balai-repos 880ms var(--ease-state) 23s both,
    zq-balai-repos 880ms var(--ease-state) 39s both,
    zq-balai-repos 880ms var(--ease-state) 55s both;
}

/* -- Decharge : la reponse a une action reussie (ajout au panier,
      lien verifie). Posee par custom.js, retiree a la fin. -- */
.zq--decharge { animation: zq-pose 620ms var(--ease-spring) both; }
.zq--decharge .zq-arc { animation: zq-arc 520ms var(--ease-current) 60ms both; }
.zq--decharge .zq-balai { animation: zq-balai 620ms var(--ease-state) 120ms both; }
.zq--decharge .zq-rim { opacity: 0.9; }

/* -- L'ENTREE. Une fois par session, sur la marque de l'en-tete :
      l'arete s'allume, la forme se charge du bas vers le haut, une
      etincelle descend la diagonale, la forme se pose, puis le nom de
      la boutique sort du flou. 1 220 ms en tout. La classe zq-entree
      est posee sur <html> par un script de tete, avant la peinture. -- */
.zq-entree .zq--nav .zq-trait { animation: zq-trace 900ms var(--ease-state) both; }
.zq-entree .zq--nav .zq-face { animation: zq-charge 560ms var(--ease-arrive) 260ms both; }
.zq-entree .zq--nav .zq-arc { animation: zq-arc 440ms var(--ease-current) 660ms both; }
.zq-entree .zq--nav { animation: zq-pose 640ms var(--ease-spring) 560ms both; }
.zq-entree .zq-nom {
  animation: zq-nom var(--motion-emphasis) var(--ease-arrive) 680ms both;
}

/* -- Le pied de page : la meme entree, plus ample (le signe y est en
      relief), declenchee quand le pied arrive a l'ecran. custom.js ne
      pose zq-attend que sur un signe encore SOUS l'ecran, puis est-vu ;
      sans script, rien n'est cache. -- */
.zq--pied.zq-attend .zq-face { clip-path: inset(100% 0 0 0); }
.zq--pied.zq-attend .zq-rim,
.zq--pied.zq-attend .zq-halo { opacity: 0; }
.zq--pied.est-vu .zq-trait { animation: zq-trace 1000ms var(--ease-state) both; }
.zq--pied.est-vu .zq-face { animation: zq-charge 640ms var(--ease-arrive) 300ms both; }
.zq--pied.est-vu .zq-rim,
.zq--pied.est-vu .zq-halo { animation: zq-apparait 700ms var(--ease-arrive) 640ms both; }
.zq--pied.est-vu .zq-arc { animation: zq-arc 480ms var(--ease-current) 760ms both; }
.zq--pied.est-vu { animation: zq-pose 700ms var(--ease-spring) 640ms both; }

/* -- L'attente pleine page (le voile de transition) : l'energie fait
      le tour de l'arete, la forme se charge et se decharge, en boucle
      tant que la page suivante n'est pas arrivee. Rien ne tourne tant
      que le voile n'est pas leve (.is-voile) : le signe est present sur
      chaque page, cache, et une boucle cachee coute quand meme. -- */
.zq--attente .zq-face { opacity: 0.35; }
.zq--attente .zq-trait { opacity: 0.9; stroke-dasharray: 0.22 0.78; }
.is-voile .zq--attente .zq-face { animation: zq-charge-boucle 1600ms var(--ease-state) infinite both; }
.is-voile .zq--attente .zq-trait { animation: zq-flux 1600ms var(--ease-current) infinite; }

@keyframes zq-trace {
  0% { stroke-dashoffset: 1; opacity: 0; }
  8% { opacity: 1; }
  52% { stroke-dashoffset: 0; opacity: 1; }
  100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes zq-charge {
  from { clip-path: inset(100% 0 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
@keyframes zq-charge-boucle {
  0% { clip-path: inset(100% 0 0 0); opacity: 0.35; }
  55% { clip-path: inset(0 0 0 0); opacity: 1; }
  80% { clip-path: inset(0 0 0 0); opacity: 1; }
  100% { clip-path: inset(0 0 0 0); opacity: 0.35; }
}
@keyframes zq-arc {
  0% { stroke-dashoffset: 0.16; opacity: 0; }
  12% { opacity: 1; }
  80% { opacity: 1; }
  100% { stroke-dashoffset: -1.04; opacity: 0; }
}
@keyframes zq-balai {
  from { transform: translateX(-118px); }
  to { transform: translateX(118px); }
}
@keyframes zq-balai-repos {
  from { transform: translateX(-118px); opacity: 0.55; }
  to { transform: translateX(118px); opacity: 0.55; }
}
@keyframes zq-pose {
  from { scale: 0.92; }
  to { scale: 1; }
}
@keyframes zq-apparait {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes zq-nom {
  from { opacity: 0; filter: blur(4px); translate: -6px 0; }
  to { opacity: 1; filter: blur(0); translate: 0 0; }
}
@keyframes zq-flux {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

/* ------------------------------------------------------------
   40b  LE CHARGEUR — la silhouette du Z, que l'energie parcourt.
   Il herite de la couleur du texte : il se pose dans un bouton
   violet comme dans un champ, sans reglage.
   ------------------------------------------------------------ */

.zq-load { overflow: visible; }
.zq-load .zq-load-piste,
.zq-load .zq-load-z { fill: none; stroke: currentColor; opacity: 0.24; }
.zq-load .zq-load-flux {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-dasharray: 0.24 0.76;
  animation: zq-flux 1100ms var(--ease-current) infinite;
}
/* La diagonale s'allume quand l'energie passe par la taille du sablier. */
.zq-load .zq-load-z { animation: zq-load-z 1100ms var(--ease-state) infinite; }
@keyframes zq-load-z {
  0%, 30%, 100% { opacity: 0.24; }
  45% { opacity: 0.85; }
  80% { opacity: 0.85; }
}

/* ------------------------------------------------------------
   40c  REUSSITE ET ERREUR
   Reussite : l'energie arrive en cyan, l'anneau se ferme, la coche
   se trace (sa longue branche a l'angle de la diagonale du Z), une
   onde part une fois, l'ensemble se pose. 920 ms.
   Erreur : l'anneau se trace, s'interrompt et recule — il reste
   ouvert — puis le point d'exclamation se pose. Pas de secousse.
   ------------------------------------------------------------ */

.zq-ok, .zq-err { overflow: visible; flex: none; }
.zq-ok { color: var(--zq-ok); animation: zq-pose 560ms var(--ease-spring) both; }
.zq-ok-anneau {
  fill: var(--zq-ok); fill-opacity: 0;
  stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 1 1; stroke-dashoffset: 0;
  transform: rotate(-90deg); transform-origin: 12px 12px;
  animation: zq-ok-anneau 520ms var(--ease-state) both;
}
.zq-ok-coche {
  fill: none; stroke: currentColor; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1 1;
  animation: zq-trace-seul 300ms var(--ease-arrive) 240ms both;
}
/* L'onde : un seul anneau qui s'eloigne et s'eteint. Une decharge
   controlee, pas un feu d'artifice. */
.zq-ok-onde {
  fill: none; stroke: var(--zq-energie); stroke-width: 1.4;
  opacity: 0; transform-origin: 12px 12px;
  animation: zq-onde 520ms var(--ease-arrive) 400ms both;
}
@keyframes zq-onde {
  0% { opacity: 0.8; scale: 1; }
  100% { opacity: 0; scale: 1.42; }
}
@keyframes zq-ok-anneau {
  0% { stroke-dashoffset: 1; stroke: var(--zq-energie); fill-opacity: 0; }
  60% { stroke-dashoffset: 0; stroke: var(--zq-energie); fill-opacity: 0; }
  100% { stroke-dashoffset: 0; stroke: currentColor; fill-opacity: 0.14; }
}
@keyframes zq-trace-seul {
  from { stroke-dashoffset: 1; }
  to { stroke-dashoffset: 0; }
}

.zq-err { color: var(--zq-err); }
.zq-err-anneau {
  fill: var(--zq-err); fill-opacity: 0.12;
  stroke: currentColor; stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 1 1; stroke-dashoffset: 0.1;
  /* l'ouverture tombe a une heure, dans l'axe de la diagonale */
  transform: rotate(-40deg); transform-origin: 12px 12px;
  animation: zq-err-anneau 460ms var(--ease-state) both;
}
.zq-err-barre {
  fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round;
  animation: zq-err-signe var(--motion-normal) var(--ease-arrive) 300ms both;
}
.zq-err-point { fill: currentColor; animation: zq-err-signe var(--motion-normal) var(--ease-arrive) 380ms both; }
@keyframes zq-err-anneau {
  0% { stroke-dashoffset: 1; fill-opacity: 0; }
  62% { stroke-dashoffset: 0.02; fill-opacity: 0; }
  100% { stroke-dashoffset: 0.1; fill-opacity: 0.12; }
}
@keyframes zq-err-signe {
  from { opacity: 0; translate: 0 -2px; }
  to { opacity: 1; translate: 0 0; }
}

/* ------------------------------------------------------------
   40d  OU LA MARQUE VIT
   ------------------------------------------------------------ */

/* Les couches internes ne portent jamais de trait herite : certaines
   regles anciennes posent stroke sur tout <svg> d'un conteneur. */
.zq-face, .zq-halo, .zq-balai { stroke: none; }

/* -- En-tete. Le signe remplace le GIF de 2,9 Mo (540 px affiches en
      32, sur fond presque noir, avec ses images de parasites). -- */
.sa-navbar-logo .zq { width: 30px; height: auto; }
.sa-footer .zq--pied { width: 44px; height: auto; }

/* -- Le voile de transition : le signe en attente remplace le Z au
      trait. La regle d'origine habille tout <svg> du sceau. -- */
.zpt-sceau .zq { width: 64px; height: auto; stroke: none; }

/* -- Notifications : elles arrivent (fondu, 8 px, net), elles ne
      tombent pas. Le depart reste instantane : l'attention est ailleurs. -- */
.sa-toast { animation: zq-arrivee var(--motion-normal) var(--ease-arrive) both; }
.sa-toast .zq-ok, .sa-toast .zq-err { margin-top: 1px; }
@keyframes zq-arrivee {
  from { opacity: 0; translate: 0 8px; filter: blur(4px); }
  to { opacity: 1; translate: 0 0; filter: blur(0); }
}

/* -- Verificateur d'invitation : l'issue s'affiche dans le champ, en
      forme ET en couleur (la couleur ne porte jamais seule). -- */
.ilc-etat {
  position: absolute;
  top: 50%;
  right: 12px;
  translate: 0 -50%;
  z-index: 3; /* le champ est en z-index 2 */
  display: block;
  pointer-events: none;
}
.ilc-field:has(.ilc-etat:not([style*="none"])) .ilc-input { padding-right: 42px; }

/* -- Les panneaux en direct (xtremez).
      STOCK : en direct, connecte. Un filet court en haut du cadre ou une
      lueur circule (donnee qui arrive), un signal a cote du titre ; au
      rafraichissement, une ligne de balayage descend le cadre.
      RACHAT : verification. Rien ne tourne ; a chaque chargement du
      cadre, une seule ligne de verification descend, puis s'eteint. -- */
.xz-rail, .xz-scan { position: absolute; pointer-events: none; z-index: 2; }
.xz-rail {
  top: 0; left: 0; right: 0; height: 2px;
  overflow: hidden;
  background: linear-gradient(90deg, transparent, rgba(var(--z-charge), 0.35) 20%, rgba(var(--z-charge), 0.35) 80%, transparent);
  opacity: 0.7;
}
.xz-rail::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: 0;
  width: 24%;
  background: linear-gradient(90deg, transparent, var(--zq-energie), transparent);
  translate: -100% 0;
  animation: zq-flux-rail 3600ms var(--ease-state) infinite;
  animation-play-state: paused;
}
.xz-en-vue .xz-rail::after { animation-play-state: running; }
@keyframes zq-flux-rail {
  0% { translate: -100% 0; }
  62%, 100% { translate: 420% 0; }
}
.xz-frais .xz-rail { animation: zq-rail-frais 1200ms var(--ease-arrive) both; }
@keyframes zq-rail-frais {
  0% { opacity: 0.7; }
  18% { opacity: 1; box-shadow: 0 0 12px 1px rgba(var(--z-charge), 0.6); }
  100% { opacity: 0.7; box-shadow: none; }
}

.xz-scan {
  left: 0; right: 0; top: 0; height: 30%;
  background: linear-gradient(180deg, transparent, rgba(var(--z-charge), 0.07) 70%, rgba(var(--z-charge), 0.22) 97%, var(--zq-energie) 100%);
  border-bottom: 1px solid var(--zq-energie);
  opacity: 0;
  translate: 0 -100%;
}
.xz.est-en-maj .xz-scan { animation: zq-scan 1100ms var(--ease-state) infinite; }
.xz--rachat.xz-frais .xz-scan { animation: zq-scan 1000ms var(--ease-state) 1 both; }
@keyframes zq-scan {
  0% { translate: 0 -100%; opacity: 0; }
  12% { opacity: 1; }
  80% { opacity: 1; }
  100% { translate: 0 340%; opacity: 0; }
}

/* Le signal du stock : un point d'etat, et une onde qui s'en eloigne
   toutes les 3,2 s — le seul battement de la page, parce qu'il dit
   une chose vraie : ce chiffre est en direct. Arrete hors ecran. */
.xz-signal {
  position: relative;
  display: inline-block;
  width: 8px; height: 8px;
  margin-right: 10px;
  vertical-align: 0.18em;
  border-radius: 50%;
  background: rgb(var(--cl-success));
}
.xz-signal::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 1.5px solid rgb(var(--cl-success));
  opacity: 0;
  animation: zq-signal 3200ms var(--ease-arrive) infinite;
  animation-play-state: paused;
}
.xz-en-vue .xz-signal::after { animation-play-state: running; }
@keyframes zq-signal {
  0% { opacity: 0.7; scale: 1; }
  45%, 100% { opacity: 0; scale: 2.6; }
}

/* Le bouton Rafraichir : le chargeur de la marque prend la place de
   l'icone le temps du rechargement. */
.xz-maj-charge { display: none; width: 16px; height: 16px; }
.xz.est-en-maj .xz-maj-ico { display: none; animation: none; }
.xz.est-en-maj .xz-maj-charge { display: block; }
.xz-charge { width: 14px; height: 14px; color: rgb(var(--cl-accent)); }
.xz-pret:not(.est-en-maj) .xz-charge *,
.xz:not(.xz-en-vue) .xz-charge * { animation-play-state: paused; }

/* -- Le catalogue. La carte reagissait deja en bloc (lumiere au
      curseur, soulevement, banniere qui joue) ; le bouton s'y joint par
      le meme reflet que la marque, une fois, a l'entree du survol. -- */
.zc-cta { overflow: hidden; }
.zc-cta::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.38) 50%, transparent 62%);
  translate: -110% 0;
  pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
  .zc-card:hover .zc-cta:not(.zc-cta--ghost)::after { animation: zq-cta 760ms var(--ease-state) 80ms both; }
  .zc-card:hover .zc-tag .zf { color: var(--zq-energie); }
}
.zc-tag .zf { transition: color var(--motion-fast) var(--ease-arrive); }
@keyframes zq-cta {
  from { translate: -110% 0; }
  to { translate: 110% 0; }
}

/* -- La jauge du heros se remplit une fois au chargement (section 9) :
      elle prend le front de charge du signe, une ligne de lumiere qui
      avance avec le remplissage puis s'eteint. L'element s'etire avec
      la meme courbe et la meme duree que la decoupe : son bord lumineux
      suit le bord du remplissage, sans calcul. -- */
@media (prefers-reduced-motion: no-preference) {
  .sa-hero-scale .zscale-fill::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0; left: 0;
    width: calc(var(--zs-fill) / var(--zs-max) * 100%);
    background: linear-gradient(90deg, transparent 84%, rgba(166, 241, 255, 0.85));
    transform-origin: left center;
    animation: zq-front 900ms var(--z-ease) both, zq-eteint 360ms var(--ease-arrive) 780ms both;
    pointer-events: none;
  }
}
@keyframes zq-front { from { transform: scaleX(0); } }
@keyframes zq-eteint { to { opacity: 0; } }

/* -- Le planificateur. Son chargeur devient celui de la marque, a
      l'arret tant que le champ ne verifie rien ; franchir un niveau
      CHARGE les paliers allumes, l'un apres l'autre, du meme reflet que
      le signe. -- */
.bp-spin.zq-load { border: 0; border-radius: 0; animation: none; }
.bp-spin .zq-load-flux, .bp-spin .zq-load-z { animation-play-state: paused; }
.is-checking .bp-spin .zq-load-flux, .is-checking .bp-spin .zq-load-z { animation-play-state: running; }
.bp-tier-blocks span { position: relative; overflow: hidden; }
.bp-tier-blocks span::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.75), transparent);
  translate: -100% 0;
}
.bp-tier.is-flash .bp-tier-blocks span.is-on::after { animation: zq-cta 420ms var(--ease-state) both; }
.bp-tier.is-flash .bp-tier-blocks span.is-on:nth-child(2)::after { animation-delay: 90ms; }
.bp-tier.is-flash .bp-tier-blocks span.is-on:nth-child(3)::after { animation-delay: 180ms; }

/* -- Les emblemes produit (avis, recherche). -- */
.ze { display: block; flex: none; overflow: visible; }
.ze--boost .ze-palier { fill: rgba(var(--z-edge), 0.34); }
.ze--boost .ze-palier.est-on { fill: url(#ze-pyr); }
.ze--nitro path { fill: url(#ze-nitro); }
.ze--flux path { fill: rgb(var(--cl-t-muted)); }
.ze--design .ze-t { fill: none; stroke: rgb(var(--z-halo)); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.ze--design rect, .ze--design circle { fill: rgb(var(--cl-background)); stroke: rgb(var(--z-halo)); stroke-width: 1.4; }
.ze--marque .zq-face { fill: url(#zq-face); }
.ze-mois circle { fill: rgb(var(--z-edge)); }
.ze-tuile {
  display: grid;
  place-items: center;
  width: 100%; height: 100%;
  border-radius: inherit;
  background: rgb(var(--cl-surface));
  box-shadow: inset 0 0 0 1px var(--cl-border);
}
.ze-tuile .ze { width: 58%; height: 58%; }
/* Dans une ligne d'avis : 20 px, a la place d'une affiche de 1600 px. */
.ze-avis { display: inline-grid; place-items: center; width: 20px; height: 20px; flex: none; }

/* ------------------------------------------------------------
   40z  MOUVEMENT REDUIT
   La marque reste entiere et nette : plus de trace, de charge ni
   d'etincelle. Les etats changent par un fondu court ; le chargeur
   garde un battement d'opacite lent (il doit dire « ca travaille »).
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .zq, .zq *, .zq-ok, .zq-ok *, .zq-err, .zq-err *, .zq-nom {
    animation: none !important;
    transition-duration: 0ms !important;
  }
  .is-voile .zq--attente .zq-face { animation: zq-doux 1600ms var(--ease-state) infinite alternate !important; }
  .zq-ok, .zq-err { animation: zq-apparait var(--motion-fast) linear both !important; }
  .zq-ok-anneau { fill-opacity: 0.14; }
  .zq-ok-onde { display: none; }
  .zq-load .zq-load-flux { animation: zq-doux 1400ms var(--ease-state) infinite alternate; stroke-dasharray: none; }
  .zq-load .zq-load-z { animation: none; }
  .sa-toast { animation: zq-apparait var(--motion-fast) linear both; }
  .xz-rail::after, .xz-signal::after { animation: none; }
  .xz-scan { display: none; }
  .zc-cta::after, .bp-tier-blocks span::after { display: none; }
}
@keyframes zq-doux {
  from { opacity: 0.35; }
  to { opacity: 1; }
}
/* ==zq:fin== */