/* ═══ UXGEN — KIT DE COMMERCE ═══════════════════════════════════════════════════════
   Collé tel quel, jamais retouché. Il ne peint qu'avec les jetons de uxgen_theme.
   Trois arbitrages sont DANS ce fichier et ne sont pas décoratifs :
     · la jauge de franco fait 3 px et n'est PAS verte — l'état débloqué se lit au
       remplissage, pas à la couleur. À 6 px elle pesait autant que la phrase.
     · le complément est une BARRE FINE, fond neutre, produit débordant à -8° avec une
       vraie drop-shadow qui suit le détourage — jamais un box-shadow de rectangle.
     · l'étiquette est un ONGLET GLISSÉ DERRIÈRE la carte : c'est l'ordre du DOM qui le
       fait, elle est écrite AVANT et la carte opaque se peint par-dessus.

   ══ 🚨 LE KIT SE SOUMET AUX RÈGLES QUE LE SERVEUR FACTURE (15/08/2026) ══════════════
   L'audit du matin (§5, « le serveur facture à l'agent ce que son propre kit peint »)
   avait nommé la classe sans la fermer. Elle est fermée ici, et les trois écarts sont
   tenus PAR CETTE FEUILLE :
     · G2 — plus aucune grille du kit n'a de piste implicite : toutes portent
       `grid-template-columns: minmax(0, 1fr)`. Une piste `auto` prend pour plancher
       la min-content de ses items, et F12 rend chaque montant insécable : les deux
       règles du serveur, mises bout à bout, fabriquaient le débordement. 447 px de
       page dans une fenêtre de 390 avant, 390 après.
     · C5 — DEUX GRAISSES, et pas une de plus : 400 et 700. Le kit en peignait quatre
       (500, 600, 700, 800) sur les cinq que l'audit compte, c'est-à-dire qu'il faisait
       échouer C5 à lui tout seul, sur toutes les pages.
     · C7 — plus aucune mention sous `--pas--1`, et le texte qui ARGUMENTE (garantie
       sous le bouton, réassurance de fiche, phrase de sortie d'abonnement, écart au
       franco) est passé à `--pas-0`. Les deux pas viennent de la grammaire servie
       quand elle est là, avec un repli pour un kit posé seul sur un file://.
     · D5 — les vignettes injectées portent leurs deux attributs ET une boîte réservée
       en CSS. Voir `.ux-palier__vignette img`.
   ═══════════════════════════════════════════════════════════════════════════════════ */

/* ══ 🚨 C9 — LA POLICE DE MONTANT, NOMMÉE ICI ET NULLE PART AILLEURS ═════════════════
   « Les prix ne sont JAMAIS dans la police des titres : grotesque neutre,
   `font-variant-numeric: tabular-nums`, colonnes alignées à la virgule. » Réclamé cent
   fois. Mesuré le 21/08/2026 sur les douze boutiques du corpus : DIX écrivaient leur
   montant dans la police du `h1`.

   Ce n'était pas dix fautes, c'était UNE : le gabarit ne connaissait que deux polices —
   « display » et « texte ». Un montant est important, une display a l'air importante,
   et l'auteur de page groupait mécaniquement son prix avec ses titres
   (`h1, h2, h3, .prix__montant { font-family: var(--police-display) }`, mot pour mot
   dans `_test-forge`). Tant qu'il n'existe pas de police DE MONTANT, la faute renaît à
   chaque génération.

   Le jeton existe maintenant, et il vaut la police de TEXTE par défaut : une display a
   des chasses inégales, deux montants l'un sous l'autre cessent de se comparer, et la
   comparaison est tout ce qu'on demande à l'acheteur devant une échelle de lots.

   🚨 IL NE DÉPEND D'AUCUNE CLASSE DE LA BOUTIQUE. Douze boutiques nomment leur prix de
   douze façons (`.prix__montant`, `.achat__montant`, `.montant`, `.fait__valeur`,
   `.prix`, `<b>`, `<dd>`…) : un sélecteur de classe n'aurait guéri qu'une page. C'est
   le SCRIPT du kit qui marque les montants au chargement (section 0), avec exactement la
   règle du détecteur — un nœud dont le texte EST un montant, pas un qui en contient un.
   Il pose le réglage en style d'élément et non par cette feuille, parce que la feuille de
   la boutique est chargée APRÈS la nôtre sur une partie du corpus et gagne à spécificité
   égale : la leçon du 18/08 au soir, un remède inerte mesuré au navigateur. */
:root { --police-montant: var(--police-texte, var(--font-text, inherit)); }
[data-ux-montant] { font-family: var(--police-montant); font-variant-numeric: tabular-nums; }

.ux-voile {
  position: fixed; inset: 0; z-index: 80;
  background: color-mix(in oklab, var(--sol-sombre, #111) 34%, transparent);
  opacity: 0; pointer-events: none;
  transition: opacity var(--duree, 260ms) var(--courbe, ease);
}
.ux-voile[data-ouvert="1"] { opacity: 1; pointer-events: auto; }

.ux-tiroir {
  position: fixed; top: 0; right: 0; z-index: 90;
  height: 100dvh; width: 100%; max-width: 26rem;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--sol-clair, #fff); color: var(--sur-clair, #141414);
  box-shadow: var(--ombre-posee, 0 18px 40px -16px rgba(0,0,0,.38));
  transform: translateX(100%); pointer-events: none;
  transition: transform 180ms var(--courbe, cubic-bezier(.22,1,.36,1));
  font: inherit;
}
/* 180 ms, et le chiffre est mesuré : l'audit de transaction relit la page 900 ms après le
   clic, le vol vers le panier dure 700 ms et le tiroir ne s'ouvre qu'APRÈS lui. Une
   transition plus longue ferait conclure « aucun panier lisible » sur un panier qui s'ouvre. */
.ux-tiroir[data-ouvert="1"] { transform: translateX(0); pointer-events: auto; }

.ux-tiroir__tete {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: 1rem 1.25rem;
  border-bottom: 1px solid color-mix(in oklab, var(--sur-clair, #141414) 12%, transparent);
}
.ux-tiroir__titre { margin: 0; font-family: var(--police-display, inherit); font-size: 1.125rem; font-weight: 700; }
.ux-tiroir__corps { flex: 1 1 auto; min-height: 22dvh; overflow-y: auto; overscroll-behavior: contain; }
/* Le pied est BORNÉ et ne peut être ni poussé hors écran ni rogné : sur un panier à
   8 articles, une base flexible avait écrasé la liste À ZÉRO et enfermé le client. */
.ux-tiroir__pied {
  flex: 0 0 auto; padding: 1.5rem 1.25rem 1rem;
  border-top: 1px solid color-mix(in oklab, var(--sur-clair, #141414) 12%, transparent);
  background: var(--sol-clair, #fff);
}

/* 🔴 PATCH B01-4 — 2.75rem = 44 px, JAMAIS MOINS : la croix de fermeture faisait 32 px et
   violait G3, la règle que le propre audit du serveur facture (3e récidive de la famille
   « le kit se contredit », après le <select> de variante). L'invariant est testé :
   commerce.test.ts refuse tout retour sous 44 px. */
.ux-icone { width: 2.75rem; height: 2.75rem; border: 0; background: none; cursor: pointer; color: inherit; border-radius: var(--rayon-pastille, 999px); }
.ux-icone:hover { background: color-mix(in oklab, var(--sur-clair, #141414) 8%, transparent); }

/* ── LA PROGRESSION VERS LE FRANCO — affichage pur ───────────────────────────────────── */
.ux-franco { padding: .9rem 1.25rem .75rem; border-bottom: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 8%, transparent); }
.ux-franco__texte { margin: 0; font-size: var(--pas-0, 1rem); line-height: 1.35; }
.ux-franco__rail { margin-top: .5rem; height: 3px; border-radius: 999px; overflow: hidden;
  background: color-mix(in oklab, var(--sur-clair, #141414) 16%, transparent); }
.ux-franco__jauge { height: 100%; border-radius: 999px; background: var(--sur-clair, #141414);
  transition: width var(--duree-ample, 500ms) var(--courbe, ease); }
@media (prefers-reduced-motion: reduce) { .ux-franco__jauge { transition: none; } }

/* ── LES LIGNES ──────────────────────────────────────────────────────────────────────── */
.ux-lignes { list-style: none; margin: 0; padding: .5rem 1.25rem; }
.ux-ligne { display: flex; gap: .75rem; padding: 1rem 0; border-top: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 8%, transparent); }
.ux-lignes > .ux-ligne:first-child { border-top: 0; }
.ux-ligne__visuel { flex: 0 0 auto; width: 5rem; height: 6.25rem; border-radius: var(--rayon, 8px);
  overflow: hidden; background: color-mix(in oklab, var(--sur-clair,#141414) 5%, transparent); }
.ux-ligne__visuel img { width: 100%; height: 100%; object-fit: contain; display: block; }
.ux-ligne__corps { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.ux-ligne__nom { margin: 0; font-size: 16px; font-weight: 700; line-height: 1.25; }
/* Le nom n'est JAMAIS tronqué : c'est ce qui vend, et un truncate coupait « GHK-Cu 100 … »
   sur la carte qui sert justement à décider. Il passe à la ligne. */
.ux-ligne__detail { margin: .15rem 0 0; font-size: 13px; line-height: 1.35; opacity: .78; }
.ux-ligne__bas { margin-top: auto; padding-top: .5rem; display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.ux-qte { display: inline-flex; align-items: center; border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 22%, transparent); border-radius: 999px; }
.ux-qte button { width: 2.75rem; height: 2.75rem; border: 0; background: none; cursor: pointer; color: inherit; border-radius: 999px; font-size: 1rem; line-height: 1; } /* ≥ 44 px (G3) */
.ux-qte span { min-width: 1.5rem; text-align: center; font-size: .875rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.ux-ligne__prix { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Le seul geste qui a un sens sur une ligne d'abonnement — les +/− n'en ont aucun. Cible
   tactile pleine (G3), et une graisse de 400 : ce n'est pas l'action principale du tiroir. */
.ux-ligne__retirer {
  min-height: 2.75rem; padding: 0 .875rem; border-radius: var(--rayon-pastille, 999px);
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 22%, transparent);
  background: none; color: inherit; font: inherit; font-size: .875rem; cursor: pointer;
}

/* ── LE COMPLÉMENT — une barre fine, pas une carte ───────────────────────────────────── */
.ux-offre { position: relative; margin-bottom: .75rem; }
.ux-offre__onglet {
  position: absolute; right: 1rem; top: 0; transform: translateY(-18px);
  border-radius: var(--rayon, 10px) var(--rayon, 10px) 0 0;
  padding: 5px .75rem .75rem; font-size: var(--pas--1, .8125rem); font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; line-height: 1;
  background: var(--marque-aplat, var(--marque, #2b2b2b)); color: var(--sur-marque, #fff);
}
.ux-offre__carte {
  position: relative; border-radius: var(--rayon, 12px);
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 14%, transparent);
  background: color-mix(in oklab, var(--sol-clair,#fff) 96%, var(--sur-clair,#141414));
  padding: .75rem .875rem .75rem 6.5rem;
}
.ux-offre--nu .ux-offre__carte { padding-left: .875rem; }
.ux-offre__visuel {
  position: absolute; top: -1rem; left: .625rem; width: 4.75rem; height: 7rem;
  pointer-events: none; transform: rotate(-8deg);
  filter: drop-shadow(0 12px 14px color-mix(in oklab, var(--sol-sombre,#111) 24%, transparent));
}
.ux-offre__visuel img { width: 100%; height: 100%; object-fit: contain; display: block; }
.ux-offre__quoi { margin: 0; font-size: var(--pas--1, .8125rem); font-weight: 700; line-height: 1.2; opacity: .7; }
.ux-offre__nom { margin: .25rem 0 0; font-size: 14.5px; font-weight: 700; line-height: 1.3; }
.ux-offre__prix { margin-top: .15rem; display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; }
.ux-offre__net { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* 🔴 PATCH B01-4 — .62 D'OPACITÉ, PAS MOINS : sur l'encre de repli (#141414 sur #fff), .55
   rendait 4,07:1 et .5 rendait 3,47:1 — sous le seuil A7 (4.5:1) que le propre audit du
   serveur facture. À .62, le mélange rend 5,15:1. Testé dans commerce.test.ts avec le calcul
   de luminance WCAG, pas à l'œil. */
.ux-offre__plein { font-size: var(--pas--1, .8125rem); opacity: .62; text-decoration: line-through; font-variant-numeric: tabular-nums; }
.ux-offre__badge { border-radius: calc(var(--rayon, 8px) / 1.5); padding: 3px .375rem; font-size: var(--pas--1, .8125rem); font-weight: 700; line-height: 1;
  background: var(--marque-aplat, var(--marque, #2b2b2b)); color: var(--sur-marque, #fff); }

/* ── LES BOUTONS. Un seul plein par écran : « Passer commande ». ──────────────────────── */
/* 🚨 18/08/2026 — TROIS DÉCLARATIONS, ET LE BOUTON NE PEUT PLUS ÉLARGIR LA PAGE.
   Relevé sur la boutique « atelier », 360 px : .ux-bouton__ligne mesurait 385 px, posée à
   x = −12, bord droit à 372 pour un document de 360 — G2 BLOQUANTE, causée par le kit. Le
   mécanisme : un bouton est un item de grille ou de flex, son min-width vaut « auto », donc
   sa contribution min-content (le libellé en « nowrap ») POUSSE la piste qui le contient. La
   boutique avait dû écrire « minmax(0,1fr) » et « min-width: 0 » depuis SA feuille pour
   éteindre un refus que notre plomberie peignait — neuvième désobéissance du relevé.
   « min-width: 0 » retire cette contribution, « max-width: 100% » borne la boîte, « overflow:
   hidden » empêche le débordement de remonter jusqu'au scrollWidth du document. Ce dernier
   est un garde-fou de DERNIER recours : c'est poserLeManque() qui fait qu'aucun libellé du
   kit ne doit plus l'atteindre — on n'y coupe pas, on écrit plus court. */
.ux-bouton, .ux-bouton--second {
  display: flex; align-items: center; justify-content: center; width: 100%;
  min-width: 0; max-width: 100%; overflow: hidden;
  height: 3rem; padding: 0 1.25rem; border-radius: var(--rayon-pastille, 999px);
  font: inherit; font-size: 16px; font-weight: 700; line-height: 1; cursor: pointer;
  text-decoration: none; transition: transform var(--duree-brève, 160ms) var(--courbe, ease);
}
.ux-bouton { border: 0; background: var(--sur-clair, #141414); color: var(--sol-clair, #fff); }
/* ── LES ÉTATS — niveau librairie (fournée 2). Un bouton qui ne répond ni au survol ni au
   clavier est un bouton dessiné, pas un composant : hover, focus-visible et active sont
   fournis ICI, cohérents avec les jetons, et la page n'a rien à écrire. ─────────────────── */
.ux-bouton:hover { filter: brightness(1.12); }
.ux-bouton--second:hover { background: color-mix(in oklab, var(--sur-clair, #141414) 7%, transparent); }
.ux-bouton:focus-visible, .ux-bouton--second:focus-visible, .ux-icone:focus-visible,
.ux-palier__bouton:focus-visible, .ux-abo__choix:focus-visible, .ux-choix__variante:focus-visible,
.ux-ancre:focus-visible {
  outline: 2px solid var(--marque, #2b2b2b); outline-offset: 2px;
}
/* ══ P14 — UN BOUTON NE PORTE QUE SON ACTION ET SON PRIX ═══════════════════════════════════
   Jusqu'au 14/08/2026 le bouton portait DEUX lignes, la seconde étant la garantie. Règle
   d'Angelo, ce jour : la garantie, le délai, le retour vont SOUS le bouton, jamais dedans.
   Ce n'est pas une préférence : le bouton de CINQ atteignait 325 px à 390 px de large à
   cause de cette seule sous-ligne, écrasait le nom du produit à 21 px et se peignait
   par-dessus sur 152 px. La classe .ux-sous-bouton est la SŒUR du bouton, jamais son enfant — donc
   hors de la cible tactile, et hors de la boîte que l'audit mesure. */
.ux-sous-bouton {
  margin: .5rem 0 0; font-size: var(--pas-0, 1rem); line-height: 1.4; font-weight: 400;
  text-align: center; opacity: .75;
}
/* F12 (fournée 2, incident ④) — UN MONTANT NE SE COUPE JAMAIS : à 360 px, « Ajouter —
   78,00 € » cassait entre « 78,00 » et « € » et l'euro partait orphelin sur sa ligne. Chaque
   montant est un bloc insécable ; si la place manque, c'est le PRIX BARRÉ entier qui passe à
   la ligne — jamais l'intérieur d'un montant. */
.ux-bouton__ligne { display: inline-flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: .1rem .5rem; }
.ux-bouton__ligne > span { white-space: nowrap; }
.ux-bouton__plein { font-size: .875em; font-weight: 700; opacity: .6; text-decoration: line-through; font-variant-numeric: tabular-nums; }
/* La classe .ux-bouton__sous a été RETIRÉE le 14/08/2026 (P14) : plus rien ne la produit.
   Voir .ux-sous-bouton plus haut — la même phrase, posée à côté du bouton et non dedans. */
.ux-bouton--second { height: 2.75rem; font-size: 14.5px;
  border: 1.5px solid var(--sur-clair, #141414); background: transparent; color: inherit; }
.ux-bouton:active, .ux-bouton--second:active { transform: scale(.985); }
@media (prefers-reduced-motion: reduce) { .ux-bouton, .ux-bouton--second { transition: none; } }

.ux-recap { padding: .25rem 1.25rem 1rem; display: grid; grid-template-columns: minmax(0, 1fr); gap: .35rem; font-size: 14px; }
.ux-recap__l { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.ux-recap__l span:last-child { font-variant-numeric: tabular-nums; font-weight: 700; }
.ux-total { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: .75rem; }
/* 🚨 C9 — c'était la SEULE ligne du kit qui écrivait un montant dans la police des titres,
   et c'est le total du panier : le montant le plus regardé de la page. */
.ux-total strong { font-family: var(--police-montant); font-size: 1.25rem; font-variant-numeric: tabular-nums; }
.ux-note { margin: .75rem 1.25rem 1.5rem; font-size: var(--pas-0, 1rem); line-height: 1.5; opacity: .75; }
.ux-vide { padding: 3rem 1.5rem; text-align: center; font-size: var(--pas-0, 1rem); }

/* ── L'ÉCRAN DE PAIEMENT — dans le tiroir, donc jamais un lien qui ne va nulle part ──── */
/* [hidden] AVANT tout le reste, et en !important : la règle de l'agent utilisateur
   ([hidden] { display: none }) perd contre n'importe quelle classe qui pose un display.
   Sans cette ligne, le formulaire de paiement — masqué en JavaScript — s'affichait EN HAUT
   du panier. Vu sur capture, pas déduit du code. */
[hidden] { display: none !important; }
.ux-paiement { padding: 1.25rem; display: grid; grid-template-columns: minmax(0, 1fr); gap: .75rem; }
.ux-paiement__titre { margin: 0; font-size: var(--pas-1, 1.15rem); font-weight: 700; line-height: 1.25; }
.ux-champ { display: grid; grid-template-columns: minmax(0, 1fr); gap: .25rem; font-size: 13px; font-weight: 700; }
.ux-champ input {
  height: 3rem; padding: 0 .875rem; font: inherit; font-size: 16px; /* 16 px : pas de zoom iOS */
  border-radius: var(--rayon, 8px); background: var(--sol-clair, #fff); color: inherit;
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 28%, transparent);
}
.ux-champ input:focus-visible { outline: 2px solid var(--marque, #2b2b2b); outline-offset: 1px; }

/* ── L'ÉCHELLE DE LOTS ────────────────────────────────────────────────────────────────────
   La forme relevée sur 14 captures, et trois arbitrages sont DANS ce fichier :
     · la carte choisie se distingue par un LISERÉ DE 2 px et un fond très légèrement teinté,
       jamais par une couleur pleine — un aplat derrière du texte est la signature d'un bloc
       généré (§13.3), et la carte contient justement le texte qui décide.
     · l'onglet est GLISSÉ DERRIÈRE la carte, exactement comme au panier : il est écrit AVANT
       dans le DOM et la carte opaque se peint par-dessus. Coins arrondis en haut seulement,
       une languette n'a pas de bas.
     · le cadeau vit SOUS un filet, à l'intérieur de la carte choisie. Il récompense un choix ;
       posé sur toutes les cartes, il ne récompenserait rien. */
/* ══ 🚨 LA PISTE EST BORNÉE, ET LE KIT NE DÉBORDE PLUS LA PAGE (15/08/2026) ════════════════
   DEUX essais indépendants du 15/08 ont relevé la même faute, sur deux briefs sans rapport :
     sauces      — « le kit fait déborder la page de 70 px à 390. .ux-paliers est une grille à
                    colonne implicite `auto` : son plancher min-content (vignettes insécables)
                    a élargi toute la section de l'offre. Il a fallu poser
                    grid-template-columns: minmax(0,1fr) DEPUIS MA PROPRE FEUILLE. »
     croquettes  — « .ux-paliers et .ux-palier__quoi n'ont pas de grid-template-columns : la
                    piste implicite se dimensionne sur la max-content de montants
                    white-space: nowrap, la page sortait 64 px trop large à 390. Un refus G2
                    CAUSÉ PAR LE KIT, PAS PAR MOI. »
   Mesuré ici avant correction, sur le corpus adversarial : scrollWidth 447 px à 390 px de
   fenêtre, soit 57 px de débordement, et le fautif est `div.ux-palier` à 431 px de large.
   LA MÉCANIQUE, en une phrase : une piste de grille `auto` prend pour PLANCHER la min-content
   de ses items, et un item qui contient `white-space: nowrap` (F12 rend chaque montant
   insécable, à raison) n'a pas de min-content compressible. La grille refuse donc de
   descendre, et pousse tout son ancêtre. `minmax(0, 1fr)` retire ce plancher : la piste peut
   valoir zéro, et le contenu se débrouille dans la largeur qu'on lui donne.
   🚨 CE N'EST PAS UN DÉTAIL DE PROPRETÉ : le kit est la seule pièce PARTAGÉE par toutes les
   boutiques, et un débordement horizontal se paie sur 100 % des pages — facturé par G2, à
   l'agent, pour une faute que le serveur a peinte. Le kit se borne LUI-MÊME. */
.ux-paliers { display: grid; grid-template-columns: minmax(0, 1fr); gap: .625rem; margin: 0 0 1rem; }
/* L'onglet déborde VERS LE HAUT : sans place réservée, il se posait sur le bas de la carte
   précédente et « on dirait qu'il est entre les deux » — le défaut exact déjà payé au panier
   (§13.3). On ouvre donc la gouttière, mais SEULEMENT au-dessus des cartes qui en portent un :
   espacer toutes les cartes aurait délié l'échelle, qui doit se lire comme un seul objet. */
.ux-palier:has(.ux-palier__onglet) { margin-top: 2rem; } /* l'onglet fait ~29 px : la gouttière suit, sinon il se pose sur la carte d'au-dessus (§13.3) */
.ux-palier { position: relative; border-radius: var(--rayon, 12px);
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 16%, transparent);
  background: var(--sol-clair, #fff);
  /* 🚨 LA CARTE SE MESURE À SA PROPRE LARGEUR, pas à celle de la fenêtre (18/08/2026).
     Une requête de MÉDIA ne sert à rien ici : la même carte vit dans une buy-box de 340 px
     à 1440 comme dans une tuile de grille de 170 px à 390. C'est le conteneur qui décide. */
  container-type: inline-size; }
/* ══ 🔴 LA CARTE DE PALIER DANS UNE COLONNE ÉTROITE (18/08/2026, HALLE) ═════════════════════
   Mesuré : à trois colonnes, la carte tenait 68 px pour un contenu de 157 px de min-content,
   et le document sortait à 425 px (écran 390) puis 1470 (écran 1440). Bloquant à TOUTES les
   largeurs dès qu'une buy-box descend sous ~380 px, marketplace ou pas. La halle a dû retirer
   `data-ux-paliers` de ses tuiles pour être lisible — et c'est ce retrait qui a déclenché le
   bug d'argent : les deux défauts se tiennent, et celui-ci est le premier des deux.
   Sous 320 px de CARTE, le prix descend sous le nom au lieu de se battre avec lui pour la
   même ligne. La vignette rétrécit, le badge prend sa ligne. Rien ne change au-dessus. */
@container (max-width: 320px) {
  .ux-palier__bouton { flex-wrap: wrap; align-items: flex-start; gap: .5rem; padding: .75rem .875rem; }
  .ux-palier__quoi { flex: 1 1 100%; }
  .ux-palier__prix { flex: 1 1 100%; justify-items: start; grid-auto-flow: column; justify-content: start;
    align-items: baseline; gap: .5rem; }
  .ux-palier__vignette { height: 2.5rem; }
  .ux-palier__meilleur { display: block; }
  .ux-palier__onglet { font-size: 12px; padding: 5px .625rem .5rem; }
}
.ux-palier[data-choisi="1"] {
  border-width: 2px; border-color: var(--marque, #2b2b2b);
  background: color-mix(in oklab, var(--sol-clair,#fff) 95%, var(--marque,#2b2b2b));
}
/* L'onglet du palier recommandé est ÉNORME (fournée 2, réf. IMG_2049) : « Most Popular »
   doit être impossible à rater — 13 px gras, padding large. La règle d'Angelo : le client
   doit SAVOIR lequel prendre, sans réfléchir. */
/* LE MOTIF D'USAGE — SOUS LE NOM DU PALIER, au même fer, en petit, jamais en gras : il
   EXPLIQUE la recommandation, il ne se bat pas avec elle. (§2 item 10 / O4)

   🚨 P6, FOURNÉE 10 — IL ÉTAIT CENTRÉ, ET IL FLOTTAIT AU-DESSUS DE TOUT.
   Il était posé en première ligne DANS la carte, en text-align:center, sur la même bande
   horizontale qu'un onglet ferré à droite. Tout le reste de la carte est ferré à gauche.
   Résultat vu à l'œil par l'architecte (§2.5 ③) : « une ligne grise orpheline, centrée,
   coincée entre le bord supérieur de la carte et un badge qui n'est pas aligné avec elle ».
   Un texte centré dans une carte ferrée à gauche n'a pas de bord commun avec ce qu'il
   explique — et l'œil ne relie que ce qui partage un bord. */
.ux-palier__motif {
  display: block; margin: .15rem 0 0; text-align: left;
  font-size: .8125rem; line-height: 1.3; font-weight: 400;
  color: color-mix(in srgb, currentColor 68%, transparent);
}
/* P3 — « le meilleur prix à la dose », dit sur la carte qui le porte quand ce n'est PAS la
   carte recommandée. En gras mais à la même taille : il informe celui qui compare, il ne
   concurrence pas le badge de recommandation — deux badges sur une échelle, et elle ne
   recommande plus rien. */
.ux-palier__meilleur { font-weight: 700; opacity: 1; }

.ux-palier__onglet {
  position: absolute; right: -1px; top: 0; transform: translateY(-100%);
  border-radius: var(--rayon, 10px) var(--rayon, 10px) 0 0;
  padding: 6px .875rem .625rem; font-size: 13px; font-weight: 700; line-height: 1;
  letter-spacing: .03em; white-space: nowrap;
  background: var(--marque-aplat, var(--marque, #2b2b2b)); color: var(--sur-marque, #fff);
}
.ux-palier__bouton {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  width: 100%; padding: .875rem 1rem; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; text-align: left; border-radius: inherit;
}
/* LA VIGNETTE DE PALIER (fournée 2, réf. IMG_2049) : le détourage du produit, répété par la
   quantité — on VOIT ce qu'on reçoit, on ne le calcule pas. Les exemplaires se chevauchent
   comme des cartes en éventail, ombre portée qui suit le détourage. aria-hidden : le libellé
   structuré (data-ux-lot) porte déjà l'information. */
.ux-palier__vignette { flex: 0 0 auto; display: flex; align-items: flex-end; height: 3.25rem; }
/* ══ 🚨 D5 — LA VIGNETTE RÉSERVE SA BOÎTE AVANT LE PREMIER OCTET (15/08/2026) ══════════════
   Écart nommé en tête des questions ouvertes de l'audit du matin (§8-1) : *« ses vignettes
   injectées n'ont ni width/height ni aspect-ratio — c'est-à-dire l'écart D5 que l'audit du
   même serveur facture. »* Le serveur facturait à l'agent ce que son propre kit peignait.
   La HAUTEUR était réservée (`height: 100%` d'un parent à 3,25 rem) ; la LARGEUR ne l'était
   pas — `width: auto` vaut zéro tant que le fichier n'est pas arrivé, puis la vignette pousse
   le nom du palier et son prix vers la droite. Sur trois cartes × 4 exemplaires, ça fait douze
   sauts horizontaux pendant le seul geste qui compte.
   `width` FIXE (et non plus `max-width`) : la boîte vaut 44 × 52 px avant le chargement comme
   après, `object-fit: contain` garde le détourage à ses proportions dedans. C'est aussi
   exactement la largeur que suppose le chevauchement en éventail (`margin-left: -1.375rem`,
   la moitié de 2,75 rem) — la valeur n'est pas nouvelle, elle était seulement facultative.
   Les attributs `width`/`height` sont posés EN PLUS, à l'injection (voir peindrePaliers) :
   c'est le premier critère de `src/dimensions.ts`, celui qui se lit sans résoudre la cascade. */
.ux-palier__vignette img { height: 100%; width: 2.75rem; aspect-ratio: 11 / 13; object-fit: contain; display: block;
  filter: drop-shadow(0 8px 10px color-mix(in oklab, var(--sol-sombre,#111) 20%, transparent)); }
.ux-palier__vignette img + img { margin-left: -1.375rem; }
/* `minmax(0,1fr)` ET `min-width: 0` : le premier borne la PISTE, le second borne l'ITEM
   FLEX. Les deux sont nécessaires et ne font pas le même travail — `min-width: 0` laissait la
   colonne rétrécir, mais sa grille intérieure refusait toujours de descendre sous la
   min-content de « le meilleur prix à la dose ». */
.ux-palier__quoi { display: grid; grid-template-columns: minmax(0, 1fr); gap: .1rem; min-width: 0; flex: 1 1 auto; text-align: left; }
.ux-palier__nom { font-size: 16px; font-weight: 700; line-height: 1.25; }
.ux-palier__eco { font-size: 13.5px; line-height: 1.3; opacity: .72; }
/* Le prix à l'unité d'usage : plus petit que l'économie, parce qu'il sert à COMPARER deux
   lots, pas à convaincre. Baymard : 81 % des sites ne l'affichent nulle part. */
.ux-palier__unite { font-size: var(--pas--1, .8125rem); line-height: 1.3; opacity: .6; font-variant-numeric: tabular-nums; }
.ux-palier__prix { display: grid; justify-items: end; gap: .05rem; flex: 0 0 auto; }
/* F12 — un MONTANT est insécable, partout : le « € » orphelin sur sa ligne à 360 px est
   l'écart que l'audit facture, et c'est le kit qui le fabriquait.
   🚨 18/08/2026 — « .ux-palier__unite » est SORTI de cette liste et remplacé par
   « .ux-palier__somme ». La règle protège une SOMME ; elle protégeait ici toute la phrase
   « 0,30 € / cuillerée le meilleur prix à la dose », dont la min-content (157 px) poussait
   la carte, la grille et le document — 425 px de large pour un écran de 390. Le kit violait
   sa propre règle F12 en croyant l'appliquer. */
.ux-palier__net, .ux-palier__plein, .ux-palier__somme,
.ux-offre__net, .ux-offre__plein, .ux-bouton__plein { white-space: nowrap; }
.ux-palier__unite { white-space: normal; overflow-wrap: anywhere; }
.ux-palier__net { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
.ux-palier__plein { font-size: 13px; opacity: .62; text-decoration: line-through; font-variant-numeric: tabular-nums; } /* ≥ 4.5:1 (A7), cf. patch B01-4 */
.ux-palier__cadeau {
  display: flex; align-items: flex-start; gap: .5rem; margin: 0 1rem; padding: .75rem 0;
  border-top: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 14%, transparent);
  font-size: 14px; line-height: 1.35;
}
.ux-palier__coche { flex: 0 0 auto; font-weight: 700; }

/* ── L'ABONNEMENT — deux pastilles, et la sortie écrite dessous ───────────────────────── */
.ux-abo { display: flex; gap: .5rem; margin: 0 0 .5rem; }
.ux-abo__choix {
  flex: 1 1 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: .15rem; justify-items: center; min-height: 2.75rem; /* ≥ 44 px (G3) */
  padding: .625rem .5rem; border-radius: var(--rayon, 10px); cursor: pointer;
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 18%, transparent);
  background: var(--sol-clair, #fff); color: inherit; font: inherit; font-size: 13.5px;
}
.ux-abo__choix[data-choisi="1"] { border-width: 2px; border-color: var(--marque, #2b2b2b); font-weight: 700; }
.ux-abo__choix strong { font-size: 15px; font-variant-numeric: tabular-nums; }
/* 🚨 La phrase de sortie n'est PAS discrète : c'est l'objection n°1 de l'abonnement. */
.ux-abo__sortie { margin: .5rem 0 1rem; font-size: var(--pas-0, 1rem); line-height: 1.45; opacity: .8; }

/* ── LA RÉASSURANCE DE FICHE — port, date, garantie, AVANT le panier ──────────────────── */
.ux-rassure { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: .35rem;
  font-size: var(--pas-0, 1rem); line-height: 1.4; opacity: .85; }

/* ── L'ANCRE POSÉE D'OFFICE — seulement quand la page n'en a aucune ─────────────────────
   Fournée 2 : elle porte une ICÔNE DE PANIER MODERNE — SVG outline 1,8 px, currentColor —
   jamais un emoji, jamais un mot « Panier » dans une pilule des années 90. */
.ux-ancre {
  position: fixed; top: .75rem; right: .75rem; z-index: 75;
  display: inline-flex; align-items: center; gap: .45rem;
  height: 2.75rem; padding: 0 .875rem; border-radius: var(--rayon-pastille, 999px); /* ≥ 44 px (G3) */
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 20%, transparent);
  background: var(--sol-clair, #fff); color: var(--sur-clair, #141414);
  font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
}
.ux-ancre__icone { display: block; }
.ux-ancre__icone svg { width: 1.25rem; height: 1.25rem; display: block; }

/* ── LES SÉLECTEURS DE LA FICHE PRODUIT ──────────────────────────────────────────────── */
.ux-choix { display: grid; grid-template-columns: minmax(0, 1fr); gap: .5rem; margin: 0 0 1rem; }
.ux-choix label { font-size: 13px; font-weight: 700; display: grid; grid-template-columns: minmax(0, 1fr); gap: .25rem; }
.ux-choix select, .ux-choix input[type="text"] {
  height: 3rem; padding: 0 .75rem; font: inherit; font-size: 16px;
  border-radius: var(--rayon, 8px); background: var(--sol-clair, #fff); color: inherit;
  border: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 28%, transparent);
}
/* P4 — l'intitulé d'une option exposée en boutons. Il portait un <label> quand l'option
   était un <select> ; un groupe de boutons n'a pas de champ à étiqueter, il a un role=group
   et un aria-label. Le texte reste PEINT, et à la même graisse que les libellés de champ. */
.ux-choix__intitule { display: block; font-size: 13px; font-weight: 700; margin: 0 0 .25rem; }
.ux-choix__variantes { display: flex; flex-wrap: wrap; gap: .5rem; }
.ux-choix__variante {
  min-height: 44px; padding: .5rem .875rem; font: inherit; font-size: 14px; font-weight: 400;
  background: transparent; color: inherit; cursor: pointer;
  border: 1px solid var(--trait, rgba(0,0,0,.18)); border-radius: var(--rayon, 8px);
}
.ux-choix__variante[aria-pressed="true"] {
  border-color: var(--marque, #2b2b2b); box-shadow: inset 0 0 0 1px var(--marque, #2b2b2b);
  font-weight: 700;
}
/* ── LE CHOIX QUI MANQUE — 18/08/2026 ────────────────────────────────────────────────────
   Ni rouge ni icône d'erreur : ce n'est pas une faute du client, c'est une étape qu'il n'a pas
   encore faite. Le groupe se souligne à la couleur de marque, le message se lit au pas du
   corps (C7 : ce qui ARGUMENTE ne descend pas sous `--pas-0`) et à la graisse 700, la seule
   graisse forte que le kit s'autorise (C5). */
.ux-choix__variantes[data-ux-manquant="1"] { outline: 2px solid var(--marque, #2b2b2b); outline-offset: 3px; border-radius: var(--rayon, 8px); }
.ux-choix__manque { margin: .5rem 0 0; font-size: var(--pas-0, 1rem); line-height: 1.4; font-weight: 700; }
.ux-choix__case { display: flex; align-items: center; gap: .5rem; font-size: 14px; font-weight: 400; }
.ux-choix__case input { width: 1.125rem; height: 1.125rem; accent-color: var(--marque, #2b2b2b); }

/* ── LA BARRE D'ACHAT COLLANTE ───────────────────────────────────────────────────────────
   Budget MESURÉ : 76 px à 390×844, soit 9 % de la hauteur visible. L'audit de transaction
   refuse tout élément fixe qui mange plus de 25 % en permanence — sur la page notée 87/100
   le 13/08, la barre collante mangeait 110 px sur chaque écran. Le plafond est ici en dur. */
.ux-barre-achat {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
  display: flex; align-items: center; gap: .75rem;
  padding: .625rem 1rem calc(.625rem + env(safe-area-inset-bottom, 0px));
  max-height: 20vh;
  background: var(--sol-clair, #fff); color: var(--sur-clair, #141414);
  border-top: 1px solid color-mix(in oklab, var(--sur-clair,#141414) 14%, transparent);
}
/* ══ P1 (fournée 4) — LA BARRE NE PEUT PLUS SE CHEVAUCHER ═══════════════════════════════
   14/08/2026, mesuré dans le DOM sur la boutique CINQ à 390 px : __quoi écrasé à 21 px, le
   bouton figé à 325 px, le texte du nom de produit sortant de sa boîte de 164 px et se
   PEIGNANT SOUS LE BOUTON sur 152 px. Le mécanisme tenait en trois déclarations :
     · .ux-bouton en « flex: 0 0 auto » — le bouton ne rétrécissait JAMAIS ;
     · __quoi en « flex: 1 1 auto; min-width: 0 » — la colonne de gauche s'écrasait à zéro ;
     · ses enfants en « white-space: nowrap » et « overflow: visible » — le texte sortait.
   Aucune des trois n'est fausse seule ; c'est leur somme qui casse, et elle ne casse que sur
   la LONGUEUR DU CONTENU. 464 tests verts sur fixtures courtes n'ont rien vu : IPPUKU passait
   parce que son bouton faisait 222 px, pas parce qu'il était mieux construit.
   Ce qui est réparé ici : la colonne de gauche COUPE (ellipse) au lieu de déborder, et le
   bouton REND de la place au lieu d'en prendre. Le détecteur « collision » de blocks/_spec
   mesure le résultat à 320/360/390/768/1440 — la règle et le refus, pas la règle seule. */
.ux-barre-achat__quoi {
  flex: 1 1 auto; min-width: 0; font-size: 13px; line-height: 1.2; display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px;
  overflow: hidden;
}
/* Un enfant de grille a « min-width: auto » : sans cette ligne, la colonne refuse de descendre
   sous la largeur de son plus long mot et le texte ressort par la droite. */
.ux-barre-achat__quoi > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* B02-N3 — l'écart au franco, DANS la barre : même calcul que le tiroir, jamais un second.
   ══ 🔴 18/08/2026, cycle « alambic » — LA PHRASE DU KIT ÉTAIT COUPÉE SUR TÉLÉPHONE ═══════
   Le peigne fin l'a relevée sur DEUX boutiques sans rapport, et le navigateur l'a reproduite :

       mcp      « Encore 4,10 € — livraison offerte. »   206 px dans 189   ← COUPÉE
       alambic  « Encore 70,00 € — livraison offerte. »  221 px dans 201   ← COUPÉE

   Notez le montant de mcp : **4,10 €**, c'est-à-dire le plus court possible. Ce n'est donc pas
   le nombre qui déborde, c'est LA PHRASE — elle ne tient dans aucun cas à 390 px.

   ── CE QUI REND CE DÉFAUT PARTICULIER ────────────────────────────────────────────────────
   Vingt lignes plus haut, ce fichier REFUSE au marchand un nom de produit de plus de 28
   signes, précisément pour qu'il ne se fasse pas couper dans cette barre (« nom-tronque-dans-
   la-barre », 15/08, relevé sur gen7-matcha). Le serveur impose donc une limite au texte du
   marchand, et écrit le sien trop long dans la même boîte, deux lignes en dessous. Le marchand
   ne peut RIEN y faire : cette phrase-là, c'est nous qui la composons.

   ── LE REMÈDE : ELLE SE REPLIE, ELLE NE SE COUPE PAS ─────────────────────────────────────
   La règle d'Angelo est explicite — *l'UI ne doit JAMAIS tronquer bêtement ; c'est le texte
   qui s'adapte à la contrainte*. Ici la contrainte ne peut pas être satisfaite par un
   raccourcissement sans inventer un second vocabulaire (« port offert » là où le tiroir dit
   « livraison offerte ») — et deux formulations pour la même promesse est très exactement le
   défaut « franco annoncé deux fois » que publier refuse. On replie donc sur deux lignes.
   Le nom du produit, lui, GARDE son ellipsis : c'est un nom, il se coupe ; ceci est une
   phrase, elle s'enroule.
   Mesuré au navigateur, à 390×844, un article au panier, AVANT puis APRÈS :

       alambic   221 px dans 201, 1 ligne, barre 65 px   →   201 dans 201, 2 lignes, barre 71 px
       mcp       206 px dans 189, 1 ligne, barre 69 px   →   189 dans 189, 2 lignes, barre 71 px

   Plus aucune coupe, et la barre gagne 2 à 6 px. Le plafond d'éléments fixes de l'obligation
   BLOQUANTE rien-ne-masque est de 169 px à cette taille : il reste 98 px de marge. La page ne
   déborde pas en largeur, avant comme après. */
.ux-barre-achat__franco {
  font-size: var(--pas--1, .8125rem); line-height: 1.2; opacity: .8; font-variant-numeric: tabular-nums;
}
/* ══ 🔴 18/08/2026, SOIR — LE REMÈDE CI-DESSUS ÉTAIT INERTE SUR LE CORPUS ═════════════════
   Le correctif de ce matin a été REMESURÉ au navigateur, kit fraîchement construit posé sur
   une copie de la boutique. À 360 px, sur «  » et sur sa fiche produit :

       « Livraison offerte dès 49,00 €. »   179 px dans 162   ← TOUJOURS COUPÉE
       white-space calculé : nowrap · overflow : hidden · text-overflow : ellipsis

   c'est-à-dire l'exact inverse des trois déclarations écrites vingt lignes plus haut. La
   cause n'est ni le kit ni la cascade interne : c'est la feuille DE LA BOUTIQUE, chargée
   après la nôtre, qui redéclare le même sélecteur à la même spécificité —

       rase.css:468  .ux-barre-achat__franco { white-space: nowrap; overflow: hidden;
                                               text-overflow: ellipsis; }

   et à spécificité égale, la dernière feuille gagne. Deux boutiques sur treize touchent ce
   sélecteur, et les deux le font pour la même raison : «  » écrit EXACTEMENT
   notre remède, avec en commentaire l'écart d'audit qui le lui a fait écrire
   (« texte-tronque-a-l-achat / F12 »). Autrement dit le serveur fait corriger DEPUIS LA
   FEUILLE DU MARCHAND un défaut que sa propre plomberie peint — la même désobéissance que
   celle relevée sur «  » du bouton, et la deuxième de la journée.

   ── LE GESTE, ET IL EST BORNÉ ────────────────────────────────────────────────────────────
   Cette phrase est COMPOSÉE PAR LE KIT : le marchand ne peut ni la raccourcir ni la
   reformuler, et la couper laisse « Livraison offerte dès 49,0… », c'est-à-dire une promesse
   commerciale amputée sur le seul élément visible en permanence à 360 px. On monte donc la
   spécificité des TROIS SEULES déclarations qui COUPENT, et d'elles seules : la taille,
   l'interligne, l'opacité et les chiffres tabulaires restent à une seule classe, donc
   entièrement thémables. Le marchand garde le style ; il ne peut plus, par recopie du
   traitement du NOM juste au-dessus, remettre l'ellipse sur une PHRASE.
   Un nom se coupe — c'est un nom. Une phrase s'enroule. */
.ux-barre-achat .ux-barre-achat__franco { white-space: normal; overflow: visible; text-overflow: clip; }
.ux-barre-achat .ux-bouton {
  width: auto; flex: 0 1 auto; max-width: 62%; min-width: 0; overflow: hidden;
  height: 2.75rem; font-size: 15px; padding: 0 .875rem;
}
/* Dans la barre, ce qui cède est le PRIX BARRÉ, jamais le montant à payer : l'un est un
   argument, l'autre est l'information sans laquelle on ne clique pas. */
.ux-barre-achat .ux-bouton .ux-bouton__ligne { max-width: 100%; flex-wrap: nowrap; }
.ux-barre-achat .ux-bouton .ux-bouton__ligne > span:first-child { flex: 0 0 auto; }
.ux-barre-achat .ux-bouton .ux-bouton__plein { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* Sous 400 px, un prix barré réduit à un sliver de trois pixels ne prouve rien et salit la
   pastille : on le retire de la BARRE (il reste entier sur la carte de lot, où il a la place
   d'être lu). Mesuré à 320 px avec un montant à quatre chiffres : sa boîte tombait à 0 px. */
@media (max-width: 400px) {
  .ux-barre-achat .ux-bouton .ux-bouton__plein { display: none; }
  /* ══ 🔴 18/08/2026, SOIR — LE BOUTON PRENAIT LA COLONNE DU NOM ═══════════════
     Vingt lignes plus haut, ce fichier fait REFUSER au marchand un nom de plus de
     « LARGEUR_COLONNE_BARRE » (164 px à 360 ce matin-là) — et la colonne promise n'existait
     pas. Relevé au navigateur sur les 13 boutiques du corpus, à 360 et à 390 :

         precommande  nom « MIDI — écran SPF 50 »   125 px dans 117   ← COUPE
                      bouton « Précommander — 29,00 € »  199 px pour 212 demandés,
                      c'est-à-dire REPLIÉ SUR DEUX LIGNES dans une boîte de 44 px de haut.

     Le nom fait 19 signes : ce n'est pas sa longueur, c'est ce que le bouton laisse.
     125 + 212 + 44 de chrome = 381 px à servir dans 360. **Il manquait 21 px, et aucune
     répartition ne les invente** — c'est de la place qu'il fallait rendre, pas un partage
     à refaire. Modéliser la largeur du bouton depuis la configuration a été essayé et
     écarté (±8 % d'erreur quand 2 px séparent « forge », sain, de « precommande », coupé).

     ══ CE QUI EST RENDU, ET C'EST DU CHROME, JAMAIS DU TEXTE ════════════════════
     Sous 400 px — là où le prix barré disparaît déjà, deux lignes plus haut :
       · la marge latérale de la barre : 1rem → .75rem   (+8 px)
       · la gouttière entre le nom et le bouton : .75rem → .5rem   (+4 px)
       · les flancs du bouton : .875rem → .625rem   (+8 px)
       · le corps du libellé : 15px → 14px   (+12 px sur le plus long du corpus)
     La HAUTEUR du bouton ne bouge pas (2.75rem = 44 px, la cible tactile de G3), et le
     libellé garde le droit de se replier : on ne lui pose PAS « white-space: nowrap », qui
     couperait le montant — l'information sans laquelle on ne clique pas.

     MESURÉ APRÈS, sur les 24 relevés (13 boutiques × 360 et 390), kit repropagé :
       precommande  colonne 117 → 136, nom entier, bouton 192 px SUR UNE SEULE LIGNE
       les 12 autres  aucun nom coupé, aucun libellé coupé ni replié, hauteur inchangée
     — et les trois libellés que le flex rognait déjà de 3 à 17 px (halle, mcp, saison)
     tiennent désormais en entier. 5 relevés fautifs → 0. */
  .ux-barre-achat { gap: .5rem; padding-left: .75rem; padding-right: .75rem; }
  .ux-barre-achat .ux-bouton { font-size: 14px; padding: 0 .625rem; }
}

.ux-compte { font-variant-numeric: tabular-nums; }
.ux-fantome { position: fixed; z-index: 100; pointer-events: none; object-fit: contain;
  border-radius: var(--rayon, 8px); filter: drop-shadow(0 12px 24px rgba(0,0,0,.25)); }
