/* =====================================================================
   druidx.css — feuille de style du NOUVEAU site (public_html/)
   S8-P0/P1, 2026-10-15.

   SOURCE DU DESIGN (aucune invention) : `http/www/_maquettes/04-product-wave-2026.html`,
   la maquette validée par Alex le 11/10/2026 (43 644 o, 207 règles, 13 variables).
   Les 207 règles sont **reprises telles quelles**, seules trois adaptations :
     1. les sélecteurs portent les noms de classes DU NOUVEAU site (le gabarit S3 est écrit en
        français sémantique : `entete`, `recherche`, `navigation`, `corps`, `pied`, `listproduct`,
        `results_wrapper`, `finalprice`…) — pas ceux de la maquette ;
     2. la police est servie par NOTRE serveur (`/assets/font/`), la maquette la chargeait chez
        Google (`fonts.googleapis.com`) ;
     3. les règles propres à la page-démonstration sont retirées (`.banner`, `.swatches`,
        `.floatbar`) : elles n'existent dans aucune page du site.

   LA PALETTE n'est pas un choix de goût : chaque valeur est une MESURE relevée sur des sites du
   métier en production (rapport tests/pal2026/RAPPORT.md, 11/10/2026), contrôlée en contraste
   paire par paire par tests/aa.php. Les variables sont recopiées sans modification.

   CE QUE CETTE FEUILLE NE FAIT PAS : elle ne change aucun texte. Le texte du site est porté de
   l'ancien site et vérifié ligne à ligne par tools/check/parite-refonte.php.
   ===================================================================== */

/* ---------- PALETTE MESURÉE 11/10/2026 (recopiée depuis la maquette validée) ---------- */
:root{
  /* Photo peinte en CSS, pas en `<img>` : mesuré le 15/10 sur l'accueil du banc, les photos sont des
     URL **des marchands** (rakuten, grosbill, darty, fnac, priceminister, media-rdc…) — 25 URL
     distinctes, **1 seule répond 200**, 6 répondent 404, 11 sont injoignables, 7 redirectent. Un `<img>`
     sur une URL morte affiche l'icône « image cassée » **et le texte alternatif** : c'est ce qui a rendu
     les cartes et la scène de fiche illisibles (mesuré sur les captures d'Alex du 12/10, portées ici).
     Un calque peint n'affiche **rien** si l'URL est morte, et le marque-page local ci-dessous prend la
     place. Le titre du produit reste dans le lien, l'image est décorative.

     **Le marque-page se LIT « photo non fournie » (mesuré 15/10 au Playwright du poste).** La première
     version ne peignait qu'une caméra de 58 px au centre d'une boîte de 470×420 : le calque vide se lisait
     comme une **page cassée**, pas comme une absence de donnée. La version ci-dessous porte le **libellé
     dans le SVG** (sous la caméra) : une URL morte → caméra + « photo non fournie » ; une URL vivante → la
     photo peinte par-dessus, le libellé disparaît sans rien demander. Le libellé est dans le SVG, pas dans
     le HTML : il ne change **aucune** ligne de texte rendue, donc la parité ne bouge pas. Le second jeton
     (--tombale-ico) est la caméra **sans libellé** : mesurée, la pastille de la vague fait 84×84, un mot de
     14 px y tiendrait en 4 px — illisible. Les tailles peintes sont **proportionnelles à la boîte**
     (`background-size` en % : 62 % par défaut, 45 % pour la scène, 78 % pour les cartes, 42 px pour la vague).
   */
  --tombale:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 150 104' fill='none' stroke='%23c9d2dc' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='43' y='10' width='64' height='46' rx='9'/%3E%3Ccircle cx='63' cy='27' r='6'/%3E%3Cpath d='M48 46l14-12 8 7 7-6 11 11'/%3E%3Ctext x='75' y='88' text-anchor='middle' font-family='sans-serif' font-size='14' font-weight='700' letter-spacing='.4' fill='%23565959' stroke='none'%3Ephoto non fournie%3C/text%3E%3C/svg%3E");
  --tombale-ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%23c9d2dc' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='7' y='13' width='50' height='38' rx='7'/%3E%3Ccircle cx='23' cy='27' r='4.5'/%3E%3Cpath d='M12 45 27 32l9 8 8-7 8 12'/%3E%3C/svg%3E");
  --bg:#ffffff; --band:#f7f9fa; --band2:#eef1f4;
  --ink:#131921; --ink2:#565959; --line:#d5dbe1; --line2:#b7c0c8; --navy:#0a3761;
  --brand:#ff690f; --brandL:#e85d00; --brand2:#b24a00;
  --link:#0f62fe; --pos:#21791d; --neg:#e31b3e;
  --posTint:#eaf4ea; --brandTint:#fff1e8; --navyTint:#eaf1f8; --linkTint:#f2f6ff;
  --inkInv:#b9c2cc;
  --r:12px;
  --lift:0 1px 2px rgba(19,25,33,.06), 0 18px 38px -20px rgba(19,25,33,.24);
  --hover:0 6px 14px rgba(10,55,97,.10), 0 34px 60px -22px rgba(10,55,97,.30);
  --spring:cubic-bezier(.2,1.4,.3,1);
}

/* ---------- police : SERVEE PAR NOUS (Plus Jakarta Sans, variable 400→800, latin + latin-ext)
   2 fichiers mesurés dans /assets/font/ : 27 348 o + 21 728 o = 49 076 o, signature wOF2 vérifiée.
   Aucun appel à fonts.googleapis.com : le site ne dépend d'aucun service extérieur, la police
   arrive avec la page. font-display:swap = le texte est lisible immédiatement, la police
   remplace la casse système une fois chargée (pas de texte invisible). ---------- */
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:400 800;font-display:swap;
  src:url('/assets/font/plusjakarta-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Plus Jakarta Sans';font-style:normal;font-weight:400 800;font-display:swap;
  src:url('/assets/font/plusjakarta-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* ---------- base ---------- */
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;overflow-x:hidden}
body{font:16px/1.6 "Plus Jakarta Sans",system-ui,sans-serif;color:var(--ink);background:var(--bg);overflow-x:hidden}
.num{font-variant-numeric:tabular-nums lining-nums}
.mono{font-family:ui-monospace,"Liberation Mono","DejaVu Sans Mono",monospace}
.wrap{max-width:1240px;width:min(1240px,100%);margin:0 auto;padding:0 20px}
a{text-decoration:none;color:var(--link)}
img{display:block;max-width:100%}
:focus-visible{outline:3px solid var(--link);outline-offset:2px;border-radius:6px}
/* Icônes dessinées dans les gabarits (aucune bibliothèque d'icônes n'est chargée par la nouvelle
   app — mesuré : `templates/pages/compte.php` rendait `<i class="fa fa-heart-o">` sans FontAwesome,
   donc **rien**). L'icône est désormais un SVG `currentColor`, la règle l'aligne sur la ligne de texte. */
.coeur{display:inline-block;vertical-align:-3px;color:var(--brand2)}

.visuellement-caché{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;padding:0}

/* ---------- entête : collant, translucide, la barre se resserre au défilement ---------- */
.entete{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);transition:box-shadow .25s}
/* **Le verre dépoli (`backdrop-filter`) est supprimé** (mesure du 13/10, deux causes convergentes) : un
   élément en `backdrop-filter` devient le **contenant** de ses descendants en `position:fixed` — le fond
   assombri du menu, posé dans l'entête, n'aurait couvert **que l'entête** ; et l'entête gainsée collait à la
   barre de recherche, ce que la capture d'Alex reproche (« une ligne sous la recherche qui ne sert à rien
   puisque collée à la recherche »). */
.entete::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;background:linear-gradient(90deg,var(--brand),var(--brandL) 34%,var(--navy))}
.entete .entete-in{display:flex;align-items:center;gap:16px;min-height:68px;flex-wrap:wrap}
.entete-marque{font-weight:800;font-size:1.5rem;letter-spacing:-.035em;color:var(--ink)}
.entete-marque i{font-style:normal;color:var(--brandL)}
.navigation{margin-left:auto}
.navigation ul{display:flex;gap:2px;list-style:none;flex-wrap:wrap}
.navigation a{color:var(--ink2);font-weight:700;font-size:.93rem;padding:9px 13px;border-radius:999px;display:inline-block;transition:background .2s,color .2s,transform .2s var(--spring)}
.navigation a:hover{background:var(--navyTint);color:var(--navy);transform:translateY(-2px)}
/* ---------- champ de recherche de l'entête (reposé le 15/10/2026, demande d'Alex) ---------- */
/* Mesure d'avant : le champ **n'existait qu'à l'accueil** (`grep 'name="s"' templates/` : un seul champ
   libre, celui du hero) → `/categories`, `/c/…`, `/p/…`, `/connexion`, `/download`, `/conditions`,
   `/cookies` et `/s/…` rendaient **aucune recherche**. Le champ est donc **partout**, et **masqué sur
   l'accueil** : la classe `accueil` du corps (`templates/layout.php`) porte la règle ci-dessous, qui
   **gagne** contre `.recherche{display:flex}` par sa spécificité (3 classes + élément contre 1 classe).
   Un attribut `hidden` ne suffisait **pas** (mesuré 15/10 : la règle de classe écrasait l'attribut). */
.entete .recherche{flex:1 1 260px;min-width:0;max-width:420px;order:2;margin:10px 0;border-width:1.5px;
    padding:4px 4px 4px 10px;box-shadow:none}
.entete .recherche input{font-size:.95rem;line-height:1.2;padding:5px 0;padding-left:10px}
.entete .recherche button{padding:9px 16px;font-size:.78rem;border-radius:10px}
.entete .navigation{order:3}
body.accueil .entete .recherche{display:none}
/* Le controle du menu (case + libellé) **n'existe pas en desktop** : la rangee de liens y tient.
   Il n'apparait que sous 768 px, dans le bloc `@media (max-width:767px)`. */
.entete-case,.entete-menu{display:none}

/* ---------- recherche : la barre du bandeau (`.bigsearch` de la maquette) ---------- */
/* **Forme reprise le 15/10 (captures d'Alex : « du carré dans du rond, le texte collé à gauche, le double
   entourage est lourd graphiquement »).** Avant : pilule `border-radius:999px`, bord **2 px noir**, ombre
   portante, et **le bouton, déjà une pilule, posait sa propre bordure** à l'intérieur de celle du champ —
   deux contours, un rond qui en contient un autre. Après : **un seul contour**, 1,5 px, couleur de ligne,
   **un seul rayon** pour la boîte et pour le bouton (16 px / 10 px), pas d'ombre, pas de brillance animée,
   pas de saut au focus. Le texte du champ **respire à gauche** (`padding-left:12px`, `letter-spacing:.01em`),
   le libellé du bouton est **espacé** (`.08em`, bas de casse, pas de majuscules collées). Le focus se lit à
   la couleur de la ligne + anneau marine, sans déplacement : un champ qui saute de 3 px déplace la page. */
.recherche{display:flex;align-items:center;gap:10px;background:#fff;border:1.5px solid var(--line2);border-radius:16px;padding:7px 7px 7px 12px;margin:18px 0 0;max-width:640px;box-shadow:none;transition:border-color .2s,box-shadow .2s}
.recherche:focus-within{border-color:var(--navy);box-shadow:0 0 0 3px var(--navyTint)}
.recherche input::placeholder,.bigsearch input::placeholder{color:var(--ink2);opacity:1;letter-spacing:.01em}
.recherche label{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;padding:0}
.recherche input{flex:1;border:0;outline:0;font:inherit;font-size:1rem;background:transparent;min-width:0;color:var(--ink);padding-left:12px;letter-spacing:.01em}
/* **Le double contour venait de là** (capture d'Alex du 15/10 : « du carré dans du rond, le double entourage est
   lourd ») : la règle des formulaires (`form input[type=search]`, ligne 501 de la feuille) posait **une bordure,
   un fond blanc et un rayon** sur le champ, **à l'intérieur** du contour de la barre — deux contours l'un dans
   l'autre — plus un anneau de focus propre au champ. Les champs de recherche sont **spécifiques par type**
   (2 classes + élément contre 1 classe + élément) : plus de bordure, plus de fond, plus d'anneau
   intermédiaire. La barre garde **son** contour, le focus se lit **sur la barre**. */
.recherche input[type=search],.bigsearch input[type=search]{border:0;border-radius:0;background:transparent;width:auto;padding:6px 0 6px 12px;box-shadow:none}
.recherche input[type=search]:focus,.bigsearch input[type=search]:focus{border:0;box-shadow:none;outline:none}
.recherche input::placeholder{color:var(--ink2);opacity:1;letter-spacing:.01em}
.recherche button{border:0;border-radius:10px;padding:12px 20px;font-weight:800;font-size:.84rem;letter-spacing:.08em;color:#fff;background:var(--brandL);cursor:pointer;transition:background .2s;display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
.recherche button:hover{background:var(--navy)}
.recherche button svg{flex:none}
/* Loupe du bouton de recherche : icône dessinée, pas du texte — elle garde sa boîte quand le mot tombe. */
.ico-loupe{display:block}
/* **Le libellé du bouton de recherche** (capture d'Alex du 13/10 sur mobile : « tu as mis un “aller” au moteur
   de recherche, c'est inapproprié — on ne va pas à une recherche, on cherche ») : le bouton dit
   « **Chercher** » et porte une **loupe**. En mobile le mot tombe, la loupe reste (l'icône se lit sans ligne).
   « Aller » ne désignait rien : il ne menait à aucune destination. */
.recherche button span{display:inline}
/* Contrôle en **surimpression** (menu mobile, ci-dessous à 767 px) : il n'existe pas en desktop, la
   navigation y reste en ligne. La `checkbox` et ses labels se déclarent ici, la feuille les réveille en mobile. */
.entete-menu,.entete-compte,.fond-menu,.entete-fermer{display:none}
/* Texte **pour le lecteur d'écran seul** (l'icône ne se nomme pas toute seule) : hors champ visuel, jamais
   `display:none` — un texte masqué n'est plus lu. */
.hp{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---------- mouvements (CSS seul, aucun JavaScript) ---------- */
@keyframes rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}
@keyframes marquee{to{transform:translateX(-50%)}}
@keyframes conicspin{to{--a:360deg}}
@keyframes beat{0%,100%{box-shadow:0 0 0 0 rgba(33,121,29,.5)}70%{box-shadow:0 0 0 11px rgba(33,121,29,0)}}
@keyframes shine{to{transform:translateX(230%)}}
@keyframes grow{from{stroke-dashoffset:100}to{stroke-dashoffset:0}}
@keyframes popline{to{transform:scaleX(1)}}
@property --a{syntax:"<angle>";initial-value:0deg;inherits:false}
.reveal{animation:rise .7s var(--spring) both}
@supports (animation-timeline: view()){.reveal{animation-timeline:view();animation-range:entry 5% cover 16%;animation-delay:0s}}

/* ---------- hero de l'accueil (transcrit de la maquette 04, 15/10) ----------
   Les sélecteurs viennent de la maquette validée ; la palette est celle des variables ci-dessus,
   mesurées sur 12 sites du métier. Le h1 du hero est plus grand que celui des autres pages : il est
   porteur (`.corps .hero h1`, trois classes, il gagne donc contre `.corps h1`). */
/* Le hero se resserre (mesure 15/10 au Playwright : il mesurait **1 182 px** à 1 366 de large, la scène
   produit tombait à y = 732, sous la ligne de flottaison d'un écran de 1 000 px). Un hero de trois
   écrans ne montre pas de produit : les cotes ci-dessous visent **≤ 800 px**, la scène visible d'emblée.
   Le texte ne bouge pas (parité), seules les cotes de typo et les marges descendent. */
.hero{position:relative;padding:52px 0 44px;overflow:hidden;
  /* **La couleur du hero** (S8-P17, barre d'Alex : « noir blanc marron, 0 graph/img/svg ») : un lavis
     multi-teintes posé sur la palette **mesurée** de la feuille (--navy/--brand/--link/--pos), en couches
     explicites (`no-repeat`, pas de valeur implicite qui s'effondre, leçon du 15/10 sur les couches). */
  background:
    radial-gradient(760px 420px at 12% -8%, var(--navyTint), transparent 68%),
    radial-gradient(620px 360px at 88% 4%, var(--brandTint), transparent 66%),
    radial-gradient(900px 520px at 50% 108%, var(--linkTint), transparent 70%),
    linear-gradient(180deg, #fff, var(--band))}
.hero::before{content:"";position:absolute;inset:0;background:
  repeating-linear-gradient(115deg, rgba(17,74,139,.055) 0 2px, transparent 2px 11px),
  linear-gradient(90deg, var(--brandLight) 0 3px, transparent 3px);opacity:.6;pointer-events:none}
.hero::before{content:"";position:absolute;inset:-30% -12% auto;height:125%;z-index:0;
  background:radial-gradient(46rem 22rem at 14% 0%,#eef4fa,transparent 62%),
             radial-gradient(38rem 20rem at 86% 8%,#fff3ea,transparent 62%)}
.hero .in{position:relative;z-index:1}
/* **Le hero de l'accueil est une grille éditoriale à deux colonnes, alignée en haut** (mesure du 13/10 à
   1 920 × 1 080, capture d'Alex : « asymétrie bizarre + hero hors du cadre »). Ce que la feuille faisait
   avant, et ce que la mesure donne :
   - `.hero .in` : **une seule colonne de 1 200 px** — le texte courait sur toute la largeur (`h1` 779 px,
     `lede` 703, `hint` 680, champ 640) ;
   - `.stage{display:grid;grid-template-columns:1.05fr 1fr;align-items:center}` : deux colonnes de **603** et
     **575** px, et `align-items:center` **centre** la boîte photo de **362 px** dans une rangée de **676 px**
     → **157 px de vide au-dessus et au-dessous de la photo**, la photo à `y = 730`, la carte de prix à `y = 573`.
   Résultat lu sur la capture : un bloc de texte **pleine largeur**, sous quoi deux boîtes qui **ne partagent
   aucune arête** (603 contre 575, décalées de 157 px), le bas de la carte de prix à **y = 1 249**, sous la ligne
   de flottaison d'un écran de 1 080 — le hero **déborde du cadre**.
   La technique posée est celle d'une **planche éditoriale à deux colonnes, `align-items:start`** : colonne de
   gauche = texte (accroche, titre, recherche, copie), colonne de droite = **la scène produit empilée**
   (pastilles, titre, photo, carte de prix). Les deux colonnes **démarrent à la même ordonnée**, la photo
   **precede** la carte au lieu de flotter à côté d'elle, et le hero tient dans le cadre. **`align-items:center`
   ne se remplace pas par un réglage de la photo** : c'est l'alignement d'une rangée dont les deux enfants n'ont
   pas la même hauteur qui produit le vide ; la hauteur se reprend en **empilant**, pas en centrant. */
/* **Le hero de l'accueil est UNE SEULE COLONNE centrée, sans carte à côté du texte** (mesure du 13/10 sur
   des sites réels, `docs/spec-accueil-2026-2026-10-15.md`, arbitrage d'Alex : « deux colonnes côte à côte sans
   relation, beaucoup de vide, aucune hiérarchie »). Ce que la mesure a trouvé sous le reproche :
   - feuille d'avant : `.hero .in` en **deux colonnes de 597 et 569 px**, la colonne de texte finissait à
     **y = 610** et la scène produit à **y = 1 165** → **555 px de blanc** sous le texte (33 % de la surface du
     hero), et le hero **1 090 px** débordait le cadre de **105 px** sur un écran de 1 080 ;
   - **aucun** des sites mesurés ne pose de carte produit encadrée à côté du texte du hero : Stripe (hero
     **685 px = 76 % du fold**, eyebrow = **un** chiffre, h1 40 px, **2 CTA**, visuel **full-bleed**),
     Linear (**une colonne**, h1 64 px sur 1282), Kayak (comparateur : hero **497 px = 55 % du fold**, **le
     widget de recherche est l'objet du fold**), Vercel (le visuel produit est **au-dessous**, centré).
   La colonne porte donc, dans l'ordre de lecture : **un fait de fraîcheur** (eyebrow), le titre, la ligne
   d'offre, **le champ de recherche** (l'action du comparateur est la recherche, pas le scroll), la ligne
   d'appoint. La preuve du prix est **un bloc entier plus bas** (`.preuve`), pas un panneau latéral. */
.hero .in{max-width:820px;margin:0 auto;text-align:center}

/* **Bande décorative du hero** (S8-P17) : trois photos **déjà mesurées** sur les produits rendus plus bas
   (mêmes URL, mêmes EAN), peintes en calque. **Pas de `<img>`** : une URL de marchand meurt à 8 s sur cinq
   (mesure du 15/10, `s8-p9-photos-hotes-recherche-entete-et-hamburger`), et une image morte au-dessus de la
   ligne de flottaison se voit. **Aucun `::before` ici** : la leçon du halo du 15/10 (`s8-p10bis`) est tenue —
   un voile peint par-derrière une photo de produit **blanchit** la photo (63 % de pixels > 245 mesuré). Ici la
   teinte est **en dessous** du calque (`.cad` posé sur un fond teinté), pas par-derrière. */
.hero-visuel{display:flex;justify-content:center;gap:14px;margin-top:26px}
.hero-visuel .cad{width:132px;height:104px;border-radius:12px;border:1px solid var(--line);background-color:var(--navyTint);
  background-repeat:no-repeat;background-position:center;background-size:88% 88%;box-shadow:0 10px 22px -20px rgba(22,35,58,.4)}
.hero-visuel .cad:nth-child(2){transform:translateY(10px)}
.hero-visuel .cad:nth-child(3){transform:translateY(-6px)}
.hero .eyebrow{margin-bottom:2px}
.corps .hero h1{margin-left:auto;margin-right:auto}
.hero .hero-lede,.hero .lede,.hero .hint{margin-left:auto;margin-right:auto}
.hero .bigsearch{margin-left:auto;margin-right:auto}
/* **La scène produit de l'accueil est UNE SEULE carte** (mesure du 13/10 à 1 920, capture d'Alex). Ce que la
   feuille rendait : **trois boîtes disjointes** — carte photo (569×298), **titre en texte nu** (569×68, sans
   cadre, collé sous la carte), carte de prix (569×564, sa **propre** bordure dégradée) — plus, à l'intérieur de
   la carte photo, un **second cadre** blanc (voir la leçon de collision de classes ci-dessous). Le rail droit
   se lisait donc comme une pile de trois objets qui ne partagent aucune arête.
   La carte unique porte la peau de la carte de prix (bordure dégradée tournante) ; la bande photo devient un
   **fond de teinte**, sans cadre ni ombre propre ; la boîte de prix **perd sa peau** à l'intérieur (bordure,
   ombre, `::before` dégradé) : la scène est **un objet**, la photo, le titre, le prix et la courbe s'y lisent
   dans un seul périmètre. */
/* **La preuve du prix est un bloc entier** (`.preuve`), la scène y tient dans **une seule carte** à deux
   colonnes **de même hauteur** (mesure de la planche de Vercel : claim + visuel produit **central** + liste
   à droite, écart de 208 px avec le bloc du haut ; règle posée dans la spec : delta de hauteur entre colonnes
   d'une même rangée **≤ 40 px**). La peau de la carte est portée par la scène ; la bande photo est une
   **teinte sans cadre** ; la boîte de prix **perd sa peau** à l'intérieur (bordure, ombre, `::before` dégradé) —
   la scène est **un objet**, pas une pile de trois boîtes qui ne partagent aucune arête (mesure du 13/10). */
.preuve{padding:64px 0}
.preuve .stage.preuve-scene{position:relative;margin-top:0;padding:16px;border-radius:var(--r);background:#fff;
   border:1px solid var(--line);box-shadow:var(--lift);align-items:stretch;gap:26px}
.preuve .stage.preuve-scene .photo{min-height:300px;padding:12px;border:0;box-shadow:none;border-radius:10px;
   background:var(--band)}
.preuve .stage.preuve-scene .photo .cad{width:min(100%,420px);height:min(290px,46vw)}
.preuve .stage.preuve-scene .meta{min-width:0}
.preuve .stage.preuve-scene .chips{margin-bottom:10px}
.preuve .stage.preuve-scene .pricebox{border:0;box-shadow:none;padding:0;margin:14px 0 0;border-radius:0}
.preuve .stage.preuve-scene .pricebox::before{display:none}
/* Blocs de la home : un rythme, pas d'empilement sans respiration (mesure 13/10 : écart entre blocs
   **160 à 210 px** sur les sites relevés — Vercel 208 px ; Stripe porte le rythme dans ses conteneurs).
   Le contenu reste dans la colonne du site (mesures : 1 000 px à 1 280 px de large selon les sites). */
.bloc{padding:64px 0}
.kicker{font-size:.78rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--brand2);margin:0 0 14px}
@media (max-width:1119px){
 .preuve .stage.preuve-scene{grid-template-columns:1fr;gap:18px}
 .preuve .stage.preuve-scene .photo .cad{width:min(100%,340px);height:min(240px,58vw)}
}
/* Sous 1 120 px la planche devient une colonne : texte, puis scène produit empilée. */
.eyebrow{display:inline-flex;align-items:center;gap:9px;border-radius:999px;padding:7px 15px;font-size:.78rem;
  font-weight:800;letter-spacing:.06em;text-transform:uppercase;background:#fff;border:1.5px solid var(--line2);
  box-shadow:var(--lift);color:var(--ink2)}
.eyebrow i{font-style:normal;width:8px;height:8px;border-radius:50%;background:var(--pos);animation:beat 2.2s infinite}
.eyebrow b{color:var(--ink);font-weight:800}
/* **Le premier mot de l'accueil** (voir `templates/pages/accueil.php`) : une ligne d'accroche, pas une
   pilule de chiffres. Taille de lecture, graisse sur ce qui porte, couleur de marque sur le montant —
   la charte, pas un drapeau. */
.hero-lede{margin:0 0 4px;font-size:1.02rem;font-weight:600;line-height:1.35;color:var(--ink2);max-width:44ch}
.hero-lede b{color:var(--ink);font-weight:800}
.hero-lede b:last-of-type{color:var(--brand2)}
.corps .hero h1{font-size:2.9rem;font-weight:800;letter-spacing:-.04em;line-height:1.05;margin:12px 0 10px;color:var(--ink);max-width:24ch}
.corps .hero h1 .u{position:relative;display:inline-block;white-space:nowrap}
.corps .hero h1 .u::after{content:"";position:absolute;left:0;right:0;bottom:.05em;height:.26em;border-radius:99px;
  background:var(--brandTint);z-index:-1;transform:scaleX(0);transform-origin:left;animation:popline .9s .45s cubic-bezier(.2,1.2,.3,1) both}
.hero .lede{color:var(--ink2);max-width:60ch;font-size:1rem;margin:0}
.bigsearch{margin-top:18px;display:flex;align-items:center;gap:10px;background:#fff;border:1.5px solid var(--line2);
  border-radius:16px;padding:7px 7px 7px 12px;max-width:640px;box-shadow:none;transition:border-color .2s,box-shadow .2s}
.bigsearch:focus-within{border-color:var(--navy);box-shadow:0 0 0 3px var(--navyTint)}
.bigsearch input{flex:1;border:0;outline:0;font:inherit;font-size:1rem;background:transparent;min-width:0;color:var(--ink);padding-left:12px;letter-spacing:.01em}
.gobtn{border:0;border-radius:10px;padding:12px 20px;font-weight:800;font-size:.84rem;letter-spacing:.08em;color:#fff;background:var(--ink);
  cursor:pointer;transition:background .2s}
.gobtn:hover{background:var(--navy)}
.hero .hint{margin-top:10px;font-size:.88rem;color:var(--ink2);max-width:66ch}
.hero .hint b{color:var(--ink)}
/* ---------- barre de marques (S8-P16, 13/10) ----------
   Mesure Stripe (13/10) : **6 logos sur un rang, posés après le hero**. La barre de druidx **nomme** les
   enseignes dont une offre est vivante sur les produits rendus aujourd'hui (comptées dans les payloads déjà
   lus par la page), **sans aucun nombre** : le compte de marchands en tête de home est un **aveu de faiblesse**
   (decision d'Alex du 13/10). Ce sont des **textes**, pas des logos téléchargés : aucun visuel tiers n'est
   récupéré sans accord. */
.marques{padding:24px 0 30px;background:linear-gradient(180deg,var(--brandTint),var(--band));border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.marques .in{max-width:1080px;margin:0 auto;text-align:center}
.marques-tete{font-size:.74rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;color:var(--ink2);margin:0 0 14px}
.marques-liste{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;justify-content:center;gap:8px 28px}
.corps .marques-liste{list-style:none;padding-left:0;margin-left:0}
.corps .marques-liste > li{margin:0}
.marques-liste li{font-size:.96rem;font-weight:800;letter-spacing:.01em;color:var(--ink2);opacity:.9}
/* ---------- tuiles de rubriques (S8-P16) : la navigation d'entrée du comparateur ----------
   Mesure Kayak : après le hero, **grille de tuiles** ; NN/g : « Reveal Content Through Examples » et
   « les utilisateurs vont à l'accueil pour trouver leur chemin ». Ce sont des **liens** vers `/c/{id}`
   (page mesurée à 200 par `tools/check/check-pages.php`), pas des filtres. */
/* 8 tuiles = **4 par rang, 2 rangs** (mesure 13/10 à 1 920 : `auto-fit minmax(210px,1fr)` rendait **5 colonnes**,
   la 2ᵉ rangée laissait **3 cases vides** et la barre d'onglet du bas se trouvait décalée). 8 est notre nombre,
   la grille doit le tenir. */
.tuiles{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.corps .tuiles{list-style:none;padding-left:0;margin-left:0}
.corps .tuiles > li{margin:0;display:flex}
.tuiles > li{display:flex}
.tuile{flex:1;display:flex;flex-direction:column;gap:7px;padding:18px;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift);text-decoration:none;color:var(--ink);
  transition:transform .3s var(--spring),box-shadow .3s,border-color .2s}

/* ** Icône dessinée par rubrique** (S8-P17, barre d'Alex : « 0 graph/img/svg ») : pastille teintée sur la palette
   mesurée, glyphe tracé à la main dans le gabarit (`$ICONES`), `aria-hidden` (décoratif). 4 teintes qui tournent
   sur les 8 tuiles : la grille de rubriques n'est plus un bloc de texte noir sur blanc. */
.tuile-ico{display:grid;place-items:center;width:42px;height:42px;border-radius:12px;color:var(--navy);
  background:var(--navyTint);transition:transform .3s var(--spring)}
.tuile:hover .tuile-ico{transform:scale(1.08) rotate(-4deg)}
.tuile-ico.teinte-brand{color:var(--brand2);background:var(--brandTint)}
.tuile-ico.teinte-pos{color:var(--pos);background:var(--posTint)}
.tuile-ico.teinte-link{color:var(--link);background:var(--linkTint)}
.tuile-ico.teinte-navy{color:var(--navy);background:var(--navyTint)}
.tuile b{font-size:1.05rem;font-weight:800;letter-spacing:-.015em;line-height:1.25}
.tuile > span{font-size:.83rem;color:var(--ink2)}
.tuile .go{margin-top:auto;display:inline-flex;align-items:center;gap:8px;font-weight:800;font-size:.86rem;color:var(--brand2);white-space:nowrap}
.tuile .go .arr{transition:transform .25s var(--spring)}
.tuile:hover{transform:translateY(-6px);box-shadow:var(--hover);border-color:var(--line2)}
.tuile:hover .go .arr{transform:translateX(5px)}
.rubriques .tuiles{margin-top:2px}
.rubriques .sechead .pill{flex:none}
/* ---------- trois colonnes d'atouts (S8-P16) : la valeur unique, en trois coups ----------
   Mesure 13/10 : les sites relevés tiennent **un rang de trois valeurs** après la preuve (Vercel : claim +
   liste à droite ; Stripe : rangée de solutions). Le texte porte ce qui est mesuré dans les questions
   portées de l'ancien site (extension, code-barres, financement par les boutiques) : **rien d'inventé**. */
.atouts-tete{max-width:62ch;margin-bottom:22px}
.atouts{background:linear-gradient(180deg,var(--linkTint),var(--band));border-top:1px solid var(--line)}
.atouts .atouts-tete h2{margin:0}
.atouts-tete p{color:var(--ink2);margin:8px 0 0}
.atouts-liste{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:16px}
.corps .atouts-liste{list-style:none;padding-left:0;margin-left:0}
.corps .atouts-liste > li{margin:0}
.atout{display:flex;flex-direction:column;gap:10px;padding:22px;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift)}
/* **Pastille colorée de l'atout** (S8-P17) : la leçon des piles de calque du 15/10 est tenue — un seul calque,
   posé **à plat** (pas de `::before` derrière une photo). Les trois atouts tournent sur trois teintes mesurées. */
.atout svg{color:var(--brand2);background:var(--brandTint);width:42px;height:42px;padding:9px;border-radius:12px;box-sizing:border-box}
.atout:nth-child(2) svg{color:var(--link);background:var(--linkTint)}
.atout:nth-child(3) svg{color:var(--pos);background:var(--posTint)}
.atout b{font-size:1.05rem;font-weight:800;letter-spacing:-.015em}
.atout p{color:var(--ink2);font-size:.95rem;margin:0;max-width:none}
.stage{display:grid;grid-template-columns:1.05fr 1fr;gap:22px;align-items:center;margin-top:22px}
.stage .photo{position:relative;border-radius:12px;background:linear-gradient(180deg,#fff,var(--navyTint));border:1px solid var(--line);box-shadow:var(--lift);
  padding:10px;display:flex;align-items:center;justify-content:center;min-height:340px;overflow:hidden}
/* Le halo radial est **supprimé** (capture d'Alex du 15/10 : « une espèce de lumière sur les images des hero
   qui efface la photo »). Contrôle fait en deux captures de l'élément seul, halo activé puis désactivé par
   injection de `.stage .photo::before{display:none}` dans la page : **avec**, la souris est **lavée de blanc**
   au centre ; **sans**, elle est pleine et noire. La carte reste blanche et plate, la photo est la seule
   lumière de la carte. La règle est gardée en **commentaire** pour que la mesure reste dans la feuille. */
/* .stage .photo::before{content:"";position:absolute;inset:0;background:radial-gradient(60% 55% at 50% 42%,#f4f8fc,transparent 70%)} */
.cad{display:block;background-color:transparent;background-repeat:no-repeat;background-position:center,center;
  background-size:contain,62%;background-image:var(--photo,none),var(--tombale)}
/* `background-repeat` en **valeur unique** (mesure du 15/10 sur la capture d'Alex : le hero du mobile rendait
   **trois souris côte à côte**). La feuille posait `no-repeat,no-repeat` (une valeur par couche) : avec
   **une seule** couche peinte — le cas `.cad.avec-photo`, où la couche du calque tombe — le navigateur rend
   `background-repeat: repeat` (mesuré en `getComputedStyle` sur `.stage .photo .cad` : `repeat`, et la
   photo `contain` de 220 px **se répétait** dans la boîte de 340). Une valeur unique vaut pour toutes les
   couches : plus de liste à faire coller au nombre de couches. */
/* Le calque « photo non fournie » **ne se rend que quand il n'y a pas de photo** (capture d'Alex du 15/10
   sur mobile : la souris du hero était **entourée des lettres « p » et « e » du libellé du calque** — la
   seconde couche, centrée, dépasse sur les côtés d'une photo haute et étroite). La classe `photo` est posée
   par les producteurs **seulement quand l'URL existe** (`Accueil`/`Filet`/`RechercheController`, gabarits
   d'accueil et de fiche) : la couche du calque tombe, il ne reste que la photo. */
.cad.avec-photo{background-image:var(--photo);background-repeat:no-repeat;background-position:center;background-size:contain}
/* Photo de fiche en vraie image (SEO 2026, 15/10) : la plaque garde sa taille, l'image s'y loge sans être
   déformée ; le calque « photo non fournie » tombe tant que l'image est là (`images.js` le rend si elle échoue). */
.cad.avec-img{background-image:none}
.cad.avec-img > img{display:block;width:100%;height:100%;object-fit:contain}
/* Les faits de la fiche en une phrase (SEO/GEO 2026) : texte courant, discret, sous la scène. */
.resume-faits{margin:18px auto 0;max-width:72ch;font-size:.92rem;line-height:1.55;color:var(--ink2)}
.stage .photo .cad{position:relative;z-index:1;width:min(100%,430px);height:min(340px,52vw);background-size:contain,45%;transition:transform .6s var(--spring)}
/* `z-index` (mesure du 15/10, capture d'Alex puis capture de l'élément seul) : le tampon « 11 marchands »
   **disparaissait sous la photo**. `.cad` est `position:relative` et vient **après** le tampon dans le DOM :
   il peint par‑dessus, et la photo du producteur a son **propre fond blanc**. Le tampon et l'étiquette
   passent à `z-index:3`, la photo à `1` : ce qui est écrit sur la carte reste au‑dessus de ce qui l'illustre. */
.stage .photo:hover .cad{transform:scale(1.07) rotate(-1deg)}
.stamp{position:absolute;z-index:3;left:16px;top:16px;border-radius:999px;background:#fff;padding:8px 14px;font-size:.75rem;
  font-weight:800;box-shadow:var(--lift);color:var(--ink2)}
.stamp b{color:var(--ink)}
.tag{position:absolute;z-index:3;right:16px;bottom:16px;border-radius:999px;background:var(--ink);color:#fff;padding:8px 14px;
  font-size:.74rem;font-weight:800}
/* Le produit du hero doit être **cliquable** (mesure du 15/10 : la scène entière n'avait **qu'un** lien,
   la boîte de prix ; ni la photo, ni le titre, ni le prix ne menaient à la fiche). L'ancre occupe la
   photo entière, **sans balisage de texte** ajouté : le tampon et l'étiquette restent des `<span>`.
   `text-decoration:none` + `color:inherit` : un lien n'est pas dessiné, c'est le curseur et le focus
   qui le disent. */
.stage .photo .photo-link{display:flex;align-items:center;justify-content:center;width:100%;height:100%;
  color:inherit;text-decoration:none;border-radius:26px}
.stage .photo .photo-link:focus-visible{outline:3px solid var(--navy);outline-offset:3px}
.stage .meta h2.t a{color:inherit;text-decoration:none;background-image:linear-gradient(var(--brandL),var(--brandL));
  background-size:0% 2px;background-position:0 100%;background-repeat:no-repeat;transition:background-size .25s}
.stage .meta h2.t a:hover,.stage .meta h2.t a:focus-visible{background-size:100% 2px}
/* **L'ancre du prix ne se pose pas en `inline-block`** (mesure du 13/10, capture d'Alex : « 35,99 € ●+15 % »,
   un point noir collé entre la monnaie et la variation). Le `<a>` du prix (posé S8-P10, hero cliquable) en
   `display:inline-block`, avec le `letter-spacing:-.05em` de `.v`, **serrait** la boîte du `<em>` : mesuré en
   `getBoundingClientRect`, `<em>` faisait **14 px** de large pour un glyphe de **18,4 px** (1,15 rem) — le glyphe
   **déborde** de sa boîte, mord sur la marge de la pastille de variation et se lit comme un point. L'ancre reste
   **inline**, le glyphe garde sa place, et la monnaie est **séparée** de la variation. */
.pricebox .v a{color:inherit;text-decoration:none;display:inline}
.pricebox .v a:hover{color:var(--navy)}
.pricebox .v a:focus-visible{outline:3px solid var(--navy);outline-offset:4px;border-radius:8px}
.stage .meta .chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
/* **Le fil de rubrique se clique** (Alex, 13/10 : « la rubrique qui sert de breadcrumb n’est pas cliquable
   dans la fiche produit ») : mêmes pastilles que les `.chip` d’information, mais ce sont des **ancres** —
   elles prennent la peau d’un lien : soulignement au survol ET au focus clavier, couleur d’action, et 44 px
   de hauteur tactile en mobile (leçon des cotes de la passe S8-P11 : les cibles de la fiche mobile
   étaient trop basses). L’étage courant porte `aria-current` : il se clique et il dit où on est. */
/* **Une puce, la rubrique du produit, et elle se clique** (S8-P22 bis, 13/10 23 h 10 — Alex : « je t'ai
   juste demandé d'activer le clic »). La version « chaîne racine → feuille » peignait jusqu'à 5 niveaux
   (mesuré : 3 rangées à 390 px) et décalait la boîte de prix : la règle est posée pour **un seul** élément,
   elle ne prévoit pas de rangée d'étages. */
.stage .meta a.fil{display:inline-flex;align-items:center;margin:0 0 12px;color:var(--ink2)}
.stage .meta a.fil .chip{border:1px solid var(--line);border-radius:999px;padding:8px 16px;font-size:.9rem;line-height:1.15;background:var(--paper)}
.stage .meta a.fil:hover,.stage .meta a.fil:focus-visible{color:var(--brand);text-decoration:none}
.stage .meta a.fil:hover .chip,.stage .meta a.fil:focus-visible .chip{border-color:var(--brand);color:var(--brand)}
.stage .meta a.fil[aria-current] .chip{border-color:var(--brand);color:var(--brand)}
.stage h2.t{font-size:1.9rem;font-weight:800;letter-spacing:-.03em;line-height:1.12;color:var(--ink);margin:0}
.hero .wave{margin-top:34px}
.sechead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px;flex-wrap:wrap}
/* Titre du bloc d'offres de la fiche (S8-P18) : le bloc se **nomme comme une action** (« Les offres à
   cliquer »), la date des relevés se pose à côté, plus en pastille flottante (mesure du 13/10 : « cache du
   12/10/2026, que vient foutre une info technique en milieu de page ? »). */
.offres-tete{margin-top:26px}
.offres-tete h2{font-size:1.35rem;margin:0}
.sechead h2{font-size:1.7rem;font-weight:800;letter-spacing:-.03em;color:var(--ink);margin:0}
.sechead a{color:var(--brand2);font-weight:800;font-size:.92rem}

/* ---------- le corps de page ---------- */
.corps{padding:26px 20px 40px;max-width:1240px;margin:0 auto}
.corps h1{font-size:2.6rem;font-weight:800;letter-spacing:-.04em;line-height:1.06;margin:6px 0 14px;color:var(--ink);max-width:34ch}
/* Jeton de palette sous le titre de page (mesure 15/10 : « noir et blanc, aucune charte »). */
.corps h1:first-of-type::after{content:"";display:block;width:62px;height:4px;border-radius:999px;background:linear-gradient(90deg,var(--brandL),var(--navy));margin-top:12px}
/* **Le liseré d'accent des `<h1>` ne se rend pas dans le hero de l'accueil** (mesure du 13/10 à 1 366 et 1 920,
   capture de la home reprise) : la barre de 62 × 4 px se posait **à gauche du bloc** du titre centré, en dessous
   de la première ligne — un tiret **déconnecté du texte** (62 px pour un titre de 779 px centré). Le hero porte
   déjà son propre dispositif de soulignement (`.hero h1 .u::after`, la bande derrière « au meilleur prix ») : les
   deux marques sur le même titre se lisent comme un reste de gabarit. */
.corps .hero h1:first-of-type::after{display:none}
.corps h2{font-size:1.7rem;font-weight:800;letter-spacing:-.03em;line-height:1.15;margin:26px 0 10px;color:var(--ink)}
.corps h3{font-size:1.15rem;font-weight:700;letter-spacing:-.015em;margin:18px 0 6px;color:var(--ink)}
.corps p{margin:10px 0;color:var(--ink)}
.corps ul{list-style:disc;padding-left:1.3em;margin:10px 0}
.corps ol{list-style:decimal;padding-left:1.3em;margin:10px 0}
.corps li{margin:4px 0}
/* **Règle des listes de texte hérité** (`.corps ul{list-style:disc}`) : elle a **des puces** et elle est
   (0,1,1) — elle **gagne** contre les listes de composants déclarées en (0,1,0) (`.pills`, `.categories`,
   `.results_wrapper`, `.sous-categories`, `.menuperso`, `.liste-alertes`) : mesure du 15/10 sur la capture
   d'Alex, les pastilles de rubriques de l'accueil rendaient **une puce devant chaque pilule**
   (« • Toutes les rubriques • Adulte »). Les listes de composants se **re-déclarent donc à la même
   spécificité** (`.corps .pills`, …) et **après** la règle de texte : une règle de même poids placée
   **après** gagne. Les listes de texte des pages héritées (conditions, cookies, questions de l'accueil)
   gardent leurs puces.
*/
.corps .pills,.corps .categories,.corps .categories>li>ul,.corps .results_wrapper,.corps .sous-categories,
.corps .menuperso,.corps .liste-alertes,.corps .legendes{list-style:none;padding-left:0;margin-left:0}
.corps .pills>li,.corps .categories>li,.corps .results_wrapper>li,.corps .sous-categories>li,
.corps .menuperso>li,.corps .liste-alertes>li,.corps .legendes>li{margin:0}
.ariane{font-size:.86rem;color:var(--ink2);margin:0 0 10px}
.ariane a{color:var(--ink2)}
.ariane a:hover{color:var(--brand2)}
/* Séparateurs du fil d'ariane (mesure 15/10 : « Téléphones mobiles lu d'un bloc, aucun séparateur »).
   Collés au lien : `aria-hidden`, le fil reste lisible à un lecteur d'écran sans ponctuation chantée. */
.ariane-separateur{display:inline-block;margin:0 7px;color:var(--brandL);font-weight:800}
.ariane-portee{font-size:.9rem;color:var(--ink2);margin:-2px 0 14px}
/* Annonces de ce qui **n'est pas** rendu : le chiffre vient du compte SQL, pas du rendu. */
.ariane-plus{color:var(--ink2)}
.ariane-plus a{color:var(--brand2);font-weight:700}

/* ---------- accueil : les textes portés (questions, téléchargements, tablature) ----------
   Le bandeau et la capture de l'ancienne page (`.accueil-bandeau`, `.accueil-bulle`, `.accueil-capture`)
   sont **retirés de la feuille** depuis S8-P5/S8-P6 : mesuré sur le balisage, plus aucune règle des
   gabarits ne les référence (l'accueil est composé en `.hero`/`.stage`, la fiche en `.stage.fiche-stage`).
   La mesure qui a ouvert ce nettoyage : 109 classes déclarées par la feuille, **84** référencées par le
   balisage du nouveau site, **25** qui ne le sont pas (dont les scaffaudages de la maquette encore sans
   balisage : `.floatbar`, `.swatches`, `.flag`, `.banner`, `.offer` — eux, on les garde, c'est du kit). */
.accueil-telechargement{margin:14px 0}
.accueil-telechargement a,.btn,.button input[type=submit],.accueil-questions a{display:inline-flex;align-items:center;gap:10px;border-radius:999px;padding:15px 24px;font-weight:800;font-size:1rem;cursor:pointer;position:relative;overflow:hidden;color:#fff;background:var(--brandL);box-shadow:0 14px 30px -14px rgba(232,93,0,.55);transition:transform .25s var(--spring),box-shadow .25s,background .2s}
.accueil-telechargement a:hover,.btn:hover,.button input[type=submit]:hover{transform:translateY(-3px) scale(1.03);background:var(--navy);box-shadow:0 22px 44px -16px rgba(10,55,97,.7)}
.accueil-tablature{font-size:.86rem;color:var(--ink2)}
.accueil-questions{margin-top:26px;border-top:1px solid var(--line);padding-top:10px;color:var(--ink2)}
.accueil-questions h2{margin:22px 0 8px;color:var(--ink);display:flex;align-items:center;gap:10px}
.accueil-questions h2::before{content:"";width:10px;height:10px;border-radius:50%;background:var(--brandL);flex:none;box-shadow:0 0 0 4px var(--brandTint)}
.accueil-questions h2 + *{color:var(--ink2);max-width:78ch}

/* ---------- listes de texte (le filet de produits voisins de la fiche) ----------
   `div.listproduct` + `a.imgproduct` en image de fond : le balisage porte de l'ancien site, repris par
   `FicheController::blocFilet()`. **`RechercheController::rangees()` est supprimée du code depuis S8-P3**
   (les mots et les catégories rendent des `.card`) : il ne reste que ce bloc, et la vignette y est
   demandée à 160 px (`Vignette::LISTE`) pour une boîte de 110 px. */
.listproduct{display:grid;grid-template-columns:110px 1fr;gap:16px;align-items:center;padding:14px 16px;margin:0 0 12px;background:#fff;border:1px solid var(--line);border-radius:12px;box-shadow:var(--lift);transition:transform .3s var(--spring),box-shadow .3s}
.listproduct:hover{transform:translateY(-4px);box-shadow:var(--hover)}
.imgproduct{display:block;width:110px;height:110px;background-color:#fff;border-radius:12px;border:1px solid var(--line);padding:6px;background-size:contain;background-position:center;background-repeat:no-repeat}
.descproduct{display:flex;flex-direction:column;gap:6px;min-width:0}
.titleproduct{font-size:1.05rem;font-weight:700;line-height:1.3;letter-spacing:-.015em;color:var(--ink)}
.titleproduct:hover{color:var(--navy)}
/* ---------- scène de la fiche (S8-P6, 15/10) ----------
   Le bloc est `.stage.fiche-stage` (photo 400 px demandée au CDN par `Vignette::FICHE`, pastilles,
   boîte de prix, sparkline). **Retiré de la feuille : tout ce qui habillait `.fiche-scene`** — mesuré,
   cette scène rendait le titre **deux fois** (le `<h1>` et le lien `.titleproduct`) et reliait la fiche
   **à elle-même**. `.listproduct`, `.imgproduct`, `.descproduct` et `.titleproduct` restent en service
   pour le filet de produits voisins. */
.fiche-stage{margin-top:22px;align-items:stretch}
/* **La plaque de photo ne dépasse plus la colonne de droite** (capture d'Alex du 13/10, fiche sans offre :
   « le bloc de gauche depasse le bloc de droite »). Root cause mesuré : `.stage` pose `align-items:center`,
   donc les deux blocs se **centrent** l'un sur l'autre et la plaque (min-height 340) debordait de 20 a 25 px
   sous une colonne de droite courte. `stretch` egalise les deux colonnes sur la hauteur de la plus haute :
   l'ecart de bas de colonnes tombe a 0 (mesuré). */
.fiche-stage .photo img{display:block}
/* **La plaque de photo se clique** (Alex, 13/10/2026 : « si on a un tarif, ajoute un clic sur l'image du
   produit directement sur le lien affiche (le même que "l'offre la moins chere") »). Une ancre **par-dessus**
   la plaque, `inset:0` : toute la surface du produit est cliquable, pas un coin. `z-index:3` parce que
   `.cad` se peint **apres** tampon et etiquette (lecon S8-P14) et que le lien doit rester au premier plan.
   Le rayon suit celui de la plaque, et le focus se voit : l'ancre ne dessine **rien**, elle ne remplace
   aucune etiquette. */
.fiche-stage .photo{position:relative}
.fiche-stage .photo .photo-lien{position:absolute;inset:0;z-index:3;border-radius:inherit}
.fiche-stage .photo .photo-lien:focus-visible{outline:3px solid var(--navy);outline-offset:-3px}
.fiche-stage .photo .photo-lien:hover ~ .tag,.fiche-stage .photo .photo-lien:hover ~ .stamp{opacity:.94}
.pricebox .btn{margin-top:16px}

/* **L'action principale de la fiche : le lien de l'offre la moins chère, dans la boîte de prix**
   (S8-P18, 13/10, reproche d'Alex : « le plus important c'est le lien à cliquer le moins cher qui doit être
   prioritaire : c'est pas le cas, il est à 2 écrans en dessous »). Mesure avant la passe : 1ʳᵉ carte d'offre à
   `y = 815` pour une fenêtre de **900 px** — le lien à cliquer était **sous la ligne de flottaison**. Mesure
   après : l'ancre d'affiliation du moins cher se rend **dans la boîte de prix**, à `y` mesuré dans le fold.
   Le nombre qu'il porte est **celui qui classe la liste** (coût total, `FicheController::offresTriees`), pas
   un prix d'appel : l'étiquette le dit. */
.cta-achat{display:flex;align-items:center;gap:14px;margin-top:18px;padding:16px 20px;border-radius:12px;
  background:linear-gradient(120deg,var(--brandL),var(--brand2));color:#fff;text-decoration:none;
  box-shadow:0 18px 34px -18px rgba(225,41,44,.8);transition:transform .25s var(--spring),box-shadow .25s}
.cta-achat:hover{transform:translateY(-3px);box-shadow:0 24px 44px -18px rgba(225,41,44,.9)}
.cta-achat span:first-child{font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;opacity:.85}
.cta-achat b{font-size:1.5rem;font-weight:800;letter-spacing:-.03em;line-height:1;display:flex;flex-direction:column;gap:2px}
/* **Le prix ne se casse plus sur deux lignes** (capture d'Alex du 13/10 : « prix sur 2 lignes »). Root cause
   mesuré : `b` est une **colonne flex**, donc chaque enfant (le texte « 46,62 », le `<em>€</em>`, le `<small>`)
   devient un **item empilé** : le euro tombait seul sous le nombre. Le nombre et son symbole sont donc **un
   seul item**, `white-space:nowrap`. */
.cta-achat b .montant{white-space:nowrap;display:block}
.cta-achat b em{font-style:normal;font-size:.9rem;margin-left:2px}
.cta-achat b small{font-size:.78rem;font-weight:700;letter-spacing:0;opacity:.9}
.cta-achat .arr{margin-left:auto;font-size:1.2rem}
.secondaires{margin:12px 0 0;font-size:.9rem}
/* **Prix inactif** (décision d'Alex du 14/10 : « si mais pas en énorme et bien noir comme s'il était actif, mais
   "dernier prix" avec un graphisme de "désactivé" »). Un prix relevé il y a plus de `Fraicheur::ACTIF_JOURS` jours
   n'est pas un prix de vente : petit, gris, sans le dégradé de la boîte active ; le lien reste, en contour. */
.pricebox.inactif{background:#f4f6f8;border-color:var(--line);box-shadow:none}
.pricebox.inactif::before{display:none}
.pricebox.inactif .l{color:var(--ink2)}
.pricebox .dernier{margin:10px 0 0;font-size:.95rem;color:var(--ink2)}
.pricebox .dernier b{font-weight:700;color:var(--ink2)}
.voir-prix{display:inline-flex;align-items:center;gap:10px;margin-top:16px;padding:12px 18px;border-radius:12px;
  border:1px solid var(--line2);background:#fff;color:var(--ink);font-weight:700;text-decoration:none}
.voir-prix:hover{border-color:var(--navy);color:var(--navy)}
.voir-prix:focus-visible{outline:3px solid var(--navy);outline-offset:3px}
/* **Listes : produits sans vendeur actif** (`RechercheController::cartesParEtat`, 14/10) — bloc à part, cartes grisées,
   le dernier prix en petit dans la ligne du bas, jamais en grand dans la photo. */
.grid.inactifs{margin-top:14px}
.sans-vendeur{margin:40px 0 6px;font-size:1.25rem;font-weight:800;letter-spacing:-.02em;color:var(--ink2)}
.card.inactif{background:#f4f6f8;box-shadow:none}
.card.inactif .cad{filter:grayscale(1);opacity:.6}
.card.inactif h3{color:var(--ink2)}
.card.inactif .go{color:var(--ink2)}
.secondaires a{color:var(--link);font-weight:700;text-decoration:none;border-bottom:1px solid var(--linkTint)}
.secondaires a:hover{border-bottom-color:var(--link)}

/* **Outil d'alerte prix** (S8-P18) : le geste est posé sur la fiche, et **déconnecté il mène à
   l'inscription avec le produit en poche** (demande d'Alex : « capter du user à l'inscription et les refaire
   revenir plus tard »). Le bloc se distingue des blocs d'information par un liseré et un fond teinté. */
.alerte-outil{margin-top:18px;padding:16px;border:1px dashed var(--line2);border-radius:12px;background:var(--posTint)}
.alerte-form{display:flex;flex-direction:column;gap:8px}
.alerte-form label{font-size:.86rem;font-weight:800;color:var(--ink)}
.alerte-champs{display:flex;gap:8px;flex-wrap:wrap}
.alerte-champs input{flex:1;min-width:120px;border:1px solid var(--line2);border-radius:10px;padding:12px 14px;
  font:inherit;font-weight:700;background:#fff}
.alerte-champs input:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 3px var(--navyTint)}
.alerte-outil .hint{margin:6px 0 0;font-size:.8rem;color:var(--ink2)}
.alerte-ok{margin:0;font-size:.92rem;color:var(--pos)}
.alerte-hint{margin:0;font-size:.9rem;color:var(--ink2)}
.alerte-inite{margin:0;font-size:.92rem;color:var(--ink)}
.alerte-inite a{color:var(--link);font-weight:800;text-decoration:none;border-bottom:1px solid var(--linkTint)}
.alerte-inite a:hover{border-bottom-color:var(--link)}
/* **Le geste d'alerte reste visible, la modale s'ouvre au clic, sans script** (recadrage d'Alex du 13/10).
   Case à cocher + `label` (mécanisme du menu mobile, S8-P11) : la case est **hors champ visible** (`opacity:0,
   position:absolute`) — pas `display:none`, qui la sortirait du focus clavier. */
.p-alerte{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
/* Le declencheur de la modale, quand le visiteur n'a pas de compte : c'est un `label` habille en bouton
   (`.alerte-clic`), pas un `button` — le geste est maniable avant la barriere de connexion. */
.alerte-form .alerte-clic{display:block;width:100%;padding:14px 18px;border:0;border-radius:12px;text-align:center;
  font-size:.95rem;letter-spacing:.02em;cursor:pointer;color:#fff;background:var(--brand2)}
.alerte-form .alerte-clic:hover{background:var(--brand)}
#p-alerte:focus-visible ~ .modale-alerte .modale-boite{outline:3px solid var(--navy);outline-offset:3px}
/* **Le slider de l'alerte, bornes de l'ancien site** (13/10, capture d'Alex : « on avait demande le slide pour
   l'alerte de prix comme dans le legacy ») : la valeur se lit au-dessus, les bornes min/max de part et d'autre
   de la glissiere (mesure legacy : `min = ceil(prix/2)`, `max = ceil(prix*1,2)`, euros). */
.alerte-titre{margin:0;font-size:.95rem;color:var(--ink)}
/* Le formulaire de la glissière tient ses lignes en colonne, sans margin héritée des `<p>` du corps. */
.alerte-slide{gap:6px}
.alerte-slide .alerte-titre,.alerte-slide .alerte-slider{margin:0}
.alerte-titre b{font-weight:800}
.alerte-titre output{font-weight:800;font-size:1.05rem}
.alerte-titre em{font-style:normal;font-weight:800}
.alerte-slider{display:flex;align-items:center;gap:10px;margin:2px 0 0}
.alerte-slider .pas{font-size:.78rem;font-weight:800;color:var(--ink2);min-width:2.6em;text-align:center}
.slider{flex:1;min-width:0;width:100%;height:26px;accent-color:var(--brand);cursor:ew-resize}
.alerte-form .gobtn{align-self:flex-start;margin-top:2px}
/* **La glissière est le bouton** (Alex, 13/10/2026 : « on n'a jamais demande un bouton "poser l'alerte" =>
   le slide EST LE BOUTON ») : la ligne du bas **dit** le geste, elle ne se clique pas. Le controle de
   soumission n'existe que dans le `<noscript>` du gabarit (sans script, il faut bien un bouton). */
/* **La ligne du geste n’existe plus** (Alex, 13/10/2026 : « le slide est un CTA, il doit générer de la friction :
   modale directe sans explication si pas connecté. le blabla ne va que freiner les clients »). La glissière se
   suffit : titre, valeur, devise, deux bornes, curseur. Le contrôle de repli vit dans le `<noscript>` du gabarit. */
.alerte-form noscript{display:flex;justify-content:flex-start}
.alerte-relance .hint{margin-top:10px}
.modale-alerte{position:fixed;inset:0;z-index:80;display:none;align-items:center;justify-content:center;padding:20px}
#p-alerte:checked ~ .modale-alerte{display:flex}
.modale-alerte .fond-alerte{position:absolute;inset:0;background:rgba(11,32,54,.55);cursor:pointer}
.modale-boite{position:relative;max-width:520px;background:#fff;border-radius:14px;padding:20px 22px;
  box-shadow:0 40px 80px -30px rgba(11,32,54,.6)}
.modale-tete{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:0 0 10px;font-size:1.05rem}
.modale-fermer{font-size:.78rem;font-weight:700;color:var(--ink2);cursor:pointer;border-bottom:1px solid var(--line2)}
.modale-boite p{margin:0 0 12px;font-size:.92rem;color:var(--ink2);line-height:1.55}
.modale-choix{display:flex;gap:10px;flex-wrap:wrap}
.modale-choix a{text-decoration:none}
.gobtn.secondaire{background:#fff;color:var(--link);border:2px solid var(--link)}

/* **Mentions en bas de fiche** (S8-P18) : les deux phrases qui décrivent **l'origine des nombres**
   (mesure du 13/10 : « Ces résultats sont tirés des produits d'une centaine de commerçants » se peignait à
   `y = 589`, **avant** les offres à `y = 815`, sans titre et sans espacement). Elles descendent, se nomment,
   et s'espacent. Le texte porté reste **mot à mot** (parité). */
.mentions{margin-top:34px;padding:24px;background:var(--band);border:1px solid var(--line);border-radius:var(--r)}
.mentions h2{font-size:1.1rem;margin:0 0 14px;color:var(--ink)}
.mentions .avertissementresultats{margin:0 0 16px}
.mentions .mention-prix{margin:0;font-size:.86rem;color:var(--ink2);line-height:1.6}
.fiche-stage .tot{margin-top:10px}
.mention-prix{font-size:.86rem;color:var(--ink2);margin-top:18px;border-top:1px solid var(--line);padding-top:12px}
/* Lignes d'offre de la fiche (S8-P6, 15/10) : le balisage **de la maquette** est posé depuis S8-P7
   (`li.offer`, voir le bloc « liste d'offres » plus bas). **S8-P6 avait habillé le balisage de l'ancien
   site** (`.results_li`, `.desc`, `.title` n'avaient **aucune** règle — mesuré par `tools/check/feuilles.php`
   : 6 classes rendues sans style, le prix tombait collé au marchand). Ces règles sont **retirées** avec le
   balisage qu'elles habillaient : la feuille ne porte pas de style sans balisage. */
/* Filet de produits voisins (fiche sans offre en ligne, `FicheController::blocFilet()`) : **aucune
   règle non plus** avant cette passe. Le bloc rend un titre, une phrase d'explication et des
   `.listproduct` ; l'étiquette de catégorie n'était qu'un texte brut collé au titre du produit. */
.filetovivant{margin-top:26px}
.filetovivant h2{font-size:1.35rem;margin:0 0 6px}
.filetovivant > p{color:var(--ink2);max-width:78ch;margin:0 0 16px}
.filetcategorie{display:inline-block;max-width:100%;border-radius:999px;padding:4px 10px;font-size:.76rem;
  font-weight:700;background:var(--navyTint);color:var(--navy);margin-top:6px}

.courbe{margin-top:26px;padding:20px;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift)}
.courbe h2{font-size:1.15rem;margin:0 0 4px;color:var(--ink)}
/* **Le graphique occupe le cadre** (S8-P18, 13/10, capture d'Alex sur `/p/5414709190743` : « le graph est
   minuscule dans un grand cadre »). Mesuré avant la passe : cadre de **914 px** posé dans une colonne de
   **1 200** (`max-width:78ch`, un tiers vide à sa droite) et tracé de **150 px** de haut dans un cadre de
   **379 px**. Le cadre prend donc la **largeur de la colonne**, et le tracé **suit son rapport**
   (`aspect-ratio` = celui du viewBox, `340/118`), plafonné à 340 px de haut pour tenir dans un écran.
   Les caractères du SVG se règlent en **unités du viewBox** (le tracé est agrandi d'un facteur ~2,9) :
   10 unités s'y rendaient **29 px** ; les libellés redescendent à 4,4 unités (~13 px rendus).
   Les traits gardent leur épaisseur à l'écran grâce à `vector-effect:non-scaling-stroke` (sinon le trait de
   3 unités devient 8,6 px : une barre, pas une courbe). Les points grossissent avec le cadre (r = 3,5 →
   ~10 px) : c'est la cible de survol, et chaque point porte son relevé dans un `<title>` — le survol se lit
   sans script (mesure du 13/10 : « pas de hover sur les prix pour suivre l'évolution »). */
/* **Le cadre du graphique prend le rapport exact du viewBox** (`340/118`, `Sparkline::CADRE`) : mesuré, la
   boîte posée à `340/118` avec un viewBox de 118 **letterboxait** le tracé à 82 % de sa propre largeur, le
   dessin flottait et les repères de survol ne tombaient plus sur leurs points. */
/* **La boîte du graphique prend le rapport exact du viewBox, sans plafond hérité** (mesure du 13/10 : la
   feuille posait `max-height:128px` sur `.spark` — **plafond de la mini sparkline de la boîte de prix** — qui
   s'appliquait au grand graphique : boîte mesurée **950 × 128** pour un viewBox de rapport 2,881, donc le
   tracé **letterboxait** à l'intérieur et les repères de survol tombaient jusqu'à **291 px** à côté de leurs
   points. Le plafond est **levé ici**, la largeur est plafonnée, la hauteur suit le rapport). */
.courbe .spark{width:100%;max-width:950px;max-height:none;aspect-ratio:340/118;height:auto;margin:14px auto 0;display:block}
.courbe .cadre-courbe{position:relative;max-width:950px;margin:0 auto}
/* Même mécanisme dans la **preuve de l accueil** (S8-P19) : le cadre est relatif, les cibles se posent en
   pourcent du cadre, donc sur leurs points (rapport de la boîte = rapport du viewBox). */
.pricebox .cadre-reperes{position:relative;display:block;font-size:0;line-height:0}
/* Le **cadre de repères** est un `span` rendu en bloc : les **espaces** entre le SVG et les cibles creaient
   une ligne anonyme de 6 px, qui decale les cibles (mesure 13/10 : cadre 328 × 120 pour un SVG de 328 × 114,
   ratio 2,724 au lieu de 2,881). `font-size:0` supprime cette ligne ; les cibles portent leur propre corps. */
/* Le SVG ne doit **pas** deposer de marge dans son cadre de reperes : la marge du haut decalait les cibles
   de 6 px par rapport a leurs points (mesure 13/10). */
.pricebox .cadre-reperes > .spark{margin-top:0;margin-bottom:0}
.pricebox .pt-b{position:absolute;width:22px;height:22px;transform:translate(-50%,-50%);display:block;font-size:.72rem;line-height:1.35}
.pricebox .pt-b i{position:absolute;left:50%;bottom:125%;transform:translateX(-50%);white-space:nowrap;
  padding:5px 8px;border-radius:8px;background:var(--navy);color:#fff;font-size:.72rem;font-style:normal;
  font-weight:700;letter-spacing:0;opacity:0;pointer-events:none;transition:opacity .15s ease}
.pricebox .pt-b:hover i,.pricebox .pt-b:focus i,.pricebox .pt-b:focus-within i{opacity:1}
.pricebox .pt-b.extrème i{background:var(--brandL)}
/* **Infobulle de survol rendue par la feuille** (capture du 13/10 : « le hover sur les points ne fonctionne
   pas »). Deux causes mesurées : les points étaient **peints sous l'appoint** (le pointeur tombait sur le
   polygone), et l'infobulle native `<title>` met ~1 s à sortir. Chaque point reçoit donc une **cible** de
   26 px centrée sur lui (`left/top` en pourcent du cadre), et son relevé sort au **survol** et au **focus**. */
.courbe .pt-b{position:absolute;width:26px;height:26px;transform:translate(-50%,-50%);display:block}
.courbe .pt-b i{position:absolute;left:50%;bottom:120%;transform:translateX(-50%);white-space:nowrap;
  padding:6px 9px;border-radius:8px;background:var(--navy);color:#fff;font-size:.78rem;font-style:normal;
  font-weight:700;letter-spacing:0;opacity:0;pointer-events:none;transition:opacity .15s ease}
.courbe .pt-b i::after{content:"";position:absolute;left:50%;top:100%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:var(--navy)}
.courbe .pt-b:hover i,.courbe .pt-b:focus i,.courbe .pt-b:focus-within i{opacity:1}
.courbe .pt-b.extrème i{background:var(--brandL)}
.courbe .pt-b.extrème i::after{border-top-color:var(--brandL)}
.courbe .spark text{font-size:4.4px}
.courbe .spark .ax{font-size:4px}
.courbe .spark .lo-l,.courbe .spark .hi-l{font-size:4.8px}
.courbe .spark polyline,.courbe .spark .base,.courbe .spark .pt,.courbe .spark .lo,.courbe .spark .hi{vector-effect:non-scaling-stroke}
.courbe .legendes{list-style:none;padding:0;margin:12px 0 0;display:grid;gap:6px;font-size:.85rem;color:var(--ink2)}
.courbe .legendes li{display:flex;align-items:baseline;gap:9px}
.courbe .legendes li::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--brandL);transform:translateY(-2px)}
.avertissementresultats{font-size:.9rem;color:var(--ink2);background:var(--navyTint);border-left:4px solid var(--navy);border-radius:0 12px 12px 0;padding:14px 16px;margin:0 0 20px;max-width:92ch}
/* **Aucune offre : le mot le dit et le bouton ne ment pas** (capture d'Alex du 13/10 : « le bouton
   voir les offres n'est pas clickable en cas de pas de résultat. Pas d'indication qu'il y a pas
   d'offres »). Le bouton n'est **pas rendu** quand il n'y a rien à voir ; une phrase **nomme** l'absence.
   `max-width:78ch` : une boîte seule dans une page de 1 240 px ne se pose plus en bande pleine largeur. */
.sansoffre{padding:20px;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift);color:var(--ink2);margin:14px 0 0;max-width:78ch}
.sansoffre b{color:var(--ink)}
.sansoffre a{color:var(--brand2);font-weight:800}
/* **Les boîtes de la fiche se ressemblent toutes** (capture d'Alex du 13/10 : « bloc blanc arrondi, puis
   bloc gris quarré, les espacements ne sont pas cohérents ») : une seule famille, même rayon, même bord,
   même ombre, même respiration, largeur de lecture. La notice méthodologique n'est plus un coin gris posé
   au milieu de la page (voir `templates/pages/fiche.php`). */
.piednotice{margin-top:26px;padding:20px;background:#fff;border:1px solid var(--line);border-radius:var(--r);
   box-shadow:var(--lift);color:var(--ink2);max-width:78ch}
.piednotice h2{font-size:1.15rem;margin:0 0 10px;color:var(--ink)}
.piednotice p{margin:8px 0 0;max-width:none}
.piednotice ul{margin:10px 0 0;padding-left:1.1em}
.piednotice li{margin:6px 0}
.piednotice b{color:var(--ink)}

/* la liste d'offres : RANGEES DE TUILES MARCHANDS, pas de <table> (règle D5/D18 de la refonte).

   **LE KIT DE LA MAQUETTE EST DESCENDU DANS LE BALISAGE (S8-P7, 15/10, mesuré au Playwright du poste).**
   Avant : la fiche rendait le markup de l'ancien site (`.results_li > .finalprice` + `.liright >
   .merchantname` + `.desc`), habillé par des sélecteurs de traduction. Le rendu mesuré affichait
   « 35,99 € EUR » en 2rem **puis** « 35,99 € EUR » en 0,83rem : le même montant écrit deux fois, avec le
   symbole **et** le code de monnaie, sans signature de l'offre la moins chère, sans action visible.
   La carte prend les classes de la maquette (`.offer`, `.offer .m`, `.offer .p`, `.offer .tot`,
   `.offer .go`, `.offer .flag`, `.offer.best` — mesurées dans `tests/maquettes/04-product-wave-2026.html`
   l.179-198) et **ne rend que ce qui est mesuré dans le payload** (`tests/mesurer-offres.php`, échantillon
   déterministe `id_ean MOD 97 = 13`, 877 offres de `prod_ro`) : prix **877/877**, livraison > 0 sur
   **436/877 (50 %)**, total > 0 sur **877/877** dont **440 égaux au prix** (port non communiqué) et
   **437 égaux à prix + port**, **0** au-dessus ; monnaie **EUR sur 877/877** ; **aucune date par offre** ;
   image que pour kelkoo (177/177) et 4 sur 700 pour awin. **Donc : pas de fraîcheur par offre, pas de
   photo par offre, rien d'inventé.** Le prix de tête est le **total** (c'est le nombre qui classe la liste
   et la promesse du site est « frais de port inclus ») ; la ligne du bas **dit si le port est compté**
   (« livraison 4,95 € · prix 31,04 € ») ou **non communiqué** (« port non communiqué · prix 35,99 € ») :
   un port inconnu n'est pas un port gratuit, et 50 % du flux n'a pas de port. */
.results_wrapper{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px}
.offer{position:relative;display:flex;flex-direction:column;gap:9px;background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--lift);transition:transform .3s var(--spring),box-shadow .3s}
.offer:hover{transform:translateY(-7px) rotate(-.3deg);box-shadow:var(--hover)}
/* L'offre la moins chère se **signe** : elle ne se devine pas. Le classement est le coût total croissant
   (`FicheController::offresTriees()`), la première carte rendue est donc la moins chère. Bordure dégradée au
   même modèle que `.pricebox::before`, ruban en haut à droite (maquette l.190-194). */
.offer.best{border:2px solid transparent;background:linear-gradient(#fff,#fff) padding-box,linear-gradient(120deg,var(--brandL),var(--navy)) border-box}
.offer .flag{position:absolute;top:-1px;right:16px;border-radius:0 0 11px 11px;background:var(--posTint);color:var(--pos);font-size:.68rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase;padding:7px 12px}
.offer .m{font-weight:800;font-size:1.05rem;color:var(--ink);transition:color .2s}
.offer .m small{display:block;font-weight:600;font-size:.82rem;color:var(--ink2)}
.offer .p{font-size:2rem;font-weight:800;letter-spacing:-.04em;line-height:1;color:var(--ink)}
.offer .p em{font-style:normal;font-size:.95rem;font-weight:800;color:var(--ink2)}
.offer .tot{font-size:.83rem;color:var(--ink2)}
.offer .tot b{color:var(--ink)}
.offer .titre{font-size:.9rem;line-height:1.35;color:var(--ink2);overflow-wrap:anywhere}
.offer .go{margin-top:auto;border-radius:10px;padding:13px 18px;font-weight:800;background:var(--brandLight);color:var(--brand2);text-align:center;transition:background .25s var(--spring),color .25s,box-shadow .25s;box-shadow:inset 0 0 0 1px var(--brandL)}
/* **La carte d'offre se lit comme une action, pas comme une information** (S8-P18, 13/10, capture d'Alex :
   « les blocs de liens sont pas identifiables, on dirait juste des blocs d'informations parmi d'autres »).
   Mesuré avant : `Voir l'offre` en pastille **grise** (`--band`/`--ink`), la même peau que les lignes
   d'information du bloc. Le bouton devient **plein**, sur la couleur d'action du site, et l'effet au survol
   ne se limite plus à un changement de fond. */
.offer:hover .go{background:var(--brandL);color:#fff;box-shadow:0 12px 26px -12px rgba(225,41,44,.75)}
.offer:hover .m{color:var(--navy)}
.offer a.offrelink{position:absolute;inset:0;z-index:5;border-radius:var(--r)}
.offer a.offrelink:focus-visible{outline:3px solid var(--link);outline-offset:2px}
/* La date qui tête la liste vient du cache : `offer_cache.datecache` est un **int Ymd** (mesuré sur
   `prod_ro` : min 20261009, max 20261012 pour 105 278 lignes, 82 921 avec offres). Ce n'est **pas** un
   timestamp : `FROM_UNIXTIME(datecache)` rend 1970. Rien n'est rendu sans date mesurée. */
.offre-date{font-size:.86rem;font-weight:700;color:var(--ink2)}
.offer .fresh{display:inline-flex;align-items:center;gap:7px;font-size:.78rem;color:var(--ink2)}
.offer .fresh i{width:7px;height:7px;border-radius:50%;background:var(--pos)}
.offer .fresh.old i{background:var(--neg)}
.offer .fresh.old{color:var(--neg)}

/* ---------- tuiiles et composants posés pour les étapes suivantes (P2 → P4) ---------- */
.pricebox{position:relative;border-radius:var(--r);margin:14px 0 12px;padding:18px;background:#fff;border:1px solid var(--line);box-shadow:var(--lift);overflow:hidden}
.pricebox::before{content:"";position:absolute;inset:0;border-radius:var(--r);padding:2px;
  background:linear-gradient(calc(var(--a) + 90deg),var(--brandL),var(--navy),var(--brandL),var(--brandL));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;
  animation:conicspin 9s linear infinite;pointer-events:none}
@supports not (background:linear-gradient(calc(1deg),red,blue)){.pricebox{border:2px solid var(--brandL)}}
.pricebox .l{font-size:.74rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--ink2)}
.pricebox .v{font-size:3.3rem;font-weight:800;letter-spacing:-.05em;line-height:.95;color:var(--ink)}
.pricebox .v em{font-style:normal;font-size:1.15rem;font-weight:800;color:var(--ink2);letter-spacing:0;padding-right:8px;white-space:nowrap}
/* **L'ancre du prix ne se pose pas en `inline-block`** (mesure du 13/10 sur la capture d'Alex : « 35,99 € ●+15 %",
   un point noir entre la monnaie et la variation). Le <a> du prix (pose S8-P10, hero cliquable) en
   `display:inline-block` **et** le `letter-spacing:-.05em` de `.v` serraient la boîte du `<em>` : mesure
   `getBoundingClientRect` — `<em>` **14 px** de large pour un glyphede 18,4 px (1,15rem) : le glyphe **déborde**
   de sa boîte, mord sur la marge de la pastille de variation et se lit comme un point. L'ancre reste **inline**,
   le glyphe garde sa place, la monnaie est séparée de la variation par 8 px. */
/* Pastille de variation dans le pricebox : la maquette ne la declare que pour `.pcard` et `.card`
   (mesure 15/10). Elle est recopiée sur le meme modele (--pos pour une baisse, --neg pour une hausse),
   pas inventee : la maquette n'a pas de troisieme couleur de mouvement. */
.pricebox .dn{color:var(--pos);font-weight:800;font-size:1.1rem;margin-left:10px}
.pricebox .up{color:var(--neg);font-weight:800;font-size:1.1rem;margin-left:10px}
.pricebox .tot{margin-top:8px;font-size:.93rem;color:var(--ink2)}
.pricebox .tot b{color:var(--ink)}
/* **Une ligne par explication** (mesure du 15/10, capture d'Alex : « le blabla sous le graph est
   incompréhensible »). La légende était rendue en **un seul paragraphe** avec des `·` : 4 informations,
   230 signes, collées. Elles sont maintenant **une par ligne**, précédées d'un point de la couleur de
   marque (pas de puce : `.corps ul{list-style:disc}` est neutralisée par la règle des listes de composants,
   ligne 239 de la feuille). Les phrases sont écrites par `Sparkline::legendes()` et se lisent seules. */
.pricebox .legendes{list-style:none;padding:0;margin:12px 0 0;display:grid;gap:6px;font-size:.85rem;color:var(--ink2)}
/* **Une colonne, une phrase par ligne** — la mesure du 15/10 tranche : à 1 366 px, deux colonnes de 265 px
   faisaient **tomber chaque phrase sur deux lignes** (case mesurée 44 px de haut au lieu de 22) et le hero
   montait à **1 188 px** ; une colonne tient chaque phrase sur **une ligne** (case 22 px, hero 1 172 px),
   le graphique et le prix restent au‑dessus de la ligne de flottaison (prix à y = 768, courbe à y = 965 sur
   un écran de 1 000 px). */
.pricebox .legendes li{display:flex;align-items:baseline;gap:9px}
.pricebox .legendes li::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--brand);transform:translateY(-2px)}
.chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.chip{display:inline-flex;align-items:center;gap:7px;border-radius:999px;border:1.5px solid var(--line2);background:#fff;padding:8px 14px;font-size:.85rem;font-weight:700;color:var(--ink2);box-shadow:var(--lift);transition:transform .25s var(--spring),border-color .2s}
.chip:hover{transform:translateY(-3px);border-color:var(--navy);color:var(--navy)}
.chip i{font-style:normal;width:8px;height:8px;border-radius:50%;background:var(--pos);animation:beat 2.4s infinite}
.pills{display:flex;gap:8px;flex-wrap:wrap;list-style:none;padding:0;margin:0 0 18px}
.pill,.sous-categories li,.menuperso a,.liste-alertes a{border-radius:999px;padding:9px 16px;font-weight:700;font-size:.9rem;background:#fff;border:1.5px solid var(--line2);box-shadow:var(--lift);color:var(--ink);display:inline-block;transition:transform .25s var(--spring),border-color .2s,color .2s,background .2s}
.pill:hover,.sous-categories li a:hover,.menuperso a:hover,.liste-alertes a:hover{transform:translateY(-3px) rotate(-1deg);border-color:var(--navy);color:var(--navy)}
.pill.on{background:var(--ink);color:#fff;border-color:transparent}
.sous-categories,.menuperso,.liste-alertes{list-style:none;padding:0;margin:0 0 18px;display:flex;gap:8px;flex-wrap:wrap}
.sous-categories a{background:var(--brandTint);border-color:var(--brandLight);color:var(--brand2)}
.sous-categories a:hover{background:var(--brandL);border-color:var(--brandL);color:#fff}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(238px,1fr));gap:16px}
.grid.wide{grid-template-columns:repeat(auto-fill,minmax(180px,1fr))}
.card{position:relative;display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift);overflow:hidden;transition:transform .32s var(--spring),box-shadow .32s}
.card:hover{transform:translateY(-8px);box-shadow:var(--hover)}
.card .ph{position:relative;aspect-ratio:1/.92;display:flex;align-items:center;justify-content:center;background:linear-gradient(160deg,#f7f9fc,#eaf0f6);overflow:hidden}
/* Le calque se **remonte** dans la carte (mesure 15/10 au Playwright) : à `center`, le libellé « photo non
   fournie » tombait **derrière la pastille de prix** (pastille collée en `left:12px;bottom:12px`, boîte
   de 168×151, libellé à y ≈ 107 sur 151) et le rendu ne lisait plus que « urnie ». Le second calque est
   donc posé à 6 % depuis le haut : le libellé reste dans la boîte, il ne touche plus la pastille. */
.card .ph .cad{position:relative;width:88%;height:86%;background-size:contain,78%;background-position:center,center 6%;transition:transform .5s var(--spring)}
.card:hover .ph .cad{transform:scale(1.08) rotate(1.2deg)}
.card .price{position:absolute;left:12px;bottom:12px;background:#fff;border-radius:10px;padding:9px 14px;font-weight:800;font-size:1.15rem;color:var(--navy);box-shadow:0 12px 26px -12px rgba(19,25,33,.55),inset 0 1px 0 #fff;transition:transform .3s var(--spring)}
.card:hover .price{transform:scale(1.06) translateY(-3px)}
.card .price small{display:block;font-size:.7rem;font-weight:600;color:var(--ink2)}
.card .flag{position:absolute;right:10px;top:10px;border-radius:999px;padding:6px 11px;font-size:.71rem;font-weight:800;background:#fff;color:var(--pos);box-shadow:var(--lift)}
.card .flag.hot{background:var(--ink);color:#fff}
.card .body{padding:15px 16px 17px;display:flex;flex-direction:column;gap:8px}
.card h3{margin:0;font-size:.95rem;font-weight:700;line-height:1.32;letter-spacing:-.015em;color:var(--ink);transition:color .2s;
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;max-height:calc(3*1.32em)}
.card .dn{color:var(--pos);font-weight:800;font-size:.82rem}
.card .up{color:var(--neg);font-weight:800;font-size:.82rem}
.card:hover h3{color:var(--navy)}
.card .m{font-size:.85rem;color:var(--ink2)}
/* `white-space:nowrap` (mesure 15/10) : à 1,93 rem de large par carte, l’action se coupait en deux
   lignes et la flèche restait collée en fin de première ligne — « Comparer les 8 offres › / offres ».
   L’action tient sur une ligne, la flèche suit le dernier mot. */
.card .go{margin-top:auto;display:inline-flex;align-items:center;gap:8px;font-weight:800;color:var(--brand2);font-size:.88rem;white-space:nowrap}
.card .go .arr{transition:transform .25s var(--spring)}
.card:hover .go .arr{transform:translateX(6px)}
.wave{position:relative;overflow:hidden;border-radius:var(--r);background:linear-gradient(180deg,var(--posTint),var(--band));border:1px solid var(--line);margin-top:34px}
.wave::before,.wave::after{content:"";position:absolute;top:0;bottom:0;width:70px;z-index:3;pointer-events:none}
.wave::before{left:0;background:linear-gradient(90deg,var(--band),transparent)}
.wave::after{right:0;background:linear-gradient(-90deg,var(--band),transparent)}
.wave .track{display:flex;width:max-content;padding:14px 0;animation:marquee 42s linear infinite}
.wave:hover .track{animation-play-state:paused}
.pcard{display:flex;align-items:center;gap:12px;padding:10px 18px;border-right:1px solid var(--line);max-width:340px}
/* La pastille de la vague mesure **84×84** (mesuré 15/10) : le libellé « photo non fournie » y tiendrait
   en 4 px de haut. Elle prend le jeton **sans libellé**, la caméra seule. */
.pcard .cad{width:84px;height:84px;flex:none;border-radius:12px;background-color:#fff;border:1px solid var(--line);
  background-size:contain,42px;background-image:var(--photo,none),var(--tombale-ico);box-shadow:inset 0 0 0 1px rgba(255,255,255,.7)}
/* **Le tronage à 2 lignes était neutralisé par une règle jumelle** (capture d'Alex du 15/10 : le carrousel
   rend « Western Digital WDN17GN010RRK- », **coupé à mi-mot et sans points de suspension**). La feuille
   déclarait `.pcard b` **deux fois** : la premiere pose `display:-webkit-box` + `-webkit-line-clamp:2`, la
   seconde (transcrite de la maquette, plus bas) posait `display:block` — **la dernière gagne**, le
   `-webkit-box` saute, donc le tronage ne s'applique plus : la boîte coupait au ras de `max-height` (36 px),
   à mi-caractère, sans ellipsis. Les deux règles sont **fusionnées en une seule** (`display`, taille,
   graisse et couleur ensemble).`word-break:normal` garde les mots entiers ; `text-overflow:ellipsis` rend
   les points de suspension ; le **titre complet est dans `title=`** (producteur). */
.pcard b{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;max-height:2.6em;line-height:1.3;
  font-size:.86rem;font-weight:700;color:var(--ink);
  text-overflow:ellipsis;word-break:normal;overflow-wrap:break-word}
/* **`max-height:2.6em` se lit avec le `line-height` de la boîte** (mesure du 15/10) : la page donne
   `line-height:1.6` (le `body`), soit 22 px par ligne à 13,76 px de police → 2 lignes = **44 px**, et la
   boîte était bornée à **35,8 px = 1,6 ligne** : la seconde ligne était **tranchée** (`clientHeight 36` contre
   `scrollHeight 44`, 16 pavés sur 24). Le `2.6em` venait de la maquette, qui posait un `line-height` de 1,3.
   Le `line-height` de la boîte se pose donc **avec sa hauteur maximale** : 1,3 × 2 = 2,6em, les deux lignes
   rendues en entier. */
/* **La colonne du titre n'avait pas de place** : l'ancre de la carte est une rangée de trois boîtes (photo,
   texte, prix) et la boîte du texte **ne grossissait pas** (`flex` non posé) → titre mesuré à **124 px** de
   large à 390, soit **25 signes par ligne** (mesure au canvas : 4,82 px par signe à 13,76 px de police).
   Avec `flex:1` la boîte prend la place laissée par la photo (84) et par le prix (`nowrap`). */
.wave .pcard>a>span:nth-child(2){flex:1 1 auto;min-width:0}
/* La ligne secondaire **se repliait sur deux lignes** (capture : « livraison comprise · 6 / offres · Disques
   durs ») : elle se tient sur **une ligne**. Le producteur la coupe **à 26 signes sur un mot** (mesure : 11,84 px
   de police, ~4,15 px par signe, boîte de 124 px = 30 signes, le motage en mange 3 à 4) ; l'ellipsis CSS reste
   **garde-fou**, il ne doit plus se voir (mesuré : secondaires tronquées par la feuille **0 sur 24**). */
.pcard s{text-decoration:none;display:block;color:var(--ink2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pcard .pp{white-space:nowrap}
/* Le pavé entier de la vague est cliquable : la carte de la maquette est un lien (l.331, `pcard`),
   et un pavé qui ne mène nulle part est exactement le reproche fait au site. L'ancre prend donc la
   boîte de la carte, la mise en page de `.pcard` est recopiée sur l'ancre, pas inventée. */
.wave .pcard>a{display:flex;align-items:center;gap:12px;width:100%;color:inherit;text-decoration:none}
.wave .pcard:hover{background:#fff}
.pcard img{width:56px;height:56px;object-fit:contain;background:#fff;border-radius:12px;border:1px solid var(--line);padding:4px}
.pcard .pp{font-weight:800;font-size:1.02rem;color:var(--ink)}
.pcard .dn{color:var(--pos);font-weight:800;font-size:.8rem}
.pcard .up{color:var(--neg);font-weight:800;font-size:.8rem}
/* Le rapport est celui du **cadre rendu** (mesure 15/10) : le cadre de `Sparkline::svg()` fait
   340×118 depuis que le tracé est descendu de 14 unités pour libérer les libellés. Poser un rapport
   inventé ferait **étirer** le tracé, exactement le défaut relevé sur les captures du 12/10. */
.spark{width:100%;height:auto;aspect-ratio:340/118;max-height:128px;margin-top:10px}
/* **Le trait n'est plus haché par une animation de_dash** (capture d'Alex du 13/10 : « la ligne est
   discontinue »). Root cause mesuré : la feuille posait un `stroke-dasharray` **fixe** (420, puis 100 unités)
   pour l'animation d'écriture. Chrome calcule ce motif **dans les unités réelles du tracé** (`getTotalLength()`
   du grand graphique mesuré = **374 unités**, et `pathLength="100"` ne change rien au calcul du motif) :
   avec un motif de 100, le tratto ne rend que 0→100, **sautait** 100→200 (le segment le plus raide, celui que
   la capture montre coupé), puis reprenait. Le motif est donc **supprimé** : un trait ne doit sa vie qu'à sa
   géométrie. L'animation d'écriture est **remplacée par une apparition en fondu**, qui ne dépend d'aucune
   longueur de chemin. */
.spark polyline{fill:none;stroke:var(--brandL);stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.spark{animation:apparait .8s ease both}
@keyframes apparait{from{opacity:0}to{opacity:1}}
.spark .area{fill:url(#gsp);opacity:.16;stroke:none}
.spark .lo{fill:var(--pos);stroke:#fff;stroke-width:2}
.spark .hi{fill:var(--ink2)}
.spark text{font:700 10px "Plus Jakarta Sans",system-ui;fill:var(--ink2)}
.spark .lo-l{fill:var(--pos)}
/* Libellé du **point haut** : même ligne que celui du bas (au‑dessus du tracé), couleur d'encre, pour que les
   deux prix extrêmes se lisent **ensemble** et que la ligne du bas reste **aux dates**. */
.spark .hi-l{fill:var(--ink)}
/* Les **points** de la courbe portent leur relevé dans un `<title>` (infobulle du navigateur, sans script) ;
   les **dates** de l'abscisse portent la classe `ax`. */
.spark .pt{fill:#fff;stroke:var(--brandL);stroke-width:2}
.spark .ax{font:700 9px "Plus Jakarta Sans",system-ui;fill:var(--ink2);letter-spacing:.02em}
/* La ligne de **base de l'abscisse** : trois dates sans axe se lisent comme un texte posé sous un tracé. */
.spark .base{stroke:var(--line2);stroke-width:1}
.note{font-size:.86rem;color:var(--ink2);margin-top:14px}
.avertissementresultats.note{margin-top:0}

/* ---------- formulaires ---------- */
form{margin:16px 0;max-width:620px}
form label{display:block;font-weight:700;font-size:.9rem;margin:14px 0 5px;color:var(--ink)}
form input[type=text],form input[type=email],form input[type=password],form input[type=search],form textarea,form select{
  width:100%;font:inherit;color:var(--ink);background:#fff;border:1.5px solid var(--line2);border-radius:14px;padding:13px 15px;transition:border-color .2s,box-shadow .2s,transform .2s var(--spring)}
form input:focus,form textarea:focus,form select:focus{outline:none;border-color:var(--navy);box-shadow:0 0 0 4px var(--navyTint)}
form input[type=checkbox]{width:18px;height:18px;accent-color:var(--navy);margin-right:8px}
/* Choix à cocher sur une ligne, case puis libellé (page d'adieu de l'extension, sujet 5) */
.choix{display:flex;align-items:center;gap:10px;margin:12px 0}
.choix label{display:inline;margin:0}
.button{margin:18px 0}
.button input[type=submit]{font:inherit;border:0}
.buttonorange input[type=submit],.btn.gh{background:#fff;border:1.5px solid var(--line2);color:var(--ink);box-shadow:var(--lift)}
.buttonorange input[type=submit]:hover,.btn.gh:hover{border-color:var(--navy);color:var(--navy);background:#fff}
.confirmation{background:var(--posTint);color:var(--pos);border-radius:14px;padding:12px 15px;font-weight:700;margin:14px 0}
.aide{font-size:.88rem;color:var(--ink2)}

/* Le focus et la case à cocher des formulaires **reprise** (mesure 15/10 sur `/connexion` : les champs de
   l'ancien site — `input` **sans** classe `.champ` — prenaient l'**anneau de focus du navigateur**, un
   bleu-violet hors charte, et la case « Se souvenir de moi » la case native bleue. La règle est donc posée sur
   les **contrôles**, pas sur `.champ` : elle couvre le balisage ancien et le nouveau.)
   Mesuré aussi ce jour-là : le pied de page **flottait** à y = 729 dans une fenêtre de 1 000 — **186 px de blanc**
   sous le bandeau marine. Le corps se pose en colonne, `main` prend la place restante, le pied ferme la page. */
:focus-visible{outline:3px solid var(--navy);outline-offset:2px}
input[type=checkbox],input[type=radio]{accent-color:var(--navy);width:18px;height:18px}
body{min-height:100vh;display:flex;flex-direction:column}
main{flex:1 1 auto;min-width:0;width:100%}
/* **Le responsive était cassé par cette ligne** (mesure 15/10, 390 px) : `flex:1 0 auto` pose
   `flex-shrink:0` → l'élément **ne peut pas se rétrécir** en dessous de la largeur de son contenu. Le
   contenu du `main` veut 1 240 px (`.wrap`/`.corps` = `max-width:1240px`), donc `main` mesurait **1 240 px
   dans une fenêtre de 390** : l'accueil était **tronqué de 850 px**, et **inatteignable** — `html` et `body`
   portent `overflow-x:hidden`, qui **mange** le débordement : `scrollWidth` reste à 390, la page ne peut pas
   être scrollée, ce que la feuille ne montre pas est **perdu**, pas « rangé ».
   Le contrôle « pas de débordement horizontal » qui regarde `scrollWidth` est donc **aveugle ici** : il faut
   comparer la **largeur de chaque élément** à celle de la fenêtre (`main` 1 240 px / fenêtre 390 px), pas
   l'ampleur du document. `flex:1 1 auto` + `min-width:0` : le `main` se rétrécit, les grilles se replient.
   Le pied ferme toujours la page (`body{min-height:100vh}` en colonne, `main` prend la place restante). */
.otherlinks{font-size:.9rem;color:var(--ink2);margin-top:18px;border-top:1px solid var(--line);padding-top:12px}
.otherlinks a{color:var(--ink2);font-weight:700}
.otherlinks a:hover{color:var(--brand2)}
.alerte-supprimer{border:0;background:transparent;color:var(--neg);font:inherit;font-weight:700;cursor:pointer;text-decoration:underline}

/* ---------- categories : l'arbre en cartes, pas en liste à puces (vue « wiki 2000 », 15/10) ---------- */
.categories{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;list-style:none;padding:0;margin:16px 0}
.categories > li{display:flex;flex-direction:column;gap:12px;background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--lift);transition:transform .3s var(--spring),box-shadow .3s}
.categories > li:hover{transform:translateY(-6px);box-shadow:var(--hover)}
.categories > li > a:first-child{font-weight:800;font-size:1.08rem;letter-spacing:-.02em;color:var(--ink)}
.categories > li > a:first-child:hover{color:var(--navy)}
.categories > li > ul{list-style:none;display:flex;flex-wrap:wrap;gap:7px;padding:0;margin:0}
.categories > li > ul > li > a{display:inline-block;border-radius:999px;padding:7px 13px;font-size:.86rem;font-weight:700;background:var(--band);border:1px solid var(--line2);color:var(--ink2);transition:transform .2s var(--spring),border-color .2s,color .2s,background .2s}
.categories > li > ul > li > a:hover{transform:translateY(-2px);border-color:var(--navy);background:var(--navyTint);color:var(--navy)}

/* ---------- tuiles d’installation de `/download` (S8-P7, 15/10) ----------
   Mesure : ces deux classes **n’avaient aucune règle**. `tools/check/feuilles.php` ne lisait que les
   fichiers `.php` : les pages statiques de `templates/content/` (`download.html`, `conditions.html`,
   `cookies.html`) étaient son **angle mort** — la porte est donc élargie aux `.html` (mesure : elle rougit
   désormais sur `.download` et `.downloadlabel`, et le texte rendu des pages statiques est lu). Rendu mesuré
   avant : les deux tuiles **empilées**, en lien bleu, icônes en 404 (elles pointaient `/images/`, dossier
   absent de `public_html/` — corrigé, voir `templates/content/download.html`). Le balisage et **le texte**
   viennent de l’ancien site et ne bougent pas (« Télécharger druidX pour Chrome » / « … Firefox ») : seules
   les cotes changent, les tuiles deviennent des tuiles de la charte, côte à côte. */
#download{display:flex;flex-wrap:wrap;justify-content:center;gap:16px;margin:18px 0}
#download > a{text-decoration:none}
.download{display:flex;flex-direction:column;align-items:center;gap:12px;width:260px;background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:22px 18px;box-shadow:var(--lift);transition:transform .3s var(--spring),box-shadow .3s}
.download:hover{transform:translateY(-6px) rotate(-.3deg);box-shadow:var(--hover)}
.download img{width:56px;height:56px;object-fit:contain}
.downloadlabel{font-weight:800;font-size:1rem;line-height:1.35;text-align:center;color:var(--ink)}
.download:hover .downloadlabel{color:var(--navy)}

/* ---------- pied : bandeau marine, la palette validée ferme la page ---------- */
.pied{margin-top:46px;background:var(--navy);color:var(--inkInv);padding:30px 0}
.pied .pied-in{max-width:1240px;margin:0 auto;padding:0 20px;display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap}
.pied p{margin:0;color:var(--inkInv)}
.pied a{color:#fff;font-weight:700}
.pied a:hover{color:var(--brand)}

/* ---------- points de rupture : 1120 / 1023 / 767 / 360 (ceux de la maquette) ---------- */
@media (max-width:1120px){
 .stage{gap:18px}
 .stage .photo{min-height:280px}
 .pricebox .v{font-size:3.4rem}
}
/* La scène de l'accueil ne passait **qu'une colonne** dès 1 120 px (mesure 15/10 à 768 : hero de **1 431 px**,
   photo au‑dessus du prix, page à 4 731 px). Deux colonnes tiennent à 1 024 **et** à 768 (photo 380, prix 370) :
   la colonne unique ne descend qu'à **740 px**, sous la table de la navigation mobile (767). */
@media (max-width:740px){
 .stage{grid-template-columns:1fr;gap:20px}
 .stage .photo{min-height:320px}
}
@media (max-width:1023px){
 .corps h1{font-size:2.2rem}
 .corps .hero h1{font-size:2.7rem}
 .grid,.grid.wide{grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}
 .results_wrapper{grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
 /* 4 colonnes de tuiles **jusqu'à 1 023 px**, 2 en dessous (mesure 13/10 : à 1 024, 4 tuiles de 244 px
   passaient ; sous 1 023 la feuille laisse déjà la colonne de contenu se replier). */
 .tuiles{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:767px){
 .navigation{margin-left:0}
 /* **Entête mobile en 2 rangées : [marque · recherche compacte · Menu] puis la navigation dépliée en
    pile verticale** (reposée le 15/10/2026, demande d'Alex : « comment ça se fait que tu codes du
    responsive sans menu hamburger ? »). Ce qui était mesuré avant : la rangee de liens **se faisait
    glisser** et **rognait le dernier lien** — capture d'Alex à 390 px : `Contact` rendu en « Co… ».
    Le geste de balayage sur une page qui défile verticalement est **invisible** : un contrôl qui ne
    signale pas qu'il y a de la suite nest pas un contrôl. La rangee glissable est donc remplacée par
    le bouton « Menu », sans JavaScript (case à cocher + `:checked`, voir `templates/layout.php`).
    Le texte des liens ne bouge pas, **aucun lien n'est retiré**. */
 /* **Entête mobile : [marque · loupe de compte · hamburger] puis la barre de recherche pleine largeur, et le
    menu en panneau de surimpression** (reposée le 13/10 d'après la capture d'Alex : « “Menu” est hasbeen, le
    fait qu'il soit entouré aussi, il manque à côté de lui l'icône connexion, le clic sur le hamburger fait
    décaler le header vers le haut, l'ouverture sans animation et sans surimpression fait décaler toute la page
    est hasbeen »). Ce qui était mesuré : le bouton était une **pilule bordée avec le mot “Menu”** (84×40) et la
    navigation s'**insérait dans le flux** de l'entête (`display:block`) → l'entête passait de **89 px à 410 px**
    et **toute la page descendait de 321 px**, sans mouvement ni fond.
    Après : icônes **seules** (hamburger 44×44 sans cadre, compte 44×44), et la navigation devient un **panneau
    fixe** (`position:fixed`, hors du flux) qui **glisse** depuis le bord (`transform` + `transition`), posé sur un
    **fond assombri** qui se ferme au clic. Mesuré : l'entête **ne bouge plus** quand le menu s'ouvre, la page
    derrière ne décale pas. Le mécanisme reste **sans JavaScript** : `#p-menu:checked ~ .navigation`, la case est
    posée sur l'icône et reste atteignable au clavier. Le texte des liens ne bouge pas, **aucun lien n'est retiré**. */
 .entete .entete-in{min-height:56px;row-gap:6px}
 .entete-marque{font-size:1.25rem}
 /* La barre de recherche garde sa place de 2ᵉ rangée. **Plus collée à la ligne du bas** (mesure avant :
    `margin:2px 0 0`, le liseré de l'entête touchait la barre) : 10 px d'air sous la barre. */
 .entete .recherche{order:3;flex:1 1 100%;max-width:none;margin:2px 0 10px;padding:3px 3px 3px 8px;min-width:0;gap:6px;border-radius:14px}
 .entete .recherche input{font-size:.92rem;line-height:1.2;padding:6px 0;padding-left:8px}
 .entete .recherche button{padding:8px 10px;font-size:0;flex:0 0 auto;border-radius:9px;gap:0;min-width:38px;justify-content:center}
 /* Le mot « Chercher » tombe en mobile : la **loupe** reste (taille de police à 0 pour ne pas élargir la
    boîte, l'icône est dessinée, elle n'est pas du texte). */
 .entete .recherche button span{display:none}
 /* Case de menu : **hors champ visuel mais discutable au clavier** (jamais `display:none` : un contrôle masqué
    n'est plus atteignable au clavier). Elle est posée **sur son icône** (44×44), la zone tactile est là. */
 .entete .entete-case{display:block;position:absolute;top:8px;right:8px;width:44px;height:44px;opacity:0;margin:0;cursor:pointer;z-index:5}
 .entete-compte{display:inline-flex;order:2;align-items:center;justify-content:center;width:44px;height:44px;
    margin-left:auto;color:var(--ink);border-radius:12px}
 .entete-compte:active{background:var(--navyTint);color:var(--navy)}
 .entete-menu{display:inline-flex;order:2;align-items:center;justify-content:center;width:44px;height:44px;
    color:var(--ink);cursor:pointer;user-select:none;border:0;border-radius:12px}
 .entete-menu:active{background:var(--navyTint);color:var(--navy)}
 #p-menu:focus-visible ~ .entete-menu{outline:3px solid var(--navy);outline-offset:2px}
 #p-menu:focus-visible ~ .entete-compte{outline:3px solid var(--navy);outline-offset:2px}
 /* **Fond assombri** : la `label` suit la case dans le DOM (frères de rangée), elle est `fixed` et l'entête
    n'est plus son conteneur (plus de `backdrop-filter`). Elle **se ferme au clic** : c'est un `label` de la case. */
 .fond-menu{display:block;position:fixed;inset:0;z-index:70;background:rgba(11,32,54,.5);opacity:0;
    pointer-events:none;transition:opacity .28s ease}
 #p-menu:checked ~ .fond-menu{opacity:1;pointer-events:auto}
 /* **Le panneau** : la navigation elle-même, posée hors du flux. Fermée = hors cadre (`translateX(103%)`),
    ouverte = `translateX(0)`, 300 ms. Elle n'ajoute **aucune** hauteur à l'entête. */
 .entete .navigation{position:fixed;top:0;right:0;bottom:0;z-index:75;display:block;order:unset;flex:none;
    width:min(84vw,340px);margin:0;padding:14px 16px 26px;background:#fff;border-left:1px solid var(--line);
    box-shadow:-18px 0 44px -18px rgba(11,32,54,.45);overflow-y:auto;overscroll-behavior:contain;
    transform:translateX(103%);transition:transform .3s var(--spring)}
 #p-menu:checked ~ .navigation{transform:translateX(0)}
 .entete-fermer{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 4px 14px;
    font-weight:800;font-size:.82rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink2);cursor:pointer}
 .entete-fermer span{font-size:1.4rem;line-height:1;color:var(--ink)}
 .navigation ul{flex-direction:column;flex-wrap:nowrap;overflow:visible;gap:0;border-top:1px solid var(--line)}
 .navigation li{border-bottom:1px solid var(--line)}
 .navigation a{display:block;white-space:normal;border-radius:0;padding:13px 6px;font-size:.98rem}
 .navigation a:hover{transform:none;background:var(--navyTint)}
 .corps h1{font-size:1.85rem}
 .recherche{padding:7px 7px 7px 10px}
 .recherche button{padding:11px 16px;font-size:.8rem}
 .pricebox .v{font-size:3.1rem}
 .listproduct{grid-template-columns:96px 1fr;gap:12px;padding:12px}
 .imgproduct{width:96px;height:96px}
 .stage .photo{min-height:220px;padding:10px}
 /* La photo du hero de l'accueil se resserre **en mobile** (mesure du 13/10 : boîte de 298 px, hero à
    1 675 px) : 220 px, la cote qui était rendue avant et qu'Alex n'a pas reprochée. La carte de prix ne
    se resserre pas : c'est elle qui se clique. */
 /* La preuve du prix s'empile en mobile : photo au-dessus du prix, pleine largeur (mesure du 13/10 : la
    scène de deux colonnes rendait **1 615 px** de hero à 390 — le bloc est hors du hero depuis S8-P16, il
    n'est plus dans le premier écran). */
 .preuve .stage.preuve-scene .photo{min-height:220px}
 .preuve .stage.preuve-scene .photo .cad{width:min(100%,340px);height:min(220px,56vw)}
 .stage .photo img{max-height:320px;object-fit:contain}
 .grid,.grid.wide{grid-template-columns:repeat(auto-fill,minmax(158px,1fr));gap:12px}
 .card .price{font-size:1rem;padding:8px 11px}
 .results_wrapper{grid-template-columns:1fr;gap:12px}
 .offer{padding:16px}
 .offer .p{font-size:1.9rem}
 .offer .flag{position:static;display:inline-block;margin-bottom:2px}
 .sechead{margin-bottom:14px}
 .hero{padding-top:30px}
 /* Le hero pesait **1 483 px** à 390 (mesure 15/10) : pastille bandeau pleine largeur sur 2 lignes, boîte de
    prix de 490 px, champ de recherche de 191 px. Les cotes se resserrent, **le texte ne bouge pas** : la pastille
    ne prend que sa largeur de contenu, la boîte de prix perd ses marges, le champ de recherche garde la place
    que lui laisse le bouton. */
 .eyebrow{width:fit-content;max-width:100%}
 .pricebox{padding:16px 14px;margin:12px 0 10px}
 .pricebox .btn{margin-top:12px}
 .bigsearch{padding:8px 8px 8px 12px;gap:8px}
 .hero .bigsearch svg{display:none}
 .gobtn{padding:11px 14px;font-size:.8rem;border-radius:9px}
 /* Le titre du hero n'était **pas** repris par la feuille en mobile : la règle du hero
    (`.corps .hero h1`, 2,7 rem) **gagne** en spécificité contre `.corps h1` (1,85 rem) — mesuré à 390 px :
    `h1` à **43,2 px**. Le titre se pose donc en mobile **par la règle du hero**. */
 .corps .hero h1{font-size:1.75rem;max-width:20ch;margin-left:auto;margin-right:auto}
 .hero .lede{max-width:52ch}
 /* Le premier écran mobile tient la promesse **et** la recherche : padding resserré, titre à 1,75 rem,
    champ pleine largeur (règle de la spec : bloc du haut **≤ 92 % de la hauteur de fenêtre**). */
 .hero{padding:26px 0 30px}
 .hero .hero-lede{font-size:.95rem}
 .hero .lede,.hero .hint{font-size:.9rem}
 /* **Le pavé d'explication ne se peint pas sur le mobile** (mesure 13/10 : hero de **689 px = 82 %** de la
    fenêtre de 844, et le paragraphe « Lorsque vous êtes sur un produit… » + « C'est gratuit… » en **prenait
    la moitié**). Sur mobile la promesse tient en trois lignes : titre, accroche, champ. Le texte **reste sur
    le desktop** et vit aussi sur les pages de questions (même contenu, même source). */
 .hero .lede{display:none}
 /* **La fiche mobile remonte son action principale dans le premier écran** (S8-P18, 13/10 : mesure à 390 × 844
    avant la règle — le lien d’achat direct finissait à `y = 816`, **à la limite du cadre**, parce que la photo
    de 320 px et le titre de 2,7 rem en prenaient la moitié). Photo et titre descendent, le prix se resserre :
    le lien à cliquer doit être atteignable sans déplier la page. */
 .fiche-stage .photo{min-height:190px}
 /* Le fil de rubrique est une **cible de pouce** en mobile : 44 px de zone tactile par étage, sinon le
    doigt tombe sur l’étage voisin (mesure du 13/10 au 390 : les pastilles d’information faisaient 33 px). */
 .fiche-stage .meta a.fil .chip{min-height:44px;display:inline-flex;align-items:center;padding:10px 16px;font-size:.9rem}
 /* Sur mobile, **le prix et son lien passent avant la photo** (la scène se replie en une colonne ; le kit
    d'Alex est clair : « le plus important c'est le lien à cliquer »). Le `order` vaut sur les enfants d'une
    grille : la colonne `meta` remonte, la photo descend sous l'action. */
 .fiche-stage .meta{order:-1}
 .fiche-stage .photo{order:2}
 .fiche-stage .cad{width:min(100%,260px);height:170px}
 .corps h1{font-size:1.65rem}
 .corps .fiche-stage h1{font-size:1.65rem}
 .fiche-stage .pricebox .v{font-size:2.5rem}
 .cta-achat{padding:14px 16px}
 .cta-achat b{font-size:1.25rem}
 .alerte-outil{padding:14px}
 /* A 390 px, la glissiere de l'alerte est **l'outil principal** : la ligne de valeur prend toute la largeur, la
    valeur se lit en grand, les bornes restent de chaque cote (le legacy affichait la meme chose). */
 .alerte-titre{font-size:.9rem}
 .alerte-slider{gap:8px}
 .alerte-form .gobtn,.alerte-form .alerte-clic{width:100%;text-align:center}
 .hero-visuel{gap:8px;margin-top:18px}
 .hero-visuel .cad{width:96px;height:76px}
 .hero .bigsearch{width:100%;max-width:none}
 .marques{padding:18px 0 22px}
 .marques-liste{gap:6px 16px}
 .marques-liste li{font-size:.86rem}
 .bloc{padding:40px 0}
 .preuve{padding:40px 0}
 .tuiles{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
 .tuile{padding:14px}
 .tuile b{font-size:.95rem}
 .atouts-liste{grid-template-columns:1fr;gap:12px}
 .atout{padding:18px}
 .kicker{margin-bottom:10px}
 .stage h2.t{font-size:1.55rem}
 .download{width:100%;max-width:340px}
 .corps,.pied .pied-in{padding-left:14px;padding-right:14px}
}
@media (max-width:360px){
 .corps h1{font-size:1.6rem}
 .corps .hero h1{font-size:1.65rem}
 .pricebox .v{font-size:2.6rem}
 .grid,.grid.wide{grid-template-columns:1fr 1fr;gap:10px}
 .card .body{padding:12px}
 .card h3{font-size:.95rem}
 .results_wrapper{grid-template-columns:1fr}
}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* ---------- SUIVI DES CLICS ET DES VENTES (`/admin/clics`, refonte du 15/10/2026) ----------
   Page privée d'Alex (PIN). Mêmes jetons que le site : palette, police, rayons, ombres. Les classes
   `adm-*` ne servent qu'ici. Les couleurs d'état reprennent --pos (vert) / --neg (rouge) / --brand. */
.adm{background:var(--band);font-size:15px}
.adm-tete{background:var(--navy);color:#fff}
.adm-tete-in{max-width:1320px;margin:0 auto;padding:14px 20px;display:flex;flex-wrap:wrap;align-items:center;gap:10px 24px}
.adm-marque{font-weight:800;font-size:20px;letter-spacing:-.01em}
.adm-marque b{color:var(--brand)}
.adm-marque span{font-weight:500;font-size:14px;color:var(--inkInv);margin-left:8px}
.adm-periodes{display:flex;gap:4px;background:rgba(255,255,255,.08);border-radius:999px;padding:4px}
.adm-periodes a{color:#fff;font-weight:600;font-size:14px;padding:5px 14px;border-radius:999px}
.adm-periodes a:hover{background:rgba(255,255,255,.14)}
.adm-periodes a[aria-current]{background:var(--brandL);color:#fff}
.adm-maj{margin-left:auto;font-size:13px;color:var(--inkInv)}
.adm-corps{max-width:1320px;margin:0 auto;padding:20px;display:flex;flex-direction:column;gap:18px}
.adm-alertes{display:flex;flex-wrap:wrap;gap:10px}
.adm-alerte{flex:1 1 320px;border-radius:var(--r);padding:10px 14px;font-size:14px;border:1px solid}
.adm-alerte-ok{background:var(--posTint);border-color:#b9dbb7;color:var(--pos)}
.adm-alerte-ko{background:#fdecef;border-color:#f5b5c1;color:#a3132e}
.adm-alerte b{color:var(--ink);margin-right:6px}
.adm-tuiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:14px}
.adm-tuile,.adm-carte{background:var(--bg);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--lift)}
.adm-tuile{padding:16px 18px}
.adm-tuile-lib{font-size:13px;font-weight:600;color:var(--ink2);text-transform:uppercase;letter-spacing:.04em}
.adm-tuile-val{font-size:clamp(21px,5.6vw,30px);font-weight:800;line-height:1.2;margin:4px 0 2px;color:var(--ink)}
.adm-var{font-size:13px;font-weight:600;color:var(--ink2)}
.adm-hausse{color:var(--pos)}
.adm-baisse{color:var(--neg)}
.adm-tuile-note{font-size:13px;color:var(--ink2);margin-top:6px}
.adm-grille{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}
.adm-carte{padding:16px 18px;min-width:0}
.adm-carte h2{font-size:16px;font-weight:700;margin-bottom:10px}
.adm-carte h2 small{font-weight:500;color:var(--ink2);font-size:13px}
.adm-courbe{position:relative}
.adm-courbe svg{display:block;width:100%;height:150px;overflow:visible}
.adm-courbe rect{fill:var(--link);opacity:.85}
.adm-courbe rect:hover{fill:var(--brand);opacity:1}
.adm-axe{stroke:var(--line2);stroke-width:1;vector-effect:non-scaling-stroke}
.adm-courbe-max{position:absolute;top:16px;right:18px;font-size:12px;color:var(--ink2)}
.adm-courbe-dates{display:flex;justify-content:space-between;font-size:12px;color:var(--ink2);margin-top:4px}
.adm-defile{overflow-x:auto}
.adm-table{width:100%;border-collapse:collapse;font-size:14px}
.adm-table th{text-align:left;font-size:12px;font-weight:600;color:var(--ink2);text-transform:uppercase;letter-spacing:.03em;padding:6px 8px;border-bottom:1px solid var(--line)}
.adm-table td{padding:7px 8px;border-bottom:1px solid var(--band2);vertical-align:top}
.adm-table tbody tr:hover{background:var(--linkTint)}
.adm-table small{color:var(--ink2);font-size:12px}
.adm-n{text-align:right!important;font-variant-numeric:tabular-nums lining-nums;white-space:nowrap}
.adm-deux{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.adm-note{font-size:12px;color:var(--ink2);margin-top:10px}
.adm-vide{color:var(--ink2);font-style:italic}
.adm-pastille{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:8px;vertical-align:1px}
.adm-pastille-ok{background:var(--pos)}
.adm-pastille-ko{background:var(--neg)}
.adm-badge{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;white-space:nowrap}
.adm-badge-ok{background:var(--posTint);color:var(--pos)}
.adm-badge-attente{background:var(--brandTint);color:var(--brand2)}
.adm-badge-ko{background:#fdecef;color:#a3132e}
.adm-produits{list-style:none;counter-reset:rang}
.adm-produits li{counter-increment:rang;display:flex;gap:10px;align-items:baseline;padding:7px 0;border-bottom:1px solid var(--band2);font-size:14px}
.adm-produits li::before{content:counter(rang);font-weight:700;color:var(--ink2);min-width:1.4em}
.adm-produits a{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink)}
.adm-produits a:hover{color:var(--link)}
.adm-produits span{font-weight:700}
.adm-derniers summary{cursor:pointer;font-weight:700}
.adm-derniers[open] summary{margin-bottom:10px}
.adm-ecarte td{color:var(--ink2);text-decoration:line-through}
.adm-pin{max-width:340px;margin:12vh auto;padding:28px;display:flex;flex-direction:column;gap:12px}
.adm-pin input{font:inherit;font-size:18px;padding:10px 12px;border:1px solid var(--line2);border-radius:10px}
.adm-pin button{font:inherit;font-weight:700;padding:10px;border:0;border-radius:10px;background:var(--brandL);color:#fff;cursor:pointer}
.adm-pin .adm-alerte{flex:none}
@media (max-width:900px){
  .adm-grille,.adm-deux{grid-template-columns:minmax(0,1fr)}
  .adm-maj{margin-left:0}
}
.adm-sec{white-space:nowrap}
.adm-titre{max-width:0;width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------- bandeau de consentement (15/10, CNIL) : « Refuser » et « Accepter » ont exactement la même forme,
   la même taille et le même rang — aucun des deux n'est mis en avant. Posé en bas, hors flux : il ne décale
   rien de la page. ---------- */
.consentement{position:fixed;z-index:60;left:12px;right:12px;bottom:12px;max-width:720px;margin:0 auto;
  display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;padding:16px 18px;background:#fff;
  border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--hover)}
.consentement[hidden]{display:none}
.consentement-texte{flex:1 1 320px;font-size:.88rem;line-height:1.5;color:var(--ink2)}
.consentement-texte strong{display:block;color:var(--ink);font-size:.95rem}
.consentement-texte a{color:var(--link)}
.consentement-choix{display:flex;gap:10px;flex:0 0 auto}
.consentement-bouton{font:inherit;font-weight:700;font-size:.9rem;padding:10px 22px;border-radius:9px;cursor:pointer;
  background:var(--navy);color:#fff;border:1px solid var(--navy)}
.consentement-bouton:hover,.consentement-bouton:focus-visible{background:var(--ink);border-color:var(--ink)}
